From 6302f980fe6dad83aab7d2dc1046dadea5ffd939 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 14 Dec 2015 16:52:24 -0500 Subject: New source tree structure for modules. From commands/cs_access => chanserv/access etc. --- modules/CMakeLists.txt | 43 +- modules/botserv/CMakeLists.txt | 1 + modules/botserv/assign.cpp | 281 ++++ modules/botserv/autoassign.cpp | 37 + modules/botserv/badwords.cpp | 465 ++++++ modules/botserv/bot.cpp | 426 +++++ modules/botserv/botlist.cpp | 92 ++ modules/botserv/control.cpp | 168 ++ modules/botserv/info.cpp | 116 ++ modules/botserv/kick.cpp | 1664 ++++++++++++++++++++ modules/botserv/main/CMakeLists.txt | 1 + modules/botserv/main/botserv.cpp | 228 +++ modules/botserv/set.cpp | 221 +++ modules/bs_autoassign.cpp | 37 - modules/chanserv/CMakeLists.txt | 1 + modules/chanserv/akick.cpp | 718 +++++++++ modules/chanserv/ban.cpp | 268 ++++ modules/chanserv/clone.cpp | 221 +++ modules/chanserv/cs_access.old | 941 +++++++++++ modules/chanserv/cs_flags.old | 528 +++++++ modules/chanserv/cs_xop.old | 659 ++++++++ modules/chanserv/drop.cpp | 101 ++ modules/chanserv/enforce.cpp | 307 ++++ modules/chanserv/entrymsg.cpp | 270 ++++ modules/chanserv/getkey.cpp | 75 + modules/chanserv/info.cpp | 97 ++ modules/chanserv/invite.cpp | 109 ++ modules/chanserv/kick.cpp | 152 ++ modules/chanserv/list.cpp | 263 ++++ modules/chanserv/log.cpp | 448 ++++++ modules/chanserv/main/CMakeLists.txt | 1 + modules/chanserv/main/chanaccess.cpp | 127 ++ modules/chanserv/main/chanaccess.h | 30 + modules/chanserv/main/chanaccesstype.h | 21 + modules/chanserv/main/channel.cpp | 429 +++++ modules/chanserv/main/channel.h | 68 + modules/chanserv/main/channeltype.cpp | 45 + modules/chanserv/main/channeltype.h | 34 + modules/chanserv/main/chanserv.cpp | 587 +++++++ modules/chanserv/main/level.cpp | 33 + modules/chanserv/main/level.h | 16 + modules/chanserv/main/leveltype.h | 16 + modules/chanserv/main/mode.cpp | 33 + modules/chanserv/main/mode.h | 17 + modules/chanserv/main/modetype.h | 15 + modules/chanserv/mode.cpp | 999 ++++++++++++ modules/chanserv/register.cpp | 157 ++ modules/chanserv/seen.cpp | 477 ++++++ modules/chanserv/set.cpp | 1265 +++++++++++++++ modules/chanserv/set_misc.cpp | 211 +++ modules/chanserv/status.cpp | 115 ++ modules/chanserv/statusupdate.cpp | 67 + modules/chanserv/suspend.cpp | 339 ++++ modules/chanserv/sync.cpp | 77 + modules/chanserv/topic.cpp | 276 ++++ modules/chanserv/unban.cpp | 120 ++ modules/chanserv/updown.cpp | 261 +++ modules/commands/bs_assign.cpp | 281 ---- modules/commands/bs_badwords.cpp | 465 ------ modules/commands/bs_bot.cpp | 426 ----- modules/commands/bs_botlist.cpp | 92 -- modules/commands/bs_control.cpp | 168 -- modules/commands/bs_info.cpp | 116 -- modules/commands/bs_kick.cpp | 1664 -------------------- modules/commands/bs_set.cpp | 221 --- modules/commands/cs_access.cpp | 942 ----------- modules/commands/cs_akick.cpp | 718 --------- modules/commands/cs_ban.cpp | 268 ---- modules/commands/cs_clone.cpp | 221 --- modules/commands/cs_drop.cpp | 101 -- modules/commands/cs_enforce.cpp | 307 ---- modules/commands/cs_entrymsg.cpp | 270 ---- modules/commands/cs_flags.cpp | 530 ------- modules/commands/cs_getkey.cpp | 75 - modules/commands/cs_info.cpp | 97 -- modules/commands/cs_invite.cpp | 109 -- modules/commands/cs_kick.cpp | 152 -- modules/commands/cs_list.cpp | 263 ---- modules/commands/cs_log.cpp | 448 ------ modules/commands/cs_mode.cpp | 999 ------------ modules/commands/cs_register.cpp | 157 -- modules/commands/cs_seen.cpp | 477 ------ modules/commands/cs_set.cpp | 1265 --------------- modules/commands/cs_set_misc.cpp | 211 --- modules/commands/cs_status.cpp | 115 -- modules/commands/cs_suspend.cpp | 339 ---- modules/commands/cs_sync.cpp | 77 - modules/commands/cs_topic.cpp | 276 ---- modules/commands/cs_unban.cpp | 120 -- modules/commands/cs_updown.cpp | 261 --- modules/commands/cs_xop.cpp | 660 -------- modules/commands/gl_global.cpp | 58 - modules/commands/greet.cpp | 212 --- modules/commands/help.cpp | 197 --- modules/commands/hs_del.cpp | 109 -- modules/commands/hs_group.cpp | 117 -- modules/commands/hs_list.cpp | 159 -- modules/commands/hs_off.cpp | 60 - modules/commands/hs_on.cpp | 70 - modules/commands/hs_request.cpp | 414 ----- modules/commands/hs_set.cpp | 216 --- modules/commands/ms_cancel.cpp | 91 -- modules/commands/ms_check.cpp | 80 - modules/commands/ms_del.cpp | 148 -- modules/commands/ms_ignore.cpp | 147 -- modules/commands/ms_info.cpp | 203 --- modules/commands/ms_list.cpp | 163 -- modules/commands/ms_read.cpp | 203 --- modules/commands/ms_rsend.cpp | 105 -- modules/commands/ms_send.cpp | 81 - modules/commands/ms_sendall.cpp | 60 - modules/commands/ms_set.cpp | 314 ---- modules/commands/ms_staff.cpp | 57 - modules/commands/ns_access.cpp | 257 --- modules/commands/ns_ajoin.cpp | 381 ----- modules/commands/ns_alist.cpp | 133 -- modules/commands/ns_cert.cpp | 364 ----- modules/commands/ns_drop.cpp | 91 -- modules/commands/ns_getemail.cpp | 66 - modules/commands/ns_getpass.cpp | 74 - modules/commands/ns_group.cpp | 385 ----- modules/commands/ns_identify.cpp | 125 -- modules/commands/ns_info.cpp | 281 ---- modules/commands/ns_list.cpp | 290 ---- modules/commands/ns_logout.cpp | 100 -- modules/commands/ns_recover.cpp | 262 --- modules/commands/ns_register.cpp | 426 ----- modules/commands/ns_resetpass.cpp | 141 -- modules/commands/ns_set.cpp | 1244 --------------- modules/commands/ns_set_misc.cpp | 225 --- modules/commands/ns_status.cpp | 88 -- modules/commands/ns_suspend.cpp | 339 ---- modules/commands/ns_update.cpp | 67 - modules/commands/os_akill.cpp | 437 ----- modules/commands/os_chankill.cpp | 113 -- modules/commands/os_config.cpp | 147 -- modules/commands/os_defcon.cpp | 619 -------- modules/commands/os_dns.cpp | 1012 ------------ modules/commands/os_forbid.cpp | 577 ------- modules/commands/os_ignore.cpp | 393 ----- modules/commands/os_info.cpp | 260 --- modules/commands/os_jupe.cpp | 72 - modules/commands/os_kick.cpp | 81 - modules/commands/os_kill.cpp | 63 - modules/commands/os_list.cpp | 238 --- modules/commands/os_login.cpp | 148 -- modules/commands/os_logsearch.cpp | 151 -- modules/commands/os_mode.cpp | 187 --- modules/commands/os_modinfo.cpp | 214 --- modules/commands/os_module.cpp | 179 --- modules/commands/os_news.cpp | 459 ------ modules/commands/os_noop.cpp | 98 -- modules/commands/os_oline.cpp | 74 - modules/commands/os_oper.cpp | 241 --- modules/commands/os_reload.cpp | 62 - modules/commands/os_session.cpp | 768 --------- modules/commands/os_set.cpp | 243 --- modules/commands/os_shutdown.cpp | 101 -- modules/commands/os_stats.cpp | 271 ---- modules/commands/os_svs.cpp | 171 -- modules/commands/os_sxline.cpp | 673 -------- modules/commands/os_update.cpp | 49 - modules/cs_statusupdate.cpp | 67 - modules/database/CMakeLists.txt | 1 + modules/database/db_flatfile.cpp | 206 --- modules/database/db_old.cpp | 1428 ----------------- modules/database/db_redis.cpp | 434 ----- modules/database/db_sql.cpp | 372 ----- modules/database/flatfile.cpp | 206 +++ modules/database/old.cpp | 1428 +++++++++++++++++ modules/database/redis.cpp | 434 +++++ modules/database/sql.cpp | 372 +++++ modules/dns.cpp | 1136 +++++++++++++ modules/dnsbl.cpp | 202 +++ modules/encryption/CMakeLists.txt | 1 + modules/encryption/bcrypt.cpp | 969 ++++++++++++ modules/encryption/enc_bcrypt.cpp | 969 ------------ modules/encryption/enc_md5.cpp | 402 ----- modules/encryption/enc_none.cpp | 76 - modules/encryption/enc_old.cpp | 118 -- modules/encryption/enc_sha1.cpp | 254 --- modules/encryption/enc_sha256.cpp | 340 ---- modules/encryption/md5.cpp | 402 +++++ modules/encryption/none.cpp | 76 + modules/encryption/old.cpp | 118 ++ modules/encryption/sha1.cpp | 254 +++ modules/encryption/sha256.cpp | 340 ++++ modules/extra/ldap.cpp | 623 ++++++++ modules/extra/ldap_authentication.cpp | 302 ++++ modules/extra/ldap_oper.cpp | 134 ++ modules/extra/m_ldap.cpp | 623 -------- modules/extra/m_ldap_authentication.cpp | 302 ---- modules/extra/m_ldap_oper.cpp | 134 -- modules/extra/m_mysql.cpp | 614 -------- modules/extra/m_sasl_dh-aes.cpp | 184 --- modules/extra/m_sasl_dh-blowfish.cpp | 194 --- modules/extra/m_sql_authentication.cpp | 143 -- modules/extra/m_sql_log.cpp | 110 -- modules/extra/m_sql_oper.cpp | 148 -- modules/extra/m_sqlite.cpp | 380 ----- modules/extra/m_ssl_gnutls.cpp | 658 -------- modules/extra/m_ssl_openssl.cpp | 416 ----- modules/extra/mysql.cpp | 614 ++++++++ modules/extra/sasl_dh-aes.cpp | 184 +++ modules/extra/sasl_dh-blowfish.cpp | 194 +++ modules/extra/sql_authentication.cpp | 143 ++ modules/extra/sql_log.cpp | 110 ++ modules/extra/sql_oper.cpp | 148 ++ modules/extra/sqlite.cpp | 380 +++++ modules/extra/ssl_gnutls.cpp | 658 ++++++++ modules/extra/ssl_openssl.cpp | 416 +++++ modules/extra/stats/chanstats.cpp | 648 ++++++++ modules/extra/stats/m_chanstats.cpp | 648 -------- modules/global/CMakeLists.txt | 1 + modules/global/global.cpp | 58 + modules/global/main/CMakeLists.txt | 1 + modules/global/main/global.cpp | 107 ++ modules/greet.cpp | 212 +++ modules/help.cpp | 197 +++ modules/helpchan.cpp | 32 + modules/hostserv/CMakeLists.txt | 1 + modules/hostserv/del.cpp | 109 ++ modules/hostserv/group.cpp | 117 ++ modules/hostserv/list.cpp | 159 ++ modules/hostserv/main/CMakeLists.txt | 1 + modules/hostserv/main/hostserv.cpp | 135 ++ modules/hostserv/off.cpp | 60 + modules/hostserv/on.cpp | 70 + modules/hostserv/request.cpp | 414 +++++ modules/hostserv/set.cpp | 216 +++ modules/httpd.cpp | 473 ++++++ modules/m_dns.cpp | 1136 ------------- modules/m_dnsbl.cpp | 202 --- modules/m_helpchan.cpp | 32 - modules/m_httpd.cpp | 473 ------ modules/m_proxyscan.cpp | 380 ----- modules/m_redis.cpp | 598 ------- modules/m_rewrite.cpp | 187 --- modules/m_sasl.cpp | 287 ---- modules/m_xmlrpc.cpp | 221 --- modules/m_xmlrpc_main.cpp | 279 ---- modules/memoserv/CMakeLists.txt | 1 + modules/memoserv/cancel.cpp | 91 ++ modules/memoserv/check.cpp | 80 + modules/memoserv/del.cpp | 148 ++ modules/memoserv/ignore.cpp | 147 ++ modules/memoserv/info.cpp | 203 +++ modules/memoserv/list.cpp | 163 ++ modules/memoserv/main/CMakeLists.txt | 1 + modules/memoserv/main/ignore.cpp | 27 + modules/memoserv/main/ignore.h | 15 + modules/memoserv/main/ignoretype.cpp | 10 + modules/memoserv/main/ignoretype.h | 10 + modules/memoserv/main/memo.cpp | 72 + modules/memoserv/main/memo.h | 29 + modules/memoserv/main/memoinfo.cpp | 63 + modules/memoserv/main/memoinfo.h | 23 + modules/memoserv/main/memoinfotype.cpp | 10 + modules/memoserv/main/memoinfotype.h | 10 + modules/memoserv/main/memoserv.cpp | 298 ++++ modules/memoserv/main/memotype.cpp | 14 + modules/memoserv/main/memotype.h | 13 + modules/memoserv/read.cpp | 203 +++ modules/memoserv/rsend.cpp | 105 ++ modules/memoserv/send.cpp | 81 + modules/memoserv/sendall.cpp | 60 + modules/memoserv/set.cpp | 314 ++++ modules/memoserv/staff.cpp | 57 + modules/nickserv/CMakeLists.txt | 1 + modules/nickserv/access.cpp | 257 +++ modules/nickserv/ajoin.cpp | 381 +++++ modules/nickserv/alist.cpp | 133 ++ modules/nickserv/cert.cpp | 364 +++++ modules/nickserv/drop.cpp | 91 ++ modules/nickserv/getemail.cpp | 66 + modules/nickserv/getpass.cpp | 74 + modules/nickserv/group.cpp | 385 +++++ modules/nickserv/identify.cpp | 125 ++ modules/nickserv/info.cpp | 281 ++++ modules/nickserv/list.cpp | 290 ++++ modules/nickserv/logout.cpp | 100 ++ modules/nickserv/main/CMakeLists.txt | 1 + modules/nickserv/main/account.cpp | 129 ++ modules/nickserv/main/account.h | 35 + modules/nickserv/main/accounttype.cpp | 42 + modules/nickserv/main/accounttype.h | 23 + modules/nickserv/main/identifyrequest.cpp | 51 + modules/nickserv/main/identifyrequest.h | 13 + modules/nickserv/main/mode.cpp | 23 + modules/nickserv/main/mode.h | 14 + modules/nickserv/main/modetype.h | 14 + modules/nickserv/main/nick.cpp | 192 +++ modules/nickserv/main/nick.h | 46 + modules/nickserv/main/nickserv.cpp | 686 ++++++++ modules/nickserv/main/nicktype.cpp | 45 + modules/nickserv/main/nicktype.h | 32 + modules/nickserv/maxemail.cpp | 80 + modules/nickserv/recover.cpp | 262 +++ modules/nickserv/register.cpp | 426 +++++ modules/nickserv/resetpass.cpp | 141 ++ modules/nickserv/set.cpp | 1244 +++++++++++++++ modules/nickserv/set_misc.cpp | 225 +++ modules/nickserv/status.cpp | 88 ++ modules/nickserv/suspend.cpp | 339 ++++ modules/nickserv/update.cpp | 67 + modules/ns_maxemail.cpp | 80 - modules/operserv/CMakeLists.txt | 1 + modules/operserv/akill.cpp | 437 +++++ modules/operserv/chankill.cpp | 113 ++ modules/operserv/config.cpp | 147 ++ modules/operserv/defcon.cpp | 619 ++++++++ modules/operserv/dns.cpp | 1012 ++++++++++++ modules/operserv/forbid.cpp | 577 +++++++ modules/operserv/ignore.cpp | 393 +++++ modules/operserv/info.cpp | 260 +++ modules/operserv/jupe.cpp | 72 + modules/operserv/kick.cpp | 81 + modules/operserv/kill.cpp | 63 + modules/operserv/list.cpp | 238 +++ modules/operserv/login.cpp | 148 ++ modules/operserv/logsearch.cpp | 151 ++ modules/operserv/main/CMakeLists.txt | 1 + modules/operserv/main/operserv.cpp | 337 ++++ modules/operserv/mode.cpp | 187 +++ modules/operserv/modinfo.cpp | 214 +++ modules/operserv/module.cpp | 179 +++ modules/operserv/news.cpp | 459 ++++++ modules/operserv/noop.cpp | 98 ++ modules/operserv/oline.cpp | 74 + modules/operserv/oper.cpp | 241 +++ modules/operserv/reload.cpp | 62 + modules/operserv/session.cpp | 768 +++++++++ modules/operserv/set.cpp | 243 +++ modules/operserv/shutdown.cpp | 101 ++ modules/operserv/stats.cpp | 271 ++++ modules/operserv/svs.cpp | 171 ++ modules/operserv/sxline.cpp | 673 ++++++++ modules/operserv/update.cpp | 49 + modules/protocol/CMakeLists.txt | 1 + modules/proxyscan.cpp | 380 +++++ modules/pseudoclients/botserv/CMakeLists.txt | 1 - modules/pseudoclients/botserv/botserv.cpp | 228 --- modules/pseudoclients/chanserv/CMakeLists.txt | 1 - modules/pseudoclients/chanserv/chanaccess.cpp | 127 -- modules/pseudoclients/chanserv/chanaccess.h | 30 - modules/pseudoclients/chanserv/chanaccesstype.h | 21 - modules/pseudoclients/chanserv/channel.cpp | 429 ----- modules/pseudoclients/chanserv/channel.h | 68 - modules/pseudoclients/chanserv/channeltype.cpp | 45 - modules/pseudoclients/chanserv/channeltype.h | 34 - modules/pseudoclients/chanserv/chanserv.cpp | 587 ------- modules/pseudoclients/chanserv/level.cpp | 33 - modules/pseudoclients/chanserv/level.h | 16 - modules/pseudoclients/chanserv/leveltype.h | 16 - modules/pseudoclients/chanserv/mode.cpp | 33 - modules/pseudoclients/chanserv/mode.h | 17 - modules/pseudoclients/chanserv/modetype.h | 15 - modules/pseudoclients/global.cpp | 107 -- modules/pseudoclients/hostserv.cpp | 135 -- modules/pseudoclients/memoserv/CMakeLists.txt | 1 - modules/pseudoclients/memoserv/ignore.cpp | 27 - modules/pseudoclients/memoserv/ignore.h | 15 - modules/pseudoclients/memoserv/ignoretype.cpp | 10 - modules/pseudoclients/memoserv/ignoretype.h | 10 - modules/pseudoclients/memoserv/memo.cpp | 72 - modules/pseudoclients/memoserv/memo.h | 29 - modules/pseudoclients/memoserv/memoinfo.cpp | 63 - modules/pseudoclients/memoserv/memoinfo.h | 23 - modules/pseudoclients/memoserv/memoinfotype.cpp | 10 - modules/pseudoclients/memoserv/memoinfotype.h | 10 - modules/pseudoclients/memoserv/memoserv.cpp | 298 ---- modules/pseudoclients/memoserv/memotype.cpp | 14 - modules/pseudoclients/memoserv/memotype.h | 13 - modules/pseudoclients/nickserv/CMakeLists.txt | 1 - modules/pseudoclients/nickserv/account.cpp | 129 -- modules/pseudoclients/nickserv/account.h | 35 - modules/pseudoclients/nickserv/accounttype.cpp | 42 - modules/pseudoclients/nickserv/accounttype.h | 23 - modules/pseudoclients/nickserv/identifyrequest.cpp | 51 - modules/pseudoclients/nickserv/identifyrequest.h | 13 - modules/pseudoclients/nickserv/mode.cpp | 23 - modules/pseudoclients/nickserv/mode.h | 14 - modules/pseudoclients/nickserv/modetype.h | 14 - modules/pseudoclients/nickserv/nick.cpp | 192 --- modules/pseudoclients/nickserv/nick.h | 46 - modules/pseudoclients/nickserv/nickserv.cpp | 686 -------- modules/pseudoclients/nickserv/nicktype.cpp | 45 - modules/pseudoclients/nickserv/nicktype.h | 32 - modules/pseudoclients/operserv.cpp | 337 ---- modules/redis.cpp | 598 +++++++ modules/rewrite.cpp | 187 +++ modules/sasl.cpp | 287 ++++ modules/xmlrpc.cpp | 221 +++ modules/xmlrpc_main.cpp | 279 ++++ 394 files changed, 46984 insertions(+), 46964 deletions(-) create mode 100644 modules/botserv/CMakeLists.txt create mode 100644 modules/botserv/assign.cpp create mode 100644 modules/botserv/autoassign.cpp create mode 100644 modules/botserv/badwords.cpp create mode 100644 modules/botserv/bot.cpp create mode 100644 modules/botserv/botlist.cpp create mode 100644 modules/botserv/control.cpp create mode 100644 modules/botserv/info.cpp create mode 100644 modules/botserv/kick.cpp create mode 100644 modules/botserv/main/CMakeLists.txt create mode 100644 modules/botserv/main/botserv.cpp create mode 100644 modules/botserv/set.cpp delete mode 100644 modules/bs_autoassign.cpp create mode 100644 modules/chanserv/CMakeLists.txt create mode 100644 modules/chanserv/akick.cpp create mode 100644 modules/chanserv/ban.cpp create mode 100644 modules/chanserv/clone.cpp create mode 100644 modules/chanserv/cs_access.old create mode 100644 modules/chanserv/cs_flags.old create mode 100644 modules/chanserv/cs_xop.old create mode 100644 modules/chanserv/drop.cpp create mode 100644 modules/chanserv/enforce.cpp create mode 100644 modules/chanserv/entrymsg.cpp create mode 100644 modules/chanserv/getkey.cpp create mode 100644 modules/chanserv/info.cpp create mode 100644 modules/chanserv/invite.cpp create mode 100644 modules/chanserv/kick.cpp create mode 100644 modules/chanserv/list.cpp create mode 100644 modules/chanserv/log.cpp create mode 100644 modules/chanserv/main/CMakeLists.txt create mode 100644 modules/chanserv/main/chanaccess.cpp create mode 100644 modules/chanserv/main/chanaccess.h create mode 100644 modules/chanserv/main/chanaccesstype.h create mode 100644 modules/chanserv/main/channel.cpp create mode 100644 modules/chanserv/main/channel.h create mode 100644 modules/chanserv/main/channeltype.cpp create mode 100644 modules/chanserv/main/channeltype.h create mode 100644 modules/chanserv/main/chanserv.cpp create mode 100644 modules/chanserv/main/level.cpp create mode 100644 modules/chanserv/main/level.h create mode 100644 modules/chanserv/main/leveltype.h create mode 100644 modules/chanserv/main/mode.cpp create mode 100644 modules/chanserv/main/mode.h create mode 100644 modules/chanserv/main/modetype.h create mode 100644 modules/chanserv/mode.cpp create mode 100644 modules/chanserv/register.cpp create mode 100644 modules/chanserv/seen.cpp create mode 100644 modules/chanserv/set.cpp create mode 100644 modules/chanserv/set_misc.cpp create mode 100644 modules/chanserv/status.cpp create mode 100644 modules/chanserv/statusupdate.cpp create mode 100644 modules/chanserv/suspend.cpp create mode 100644 modules/chanserv/sync.cpp create mode 100644 modules/chanserv/topic.cpp create mode 100644 modules/chanserv/unban.cpp create mode 100644 modules/chanserv/updown.cpp delete mode 100644 modules/commands/bs_assign.cpp delete mode 100644 modules/commands/bs_badwords.cpp delete mode 100644 modules/commands/bs_bot.cpp delete mode 100644 modules/commands/bs_botlist.cpp delete mode 100644 modules/commands/bs_control.cpp delete mode 100644 modules/commands/bs_info.cpp delete mode 100644 modules/commands/bs_kick.cpp delete mode 100644 modules/commands/bs_set.cpp delete mode 100644 modules/commands/cs_access.cpp delete mode 100644 modules/commands/cs_akick.cpp delete mode 100644 modules/commands/cs_ban.cpp delete mode 100644 modules/commands/cs_clone.cpp delete mode 100644 modules/commands/cs_drop.cpp delete mode 100644 modules/commands/cs_enforce.cpp delete mode 100644 modules/commands/cs_entrymsg.cpp delete mode 100644 modules/commands/cs_flags.cpp delete mode 100644 modules/commands/cs_getkey.cpp delete mode 100644 modules/commands/cs_info.cpp delete mode 100644 modules/commands/cs_invite.cpp delete mode 100644 modules/commands/cs_kick.cpp delete mode 100644 modules/commands/cs_list.cpp delete mode 100644 modules/commands/cs_log.cpp delete mode 100644 modules/commands/cs_mode.cpp delete mode 100644 modules/commands/cs_register.cpp delete mode 100644 modules/commands/cs_seen.cpp delete mode 100644 modules/commands/cs_set.cpp delete mode 100644 modules/commands/cs_set_misc.cpp delete mode 100644 modules/commands/cs_status.cpp delete mode 100644 modules/commands/cs_suspend.cpp delete mode 100644 modules/commands/cs_sync.cpp delete mode 100644 modules/commands/cs_topic.cpp delete mode 100644 modules/commands/cs_unban.cpp delete mode 100644 modules/commands/cs_updown.cpp delete mode 100644 modules/commands/cs_xop.cpp delete mode 100644 modules/commands/gl_global.cpp delete mode 100644 modules/commands/greet.cpp delete mode 100644 modules/commands/help.cpp delete mode 100644 modules/commands/hs_del.cpp delete mode 100644 modules/commands/hs_group.cpp delete mode 100644 modules/commands/hs_list.cpp delete mode 100644 modules/commands/hs_off.cpp delete mode 100644 modules/commands/hs_on.cpp delete mode 100644 modules/commands/hs_request.cpp delete mode 100644 modules/commands/hs_set.cpp delete mode 100644 modules/commands/ms_cancel.cpp delete mode 100644 modules/commands/ms_check.cpp delete mode 100644 modules/commands/ms_del.cpp delete mode 100644 modules/commands/ms_ignore.cpp delete mode 100644 modules/commands/ms_info.cpp delete mode 100644 modules/commands/ms_list.cpp delete mode 100644 modules/commands/ms_read.cpp delete mode 100644 modules/commands/ms_rsend.cpp delete mode 100644 modules/commands/ms_send.cpp delete mode 100644 modules/commands/ms_sendall.cpp delete mode 100644 modules/commands/ms_set.cpp delete mode 100644 modules/commands/ms_staff.cpp delete mode 100644 modules/commands/ns_access.cpp delete mode 100644 modules/commands/ns_ajoin.cpp delete mode 100644 modules/commands/ns_alist.cpp delete mode 100644 modules/commands/ns_cert.cpp delete mode 100644 modules/commands/ns_drop.cpp delete mode 100644 modules/commands/ns_getemail.cpp delete mode 100644 modules/commands/ns_getpass.cpp delete mode 100644 modules/commands/ns_group.cpp delete mode 100644 modules/commands/ns_identify.cpp delete mode 100644 modules/commands/ns_info.cpp delete mode 100644 modules/commands/ns_list.cpp delete mode 100644 modules/commands/ns_logout.cpp delete mode 100644 modules/commands/ns_recover.cpp delete mode 100644 modules/commands/ns_register.cpp delete mode 100644 modules/commands/ns_resetpass.cpp delete mode 100644 modules/commands/ns_set.cpp delete mode 100644 modules/commands/ns_set_misc.cpp delete mode 100644 modules/commands/ns_status.cpp delete mode 100644 modules/commands/ns_suspend.cpp delete mode 100644 modules/commands/ns_update.cpp delete mode 100644 modules/commands/os_akill.cpp delete mode 100644 modules/commands/os_chankill.cpp delete mode 100644 modules/commands/os_config.cpp delete mode 100644 modules/commands/os_defcon.cpp delete mode 100644 modules/commands/os_dns.cpp delete mode 100644 modules/commands/os_forbid.cpp delete mode 100644 modules/commands/os_ignore.cpp delete mode 100644 modules/commands/os_info.cpp delete mode 100644 modules/commands/os_jupe.cpp delete mode 100644 modules/commands/os_kick.cpp delete mode 100644 modules/commands/os_kill.cpp delete mode 100644 modules/commands/os_list.cpp delete mode 100644 modules/commands/os_login.cpp delete mode 100644 modules/commands/os_logsearch.cpp delete mode 100644 modules/commands/os_mode.cpp delete mode 100644 modules/commands/os_modinfo.cpp delete mode 100644 modules/commands/os_module.cpp delete mode 100644 modules/commands/os_news.cpp delete mode 100644 modules/commands/os_noop.cpp delete mode 100644 modules/commands/os_oline.cpp delete mode 100644 modules/commands/os_oper.cpp delete mode 100644 modules/commands/os_reload.cpp delete mode 100644 modules/commands/os_session.cpp delete mode 100644 modules/commands/os_set.cpp delete mode 100644 modules/commands/os_shutdown.cpp delete mode 100644 modules/commands/os_stats.cpp delete mode 100644 modules/commands/os_svs.cpp delete mode 100644 modules/commands/os_sxline.cpp delete mode 100644 modules/commands/os_update.cpp delete mode 100644 modules/cs_statusupdate.cpp create mode 100644 modules/database/CMakeLists.txt delete mode 100644 modules/database/db_flatfile.cpp delete mode 100644 modules/database/db_old.cpp delete mode 100644 modules/database/db_redis.cpp delete mode 100644 modules/database/db_sql.cpp create mode 100644 modules/database/flatfile.cpp create mode 100644 modules/database/old.cpp create mode 100644 modules/database/redis.cpp create mode 100644 modules/database/sql.cpp create mode 100644 modules/dns.cpp create mode 100644 modules/dnsbl.cpp create mode 100644 modules/encryption/CMakeLists.txt create mode 100644 modules/encryption/bcrypt.cpp delete mode 100644 modules/encryption/enc_bcrypt.cpp delete mode 100644 modules/encryption/enc_md5.cpp delete mode 100644 modules/encryption/enc_none.cpp delete mode 100644 modules/encryption/enc_old.cpp delete mode 100644 modules/encryption/enc_sha1.cpp delete mode 100644 modules/encryption/enc_sha256.cpp create mode 100644 modules/encryption/md5.cpp create mode 100644 modules/encryption/none.cpp create mode 100644 modules/encryption/old.cpp create mode 100644 modules/encryption/sha1.cpp create mode 100644 modules/encryption/sha256.cpp create mode 100644 modules/extra/ldap.cpp create mode 100644 modules/extra/ldap_authentication.cpp create mode 100644 modules/extra/ldap_oper.cpp delete mode 100644 modules/extra/m_ldap.cpp delete mode 100644 modules/extra/m_ldap_authentication.cpp delete mode 100644 modules/extra/m_ldap_oper.cpp delete mode 100644 modules/extra/m_mysql.cpp delete mode 100644 modules/extra/m_sasl_dh-aes.cpp delete mode 100644 modules/extra/m_sasl_dh-blowfish.cpp delete mode 100644 modules/extra/m_sql_authentication.cpp delete mode 100644 modules/extra/m_sql_log.cpp delete mode 100644 modules/extra/m_sql_oper.cpp delete mode 100644 modules/extra/m_sqlite.cpp delete mode 100644 modules/extra/m_ssl_gnutls.cpp delete mode 100644 modules/extra/m_ssl_openssl.cpp create mode 100644 modules/extra/mysql.cpp create mode 100644 modules/extra/sasl_dh-aes.cpp create mode 100644 modules/extra/sasl_dh-blowfish.cpp create mode 100644 modules/extra/sql_authentication.cpp create mode 100644 modules/extra/sql_log.cpp create mode 100644 modules/extra/sql_oper.cpp create mode 100644 modules/extra/sqlite.cpp create mode 100644 modules/extra/ssl_gnutls.cpp create mode 100644 modules/extra/ssl_openssl.cpp create mode 100644 modules/extra/stats/chanstats.cpp delete mode 100644 modules/extra/stats/m_chanstats.cpp create mode 100644 modules/global/CMakeLists.txt create mode 100644 modules/global/global.cpp create mode 100644 modules/global/main/CMakeLists.txt create mode 100644 modules/global/main/global.cpp create mode 100644 modules/greet.cpp create mode 100644 modules/help.cpp create mode 100644 modules/helpchan.cpp create mode 100644 modules/hostserv/CMakeLists.txt create mode 100644 modules/hostserv/del.cpp create mode 100644 modules/hostserv/group.cpp create mode 100644 modules/hostserv/list.cpp create mode 100644 modules/hostserv/main/CMakeLists.txt create mode 100644 modules/hostserv/main/hostserv.cpp create mode 100644 modules/hostserv/off.cpp create mode 100644 modules/hostserv/on.cpp create mode 100644 modules/hostserv/request.cpp create mode 100644 modules/hostserv/set.cpp create mode 100644 modules/httpd.cpp delete mode 100644 modules/m_dns.cpp delete mode 100644 modules/m_dnsbl.cpp delete mode 100644 modules/m_helpchan.cpp delete mode 100644 modules/m_httpd.cpp delete mode 100644 modules/m_proxyscan.cpp delete mode 100644 modules/m_redis.cpp delete mode 100644 modules/m_rewrite.cpp delete mode 100644 modules/m_sasl.cpp delete mode 100644 modules/m_xmlrpc.cpp delete mode 100644 modules/m_xmlrpc_main.cpp create mode 100644 modules/memoserv/CMakeLists.txt create mode 100644 modules/memoserv/cancel.cpp create mode 100644 modules/memoserv/check.cpp create mode 100644 modules/memoserv/del.cpp create mode 100644 modules/memoserv/ignore.cpp create mode 100644 modules/memoserv/info.cpp create mode 100644 modules/memoserv/list.cpp create mode 100644 modules/memoserv/main/CMakeLists.txt create mode 100644 modules/memoserv/main/ignore.cpp create mode 100644 modules/memoserv/main/ignore.h create mode 100644 modules/memoserv/main/ignoretype.cpp create mode 100644 modules/memoserv/main/ignoretype.h create mode 100644 modules/memoserv/main/memo.cpp create mode 100644 modules/memoserv/main/memo.h create mode 100644 modules/memoserv/main/memoinfo.cpp create mode 100644 modules/memoserv/main/memoinfo.h create mode 100644 modules/memoserv/main/memoinfotype.cpp create mode 100644 modules/memoserv/main/memoinfotype.h create mode 100644 modules/memoserv/main/memoserv.cpp create mode 100644 modules/memoserv/main/memotype.cpp create mode 100644 modules/memoserv/main/memotype.h create mode 100644 modules/memoserv/read.cpp create mode 100644 modules/memoserv/rsend.cpp create mode 100644 modules/memoserv/send.cpp create mode 100644 modules/memoserv/sendall.cpp create mode 100644 modules/memoserv/set.cpp create mode 100644 modules/memoserv/staff.cpp create mode 100644 modules/nickserv/CMakeLists.txt create mode 100644 modules/nickserv/access.cpp create mode 100644 modules/nickserv/ajoin.cpp create mode 100644 modules/nickserv/alist.cpp create mode 100644 modules/nickserv/cert.cpp create mode 100644 modules/nickserv/drop.cpp create mode 100644 modules/nickserv/getemail.cpp create mode 100644 modules/nickserv/getpass.cpp create mode 100644 modules/nickserv/group.cpp create mode 100644 modules/nickserv/identify.cpp create mode 100644 modules/nickserv/info.cpp create mode 100644 modules/nickserv/list.cpp create mode 100644 modules/nickserv/logout.cpp create mode 100644 modules/nickserv/main/CMakeLists.txt create mode 100644 modules/nickserv/main/account.cpp create mode 100644 modules/nickserv/main/account.h create mode 100644 modules/nickserv/main/accounttype.cpp create mode 100644 modules/nickserv/main/accounttype.h create mode 100644 modules/nickserv/main/identifyrequest.cpp create mode 100644 modules/nickserv/main/identifyrequest.h create mode 100644 modules/nickserv/main/mode.cpp create mode 100644 modules/nickserv/main/mode.h create mode 100644 modules/nickserv/main/modetype.h create mode 100644 modules/nickserv/main/nick.cpp create mode 100644 modules/nickserv/main/nick.h create mode 100644 modules/nickserv/main/nickserv.cpp create mode 100644 modules/nickserv/main/nicktype.cpp create mode 100644 modules/nickserv/main/nicktype.h create mode 100644 modules/nickserv/maxemail.cpp create mode 100644 modules/nickserv/recover.cpp create mode 100644 modules/nickserv/register.cpp create mode 100644 modules/nickserv/resetpass.cpp create mode 100644 modules/nickserv/set.cpp create mode 100644 modules/nickserv/set_misc.cpp create mode 100644 modules/nickserv/status.cpp create mode 100644 modules/nickserv/suspend.cpp create mode 100644 modules/nickserv/update.cpp delete mode 100644 modules/ns_maxemail.cpp create mode 100644 modules/operserv/CMakeLists.txt create mode 100644 modules/operserv/akill.cpp create mode 100644 modules/operserv/chankill.cpp create mode 100644 modules/operserv/config.cpp create mode 100644 modules/operserv/defcon.cpp create mode 100644 modules/operserv/dns.cpp create mode 100644 modules/operserv/forbid.cpp create mode 100644 modules/operserv/ignore.cpp create mode 100644 modules/operserv/info.cpp create mode 100644 modules/operserv/jupe.cpp create mode 100644 modules/operserv/kick.cpp create mode 100644 modules/operserv/kill.cpp create mode 100644 modules/operserv/list.cpp create mode 100644 modules/operserv/login.cpp create mode 100644 modules/operserv/logsearch.cpp create mode 100644 modules/operserv/main/CMakeLists.txt create mode 100644 modules/operserv/main/operserv.cpp create mode 100644 modules/operserv/mode.cpp create mode 100644 modules/operserv/modinfo.cpp create mode 100644 modules/operserv/module.cpp create mode 100644 modules/operserv/news.cpp create mode 100644 modules/operserv/noop.cpp create mode 100644 modules/operserv/oline.cpp create mode 100644 modules/operserv/oper.cpp create mode 100644 modules/operserv/reload.cpp create mode 100644 modules/operserv/session.cpp create mode 100644 modules/operserv/set.cpp create mode 100644 modules/operserv/shutdown.cpp create mode 100644 modules/operserv/stats.cpp create mode 100644 modules/operserv/svs.cpp create mode 100644 modules/operserv/sxline.cpp create mode 100644 modules/operserv/update.cpp create mode 100644 modules/protocol/CMakeLists.txt create mode 100644 modules/proxyscan.cpp delete mode 100644 modules/pseudoclients/botserv/CMakeLists.txt delete mode 100644 modules/pseudoclients/botserv/botserv.cpp delete mode 100644 modules/pseudoclients/chanserv/CMakeLists.txt delete mode 100644 modules/pseudoclients/chanserv/chanaccess.cpp delete mode 100644 modules/pseudoclients/chanserv/chanaccess.h delete mode 100644 modules/pseudoclients/chanserv/chanaccesstype.h delete mode 100644 modules/pseudoclients/chanserv/channel.cpp delete mode 100644 modules/pseudoclients/chanserv/channel.h delete mode 100644 modules/pseudoclients/chanserv/channeltype.cpp delete mode 100644 modules/pseudoclients/chanserv/channeltype.h delete mode 100644 modules/pseudoclients/chanserv/chanserv.cpp delete mode 100644 modules/pseudoclients/chanserv/level.cpp delete mode 100644 modules/pseudoclients/chanserv/level.h delete mode 100644 modules/pseudoclients/chanserv/leveltype.h delete mode 100644 modules/pseudoclients/chanserv/mode.cpp delete mode 100644 modules/pseudoclients/chanserv/mode.h delete mode 100644 modules/pseudoclients/chanserv/modetype.h delete mode 100644 modules/pseudoclients/global.cpp delete mode 100644 modules/pseudoclients/hostserv.cpp delete mode 100644 modules/pseudoclients/memoserv/CMakeLists.txt delete mode 100644 modules/pseudoclients/memoserv/ignore.cpp delete mode 100644 modules/pseudoclients/memoserv/ignore.h delete mode 100644 modules/pseudoclients/memoserv/ignoretype.cpp delete mode 100644 modules/pseudoclients/memoserv/ignoretype.h delete mode 100644 modules/pseudoclients/memoserv/memo.cpp delete mode 100644 modules/pseudoclients/memoserv/memo.h delete mode 100644 modules/pseudoclients/memoserv/memoinfo.cpp delete mode 100644 modules/pseudoclients/memoserv/memoinfo.h delete mode 100644 modules/pseudoclients/memoserv/memoinfotype.cpp delete mode 100644 modules/pseudoclients/memoserv/memoinfotype.h delete mode 100644 modules/pseudoclients/memoserv/memoserv.cpp delete mode 100644 modules/pseudoclients/memoserv/memotype.cpp delete mode 100644 modules/pseudoclients/memoserv/memotype.h delete mode 100644 modules/pseudoclients/nickserv/CMakeLists.txt delete mode 100644 modules/pseudoclients/nickserv/account.cpp delete mode 100644 modules/pseudoclients/nickserv/account.h delete mode 100644 modules/pseudoclients/nickserv/accounttype.cpp delete mode 100644 modules/pseudoclients/nickserv/accounttype.h delete mode 100644 modules/pseudoclients/nickserv/identifyrequest.cpp delete mode 100644 modules/pseudoclients/nickserv/identifyrequest.h delete mode 100644 modules/pseudoclients/nickserv/mode.cpp delete mode 100644 modules/pseudoclients/nickserv/mode.h delete mode 100644 modules/pseudoclients/nickserv/modetype.h delete mode 100644 modules/pseudoclients/nickserv/nick.cpp delete mode 100644 modules/pseudoclients/nickserv/nick.h delete mode 100644 modules/pseudoclients/nickserv/nickserv.cpp delete mode 100644 modules/pseudoclients/nickserv/nicktype.cpp delete mode 100644 modules/pseudoclients/nickserv/nicktype.h delete mode 100644 modules/pseudoclients/operserv.cpp create mode 100644 modules/redis.cpp create mode 100644 modules/rewrite.cpp create mode 100644 modules/sasl.cpp create mode 100644 modules/xmlrpc.cpp create mode 100644 modules/xmlrpc_main.cpp (limited to 'modules') diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 522ceb674..68a2a89b0 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -20,6 +20,13 @@ macro(build_modules SRC) # Convert the real source file extension to have a .so extension string(REGEX REPLACE "\\.cpp$" "" SO ${FNAME}) + file(RELATIVE_PATH DEST "${Anope_SOURCE_DIR}/modules" ${SRC}) + if(DEST) + set(TARG "module_${DEST}.${SO}") + else(DEST) + set(TARG "module_${SO}") + endif(DEST) + # Reset linker flags set(TEMP_LDFLAGS) # Reset extra dependencies @@ -33,31 +40,31 @@ macro(build_modules SRC) set(WIN32_MEMORY) endif(MSVC) # Generate the module and set its linker flags, also set it to depend on the main Anope executable to be built beforehand - add_library(${SO} MODULE ${MODULE_SRC}) + add_library(${TARG} MODULE ${MODULE_SRC}) # Windows requires this because it's weird if(WIN32) set(WIN32_NO_LIBS "/nodefaultlib:\"libcmt.lib\" /OPT:NOREF") else(WIN32) set(WIN32_NO_LIBS) endif(WIN32) - set_target_properties(${SO} PROPERTIES LINKER_LANGUAGE CXX PREFIX "" SUFFIX ".so" LINK_FLAGS "${TEMP_LDFLAGS} ${WIN32_NO_LIBS}" INSTALL_RPATH_USE_LINK_PATH ON BUILD_WITH_INSTALL_RPATH ON) - add_dependencies(${SO} ${PROGRAM_NAME}) + set_target_properties(${TARG} PROPERTIES LINKER_LANGUAGE CXX PREFIX "" LINK_FLAGS "${TEMP_LDFLAGS} ${WIN32_NO_LIBS}" INSTALL_RPATH_USE_LINK_PATH ON BUILD_WITH_INSTALL_RPATH ON OUTPUT_NAME "${SO}") + add_dependencies(${TARG} ${PROGRAM_NAME}) if(GETTEXT_FOUND) - add_dependencies(${SO} module_language) + add_dependencies(${TARG} module_language) endif(GETTEXT_FOUND) - target_link_libraries(${SO} ${TEMP_DEPENDENCIES}) + target_link_libraries(${TARG} ${TEMP_DEPENDENCIES}) # For Windows only, have the module link to the export library of Anope as well as wsock32 and Ws2_32 libraries (most of the modules probably don't need this, but this is to be on the safe side), also set its version if(WIN32) - target_link_libraries(${SO} ${PROGRAM_NAME} wsock32 Ws2_32 ${WIN32_MEMORY}) + target_link_libraries(${TARG} ${PROGRAM_NAME} wsock32 Ws2_32 ${WIN32_MEMORY}) set_target_properties(${PROGRAM_NAME} PROPERTIES VERSION "${VERSION_DOTTED}") else(WIN32) if(APPLE) - target_link_libraries(${SO} ${PROGRAM_NAME}) + target_link_libraries(${TARG} ${PROGRAM_NAME}) endif(APPLE) endif(WIN32) calculate_dependencies(${MODULE_SRC}) # Set the module to be installed to the module directory under the data directory - install(TARGETS ${SO} DESTINATION ${LIB_DIR}/modules) + install(TARGETS ${TARG} DESTINATION ${LIB_DIR}/modules/${DEST}) endif(CPP) endif(IS_DIRECTORY "${MODULE_SRC}") endforeach(MODULE_SRC ${MODULES_SRCS}) @@ -68,8 +75,14 @@ macro(build_subdir) file(GLOB_RECURSE MODULES_SUBDIR_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.cpp") list(SORT MODULES_SUBDIR_SRCS) - GET_FILENAME_COMPONENT(FOLDER_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) - set(SO "${FOLDER_NAME}") + file(RELATIVE_PATH DEST "${Anope_SOURCE_DIR}/modules" ${CMAKE_CURRENT_SOURCE_DIR}) + GET_FILENAME_COMPONENT(DIR_NAME ${DEST} DIRECTORY) + GET_FILENAME_COMPONENT(FOLDER_NAME ${DEST} NAME) + if(DIR_NAME) + set(SO "${DIR_NAME}.${FOLDER_NAME}") + else(DIR_NAME) + set(SO "${FOLDER_NAME}") + endif(DIR_NAME) # Set all the files to use C++ as well as set their compile flags (use the module-specific compile flags, though) set_source_files_properties(${MODULES_SUBDIR_SRCS} PROPERTIES LANGUAGE CXX COMPILE_FLAGS "${CXXFLAGS}") @@ -107,15 +120,15 @@ macro(build_subdir) set(WIN32_MEMORY) endif(MSVC) - # Generate the module and set it's linker flags, also set it to depend on the main Anope executable to be built beforehand + # Generate the module and set its linker flags, also set it to depend on the main Anope executable to be built beforehand add_library(${SO} MODULE ${MODULES_SUBDIR_SRCS}) - set_target_properties(${SO} PROPERTIES LINKER_LANGUAGE CXX PREFIX "" SUFFIX ".so" LINK_FLAGS "${SUBDIR_LDFLAGS}" INSTALL_RPATH_USE_LINK_PATH ON BUILD_WITH_INSTALL_RPATH ON) + set_target_properties(${SO} PROPERTIES LINKER_LANGUAGE CXX PREFIX "" LINK_FLAGS "${SUBDIR_LDFLAGS}" INSTALL_RPATH_USE_LINK_PATH ON BUILD_WITH_INSTALL_RPATH ON OUTPUT_NAME "${FOLDER_NAME}") add_dependencies(${SO} ${PROGRAM_NAME}) if(GETTEXT_FOUND) add_dependencies(${SO} module_language) endif(GETTEXT_FOUND) target_link_libraries(${SO} ${SUBDIR_EXTRA_DEPENDS}) - # For Windows only, have the module link to the export library of Anope as well as wsock32 and Ws2_32 libraries (most of the modules probably don't need this, but this is to be on the safe side), also set it's version + # For Windows only, have the module link to the export library of Anope as well as wsock32 and Ws2_32 libraries (most of the modules probably don't need this, but this is to be on the safe side), also set its version if(WIN32) target_link_libraries(${SO} ${PROGRAM_NAME} wsock32 Ws2_32 ${WIN32_MEMORY}) set_target_properties(${PROGRAM_NAME} PROPERTIES VERSION "${VERSION_DOTTED}") @@ -125,10 +138,8 @@ macro(build_subdir) endif(APPLE) endif(WIN32) - # Set the module to be installed to the module directory under the data directory - install(TARGETS ${SO} DESTINATION ${LIB_DIR}/modules) + install(TARGETS ${SO} DESTINATION ${LIB_DIR}/modules/${DIR_NAME}) endmacro(build_subdir) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/botserv/CMakeLists.txt b/modules/botserv/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/botserv/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/botserv/assign.cpp b/modules/botserv/assign.cpp new file mode 100644 index 000000000..840651dfc --- /dev/null +++ b/modules/botserv/assign.cpp @@ -0,0 +1,281 @@ +/* BotServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/bs_info.h" + +class CommandBSAssign : public Command +{ + public: + CommandBSAssign(Module *creator) : Command(creator, "botserv/assign", 2, 2) + { + this->SetDesc(_("Assigns a bot to a channel")); + this->SetSyntax(_("\037channel\037 \037nickname\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &nick = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, bot assignment is temporarily disabled.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + ServiceBot *bi = ServiceBot::Find(nick, true); + if (!bi) + { + source.Reply(_("Bot \002{0}\002 does not exist."), nick); + return; + } + + ChanServ::AccessGroup access = source.AccessFor(ci); + if (!access.HasPriv("ASSIGN") && !source.HasPriv("botserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "ASSIGN", ci->GetName()); + return; + } + + if (ci->HasFieldS("BS_NOBOT")) + { + source.Reply(_("Access denied. \002{0}\002 may not have a bot assigned to it because a Services Operator has disallowed it."), ci->GetName()); + return; + } + + if (bi->bi->GetOperOnly() && !source.HasPriv("botserv/administration")) + { + source.Reply(_("Access denied. Bot \002{0}\002 is for operators only."), bi->nick); + return; + } + + if (ci->GetBot() == bi) + { + source.Reply(_("Bot \002{0}\002 is already assigned to \002{1}\002."), ci->GetBot()->nick, ci->GetName()); + return; + } + + bool override = !access.HasPriv("ASSIGN"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << bi->nick; + + bi->Assign(source.GetUser(), ci); + source.Reply(_("Bot \002{0}\002 has been assigned to \002{1}\002."), bi->nick, ci->GetName()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Assigns the bot \037nickname\037 to \037channel\037." + " You can then configure the bot for the channel so it fits your needs.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037." + "\n" + "Example:\n" + " {command} #anope Botox\n" + " Assigns the bot Botox to #anope.\n"), + "ASSIGN", "command"_kw = source.command); + return true; + } +}; + +class CommandBSUnassign : public Command +{ + public: + CommandBSUnassign(Module *creator) : Command(creator, "botserv/unassign", 1, 1) + { + this->SetDesc(_("Unassigns a bot from a channel")); + this->SetSyntax(_("\037channel\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, bot assignment is temporarily disabled.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + ChanServ::AccessGroup access = source.AccessFor(ci); + if (!source.HasPriv("botserv/administration") && !access.HasPriv("ASSIGN")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "ASSIGN", ci->GetName()); + return; + } + + if (!ci->GetBot()) + { + source.Reply(_("There is no bot assigned to \002{0}\002."), ci->GetName()); + return; + } + + if (ci->HasFieldS("PERSIST") && !ModeManager::FindChannelModeByName("PERM")) + { + source.Reply(_("You cannot unassign bots while persist is set on the channel.")); + return; + } + + bool override = !access.HasPriv("ASSIGN"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << ci->GetBot()->nick; + + ServiceBot *bi = ci->GetBot(); + bi->UnAssign(source.GetUser(), ci); + source.Reply(_("Bot \002{0}\002 has been unassigned from \002{1}\002."), bi->nick, ci->GetName()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Unassigns a bot from \037channel\037." + "Bot configuration is kept, so you will always be able to reassign a bot later without losing your settings.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037." + "\n" + "Example:\n" + " {command} #anope\n" + " Unassigns the current bot from #anope.\n"), + "ASSIGN", "command"_kw = source.command); + return true; + } +}; + +class CommandBSSetNoBot : public Command +{ + public: + CommandBSSetNoBot(Module *creator, const Anope::string &sname = "botserv/set/nobot") : Command(creator, sname, 2, 2) + { + this->SetDesc(_("Prevent a bot from being assigned to a channel")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &value = params[1]; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (value.equals_ci("ON")) + { + Log(LOG_ADMIN, source, this, ci) << "to enable nobot"; + + ci->SetS("BS_NOBOT", true); + if (ci->GetBot()) + ci->GetBot()->UnAssign(source.GetUser(), ci); + source.Reply(_("No-bot mode is now \002on\002 for \002{0}\002."), ci->GetName()); + } + else if (value.equals_ci("OFF")) + { + Log(LOG_ADMIN, source, this, ci) << "to disable nobot"; + + ci->UnsetS("BS_NOBOT"); + source.Reply(_("No-bot mode is now \002off\002 for \002{0}\002."), ci->GetName()); + } + else + this->OnSyntaxError(source, source.command); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("If no-bot is set on a channel, then the channel will be unabled to have a bot assigned to it." + " If a bot is already assigned to the channel, it is unassigned automatically when no-bot is enabled." + "\n" + "Example:\n" + " {command} #anope on\n" + " Prevents a service bot from being assigned to #anope.\n"), + "command"_kw = source.command); + return true; + } +}; + +class BSAssign : public Module + , public EventHook + , public EventHook +{ + Serialize::Field nobot; + + CommandBSAssign commandbsassign; + CommandBSUnassign commandbsunassign; + CommandBSSetNoBot commandbssetnobot; + + public: + BSAssign(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + + , nobot(this, botinfo, "BS_NOBOT") + + , commandbsassign(this) + , commandbsunassign(this) + , commandbssetnobot(this) + { + } + + void OnInvite(User *source, Channel *c, User *targ) override + { + ServiceBot *bi; + if (Anope::ReadOnly || !c->ci || targ->server != Me || !(bi = ServiceBot::Find(targ->nick, true))) + return; + + ChanServ::AccessGroup access = c->ci->AccessFor(source); + if (!access.HasPriv("ASSIGN") && !source->HasPriv("botserv/administration")) + { + targ->SendMessage(bi, _("Access denied. You do not have privilege \002ASSIGN\002 on \002{0}\002."), c->ci->GetName()); + return; + } + + if (nobot.HasExt(c->ci)) + { + targ->SendMessage(bi, _("Access denied. \002{0}\002 may not have a bot assigned to it because a Services Operator has disallowed it."), c->ci->GetName()); + return; + } + + if (bi->bi->GetOperOnly() && !source->HasPriv("botserv/administration")) + { + targ->SendMessage(bi, _("Access denied. Bot \002{0}\002 is for operators only."), bi->nick); + return; + } + + if (c->ci->GetBot() == bi) + { + targ->SendMessage(bi, _("Bot \002{0}\002 is already assigned to \002{1}\002."), bi->nick, c->ci->GetName()); + return; + } + + bi->Assign(source, c->ci); + targ->SendMessage(bi, _("Bot \002{0}\002 has been assigned to \002{1}\002."), bi->nick, c->ci->GetName()); + } + + void OnServiceBot(CommandSource &source, ServiceBot *bi, ChanServ::Channel *ci, InfoFormatter &info) override + { + if (nobot.HasExt(ci)) + info.AddOption(_("No bot")); + } +}; + +MODULE_INIT(BSAssign) diff --git a/modules/botserv/autoassign.cpp b/modules/botserv/autoassign.cpp new file mode 100644 index 000000000..8978499ed --- /dev/null +++ b/modules/botserv/autoassign.cpp @@ -0,0 +1,37 @@ +/* + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + */ + +#include "module.h" + +class BSAutoAssign : public Module + , public EventHook +{ + public: + BSAutoAssign(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + { + } + + void OnChanRegistered(ChanServ::Channel *ci) override + { + const Anope::string &bot = Config->GetModule(this)->Get("bot"); + if (bot.empty()) + return; + + ServiceBot *bi = ServiceBot::Find(bot, true); + if (bi == NULL) + { + Log(this) << "bs_autoassign is configured to assign bot " << bot << ", but it does not exist?"; + return; + } + + bi->Assign(NULL, ci); + } +}; + +MODULE_INIT(BSAutoAssign) diff --git a/modules/botserv/badwords.cpp b/modules/botserv/badwords.cpp new file mode 100644 index 000000000..0f8e9e7d0 --- /dev/null +++ b/modules/botserv/badwords.cpp @@ -0,0 +1,465 @@ +/* BotServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/bs_badwords.h" + +static EventHandlers *bwevents; + +class BadWordImpl : public BadWord +{ + public: + BadWordImpl(Serialize::TypeBase *type) : BadWord(type) { } + BadWordImpl(Serialize::TypeBase *type, Serialize::ID id) : BadWord(type, id) { } + + ChanServ::Channel *GetChannel() override; + + void SetChannel(ChanServ::Channel *c) override; + + Anope::string GetWord() override; + + void SetWord(const Anope::string &w) override; + + BadWordType GetType() override; + + void SetType(const BadWordType &t) override; +}; + +class BadWordsType : public Serialize::Type +{ + public: + Serialize::ObjectField channel; + Serialize::Field word; + Serialize::Field type; + + BadWordsType(Module *me) : Serialize::Type(me, "BadWord") + , channel(this, "ci", true) + , word(this, "word") + , type(this, "type") + { + } +}; + +ChanServ::Channel *BadWordImpl::GetChannel() +{ + return Get(&BadWordsType::channel); +} + +void BadWordImpl::SetChannel(ChanServ::Channel *c) +{ + Set(&BadWordsType::channel, c); +} + +Anope::string BadWordImpl::GetWord() +{ + return Get(&BadWordsType::word); +} + +void BadWordImpl::SetWord(const Anope::string &w) +{ + Set(&BadWordsType::word, w); +} + +BadWordType BadWordImpl::GetType() +{ + return Get(&BadWordsType::type); +} + +void BadWordImpl::SetType(const BadWordType &t) +{ + Set(&BadWordsType::type, t); +} + +struct BadWordsImpl : BadWords +{ + BadWordsImpl(Module *me) : BadWords(me) { } + + BadWord* AddBadWord(ChanServ::Channel *ci, const Anope::string &word, BadWordType type) override + { + BadWord *bw = badword.Create(); + bw->SetChannel(ci); + bw->SetWord(word); + bw->SetType(type); + + (*bwevents)(&Event::BadWordEvents::OnBadWordAdd, ci, bw); + + return bw; + } + + std::vector GetBadWords(ChanServ::Channel *ci) + { + return ci->GetRefs(badword); + } + + BadWord* GetBadWord(ChanServ::Channel *ci, unsigned index) override + { + auto bw = GetBadWords(ci); + return index < bw.size() ? bw[index] : nullptr; + } + + unsigned GetBadWordCount(ChanServ::Channel *ci) override + { + return GetBadWords(ci).size(); + } + + void EraseBadWord(ChanServ::Channel *ci, unsigned index) override + { + auto bws = GetBadWords(ci); + if (index >= bws.size()) + return; + + BadWord *bw = bws[index]; + (*bwevents)(&Event::BadWordEvents::OnBadWordDel, ci, bw); + + delete bw; + } + + void ClearBadWords(ChanServ::Channel *ci) override + { + for (BadWord *bw : GetBadWords(ci)) + delete bw; + } +}; + +class CommandBSBadwords : public Command +{ + private: + void DoList(CommandSource &source, ChanServ::Channel *ci, const Anope::string &word) + { + bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "LIST"; + ListFormatter list(source.GetAccount()); + + list.AddColumn(_("Number")).AddColumn(_("Word")).AddColumn(_("Type")); + + if (!badwords->GetBadWordCount(ci)) + { + source.Reply(_("The bad word list of \002{0}\002 is empty."), ci->GetName()); + return; + } + + if (!word.empty() && word.find_first_not_of("1234567890,-") == Anope::string::npos) + { + NumberList(word, false, + [&](unsigned int num) + { + if (!num || num > badwords->GetBadWordCount(ci)) + return; + + BadWord *b = badwords->GetBadWord(ci, num - 1); + ListFormatter::ListEntry entry; + entry["Number"] = stringify(num); + entry["Word"] = b->GetWord(); + entry["Type"] = b->GetType() == BW_SINGLE ? "(SINGLE)" : (b->GetType() == BW_START ? "(START)" : (b->GetType() == BW_END ? "(END)" : "")); + list.AddEntry(entry); + }, + [](){}); + } + else + { + for (unsigned i = 0, end = badwords->GetBadWordCount(ci); i < end; ++i) + { + BadWord *b = badwords->GetBadWord(ci, i); + + if (!word.empty() && !Anope::Match(b->GetWord(), word)) + continue; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(i + 1); + entry["Word"] = b->GetWord(); + entry["Type"] = b->GetType() == BW_SINGLE ? "(SINGLE)" : (b->GetType() == BW_START ? "(START)" : (b->GetType() == BW_END ? "(END)" : "")); + list.AddEntry(entry); + } + } + + if (list.IsEmpty()) + { + source.Reply(_("No matching entries on the bad word list of \002{0}\002."), ci->GetName()); + return; + } + + std::vector replies; + list.Process(replies); + + source.Reply(_("Bad words list for \002{0}\002:"), ci->GetName()); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of bad words list.")); + } + + void DoAdd(CommandSource &source, ChanServ::Channel *ci, const Anope::string &word) + { + size_t pos = word.rfind(' '); + BadWordType bwtype = BW_ANY; + Anope::string realword = word; + + if (pos != Anope::string::npos) + { + Anope::string opt = word.substr(pos + 1); + if (!opt.empty()) + { + if (opt.equals_ci("SINGLE")) + bwtype = BW_SINGLE; + else if (opt.equals_ci("START")) + bwtype = BW_START; + else if (opt.equals_ci("END")) + bwtype = BW_END; + } + realword = word.substr(0, pos); + } + + unsigned badwordsmax = Config->GetModule(this->module)->Get("badwordsmax"); + if (badwords->GetBadWordCount(ci) >= badwordsmax) + { + source.Reply(_("Sorry, you can only have \002{0}\002 bad words entries on a channel."), badwordsmax); + return; + } + + bool casesensitive = Config->GetModule(this->module)->Get("casesensitive"); + + for (BadWord *bw : badwords->GetBadWords(ci)) + if ((casesensitive && realword.equals_cs(bw->GetWord())) || (!casesensitive && realword.equals_ci(bw->GetWord()))) + { + source.Reply(_("\002{0}\002 already exists in \002{1}\002 bad words list."), bw->GetWord(), ci->GetName()); + return; + } + + bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "ADD " << realword; + badwords->AddBadWord(ci, realword, bwtype); + + source.Reply(_("\002{0}\002 added to \002{1}\002 bad words list."), realword, ci->GetName()); + } + + void DoDelete(CommandSource &source, ChanServ::Channel *ci, const Anope::string &word) + { + if (!badwords->GetBadWordCount(ci)) + { + source.Reply(_("Bad word list for \002{0}\002 is empty."), ci->GetName()); + return; + } + + bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); + + /* Special case: is it a number/list? Only do search if it isn't. */ + if (!word.empty() && isdigit(word[0]) && word.find_first_not_of("1234567890,-") == Anope::string::npos) + { + unsigned int deleted = 0; + + NumberList(word, true, + [&](unsigned int num) + { + if (!num || num > badwords->GetBadWordCount(ci)) + return; + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "DEL " << badwords->GetBadWord(ci, num - 1)->GetWord(); + ++deleted; + badwords->EraseBadWord(ci, num - 1); + }, + [&]() + { + if (!deleted) + source.Reply(_("No matching entries on the bad word list of \002{0}\002."), ci->GetName()); + else if (deleted == 1) + source.Reply(_("Deleted \0021\002 entry from bad word list of \002{0}\002."), ci->GetName()); + else + source.Reply(_("Deleted \002{0}\002 entries from the bad word list of \002{1}\002."), deleted, ci->GetName()); + }); + } + else + { + unsigned i, end; + BadWord *bw; + + for (i = 0, end = badwords->GetBadWordCount(ci); i < end; ++i) + { + bw = badwords->GetBadWord(ci, i); + + if (word.equals_ci(bw->GetWord())) + break; + } + + if (i == end) + { + source.Reply(_("\002{0}\002 was not found on the bad word list of \002{1}\002."), word, ci->GetName()); + return; + } + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "DEL " << bw->GetWord(); + + source.Reply(_("\002{0}\002 deleted from \002{1}\002 bad words list."), bw->GetWord(), ci->GetName()); + + bw->Delete(); + } + } + + void DoClear(CommandSource &source, ChanServ::Channel *ci) + { + bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "CLEAR"; + + badwords->ClearBadWords(ci); + source.Reply(_("Bad words list is now empty.")); + } + + public: + CommandBSBadwords(Module *creator) : Command(creator, "botserv/badwords", 2, 3) + { + this->SetDesc(_("Maintains the bad words list")); + this->SetSyntax(_("\037channel\037 ADD \037word\037 [\037SINGLE\037 | \037START\037 | \037END\037]")); + this->SetSyntax(_("\037channel\037 DEL {\037word\037 | \037entry-num\037 | \037list\037}")); + this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 CLEAR")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &cmd = params[1]; + const Anope::string &word = params.size() > 2 ? params[2] : ""; + bool need_args = cmd.equals_ci("LIST") || cmd.equals_ci("CLEAR"); + + if (!need_args && word.empty()) + { + this->OnSyntaxError(source, cmd); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!source.AccessFor(ci).HasPriv("BADWORDS") && (!need_args || !source.HasPriv("botserv/administration"))) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "BADWORDS", ci->GetName()); + return; + } + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, bad words list modification is temporarily disabled.")); + return; + } + + if (cmd.equals_ci("ADD")) + this->DoAdd(source, ci, word); + else if (cmd.equals_ci("DEL")) + this->DoDelete(source, ci, word); + else if (cmd.equals_ci("LIST")) + this->DoList(source, ci, word); + else if (cmd.equals_ci("CLEAR")) + this->DoClear(source, ci); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + { + source.Reply(_("Adds \037word\037 to the bad words list for \037channel\037. If \002SINGLE\002 is specified then the user must say the enire word for it to be considered a match." + " If \002START\002 is specified then the user only must say a word that starts with \037word\037." + " Likewise if \002END\002 is specified then the user must only say a word that ends with \037word\037." + " If no argument is specified, then any word which contains \037word\037 will be considered a match.\n" + "\n" + "Examples:\n" + " {command} #anope ADD raw SINGLE\n" + " Adds the bad word \"raw\" to the bad word list of #anope."), + "command"_kw = source.command); + } + else if (subcommand.equals_ci("DEL")) + { + source.Reply(_("Removes \037word\037 from the bad words list for \037channel\037. If a number or list of numbers is given, those entries are deleted.\n" + "\n" + "Examples:\n" + " {command} #anope DEL raw\n" + " Removes the bad word \"raw\" from the bad word list of #anope.\n" + "\n" + " {command} #anope DEL 2-5,7-9\n" + " Removes bad words entries numbered 2 through 5 and 7 through 9 on #anope."), + "command"_kw = source.command); + + } + else if (subcommand.equals_ci("LIST")) + { + source.Reply(_("Lists the badwords for \037channel\037." + " If a wildcard mask is given, only those entries matching the mask are displayed." + " If a list of entry numbers is given, only those entries are shown.\n" + "\n" + "Examples:\n" + " {command} #anope LIST\n" + " Lists the bad words for #anope.\n" + "\n" + " {command} #anope LIST 2-5,7-9\n" + " Lists bad words entries numbered 2 thorough 5 and 7 through 9 on #anope."), + "command"_kw = source.command); + } + else if (subcommand.equals_ci("CLEAR")) + { + source.Reply(_("Clears the bad words for \037channel\037." + "\n" + "\n" + "Example:\n" + " {command} #anope CLEAR\n" + " Clears the bad word list for #anope."), + "command"_kw = source.command); + } + else + { + source.Reply(_("Maintains the bad words list for a channel." + " When a word on the bad words list is said, action may be taken against the offending user.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037."), + "BADWORDS"); + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("\n" + "For help configuring the bad word kicker use \002{msg}{service} HELP KICK BADWORDS\002.\n"//XXX + "\n" + "The \002ADD\002 command adds \037word\037 to the badwords list.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "The \002DEL\002 command removes \037word\037 from the badwords list.\n" + "\002{msg}{service} {help} {command} DEL\002 for more information.\n" + "\n" + "The \002LIST\002 and \002CLEAR\002 commands show and clear the bad words list, respectively.\n" + "\002{msg}{service} {help} {command} LIST\002 and \002{msg}{service} {help} {command} CLEAR\002 for more information.\n"), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "command"_kw = source.command, "help"_kw = help->cname); + } + return true; + } +}; + +class BSBadwords : public Module +{ + CommandBSBadwords commandbsbadwords; + BadWordsImpl badwords; + EventHandlers events; + BadWordsType bwtype; + + public: + BSBadwords(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandbsbadwords(this) + , badwords(this) + , events(this) + , bwtype(this) + { + bwevents = &events; + } +}; + +MODULE_INIT(BSBadwords) diff --git a/modules/botserv/bot.cpp b/modules/botserv/bot.cpp new file mode 100644 index 000000000..726c02ab2 --- /dev/null +++ b/modules/botserv/bot.cpp @@ -0,0 +1,426 @@ +/* BotServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/bs_bot.h" + +class CommandBSBot : public Command +{ + EventHandlers OnBotCreate; + EventHandlers OnBotChange; + EventHandlers OnBotDelete; + + void DoAdd(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &nick = params[1]; + const Anope::string &user = params[2]; + const Anope::string &host = params[3]; + const Anope::string &real = params[4]; + + if (ServiceBot::Find(nick, true)) + { + source.Reply(_("Bot \002{0}\002 already exists."), nick); + return; + } + + Configuration::Block *networkinfo = Config->GetBlock("networkinfo"); + + if (nick.length() > networkinfo->Get("nicklen")) + { + source.Reply(_("Bot nicks may only be \002{0}\002 characters long."), networkinfo->Get("nicklen")); + return; + } + + if (user.length() > networkinfo->Get("userlen")) + { + source.Reply(_("Bot idents may only be \002{0}\002 characters long."), networkinfo->Get("userlen")); + return; + } + + if (host.length() > networkinfo->Get("hostlen")) + { + source.Reply(_("Bot hosts may only be \002{0}\002 characters long."), networkinfo->Get("hostlen")); + return; + } + + if (!IRCD->IsNickValid(nick)) + { + source.Reply(_("Bot nicks may only contain valid nick characters.")); + return; + } + + if (!IRCD->IsIdentValid(user)) + { + source.Reply(_("Bot idents may only contain valid ident characters.")); + return; + } + + if (!IRCD->IsHostValid(host)) + { + source.Reply(_("Bot hosts may only contain valid host characters.")); + return; + } + + /* We check whether the nick is registered, and inform the user + * if so. You need to drop the nick manually before you can use + * it as a bot nick from now on -GD + */ + NickServ::Nick *na = NickServ::FindNick(nick); + if (na) + { + source.Reply(_("\002{0}\002 is already registered!"), na->GetNick()); + return; + } + + User *targ = User::Find(nick, true); + if (targ) + { + source.Reply(_("\002{0}\002 is currently in use."), targ->nick); + return; + } + + ServiceBot *bi = new ServiceBot(nick, user, host, real); + + Log(LOG_ADMIN, source, this) << "ADD " << bi->GetMask() << " " << bi->realname; + + source.Reply(_("\002{0}!{1}@{2}\002 (\002{3}\002) added to the bot list."), bi->nick, bi->GetIdent(), bi->host, bi->realname); + + this->OnBotCreate(&Event::BotCreate::OnBotCreate, bi); + } + + void DoChange(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &oldnick = params[1]; + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + const Anope::string &user = params.size() > 3 ? params[3] : ""; + const Anope::string &host = params.size() > 4 ? params[4] : ""; + const Anope::string &real = params.size() > 5 ? params[5] : ""; + + if (oldnick.empty() || nick.empty()) + { + this->OnSyntaxError(source, "CHANGE"); + return; + } + + ServiceBot *bi = ServiceBot::Find(oldnick, true); + if (!bi) + { + source.Reply(_("Bot \002{0}\002 does not exist."), oldnick); + return; + } + + if (bi->bi->conf) + { + source.Reply(_("Bot \002{0}\002 is not changeable because it is configured in services configuration."), bi->nick.c_str()); + return; + } + + Configuration::Block *networkinfo = Config->GetBlock("networkinfo"); + + if (nick.length() > networkinfo->Get("nicklen")) + { + source.Reply(_("Bot nicknames may only be \002{0}\002 characters long."), networkinfo->Get("nicklen")); + return; + } + + if (user.length() > networkinfo->Get("userlen")) + { + source.Reply(_("Bot usernames may only be \002{0}\002 characters long."), networkinfo->Get("userlen")); + return; + } + + if (host.length() > networkinfo->Get("hostlen")) + { + source.Reply(_("Bot hostnames may only be \002{0}\002 characters long."), networkinfo->Get("hostlen")); + return; + } + + /* Checks whether there *are* changes. + * Case sensitive because we may want to change just the case. + * And we must finally check that the nick is not already + * taken by another bot. + */ + if (nick.equals_cs(bi->nick) + && (user.empty() || user.equals_cs(bi->GetIdent())) + && (host.empty() || host.equals_cs(bi->host)) + && (real.empty() || real.equals_cs(bi->realname))) + { + source.Reply(_("There is no difference between the current settings and the new settings.")); + return; + } + + if (!IRCD->IsNickValid(nick)) + { + source.Reply(_("Bot nicknames may only contain valid nickname characters.")); + return; + } + + if (!user.empty() && !IRCD->IsIdentValid(user)) + { + source.Reply(_("Bot uesrnames may only contain valid username characters.")); + return; + } + + if (!host.empty() && !IRCD->IsHostValid(host)) + { + source.Reply(_("Bot hostnames may only contain valid hostname characters.")); + return; + } + + ServiceBot *newbi = ServiceBot::Find(nick, true); + if (newbi && bi != newbi) + { + source.Reply(_("Bot \002{0}\002 already exists."), newbi->nick); + return; + } + + User *target = User::Find(nick, true); + if (target) + { + source.Reply(_("\002{0}\002 is currently in use."), target->nick); + return; + } + + if (!nick.equals_ci(bi->nick)) + { + /* We check whether the nick is registered, and inform the user + * if so. You need to drop the nick manually before you can use + * it as a bot nick from now on -GD + */ + NickServ::Nick *na = NickServ::FindNick(nick); + if (na) + { + source.Reply(_("\002{0}\002 is already registered."), na->GetNick()); + return; + } + + /* The new nick is really different, so we remove the Q line for the old nick. */ + //XLine x_del(bi->nick); + //IRCD->SendSQLineDel(&x_del); + + /* Add a Q line for the new nick */ + //XLine x(nick, "Reserved for services"); + //IRCD->SendSQLine(NULL, &x); + } + + if (!user.empty()) + { + IRCD->SendQuit(bi, "Quit: Be right back"); + bi->introduced = false; + } + else + IRCD->SendNickChange(bi, nick); + + if (!nick.equals_cs(bi->nick)) + { + bi->SetNewNick(nick); + bi->bi->SetNick(nick); + } + + if (!user.equals_cs(bi->GetIdent())) + { + bi->SetIdent(user); + bi->bi->SetUser(user); + } + if (!host.equals_cs(bi->host)) + { + bi->host = host; + bi->bi->SetHost(host); + } + if (real.equals_cs(bi->realname)) + { + bi->realname = real; + bi->bi->SetRealName(real); + } + + if (!user.empty()) + bi->OnKill(); + + source.Reply(_("Bot \002{0}\002 has been changed to \002{1}!{2}@{3}\002 (\002{4}\002)."), oldnick, bi->nick, bi->GetIdent(), bi->host, bi->realname); + Log(LOG_ADMIN, source, this) << "CHANGE " << oldnick << " to " << bi->GetMask() << " " << bi->realname; + + this->OnBotChange(&Event::BotChange::OnBotChange, bi); + } + + void DoDel(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &nick = params[1]; + + if (nick.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + ServiceBot *bi = ServiceBot::Find(nick, true); + if (!bi) + { + source.Reply(_("Bot \002{0}\002 does not exist."), nick); + return; + } + + if (bi->bi->conf) + { + source.Reply(_("Bot \002{0}\002 is can not be deleted because it is configured in services configuration."), bi->nick); + return; + } + + this->OnBotDelete(&Event::BotDelete::OnBotDelete, bi); + + Log(LOG_ADMIN, source, this) << "DEL " << bi->nick; + + source.Reply(_("Bot \002{0}\002 has been deleted."), bi->nick); + delete bi; + } + + public: + CommandBSBot(Module *creator) : Command(creator, "botserv/bot", 1, 6) + , OnBotCreate(creator) + , OnBotChange(creator) + , OnBotDelete(creator) + { + this->SetDesc(_("Maintains network bot list")); + this->SetSyntax(_("\002ADD \037nicknae\037 \037username\037 \037hostname\037 \037realname\037\002")); + this->SetSyntax(_("\002CHANGE \037oldnickname\037 \037newnickname\037 [\037username\037 [\037hostname\037 [\037realname\037]]]\002")); + this->SetSyntax(_("\002DEL \037nickname\037\002")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + if (cmd.equals_ci("ADD")) + { + if (!source.HasCommand("botserv/bot/add")) + { + source.Reply(_("Access denied. You do not have access to the operator command \002{0}\002."), "botserv/bot/add"); + return; + } + + // ADD nick user host real - 5 + if (params.size() < 5) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + std::vector tempparams = params; + // ADD takes less params than CHANGE, so we need to take 6 if given and append it with a space to 5. + if (tempparams.size() >= 6) + tempparams[4] = tempparams[4] + " " + tempparams[5]; + + this->DoAdd(source, tempparams); + } + else if (cmd.equals_ci("CHANGE")) + { + // CHANGE oldn newn user host real - 6 + // but only oldn and newn are required + if (!source.HasCommand("botserv/bot/change")) + { + source.Reply(_("Access denied. You do not have access to the operator command \002{0}\002."), "botserv/bot/change"); + return; + } + + if (params.size() < 3) + { + this->OnSyntaxError(source, "CHANGE"); + return; + } + + this->DoChange(source, params); + } + else if (cmd.equals_ci("DEL")) + { + // DEL nick + if (!source.HasCommand("botserv/bot/del")) + { + source.Reply(_("Access denied. You do not have access to the operator command \002{0}\002."), "botserv/bot/del"); + return; + } + + if (params.size() < 1) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + this->DoDel(source, params); + } + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + source.Reply(_("\002{command} ADD\002 adds a bot with the given \037nickname\037, \037username\037, \037hostname\037 and \037realname\037." + " You can not create a bot with a nickname that is currently registered. If an unregistered user is currently using the nick, they will be killed.\n" + " Once a bot is created, users will be able to assign the bot to their channels. This command requires the opererator privilege for command \002{0}\002." + "\n" + "Example:\n" + " {command} ADD Botox Botox services.anope.org Botox\n" + " Adds a service bot with nickname \"Botox\", username \"Botox\", hostname \"services.anope.org\", and realname \"Botox\" to the bot list."), + "botserv/bot/add", "command"_kw = source.command); + else if (subcommand.equals_ci("CHANGE")) + source.Reply(_("\002{command} CHANGE\002 allows changing the \037nickname\037, \037username\037, \037hostname\037 and \037realname\037 of bot \037oldnickname\037." + " If a new username, hostname, or realname is specified, then the bot \037nickname\037 will quit and rejoin all of its channels using the new mask." + " Otherwise, the bot simply change nick to \037newnickname\037. All settings on the bot, such as channels and no-bot, are retained." + " This command requires the operator privilege for command \002{0}\002." + "\n" + "Example:\n" + " {command} CHANGE Botox peer connection reset.by peer\n" + " Renames the bot \"Botox\" to \"peer\" with the given username, hostname, and realname."), + "botserv/bot/change", "command"_kw = source.command); + else if (subcommand.equals_ci("DEL")) + source.Reply(_("\002{command} DEL\002 removes the bot \037nickname\037 from the bot list. The bot will quit from any channels it is in, and will not be replaced." + " This command requires the operator privilege for command \002{0}\002.\n" + "\n" + "Example:\n" + " {command} DEL peer\n" + " Removes the bot \"peer\" from the bot list."), + "botserv/bot/del", "command"_kw = source.command); + else + { + source.Reply(_("Allows Services Operators to create, modify, and delete bots that users will be able to use on their channels.")); + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("\n" + "The \002ADD\002 command adds a bot with the given \037nickname\037, \037username\037, \037hostname\037 and \037realname\037 to the bot list.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "The \002CHANGE\002 command allows changing the \037nickname\037, \037username\037, \037hostname\037 and \037realname\037 of bot \037oldnickname\037.\n" + "\002{msg}{service} {help} {command} CHANGE\002 for more information.\n" + "\n" + "The \002{command} DEL\002 removes the bot \037nickname\037 from the bot list.\n" + "\002{msg}{service} {help} {command} DEL\002 for more information."), + "msg"_kw = Config->StrictPrivmsg, "help"_kw = help->cname, "command"_kw = source.command); + } + return true; + } +}; + +class BSBot : public Module +{ + CommandBSBot commandbsbot; + + public: + BSBot(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandbsbot(this) + { + + } +}; + +MODULE_INIT(BSBot) diff --git a/modules/botserv/botlist.cpp b/modules/botserv/botlist.cpp new file mode 100644 index 000000000..ce899719e --- /dev/null +++ b/modules/botserv/botlist.cpp @@ -0,0 +1,92 @@ +/* BotServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandBSBotList : public Command +{ + public: + CommandBSBotList(Module *creator) : Command(creator, "botserv/botlist", 0, 0) + { + this->SetDesc(_("Lists available bots")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + unsigned count = 0; + ListFormatter list(source.GetAccount()); + + list.AddColumn(_("Nick")).AddColumn(_("Mask")); + + for (BotInfo *bi : Serialize::GetObjects(botinfo)) + { + if (source.HasPriv("botserv/administration") || !bi->GetOperOnly()) + { + ++count; + ListFormatter::ListEntry entry; + entry["Nick"] = (bi->GetOperOnly() ? "* " : "") + bi->GetNick(); + entry["Mask"] = bi->GetUser() + "@" + bi->GetHost(); + list.AddEntry(entry); + } + } + + std::vector replies; + list.Process(replies); + + if (!count) + { + source.Reply(_("There are no bots available")); + return; + } + + source.Reply(_("Bot list:")); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("{0} bots available."), count); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + ServiceBot *bi; + Anope::string name; + Command::FindCommandFromService("botserv/assign", bi, name); + if (!bi) + return false; + + source.Reply(_("Lists all available bots. You may use the \002{msg}{service} {assign}\002 command to assign a bot to your channel." + "The bot names are vanity; they all proviate the same commands and features."), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = bi->nick, "assign"_kw = name); + if (source.HasPriv("botserv/administration")) + source.Reply(_("Bots prefixed by a * are reserved for Services Operators with the privilege \002{0}\002."), + "botserv/administration"); + source.Reply(_("\n" + "Example:\n" + " {command} BOTLIST"), + "command"_kw = source.command); + return true; + } +}; + +class BSBotList : public Module +{ + CommandBSBotList commandbsbotlist; + + public: + BSBotList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandbsbotlist(this) + { + + } +}; + +MODULE_INIT(BSBotList) diff --git a/modules/botserv/control.cpp b/modules/botserv/control.cpp new file mode 100644 index 000000000..52c0933c5 --- /dev/null +++ b/modules/botserv/control.cpp @@ -0,0 +1,168 @@ +/* BotServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandBSSay : public Command +{ + public: + CommandBSSay(Module *creator) : Command(creator, "botserv/say", 2, 2) + { + this->SetDesc(_("Makes the bot say the specified text on the specified channel")); + this->SetSyntax(_("\037channel\037 \037text\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &text = params[1]; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!source.AccessFor(ci).HasPriv("SAY") && !source.HasPriv("botserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SAY", ci->GetName()); + return; + } + + if (!ci->GetBot()) + { + source.Reply(_("There is no bot assigned to \002{0}\002. One must be assigned to the channel before this command can be used."), ci->GetName()); + ServiceBot *bi; + Anope::string name; + Command::FindCommandFromService("botserv/assign", bi, name); + CommandInfo *help = source.service->FindCommand("generic/help"); + if (bi && help) + source.Reply(_("See \002{msg}{service} {help} {command}\002 for information on assigning bots."), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = bi->nick, "help"_kw = help->cname, "command"_kw = name); + return; + } + + if (!ci->c || !ci->c->FindUser(ci->GetBot())) + { + source.Reply(_("Bot \002{0}\002 is not on channel \002{1}\002."), ci->GetBot()->nick, ci->GetName()); + return; + } + + if (text[0] == '\001') + { + this->OnSyntaxError(source, ""); + return; + } + + IRCD->SendPrivmsg(ci->GetBot(), ci->GetName(), "%s", text.c_str()); + ci->GetBot()->lastmsg = Anope::CurTime; + + bool override = !source.AccessFor(ci).HasPriv("SAY"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to say: " << text; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Makes the bot say the given \037text\037 on \037channel\037.\n" + "\n" + "Example:\n" + " {command} #anope mmm pie\n" + " Makes the assigned service bot say \"mmm pie\".")); + return true; + } +}; + +class CommandBSAct : public Command +{ + public: + CommandBSAct(Module *creator) : Command(creator, "botserv/act", 2, 2) + { + this->SetDesc(_("Makes the bot do the equivalent of a \"/me\" command")); + this->SetSyntax(_("\037channel\037 \037text\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + Anope::string message = params[1]; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!source.AccessFor(ci).HasPriv("SAY") && !source.HasPriv("botserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SAY", ci->GetName()); + return; + } + + if (!ci->GetBot()) + { + source.Reply(_("There is no bot assigned to \002{0}\002. One must be assigned to the channel before this command can be used."), ci->GetName()); + ServiceBot *bi; + Anope::string name; + Command::FindCommandFromService("botserv/assign", bi, name); + CommandInfo *help = source.service->FindCommand("generic/help"); + if (bi && help) + source.Reply(_("See \002{msg}{service} {help} {command}\002 for information on assigning bots."), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = bi->nick, "help"_kw = help->cname, "command"_kw = name); + return; + } + + if (!ci->c || !ci->c->FindUser(ci->GetBot())) + { + source.Reply(_("Bot \002{0}\002 is not on channel \002{1}\002."), ci->GetBot()->nick, ci->GetName()); + return; + } + + message = message.replace_all_cs("\1", ""); + if (message.empty()) + return; + + IRCD->SendAction(ci->GetBot(), ci->GetName(), "%s", message.c_str()); + ci->GetBot()->lastmsg = Anope::CurTime; + + bool override = !source.AccessFor(ci).HasPriv("SAY"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to say: " << message; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Makes the assigned bot do the equivalent of a \"/me\" command on \037channel\037 using the given \037text\037.\n" + "\n" + "Example:\n" + " {command} #anope slaps Cronus\n" + " Shows the assigned service bot \"slapping\" Cronus."), + "command"_kw = source.command); + return true; + } +}; + +class BSControl : public Module +{ + CommandBSSay commandbssay; + CommandBSAct commandbsact; + + public: + BSControl(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandbssay(this), commandbsact(this) + { + + } +}; + +MODULE_INIT(BSControl) diff --git a/modules/botserv/info.cpp b/modules/botserv/info.cpp new file mode 100644 index 000000000..6d74c308e --- /dev/null +++ b/modules/botserv/info.cpp @@ -0,0 +1,116 @@ +/* BotServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + * + * + */ + +#include "module.h" +#include "modules/bs_info.h" + +class CommandBSInfo : public Command +{ + EventHandlers &onbotinfo; + + public: + CommandBSInfo(Module *creator, EventHandlers &event) : Command(creator, "botserv/info", 1, 1), onbotinfo(event) + { + this->SetSyntax(_("{\037channel\037 | \037nickname\037}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &query = params[0]; + + ServiceBot *bi = ServiceBot::Find(query, true); + ChanServ::Channel *ci = ChanServ::Find(query); + InfoFormatter info(source.nc); + + if (bi) + { + source.Reply(_("Information for bot \002%s\002:"), bi->nick.c_str()); + info[_("Mask")] = bi->GetIdent() + "@" + bi->host; + info[_("Real name")] = bi->realname; + info[_("Created")] = Anope::strftime(bi->bi->GetCreated(), source.GetAccount()); + info[_("Options")] = bi->bi->GetOperOnly() ? _("Private") : _("None"); + info[_("Used on")] = stringify(bi->GetChannelCount()) + " channel(s)"; + + this->onbotinfo(&Event::ServiceBotEvent::OnServiceBot, source, bi, ci, info); + + std::vector replies; + info.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + if (source.HasPriv("botserv/administration")) + { + Anope::string buf; + for (ChanServ::Channel *ci2 : bi->GetChannels()) + buf += " " + ci2->GetName(); + source.Reply(buf); + } + + } + else if (ci) + { + if (!source.AccessFor(ci).HasPriv("INFO") && !source.HasPriv("botserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "INFO", ci->GetName()); + return; + } + + source.Reply(_("Information for channel \002{0}\002:"), ci->GetName()); + info[_("Bot nick")] = ci->GetBot() ? ci->GetBot()->nick : _("not assigned yet"); + + Anope::string enabled = Language::Translate(source.nc, _("Enabled")); + Anope::string disabled = Language::Translate(source.nc, _("Disabled")); + + this->onbotinfo(&Event::ServiceBotEvent::OnServiceBot, source, bi, ci, info); + + std::vector replies; + info.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + else + source.Reply(_("\002{0}\002 is not a valid bot or registered channel."), query); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows you to see {0} information about a channel or a bot." + " If the parameter is a channel, then you'll get information such as enabled kickers." + " If the parameter is a bot nickname, you'll get information about a bot, such as creation time and number of channels it is on."), + source.service->nick); + return true; + } + + const Anope::string GetDesc(CommandSource &source) const override + { + return Anope::printf(Language::Translate(source.GetAccount(), _("Allows you to see %s information about a channel or a bot")), source.service->nick.c_str()); + } +}; + +class BSInfo : public Module +{ + CommandBSInfo commandbsinfo; + EventHandlers onbotinfo; + + public: + BSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandbsinfo(this, onbotinfo) + , onbotinfo(this) + { + + } +}; + +MODULE_INIT(BSInfo) diff --git a/modules/botserv/kick.cpp b/modules/botserv/kick.cpp new file mode 100644 index 000000000..70ed5b2ad --- /dev/null +++ b/modules/botserv/kick.cpp @@ -0,0 +1,1664 @@ +/* BotServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + * + * + */ + +#include "module.h" +#include "modules/bs_kick.h" +#include "modules/bs_badwords.h" +#include "modules/bs_info.h" + +static Module *me; + +enum TTBType +{ + TTB_BOLDS, + TTB_COLORS, + TTB_REVERSES, + TTB_UNDERLINES, + TTB_BADWORDS, + TTB_CAPS, + TTB_FLOOD, + TTB_REPEAT, + TTB_ITALICS, + TTB_AMSGS, + TTB_SIZE +}; + +class KickerDataImpl : public KickerData +{ + public: + KickerDataImpl(Serialize::TypeBase *type) : KickerData(type) { } + KickerDataImpl(Serialize::TypeBase *type, Serialize::ID id) : KickerData(type, id) { } + + ChanServ::Channel *GetChannel() override; + void SetChannel(ChanServ::Channel *) override; + + bool GetAmsgs() override; + void SetAmsgs(const bool &) override; + + bool GetBadwords() override; + void SetBadwords(const bool &) override; + + bool GetBolds() override; + void SetBolds(const bool &) override; + + bool GetCaps() override; + void SetCaps(const bool &) override; + + bool GetColors() override; + void SetColors(const bool &) override; + + bool GetFlood() override; + void SetFlood(const bool &) override; + + bool GetItalics() override; + void SetItalics(const bool &) override; + + bool GetRepeat() override; + void SetRepeat(const bool &) override; + + bool GetReverses() override; + void SetReverses(const bool &) override; + + bool GetUnderlines() override; + void SetUnderlines(const bool &) override; + + int16_t GetTTBBolds() override; + void SetTTBBolds(const int16_t &) override; + + int16_t GetTTBColors() override; + void SetTTBColors(const int16_t &) override; + + int16_t GetTTBReverses() override; + void SetTTBReverses(const int16_t &) override; + + int16_t GetTTBUnderlines() override; + void SetTTBUnderlines(const int16_t &) override; + + int16_t GetTTBBadwords() override; + void SetTTBBadwords(const int16_t &) override; + + int16_t GetTTBCaps() override; + void SetTTBCaps(const int16_t &) override; + + int16_t GetTTBFlood() override; + void SetTTBFlood(const int16_t &) override; + + int16_t GetTTBRepeat() override; + void SetTTBRepeat(const int16_t &) override; + + int16_t GetTTBItalics() override; + void SetTTBItalics(const int16_t &) override; + + int16_t GetTTBAmsgs() override; + void SetTTBAmsgs(const int16_t &) override; + + int16_t GetCapsMin() override; + void SetCapsMin(const int16_t &) override; + + int16_t GetCapsPercent() override; + void SetCapsPercent(const int16_t &) override; + + int16_t GetFloodLines() override; + void SetFloodLines(const int16_t &) override; + + int16_t GetFloodSecs() override; + void SetFloodSecs(const int16_t &) override; + + int16_t GetRepeatTimes() override; + void SetRepeatTimes(const int16_t &) override; + + bool GetDontKickOps() override; + void SetDontKickOps(const bool &) override; + + bool GetDontKickVoices() override; + void SetDontKickVoices(const bool &) override; +}; + +class KickerDataType : public Serialize::Type +{ + public: + Serialize::ObjectField channel; + Serialize::Field amsgs, badwords, bolds, caps, colors, flood, italics, repeat, reverses, underlines; + Serialize::Field ttb_bolds, ttb_colors, ttb_reverses, ttb_underlines, ttb_badwords, ttb_caps, ttb_flood, ttb_repeat, ttb_italics, ttb_amsgs, + capsmin, capspercent, + floodlines, floodsecs, + repeattimes; + Serialize::Field dontkickops, dontkickvoices; + + KickerDataType(Module *owner) : Serialize::Type(owner, "KickerData") + , channel(this, "channel", true) + , amsgs(this, "amsgs") + , badwords(this, "badwords") + , bolds(this, "bolds") + , caps(this, "caps") + , colors(this, "colors") + , flood(this, "flood") + , italics(this, "italics") + , repeat(this, "repeat") + , reverses(this, "reverses") + , underlines(this, "underlines") + , ttb_bolds(this, "ttb_bolds") + , ttb_colors(this, "ttb_colors") + , ttb_reverses(this, "ttb_reverses") + , ttb_underlines(this, "ttb_underlines") + , ttb_badwords(this, "ttb_badwords") + , ttb_caps(this, "ttb_caps") + , ttb_flood(this, "ttb_flood") + , ttb_repeat(this, "ttb_repeat") + , ttb_italics(this, "ttb_italics") + , ttb_amsgs(this, "ttb_amsgs") + , capsmin(this, "capsmin") + , capspercent(this, "capspercent") + , floodlines(this, "floodlines") + , floodsecs(this, "floodsecs") + , repeattimes(this, "repeattimes") + , dontkickops(this, "dontkickops") + , dontkickvoices(this, "dontkickvoices") + { + } +}; + +ChanServ::Channel *KickerDataImpl::GetChannel() +{ + return Get(&KickerDataType::channel); +} + +void KickerDataImpl::SetChannel(ChanServ::Channel *channel) +{ + Set(&KickerDataType::amsgs, channel); +} + +bool KickerDataImpl::GetAmsgs() +{ + return Get(&KickerDataType::amsgs); +} + +void KickerDataImpl::SetAmsgs(const bool &b) +{ + Set(&KickerDataType::amsgs, b); +} + +bool KickerDataImpl::GetBadwords() +{ + return Get(&KickerDataType::badwords); +} + +void KickerDataImpl::SetBadwords(const bool &b) +{ + Set(&KickerDataType::badwords, b); +} + +bool KickerDataImpl::GetBolds() +{ + return Get(&KickerDataType::bolds); +} + +void KickerDataImpl::SetBolds(const bool &b) +{ + Set(&KickerDataType::bolds, b); +} + +bool KickerDataImpl::GetCaps() +{ + return Get(&KickerDataType::caps); +} + +void KickerDataImpl::SetCaps(const bool &b) +{ + Set(&KickerDataType::caps, b); +} + +bool KickerDataImpl::GetColors() +{ + return Get(&KickerDataType::colors); +} + +void KickerDataImpl::SetColors(const bool &b) +{ + Set(&KickerDataType::colors, b); +} + +bool KickerDataImpl::GetFlood() +{ + return Get(&KickerDataType::flood); +} + +void KickerDataImpl::SetFlood(const bool &b) +{ + Set(&KickerDataType::flood, b); +} + +bool KickerDataImpl::GetItalics() +{ + return Get(&KickerDataType::italics); +} + +void KickerDataImpl::SetItalics(const bool &b) +{ + Set(&KickerDataType::italics, b); +} + +bool KickerDataImpl::GetRepeat() +{ + return Get(&KickerDataType::repeat); +} + +void KickerDataImpl::SetRepeat(const bool &b) +{ + Set(&KickerDataType::repeat, b); +} + +bool KickerDataImpl::GetReverses() +{ + return Get(&KickerDataType::reverses); +} + +void KickerDataImpl::SetReverses(const bool &b) +{ + Set(&KickerDataType::reverses, b); +} + +bool KickerDataImpl::GetUnderlines() +{ + return Get(&KickerDataType::underlines); +} + +void KickerDataImpl::SetUnderlines(const bool &b) +{ + Set(&KickerDataType::underlines, b); +} + +int16_t KickerDataImpl::GetTTBBolds() +{ + return Get(&KickerDataType::ttb_bolds); +} + +void KickerDataImpl::SetTTBBolds(const int16_t &i) +{ + Set(&KickerDataType::ttb_bolds, i); +} + +int16_t KickerDataImpl::GetTTBColors() +{ + return Get(&KickerDataType::ttb_colors); +} + +void KickerDataImpl::SetTTBColors(const int16_t &i) +{ + Set(&KickerDataType::ttb_colors, i); +} + +int16_t KickerDataImpl::GetTTBReverses() +{ + return Get(&KickerDataType::ttb_reverses); +} + +void KickerDataImpl::SetTTBReverses(const int16_t &i) +{ + Set(&KickerDataType::ttb_reverses, i); +} + +int16_t KickerDataImpl::GetTTBUnderlines() +{ + return Get(&KickerDataType::ttb_underlines); +} + +void KickerDataImpl::SetTTBUnderlines(const int16_t &i) +{ + Set(&KickerDataType::ttb_underlines, i); +} + +int16_t KickerDataImpl::GetTTBBadwords() +{ + return Get(&KickerDataType::ttb_badwords); +} + +void KickerDataImpl::SetTTBBadwords(const int16_t &i) +{ + Set(&KickerDataType::ttb_badwords, i); +} + +int16_t KickerDataImpl::GetTTBCaps() +{ + return Get(&KickerDataType::ttb_caps); +} + +void KickerDataImpl::SetTTBCaps(const int16_t &i) +{ + Set(&KickerDataType::ttb_caps, i); +} + +int16_t KickerDataImpl::GetTTBFlood() +{ + return Get(&KickerDataType::ttb_flood); +} + +void KickerDataImpl::SetTTBFlood(const int16_t &i) +{ + Set(&KickerDataType::ttb_flood, i); +} + +int16_t KickerDataImpl::GetTTBRepeat() +{ + return Get(&KickerDataType::ttb_repeat); +} + +void KickerDataImpl::SetTTBRepeat(const int16_t &i) +{ + Set(&KickerDataType::ttb_repeat, i); +} + +int16_t KickerDataImpl::GetTTBItalics() +{ + return Get(&KickerDataType::ttb_italics); +} + +void KickerDataImpl::SetTTBItalics(const int16_t &i) +{ + Set(&KickerDataType::ttb_italics, i); +} + +int16_t KickerDataImpl::GetTTBAmsgs() +{ + return Get(&KickerDataType::ttb_amsgs); +} + +void KickerDataImpl::SetTTBAmsgs(const int16_t &i) +{ + Set(&KickerDataType::ttb_amsgs, i); +} + +int16_t KickerDataImpl::GetCapsMin() +{ + return Get(&KickerDataType::capsmin); +} + +void KickerDataImpl::SetCapsMin(const int16_t &i) +{ + Set(&KickerDataType::capsmin, i); +} + +int16_t KickerDataImpl::GetCapsPercent() +{ + return Get(&KickerDataType::capspercent); +} + +void KickerDataImpl::SetCapsPercent(const int16_t &i) +{ + Set(&KickerDataType::capspercent, i); +} + +int16_t KickerDataImpl::GetFloodLines() +{ + return Get(&KickerDataType::floodlines); +} + +void KickerDataImpl::SetFloodLines(const int16_t &i) +{ + Set(&KickerDataType::floodlines, i); +} + +int16_t KickerDataImpl::GetFloodSecs() +{ + return Get(&KickerDataType::floodsecs); +} + +void KickerDataImpl::SetFloodSecs(const int16_t &i) +{ + Set(&KickerDataType::floodsecs, i); +} + +int16_t KickerDataImpl::GetRepeatTimes() +{ + return Get(&KickerDataType::repeattimes); +} + +void KickerDataImpl::SetRepeatTimes(const int16_t &i) +{ + Set(&KickerDataType::repeattimes, i); +} + +bool KickerDataImpl::GetDontKickOps() +{ + return Get(&KickerDataType::dontkickops); +} + +void KickerDataImpl::SetDontKickOps(const bool &b) +{ + Set(&KickerDataType::dontkickops, b); +} + +bool KickerDataImpl::GetDontKickVoices() +{ + return Get(&KickerDataType::dontkickvoices); +} + +void KickerDataImpl::SetDontKickVoices(const bool &b) +{ + Set(&KickerDataType::dontkickvoices, b); +} + +class CommandBSKick : public Command +{ + public: + CommandBSKick(Module *creator) : Command(creator, "botserv/kick", 0) + { + this->SetDesc(_("Configures kickers")); + this->SetSyntax(_("\037option\037 \037channel\037 {\037ON|OFF\037} [\037settings\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Configures bot kickers." + " Use of this command requires the \002SET\002 privilege on \037channel\037.\n" + "\n" + "Available kickers:")); + + Anope::string this_name = source.command; + for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + const CommandInfo &info = it->second; + + if (c_name.find_ci(this_name + " ") == 0) + { + ServiceReference command("Command", info.name); + if (command) + { + source.command = c_name; + command->OnServHelp(source); + } + } + } + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("See \002{0}{1} {2} {3} \037option\037\002 for more information on a specific option."), + Config->StrictPrivmsg, source.service->nick, help->cname, this_name); + + return true; + } +}; + +class CommandBSKickBase : public Command +{ + public: + CommandBSKickBase(Module *creator, const Anope::string &cname, int minarg, int maxarg) : Command(creator, cname, minarg, maxarg) + { + } + + virtual void Execute(CommandSource &source, const std::vector ¶ms) override anope_abstract; + + virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) override anope_abstract; + + protected: + bool CheckArguments(CommandSource &source, const std::vector ¶ms, ChanServ::Channel* &ci) + { + const Anope::string &chan = params[0]; + const Anope::string &option = params[1]; + + ci = ChanServ::Find(chan); + + if (Anope::ReadOnly) + source.Reply(_("Sorry, kicker configuration is temporarily disabled.")); + else if (ci == NULL) + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + else if (option.empty()) + this->OnSyntaxError(source, ""); + else if (!option.equals_ci("ON") && !option.equals_ci("OFF")) + this->OnSyntaxError(source, ""); + else if (!source.AccessFor(ci).HasPriv("SET") && !source.HasPriv("botserv/administration")) + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + else if (!ci->GetBot()) + source.Reply(_("There is no bot assigned to \002{0}\002."), ci->GetName()); + else + return true; + + return false; + } + + bool CheckTTB(CommandSource &source, const Anope::string &ttb, int16_t &i) + { + i = 0; + if (ttb.empty()) + return true; + + try + { + i = convertTo(ttb); + if (i < 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + i = 0; + source.Reply(_("\002{0}\002 can not be taken as times to ban. Times to ban must be a positive integer."), ttb); + return false; + } + + return true; + } + + void Process(CommandSource &source, ChanServ::Channel *ci, const Anope::string ¶m, const Anope::string &ttb, void (KickerData::*setter)(const bool &), void (KickerData::*ttbsetter)(const int16_t &), const Anope::string &optname) + { + KickerData *kd = ci->GetRef(kickerdata); + + if (param.equals_ci("ON")) + { + int16_t i; + if (!CheckTTB(source, ttb, i)) + return; + + (kd->*setter)(true); + (kd->*ttbsetter)(i); + + if (i) + source.Reply(_("Bot will now kick for \002{0}\002, and will place a ban after \002{1}\002 kicks for the same user."), optname, i); + else + source.Reply(_("Bot will now kick for \002{0}\002."), optname); + + bool override = !source.AccessFor(ci).HasPriv("SET"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enable the " << optname << " kicker"; + } + else if (param.equals_ci("OFF")) + { + bool override = !source.AccessFor(ci).HasPriv("SET"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable the " << optname << " kicker"; + + (kd->*setter)(false); + (kd->*ttbsetter)(0); + + source.Reply(_("Bot won't kick for \002{0}\002 anymore."), optname); + } + else + this->OnSyntaxError(source, ""); + } +}; + +class CommandBSKickAMSG : public CommandBSKickBase +{ + public: + CommandBSKickAMSG(Module *creator) : CommandBSKickBase(creator, "botserv/kick/amsg", 2, 3) + { + this->SetDesc(_("Configures AMSG kicker")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci; + if (CheckArguments(source, params, ci)) + Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetAmsgs, &KickerData::SetTTBAmsgs, "amsgs"); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets the AMSG kicker on or off on \037channel\037. When enabled, the bot will kick users who send the same message to multiple channels where {0} bots are.\n" + "\n" + "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system."), + source.service->nick); + return true; + } +}; + +class CommandBSKickBadwords : public CommandBSKickBase +{ + public: + CommandBSKickBadwords(Module *creator) : CommandBSKickBase(creator, "botserv/kick/badwords", 2, 3) + { + this->SetDesc(_("Configures badwords kicker")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci; + if (CheckArguments(source, params, ci)) + Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetBadwords, &KickerData::SetTTBBadwords, "badwords"); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + ServiceBot *bi; + Anope::string name; + CommandInfo *help; + source.Reply(_("Sets the bad words kicker on or off on \037channel\037. When enabled, the bot will kick users who say certain words on the channel.")); + if (Command::FindCommandFromService("botserv/badwords", bi, name) && (help = bi->FindCommand("generic/help"))) + source.Reply(_("You can define bad words for your channel using the \002{0}\002 command. See \002{1}{2} {3} {4}\002 for more information."), + name, Config->StrictPrivmsg, bi->nick, help->cname, name); + source.Reply(_("\n" + "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); + return true; + } +}; + +class CommandBSKickBolds : public CommandBSKickBase +{ + public: + CommandBSKickBolds(Module *creator) : CommandBSKickBase(creator, "botserv/kick/bolds", 2, 3) + { + this->SetDesc(_("Configures bolds kicker")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci; + if (CheckArguments(source, params, ci)) + Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetBolds, &KickerData::SetTTBBolds, "bolds"); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets the bolds kicker on or off on \037channel\037. When enabled, the bot will kick users who use \002bolds\002.\n" + "\n" + "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); + return true; + } +}; + +class CommandBSKickCaps : public CommandBSKickBase +{ + public: + CommandBSKickCaps(Module *creator) : CommandBSKickBase(creator, "botserv/kick/caps", 2, 5) + { + this->SetDesc(_("Configures caps kicker")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037 [\037min\037 [\037percent\037]]]\002")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci; + if (!CheckArguments(source, params, ci)) + return; + + KickerData *kd = GetKickerData(ci); + + if (params[1].equals_ci("ON")) + { + const Anope::string &ttb = params.size() > 2 ? params[2] : "", + &min = params.size() > 3 ? params[3] : "", + &percent = params.size() > 4 ? params[4] : ""; + + int16_t i; + if (!CheckTTB(source, ttb, i)) + return; + + kd->SetCaps(true); + kd->SetTTBCaps(i); + + kd->SetCapsMin(10); + try + { + kd->SetCapsMin(convertTo(min)); + } + catch (const ConvertException &) { } + if (kd->GetCapsMin() < 1) + kd->SetCapsMin(10); + + try + { + kd->SetCapsPercent(convertTo(percent)); + } + catch (const ConvertException &) { } + if (kd->GetCapsPercent() < 1 || kd->GetCapsPercent() > 100) + kd->SetCapsPercent(25); + + if (i) + source.Reply(_("Bot will now kick for \002caps\002 if they constitute at least {0} characters and {1}% of the entire message, and will place a ban after {2} kicks for the same user."), kd->GetCapsMin(), kd->GetCapsPercent(), i); + else + source.Reply(_("Bot will now kick for \002caps\002 if they constitute at least {0} characters and {1}% of the entire message."), kd->GetCapsMin(), kd->GetCapsPercent()); + } + else + { + kd->SetCaps(false); + kd->SetTTBCaps(0); + kd->SetCapsMin(0); + kd->SetCapsPercent(0); + source.Reply(_("Bot won't kick for \002caps\002 anymore.")); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets the caps kicker on or off on \037channel\037. When enabled, the bot will kick users who talk in CAPS." + " The bot kicks only if there are at least \002min\002 caps and they constitute at least \002percent\002% of the total text line." + " (if not given, it defaults to 10 characters and 25%).\n" + "\n" + "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); + return true; + } +}; + +class CommandBSKickColors : public CommandBSKickBase +{ + public: + CommandBSKickColors(Module *creator) : CommandBSKickBase(creator, "botserv/kick/colors", 2, 3) + { + this->SetDesc(_("Configures color kicker")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci; + if (CheckArguments(source, params, ci)) + Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetColors, &KickerData::SetTTBColors, "colors"); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets the colors kicker on or off on \037channel\037. When enabled, the bot will kick users who use \00304c\00307o\00308l\00303o\00306r\00312s\017.\n" + "\n" + "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); + return true; + } +}; + +class CommandBSKickFlood : public CommandBSKickBase +{ + public: + CommandBSKickFlood(Module *creator) : CommandBSKickBase(creator, "botserv/kick/flood", 2, 5) + { + this->SetDesc(_("Configures flood kicker")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037 [\037lines\037 [\037seconds\037]]]\002")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci; + if (!CheckArguments(source, params, ci)) + return; + + KickerData *kd = GetKickerData(ci); + + if (params[1].equals_ci("ON")) + { + const Anope::string &ttb = params.size() > 2 ? params[2] : "", + &lines = params.size() > 3 ? params[3] : "", + &secs = params.size() > 4 ? params[4] : ""; + + int16_t i; + if (!CheckTTB(source, ttb, i)) + return; + + kd->SetFlood(true); + kd->SetTTBFlood(i); + + kd->SetFloodLines(6); + try + { + kd->SetFloodLines(convertTo(lines)); + } + catch (const ConvertException &) { } + if (kd->GetFloodLines() < 2) + kd->SetFloodLines(6); + + kd->SetFloodSecs(10); + try + { + kd->SetFloodSecs(convertTo(secs)); + } + catch (const ConvertException &) { } + if (kd->GetFloodSecs() < 1) + kd->SetFloodSecs(10); + if (kd->GetFloodSecs() > Config->GetModule(me)->Get("keepdata")) + kd->SetFloodSecs(Config->GetModule(me)->Get("keepdata")); + + if (i) + source.Reply(_("Bot will now kick for \002flood\002 ({0} lines in {1} seconds and will place a ban after {2} kicks for the same user."), kd->GetFloodLines(), kd->GetFloodSecs(), i); + else + source.Reply(_("Bot will now kick for \002flood\002 ({0} lines in {1} seconds)."), kd->GetFloodLines(), kd->GetFloodSecs()); + } + else if (params[1].equals_ci("OFF")) + { + kd->SetFlood(false); + kd->SetFloodLines(0); + kd->SetFloodSecs(0); + source.Reply(_("Bot won't kick for \002flood\002 anymore.")); + } + else + this->OnSyntaxError(source, params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets the flood kicker on or off on \037channel\037. When enabled, the bot to kick users who are flooding the channel with at least \037lines\037 lines in \037seconds\037 seconds. If not given, it defaults to 6 lines in 10 seconds.\n" + "\n" + "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); + return true; + } +}; + +class CommandBSKickItalics : public CommandBSKickBase +{ + public: + CommandBSKickItalics(Module *creator) : CommandBSKickBase(creator, "botserv/kick/italics", 2, 3) + { + this->SetDesc(_("Configures italics kicker")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci; + if (CheckArguments(source, params, ci)) + Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetItalics, &KickerData::SetTTBItalics, "itlaics"); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets the italics kicker on or off on \037channel\037. When enabled, the bot will kick users who use \035italics\035." + "\n" + "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); + return true; + } +}; + +class CommandBSKickRepeat : public CommandBSKickBase +{ + public: + CommandBSKickRepeat(Module *creator) : CommandBSKickBase(creator, "botserv/kick/repeat", 2, 4) + { + this->SetDesc(_("Configures repeat kicker")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037 [\037num\037]]\002")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci; + if (!CheckArguments(source, params, ci)) + return; + + KickerData *kd = GetKickerData(ci); + + if (params[1].equals_ci("ON")) + { + const Anope::string &ttb = params.size() > 2 ? params[2] : "", + × = params.size() > 3 ? params[3] : ""; + + int16_t i; + if (!CheckTTB(source, ttb, i)) + return; + + kd->SetRepeat(true); + kd->SetTTBRepeat(i); + kd->SetRepeatTimes(3); + try + { + kd->SetRepeatTimes(convertTo(times)); + } + catch (const ConvertException &) { } + if (kd->GetRepeatTimes() < 2) + kd->SetRepeatTimes(3); + + if (i) + source.Reply(_("Bot will now kick for \002repeats\002 (users that say the same thing {0} times), and will place a ban after {1} kicks for the same user."), kd->GetRepeatTimes(), i); + else + source.Reply(_("Bot will now kick for \002repeats\002 (users that say the same thing {0} times)."), kd->GetRepeatTimes() + 1); + } + else if (params[1].equals_ci("OFF")) + { + kd->SetRepeat(false); + kd->SetTTBRepeat(0); + kd->SetRepeatTimes(0); + source.Reply(_("Bot won't kick for \002repeats\002 anymore.")); + } + else + this->OnSyntaxError(source, params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets the repeat kicker on or off. When enabled, the bot will kick users who repeat themselves \037num\037 times. If \037num\037 is not given, it defaults to 3.\n" + "\n" + "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); + return true; + } +}; + +class CommandBSKickReverses : public CommandBSKickBase +{ + public: + CommandBSKickReverses(Module *creator) : CommandBSKickBase(creator, "botserv/kick/reverses", 2, 3) + { + this->SetDesc(_("Configures reverses kicker")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci; + if (CheckArguments(source, params, ci)) + Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetReverses, &KickerData::SetTTBReverses, "reverses"); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets the reverses kicker on or off. When enabled, the bot will kick users who use \026reverses\026.\n" + "\n" + "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); + return true; + } +}; + +class CommandBSKickUnderlines : public CommandBSKickBase +{ + public: + CommandBSKickUnderlines(Module *creator) : CommandBSKickBase(creator, "botserv/kick/underlines", 2, 3) + { + this->SetDesc(_("Configures underlines kicker")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci; + if (CheckArguments(source, params, ci)) + Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetUnderlines, &KickerData::SetTTBUnderlines, "underlines"); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets the underlines kicker on or off. When enabled, the bot will kick users who use \037underlines\037\n" + "\n" + "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); + return true; + } +}; + +class CommandBSSetDontKickOps : public Command +{ + public: + CommandBSSetDontKickOps(Module *creator, const Anope::string &sname = "botserv/set/dontkickops") : Command(creator, sname, 2, 2) + { + this->SetDesc(_("To protect ops against bot kicks")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci = ChanServ::Find(params[0]); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), params[0]); + return; + } + + ChanServ::AccessGroup access = source.AccessFor(ci); + if (!source.HasPriv("botserv/administration") && !access.HasPriv("SET")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, bot option setting is temporarily disabled.")); + return; + } + + KickerData *kd = GetKickerData(ci); + + if (params[1].equals_ci("ON")) + { + bool override = !access.HasPriv("SET"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enable dontkickops"; + + kd->SetDontKickOps(true); + source.Reply(_("Bot \002won't kick ops\002 on channel \002{0}\002."), ci->GetName()); + } + else if (params[1].equals_ci("OFF")) + { + bool override = !access.HasPriv("SET"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable dontkickops"; + + kd->SetDontKickOps(false); + source.Reply(_("Bot \002will kick ops\002 on channel \002{0}\002."), ci->GetName()); + } + else + this->OnSyntaxError(source, source.command); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables \002op protection\002 mode on a channel." + " When it is enabled, ops won't be kicked by the bot, even if they don't have the \002{0}\002 privilege.\n" + "\n" + "Use of this command requires the \002{1}\002 privilege on \037channel\037."), + "NOKICK", "SET"); + return true; + } +}; + +class CommandBSSetDontKickVoices : public Command +{ + public: + CommandBSSetDontKickVoices(Module *creator, const Anope::string &sname = "botserv/set/dontkickvoices") : Command(creator, sname, 2, 2) + { + this->SetDesc(_("To protect voices against bot kicks")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci = ChanServ::Find(params[0]); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), params[0]); + return; + } + + ChanServ::AccessGroup access = source.AccessFor(ci); + if (!source.HasPriv("botserv/administration") && !access.HasPriv("SET")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, bot option setting is temporarily disabled.")); + return; + } + + KickerData *kd = GetKickerData(ci); + + if (params[1].equals_ci("ON")) + { + bool override = !access.HasPriv("SET"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enable dontkickvoices"; + + kd->SetDontKickVoices(true); + source.Reply(_("Bot \002won't kick voices\002 on channel %s."), ci->GetName().c_str()); + } + else if (params[1].equals_ci("OFF")) + { + bool override = !access.HasPriv("SET"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable dontkickvoices"; + + kd->SetDontKickVoices(false); + source.Reply(_("Bot \002will kick voices\002 on channel %s."), ci->GetName().c_str()); + } + else + this->OnSyntaxError(source, source.command); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables \002voice protection\002 mode on a channel." + " When it is enabled, ops won't be kicked by the bot, even if they don't have the \002{0}\002 privilege.\n" + "\n" + "Use of this command requires the \002{1}\002 privilege on \037channel\037."), + "NOKICK", "SET"); + return true; + } +}; + +struct BanData +{ + struct Data + { + Anope::string mask; + time_t last_use; + int16_t ttb[TTB_SIZE]; + + Data() + { + last_use = 0; + for (int i = 0; i < TTB_SIZE; ++i) + this->ttb[i] = 0; + } + }; + + private: + typedef Anope::map data_type; + data_type data_map; + + public: + Data &get(const Anope::string &key) + { + return this->data_map[key]; + } + + bool empty() const + { + return this->data_map.empty(); + } + + void purge() + { + time_t keepdata = Config->GetModule(me)->Get("keepdata"); + for (data_type::iterator it = data_map.begin(), it_end = data_map.end(); it != it_end;) + { + const Anope::string &user = it->first; + Data &bd = it->second; + ++it; + + if (Anope::CurTime - bd.last_use > keepdata) + data_map.erase(user); + } + } +}; + +struct UserData +{ + UserData() + { + last_use = last_start = Anope::CurTime; + } + + /* Data validity */ + time_t last_use; + + /* for flood kicker */ + int16_t lines = 0; + time_t last_start; + + /* for repeat kicker */ + Anope::string lasttarget; + int16_t times = 0; + + Anope::string lastline; +}; + +class BanDataPurger : public Timer +{ + public: + BanDataPurger(Module *o) : Timer(o, 300, Anope::CurTime, true) { } + + void Tick(time_t) override + { + Log(LOG_DEBUG) << "bs_main: Running bandata purger"; + + for (channel_map::iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) + { + Channel *c = it->second; + + BanData *bd = c->GetExt("bandata"); + if (bd != NULL) + { + bd->purge(); + if (bd->empty()) + c->ShrinkOK("bandata"); + } + } + } +}; + +class BSKick : public Module + , public EventHook + , public EventHook +{ + ExtensibleItem bandata; + ExtensibleItem userdata; + + KickerDataType kdtype; + + CommandBSKick commandbskick; + CommandBSKickAMSG commandbskickamsg; + CommandBSKickBadwords commandbskickbadwords; + CommandBSKickBolds commandbskickbolds; + CommandBSKickCaps commandbskickcaps; + CommandBSKickColors commandbskickcolors; + CommandBSKickFlood commandbskickflood; + CommandBSKickItalics commandbskickitalics; + CommandBSKickRepeat commandbskickrepeat; + CommandBSKickReverses commandbskickreverse; + CommandBSKickUnderlines commandbskickunderlines; + + CommandBSSetDontKickOps commandbssetdontkickops; + CommandBSSetDontKickVoices commandbssetdontkickvoices; + + BanDataPurger purger; + + EventHandlers onbotban; + + BanData::Data &GetBanData(User *u, Channel *c) + { + BanData *bd = bandata.Require(c); + return bd->get(u->GetMask()); + } + + UserData *GetUserData(User *u, Channel *c) + { + ChanUserContainer *uc = c->FindUser(u); + if (uc == nullptr) + return nullptr; + + return userdata.Require(uc); + } + + void TakeAction(ChanServ::Channel *ci, User *u, int ttb, TTBType ttbtype, const char *message, ...) + { + /* Don't ban ulines or protected users */ + if (u->IsProtected()) + return; + + BanData::Data &bd = this->GetBanData(u, ci->c); + + ++bd.ttb[ttbtype]; + if (ttb && bd.ttb[ttbtype] >= ttb) + { + bd.ttb[ttbtype] = 0; + + Anope::string mask = ci->GetIdealBan(u); + + ci->c->SetMode(NULL, "BAN", mask); + this->onbotban(&Event::BotBan::OnBotBan, u, ci, mask); + } + + if (!ci->c->FindUser(u)) + return; + + va_list args; + char buf[1024]; + + Anope::string fmt = Language::Translate(u, message); + va_start(args, message); + vsnprintf(buf, sizeof(buf), fmt.c_str(), args); + va_end(args); + + ci->c->Kick(ci->GetBot(), u, "%s", buf); + } + + public: + BSKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , bandata(this, "bandata") + , userdata(this, "userdata") + + , kdtype(this) + + , commandbskick(this) + , commandbskickamsg(this) + , commandbskickbadwords(this) + , commandbskickbolds(this) + , commandbskickcaps(this) + , commandbskickcolors(this) + , commandbskickflood(this) + , commandbskickitalics(this) + , commandbskickrepeat(this) + , commandbskickreverse(this) + , commandbskickunderlines(this) + + , commandbssetdontkickops(this) + , commandbssetdontkickvoices(this) + + , purger(this) + + , onbotban(this) + { + me = this; + } + + void OnServiceBot(CommandSource &source, ServiceBot *bi, ChanServ::Channel *ci, InfoFormatter &info) override + { + if (!ci) + return; + + Anope::string enabled = Language::Translate(source.nc, _("Enabled")); + Anope::string disabled = Language::Translate(source.nc, _("Disabled")); + KickerData *kd = ci->GetRef(&kdtype); + + if (kd && kd->GetBadwords()) + { + if (kd->GetTTBBadwords()) + info[_("Bad words kicker")] = Anope::printf("%s (%d kick(s) to ban)", enabled.c_str(), kd->GetBadwords()); + else + info[_("Bad words kicker")] = enabled; + } + else + info[_("Bad words kicker")] = disabled; + + if (kd && kd->GetBolds()) + { + if (kd->GetTTBBolds()) + info[_("Bolds kicker")] = Anope::printf("%s (%d kick(s) to ban)", enabled.c_str(), kd->GetTTBBolds()); + else + info[_("Bolds kicker")] = enabled; + } + else + info[_("Bolds kicker")] = disabled; + + if (kd && kd->GetCaps()) + { + if (kd->GetTTBCaps()) + info[_("Caps kicker")] = Anope::printf(_("%s (%d kick(s) to ban; minimum %d/%d%%)"), enabled.c_str(), kd->GetTTBCaps(), kd->GetCapsMin(), kd->GetCapsPercent()); + else + info[_("Caps kicker")] = Anope::printf(_("%s (minimum %d/%d%%)"), enabled.c_str(), kd->GetCapsMin(), kd->GetCapsPercent()); + } + else + info[_("Caps kicker")] = disabled; + + if (kd && kd->GetColors()) + { + if (kd->GetTTBColors()) + info[_("Colors kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->GetTTBColors()); + else + info[_("Colors kicker")] = enabled; + } + else + info[_("Colors kicker")] = disabled; + + if (kd && kd->GetFlood()) + { + if (kd->GetTTBFlood()) + info[_("Flood kicker")] = Anope::printf(_("%s (%d kick(s) to ban; %d lines in %ds)"), enabled.c_str(), kd->GetTTBFlood(), kd->GetFloodLines(), kd->GetFloodSecs()); + else + info[_("Flood kicker")] = Anope::printf(_("%s (%d lines in %ds)"), enabled.c_str(), kd->GetFloodLines(), kd->GetFloodSecs()); + } + else + info[_("Flood kicker")] = disabled; + + if (kd && kd->GetRepeat()) + { + if (kd->GetTTBRepeat()) + info[_("Repeat kicker")] = Anope::printf(_("%s (%d kick(s) to ban; %d times)"), enabled.c_str(), kd->GetTTBRepeat(), kd->GetRepeatTimes()); + else + info[_("Repeat kicker")] = Anope::printf(_("%s (%d times)"), enabled.c_str(), kd->GetRepeatTimes()); + } + else + info[_("Repeat kicker")] = disabled; + + if (kd && kd->GetReverses()) + { + if (kd->GetTTBReverses()) + info[_("Reverses kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->GetTTBReverses()); + else + info[_("Reverses kicker")] = enabled; + } + else + info[_("Reverses kicker")] = disabled; + + if (kd && kd->GetUnderlines()) + { + if (kd->GetTTBUnderlines()) + info[_("Underlines kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->GetTTBUnderlines()); + else + info[_("Underlines kicker")] = enabled; + } + else + info[_("Underlines kicker")] = disabled; + + if (kd && kd->GetItalics()) + { + if (kd->GetTTBItalics()) + info[_("Italics kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->GetTTBItalics()); + else + info[_("Italics kicker")] = enabled; + } + else + info[_("Italics kicker")] = disabled; + + if (kd && kd->GetAmsgs()) + { + if (kd->GetTTBAmsgs()) + info[_("AMSG kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->GetTTBAmsgs()); + else + info[_("AMSG kicker")] = enabled; + } + else + info[_("AMSG kicker")] = disabled; + + if (kd && kd->GetDontKickOps()) + info.AddOption(_("Ops protection")); + if (kd && kd->GetDontKickVoices()) + info.AddOption(_("Voices protection")); + } + + void OnPrivmsg(User *u, Channel *c, Anope::string &msg) override + { + /* Now we can make kicker stuff. We try to order the checks + * from the fastest one to the slowest one, since there's + * no need to process other kickers if a user is kicked before + * the last kicker check. + * + * But FIRST we check whether the user is protected in any + * way. + */ + ChanServ::Channel *ci = c->ci; + if (ci == NULL) + return; + KickerData *kd = c->ci->GetRef(&kdtype); + if (kd == NULL) + return; + + if (ci->AccessFor(u).HasPriv("NOKICK")) + return; + if (kd->GetDontKickOps() && (c->HasUserStatus(u, "HALFOP") || c->HasUserStatus(u, "OP") || c->HasUserStatus(u, "PROTECT") || c->HasUserStatus(u, "OWNER"))) + return; + if (kd->GetDontKickVoices() && c->HasUserStatus(u, "VOICE")) + return; + + Anope::string realbuf = msg; + + /* If it's a /me, cut the CTCP part because the ACTION will cause + * problems with the caps or badwords kicker + */ + if (realbuf.substr(0, 8).equals_ci("\1ACTION ") && realbuf[realbuf.length() - 1] == '\1') + { + realbuf.erase(0, 8); + realbuf.erase(realbuf.length() - 1); + } + + if (realbuf.empty()) + return; + + /* Bolds kicker */ + if (kd->GetBolds() && realbuf.find(2) != Anope::string::npos) + { + TakeAction(ci, u, kd->GetTTBBolds(), TTB_BOLDS, _("Don't use bolds on this channel!")); + return; + } + + /* Color kicker */ + if (kd->GetColors() && realbuf.find(3) != Anope::string::npos) + { + TakeAction(ci, u, kd->GetTTBColors(), TTB_COLORS, _("Don't use colors on this channel!")); + return; + } + + /* Reverses kicker */ + if (kd->GetReverses() && realbuf.find(22) != Anope::string::npos) + { + TakeAction(ci, u, kd->GetTTBReverses(), TTB_REVERSES, _("Don't use reverses on this channel!")); + return; + } + + /* Italics kicker */ + if (kd->GetItalics() && realbuf.find(29) != Anope::string::npos) + { + TakeAction(ci, u, kd->GetTTBItalics(), TTB_ITALICS, _("Don't use italics on this channel!")); + return; + } + + /* Underlines kicker */ + if (kd->GetUnderlines() && realbuf.find(31) != Anope::string::npos) + { + TakeAction(ci, u, kd->GetTTBUnderlines(), TTB_UNDERLINES, _("Don't use underlines on this channel!")); + return; + } + + /* Caps kicker */ + if (kd->GetCaps() && realbuf.length() >= static_cast(kd->GetCapsMin())) + { + int i = 0, l = 0; + + for (unsigned j = 0, end = realbuf.length(); j < end; ++j) + { + if (isupper(realbuf[j])) + ++i; + else if (islower(realbuf[j])) + ++l; + } + + /* i counts uppercase chars, l counts lowercase chars. Only + * alphabetic chars (so islower || isupper) qualify for the + * percentage of caps to kick for; the rest is ignored. -GD + */ + + if ((i || l) && i >= kd->GetCapsMin() && i * 100 / (i + l) >= kd->GetCapsPercent()) + { + TakeAction(ci, u, kd->GetTTBCaps(), TTB_CAPS, _("Turn caps lock OFF!")); + return; + } + } + + /* Bad words kicker */ + if (kd->GetBadwords()) + { + bool mustkick = false; + + /* Normalize the buffer */ + Anope::string nbuf = Anope::NormalizeBuffer(realbuf); + bool casesensitive = Config->GetModule("botserv")->Get("casesensitive"); + + /* Normalize can return an empty string if this only conains control codes etc */ + if (badwords && !nbuf.empty()) + for (unsigned i = 0; i < badwords->GetBadWordCount(ci); ++i) + { + BadWord *bw = badwords->GetBadWord(ci, i); + + if (bw->GetWord().empty()) + continue; // Shouldn't happen + + if (bw->GetWord().length() > nbuf.length()) + continue; // This can't ever match + + if (bw->GetType() == BW_ANY && ((casesensitive && nbuf.find(bw->GetWord()) != Anope::string::npos) || (!casesensitive && nbuf.find_ci(bw->GetWord()) != Anope::string::npos))) + mustkick = true; + else if (bw->GetType() == BW_SINGLE) + { + size_t len = bw->GetWord().length(); + + if ((casesensitive && bw->GetWord().equals_cs(nbuf)) || (!casesensitive && bw->GetWord().equals_ci(nbuf))) + mustkick = true; + else if (nbuf.find(' ') == len && ((casesensitive && bw->GetWord().equals_cs(nbuf.substr(0, len))) || (!casesensitive && bw->GetWord().equals_ci(nbuf.substr(0, len))))) + mustkick = true; + else + { + if (len < nbuf.length() && nbuf.rfind(' ') == nbuf.length() - len - 1 && ((casesensitive && nbuf.find(bw->GetWord()) == nbuf.length() - len) || (!casesensitive && nbuf.find_ci(bw->GetWord()) == nbuf.length() - len))) + mustkick = true; + else + { + Anope::string wordbuf = " " + bw->GetWord() + " "; + + if ((casesensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!casesensitive && nbuf.find_ci(wordbuf) != Anope::string::npos)) + mustkick = true; + } + } + } + else if (bw->GetType() == BW_START) + { + size_t len = bw->GetWord().length(); + + if ((casesensitive && nbuf.substr(0, len).equals_cs(bw->GetWord())) || (!casesensitive && nbuf.substr(0, len).equals_ci(bw->GetWord()))) + mustkick = true; + else + { + Anope::string wordbuf = " " + bw->GetWord(); + + if ((casesensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!casesensitive && nbuf.find_ci(wordbuf) != Anope::string::npos)) + mustkick = true; + } + } + else if (bw->GetType() == BW_END) + { + size_t len = bw->GetWord().length(); + + if ((casesensitive && nbuf.substr(nbuf.length() - len).equals_cs(bw->GetWord())) || (!casesensitive && nbuf.substr(nbuf.length() - len).equals_ci(bw->GetWord()))) + mustkick = true; + else + { + Anope::string wordbuf = bw->GetWord() + " "; + + if ((casesensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!casesensitive && nbuf.find_ci(wordbuf) != Anope::string::npos)) + mustkick = true; + } + } + + if (mustkick) + { + if (Config->GetModule(me)->Get("gentlebadwordreason")) + TakeAction(ci, u, kd->GetTTBBadwords(), TTB_BADWORDS, _("Watch your language!")); + else + TakeAction(ci, u, kd->GetTTBBadwords(), TTB_BADWORDS, _("Don't use the word \"%s\" on this channel!"), bw->GetWord().c_str()); + + return; + } + } /* for */ + } /* if badwords */ + + UserData *ud = GetUserData(u, c); + + if (ud) + { + /* Flood kicker */ + if (kd->GetFlood()) + { + if (Anope::CurTime - ud->last_start > kd->GetFloodSecs()) + { + ud->last_start = Anope::CurTime; + ud->lines = 0; + } + + ++ud->lines; + if (ud->lines >= kd->GetFloodLines()) + { + TakeAction(ci, u, kd->GetTTBFlood(), TTB_FLOOD, _("Stop flooding!")); + return; + } + } + + /* Repeat kicker */ + if (kd->GetRepeat()) + { + if (!ud->lastline.equals_ci(realbuf)) + ud->times = 0; + else + ++ud->times; + + if (ud->times >= kd->GetRepeatTimes()) + { + TakeAction(ci, u, kd->GetTTBRepeat(), TTB_REPEAT, _("Stop repeating yourself!")); + return; + } + } + + if (ud->lastline.equals_ci(realbuf) && !ud->lasttarget.empty() && !ud->lasttarget.equals_ci(ci->GetName())) + { + for (User::ChanUserList::iterator it = u->chans.begin(); it != u->chans.end();) + { + Channel *chan = it->second->chan; + ++it; + + if (chan->ci && kd->GetAmsgs() && !chan->ci->AccessFor(u).HasPriv("NOKICK")) + { + TakeAction(ci, u, kd->GetTTBAmsgs(), TTB_AMSGS, _("Don't use AMSGs!")); + return; + } + } + } + + ud->lasttarget = ci->GetName(); + ud->lastline = realbuf; + } + } +}; + +MODULE_INIT(BSKick) diff --git a/modules/botserv/main/CMakeLists.txt b/modules/botserv/main/CMakeLists.txt new file mode 100644 index 000000000..781f0ef1f --- /dev/null +++ b/modules/botserv/main/CMakeLists.txt @@ -0,0 +1 @@ +build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/botserv/main/botserv.cpp b/modules/botserv/main/botserv.cpp new file mode 100644 index 000000000..0b51691fc --- /dev/null +++ b/modules/botserv/main/botserv.cpp @@ -0,0 +1,228 @@ +/* BotServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/botserv.h" +#include "modules/help.h" + +class BotServCore : public Module, public BotServ::BotServService + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + Reference BotServ; + ExtensibleRef persist, inhabit;//XXX? + + public: + BotServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) + , BotServ::BotServService(this) + , persist("PERSIST") + , inhabit("inhabit") + { + } + + void OnReload(Configuration::Conf *conf) override + { + const Anope::string &bsnick = conf->GetModule(this)->Get("client"); + BotServ = ServiceBot::Find(bsnick, true); + } + + void OnSetCorrectModes(User *user, Channel *chan, ChanServ::AccessGroup &access, bool &give_modes, bool &take_modes) override + { + /* Do not allow removing bot modes on our service bots */ + if (chan->ci && chan->ci->GetBot() == user) + { + const Anope::string &botmodes = Config->GetModule(this)->Get("botmodes"); + for (unsigned i = 0; i < botmodes.length(); ++i) + chan->SetMode(chan->ci->GetBot(), ModeManager::FindChannelModeByChar(botmodes[i]), chan->ci->GetBot()->GetUID()); + } + } + + void OnBotAssign(User *sender, ChanServ::Channel *ci, ServiceBot *bi) override + { + if (ci->c && ci->c->users.size() >= Config->GetModule(this)->Get("minusers")) + { + ChannelStatus status(Config->GetModule(this)->Get("botmodes")); + bi->Join(ci->c, &status); + } + } + + void OnJoinChannel(User *user, Channel *c) override + { + if (!Config || !IRCD) + return; + + ServiceBot *bi = user->server == Me ? dynamic_cast(user) : NULL; + if (bi && Config->GetModule(this)->Get("smartjoin")) + { + std::vector bans = c->GetModeList("BAN"); + + /* We check for bans */ + for (unsigned int i = 0; i < bans.size(); ++i) + { + Entry ban("BAN", bans[i]); + if (ban.Matches(user)) + c->RemoveMode(NULL, "BAN", ban.GetMask()); + } + + Anope::string Limit; + unsigned limit = 0; + try + { + if (c->GetParam("LIMIT", Limit)) + limit = convertTo(Limit); + } + catch (const ConvertException &) { } + + /* Should we be invited? */ + if (c->HasMode("INVITE") || (limit && c->users.size() >= limit)) + { + ChannelMode *cm = ModeManager::FindChannelModeByName("OP"); + char symbol = cm ? anope_dynamic_static_cast(cm)->symbol : 0; + IRCD->SendNotice(bi, (symbol ? Anope::string(symbol) : "") + c->name, "%s invited %s into the channel.", user->nick.c_str(), user->nick.c_str()); + } + + ModeManager::ProcessModes(); + } + + if (user->server != Me && c->ci && c->ci->GetBot()) + { + /** + * We let the bot join even if it was an ignored user, as if we don't, + * and the ignored user doesn't just leave, the bot will never + * make it into the channel, leaving the channel botless even for + * legit users - Rob + **/ + /* This is before the user has joined the channel, so check usercount + 1 */ + if (c->users.size() + 1 >= Config->GetModule(this)->Get("minusers") && !c->FindUser(c->ci->GetBot())) + { + ChannelStatus status(Config->GetModule(this)->Get("botmodes")); + c->ci->GetBot()->Join(c, &status); + } + } + } + + void OnLeaveChannel(User *u, Channel *c) override + { + /* Channel is persistent, it shouldn't be deleted and the service bot should stay */ + if (c->ci && persist && persist->HasExt(c->ci)) + return; + + /* Channel is syncing from a netburst, don't destroy it as more users are probably wanting to join immediately + * We also don't part the bot here either, if necessary we will part it after the sync + */ + if (c->syncing) + return; + + /* Additionally, do not delete this channel if ChanServ/a BotServ bot is inhabiting it */ + if (inhabit && inhabit->HasExt(c)) + return; + + /* This is called prior to removing the user from the channnel, so c->users.size() - 1 should be safe */ + if (c->ci && c->ci->GetBot() && u != c->ci->GetBot() && c->users.size() - 1 <= Config->GetModule(this)->Get("minusers") && c->FindUser(c->ci->GetBot())) + c->ci->GetBot()->Part(c->ci->c); + } + + EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override + { + if (!params.empty()) + return EVENT_CONTINUE; + + if (source.c) + { + source.Reply(_("\002%s\002 allows you to execute \"fantasy\" commands in the channel.\n" + "Fantasy commands are commands that can be executed from messaging a\n" + "channel, and provide a more convenient way to execute commands. Commands that\n" + "require a channel as a parameter will automatically have that parameter\n" + "given.\n"), source.service->nick.c_str()); + const Anope::string &fantasycharacters = Config->GetModule("fantasy")->Get("fantasycharacter", "!"); + if (!fantasycharacters.empty()) + source.Reply(_(" \n" + "Fantasy commands may be prefixed with one of the following characters: %s\n"), fantasycharacters.c_str()); + source.Reply(_(" \n" + "Available commands are:")); + } + else if (*source.service == BotServ) + { + source.Reply(_("\002%s\002 allows you to have a bot on your own channel.\n" + "It has been created for users that can't host or\n" + "configure a bot, or for use on networks that don't\n" + "allow user bots. Available commands are listed\n" + "below; to use them, type \002%s%s \037command\037\002. For\n" + "more information on a specific command, type\n" + "\002%s%s %s \037command\037\002.\n"), + BotServ->nick.c_str(), Config->StrictPrivmsg.c_str(), BotServ->nick.c_str(), + Config->StrictPrivmsg.c_str(), BotServ->nick.c_str(), source.command.c_str()); + } + + return EVENT_CONTINUE; + } + + void OnPostHelp(CommandSource &source, const std::vector ¶ms) override + { + if (!params.empty() || source.c || source.service != *BotServ) + return; + + source.Reply(_(" \n" + "Bot will join a channel whenever there is at least\n" + "\002%d\002 user(s) on it."), Config->GetModule(this)->Get("minusers")); + const Anope::string &fantasycharacters = Config->GetModule("fantasy")->Get("fantasycharacter", "!"); + if (!fantasycharacters.empty()) + source.Reply(_("Additionally, if fantasy is enabled fantasy commands\n" + "can be executed by prefixing the command name with\n" + "one of the following characters: %s"), fantasycharacters.c_str()); + } + + EventReturn OnChannelModeSet(Channel *c, const MessageSource &source, ChannelMode *mode, const Anope::string ¶m) override + { + if (source.GetUser() && !source.GetBot() && Config->GetModule(this)->Get("smartjoin") && mode->name == "BAN" && c->ci && c->ci->GetBot() && c->FindUser(c->ci->GetBot())) + { + ServiceBot *bi = c->ci->GetBot(); + + Entry ban("BAN", param); + if (ban.Matches(bi)) + c->RemoveMode(bi, "BAN", param); + } + + return EVENT_CONTINUE; + } + + void OnCreateChan(ChanServ::Channel *ci) override + { + /* Set default bot flags */ + spacesepstream sep(Config->GetModule(this)->Get("defaults", "greet fantasy")); + for (Anope::string token; sep.GetToken(token);) + ci->SetS("BS_" + token.upper(), true); + } + + void OnUserKicked(const MessageSource &source, User *target, const Anope::string &channel, ChannelStatus &status, const Anope::string &kickmsg) override + { + ServiceBot *bi = ServiceBot::Find(target->GetUID()); + if (bi) + /* Bots get rejoined */ + bi->Join(channel, &status); + } + + void OnCreateBot(ServiceBot *bi) override + { + if (bi->botmodes.empty()) + bi->botmodes = Config->GetModule(this)->Get("botumodes"); + } +}; + +MODULE_INIT(BotServCore) + diff --git a/modules/botserv/set.cpp b/modules/botserv/set.cpp new file mode 100644 index 000000000..6e2ae998f --- /dev/null +++ b/modules/botserv/set.cpp @@ -0,0 +1,221 @@ +/* BotServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/bs_kick.h" + +class CommandBSSet : public Command +{ + public: + CommandBSSet(Module *creator) : Command(creator, "botserv/set", 3, 3) + { + this->SetDesc(_("Configures bot options")); + this->SetSyntax(_("\037option\037 \037(channel | bot)\037 \037settings\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Configures bot options.\n" + "\n" + "Available options:")); + bool hide_privileged_commands = Config->GetBlock("options")->Get("hideprivilegedcommands"), + hide_registered_commands = Config->GetBlock("options")->Get("hideregisteredcommands"); + Anope::string this_name = source.command; + for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + const CommandInfo &info = it->second; + if (c_name.find_ci(this_name + " ") == 0) + { + ServiceReference command("Command", info.name); + if (command) + { + // XXX dup + if (hide_registered_commands && !command->AllowUnregistered() && !source.GetAccount()) + continue; + + if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission)) + continue; + + source.command = it->first; + command->OnServHelp(source); + } + } + } + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("Type \002{0}{1} {2} {3} \037option\037\002 for more information on a particular option."), + Config->StrictPrivmsg, source.service->nick, help->cname, this_name); + + return true; + } +}; + +class CommandBSSetBanExpire : public Command +{ + public: + class UnbanTimer : public Timer + { + Anope::string chname; + Anope::string mask; + + public: + UnbanTimer(Module *creator, const Anope::string &ch, const Anope::string &bmask, time_t t) : Timer(creator, t), chname(ch), mask(bmask) { } + + void Tick(time_t) override + { + Channel *c = Channel::Find(chname); + if (c) + c->RemoveMode(NULL, "BAN", mask); + } + }; + + CommandBSSetBanExpire(Module *creator, const Anope::string &sname = "botserv/set/banexpire") : Command(creator, sname, 2, 2) + { + this->SetDesc(_("Configures the time bot bans expire in")); + this->SetSyntax(_("\037channel\037 \037time\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &arg = params[1]; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + ChanServ::AccessGroup access = source.AccessFor(ci); + if (!source.HasPriv("botserv/administration") && !access.HasPriv("SET")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, changing bot options is temporarily disabled.")); + return; + } + + time_t t = Anope::DoTime(arg); + if (t == -1) + { + source.Reply(_("Invalid expiry time \002{0}\002."), arg); + return; + } + + /* cap at 1 day */ + if (t > 86400) + { + source.Reply(_("Ban expiry may not be longer than 1 day.")); + return; + } + + ci->SetBanExpire(t); + + bool override = !access.HasPriv("SET"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to change banexpire to " << arg; + + if (!t) + source.Reply(_("Bot bans will no longer automatically expire.")); + else + source.Reply(_("Bot bans will automatically expire after \002{0}\002."), Anope::Duration(t, source.GetAccount())); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Sets the time bot bans expire in. If enabled, any bans placed by bots, such as by the flood kicker, badwords kicker, etc. will automatically be removed after the given time." + " Set to 0 to disable bans from automatically expiring.")); + return true; + } +}; + +class CommandBSSetPrivate : public Command +{ + public: + CommandBSSetPrivate(Module *creator, const Anope::string &sname = "botserv/set/private") : Command(creator, sname, 2, 2) + { + this->SetDesc(_("Prevent a bot from being assigned by non Services Operators")); + this->SetSyntax(_("\037botname\037 {\037ON|OFF\037}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &nick = params[9]; + const Anope::string &value = params[1]; + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + ServiceBot *bi = ServiceBot::Find(nick, true); + if (bi == NULL) + { + source.Reply(_("Bot \002{0}\002 does not exist."), nick); + return; + } + + if (value.equals_ci("ON")) + { + bi->bi->SetOperOnly(true); + source.Reply(_("Private mode of bot \002{0}\002 is now \002on\002."), bi->nick); + } + else if (value.equals_ci("OFF")) + { + bi->bi->SetOperOnly(false); + source.Reply(_("Private mode of bot \002{0}\002 is now \002off\002."), bi->nick); + } + else + this->OnSyntaxError(source, source.command); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("This option prevents a bot from being assigned to channels by users who do not have the \002{0}\002 privilege."), + "botserv/administration"); + return true; + } +}; + +class BSSet : public Module + , public EventHook +{ + CommandBSSet commandbsset; + CommandBSSetBanExpire commandbssetbanexpire; + CommandBSSetPrivate commandbssetprivate; + + public: + BSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandbsset(this) + , commandbssetbanexpire(this) + , commandbssetprivate(this) + { + } + + void OnBotBan(User *u, ChanServ::Channel *ci, const Anope::string &mask) override + { + if (!ci->GetBanExpire()) + return; + + new CommandBSSetBanExpire::UnbanTimer(this, ci->GetName(), mask, ci->GetBanExpire()); + } +}; + +MODULE_INIT(BSSet) diff --git a/modules/bs_autoassign.cpp b/modules/bs_autoassign.cpp deleted file mode 100644 index 8978499ed..000000000 --- a/modules/bs_autoassign.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - */ - -#include "module.h" - -class BSAutoAssign : public Module - , public EventHook -{ - public: - BSAutoAssign(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - { - } - - void OnChanRegistered(ChanServ::Channel *ci) override - { - const Anope::string &bot = Config->GetModule(this)->Get("bot"); - if (bot.empty()) - return; - - ServiceBot *bi = ServiceBot::Find(bot, true); - if (bi == NULL) - { - Log(this) << "bs_autoassign is configured to assign bot " << bot << ", but it does not exist?"; - return; - } - - bi->Assign(NULL, ci); - } -}; - -MODULE_INIT(BSAutoAssign) diff --git a/modules/chanserv/CMakeLists.txt b/modules/chanserv/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/chanserv/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/chanserv/akick.cpp b/modules/chanserv/akick.cpp new file mode 100644 index 000000000..8cd1f778a --- /dev/null +++ b/modules/chanserv/akick.cpp @@ -0,0 +1,718 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_akick.h" + +class AutoKickImpl : public AutoKick +{ + public: + AutoKickImpl(Serialize::TypeBase *type) : AutoKick(type) { } + AutoKickImpl(Serialize::TypeBase *type, Serialize::ID id) : AutoKick(type, id) { } + + ChanServ::Channel *GetChannel() override; + void SetChannel(ChanServ::Channel *ci) override; + + Anope::string GetMask() override; + void SetMask(const Anope::string &mask) override; + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *nc) override; + + Anope::string GetReason() override; + void SetReason(const Anope::string &r) override; + + Anope::string GetCreator() override; + void SetCreator(const Anope::string &c) override; + + time_t GetAddTime() override; + void SetAddTime(const time_t &t) override; + + time_t GetLastUsed() override; + void SetLastUsed(const time_t &t) override; +}; + +class AutoKickType : public Serialize::Type +{ + public: + Serialize::ObjectField ci; + + Serialize::Field mask; + Serialize::ObjectField nc; + + Serialize::Field reason; + Serialize::Field creator; + Serialize::Field addtime; + Serialize::Field last_time; + + AutoKickType(Module *me) + : Serialize::Type(me, "AutoKick") + , ci(this, "ci", true) + , mask(this, "mask") + , nc(this, "nc", true) + , reason(this, "reason") + , creator(this, "creator") + , addtime(this, "addtime") + , last_time(this, "last_time") + { + } +}; + +ChanServ::Channel *AutoKickImpl::GetChannel() +{ + return Get(&AutoKickType::ci); +} + +void AutoKickImpl::SetChannel(ChanServ::Channel *ci) +{ + Set(&AutoKickType::ci, ci); +} + +Anope::string AutoKickImpl::GetMask() +{ + return Get(&AutoKickType::mask); +} + +void AutoKickImpl::SetMask(const Anope::string &mask) +{ + Set(&AutoKickType::mask, mask); +} + +NickServ::Account *AutoKickImpl::GetAccount() +{ + return Get(&AutoKickType::nc); +} + +void AutoKickImpl::SetAccount(NickServ::Account *nc) +{ + Set(&AutoKickType::nc, nc); +} + +Anope::string AutoKickImpl::GetReason() +{ + return Get(&AutoKickType::reason); +} + +void AutoKickImpl::SetReason(const Anope::string &r) +{ + Set(&AutoKickType::reason, r); +} + +Anope::string AutoKickImpl::GetCreator() +{ + return Get(&AutoKickType::creator); +} + +void AutoKickImpl::SetCreator(const Anope::string &c) +{ + Set(&AutoKickType::creator, c); +} + +time_t AutoKickImpl::GetAddTime() +{ + return Get(&AutoKickType::addtime); +} + +void AutoKickImpl::SetAddTime(const time_t &t) +{ + Set(&AutoKickType::addtime, t); +} + +time_t AutoKickImpl::GetLastUsed() +{ + return Get(&AutoKickType::last_time); +} + +void AutoKickImpl::SetLastUsed(const time_t &t) +{ + Set(&AutoKickType::last_time, t); +} + +class CommandCSAKick : public Command +{ + void DoAdd(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + Anope::string mask = params[2]; + Anope::string reason = params.size() > 3 ? params[3] : ""; + NickServ::Nick *na = NickServ::FindNick(mask); + NickServ::Account *nc = NULL; + unsigned reasonmax = Config->GetModule("chanserv")->Get("reasonmax", "200"); + + if (reason.length() > reasonmax) + reason = reason.substr(0, reasonmax); + + if (IRCD->IsExtbanValid(mask)) + ; /* If this is an extban don't try to complete the mask */ + else if (IRCD->IsChannelValid(mask)) + { + /* Also don't try to complete the mask if this is a channel */ + + if (mask.equals_ci(ci->GetName()) && ci->HasFieldS("PEACE")) + { + source.Reply(_("Access denied.")); + return; + } + } + else if (!na) + { + /* If the mask contains a realname the reason must be prepended with a : */ + if (mask.find('#') != Anope::string::npos) + { + size_t r = reason.find(':'); + if (r != Anope::string::npos) + { + mask += " " + reason.substr(0, r); + mask.trim(); + reason = reason.substr(r + 1); + reason.trim(); + } + else + { + mask = mask + " " + reason; + reason.clear(); + } + } + + Entry e("", mask); + + mask = (e.nick.empty() ? "*" : e.nick) + "!" + + (e.user.empty() ? "*" : e.user) + "@" + + (e.host.empty() ? "*" : e.host); + if (!e.real.empty()) + mask += "#" + e.real; + } + else + nc = na->GetAccount(); + + /* Check excepts BEFORE we get this far */ + if (ci->c) + { + std::vector modes = ci->c->GetModeList("EXCEPT"); + for (unsigned int i = 0; i < modes.size(); ++i) + { + if (Anope::Match(modes[i], mask)) + { + source.Reply(_("\002{0}\002 matches an except on \002{1}\002 and cannot be banned until the except has been removed."), mask, ci->GetName()); + return; + } + } + } + + bool override = !source.AccessFor(ci).HasPriv("AKICK"); + /* Opers overriding get to bypass PEACE */ + if (override) + ; + /* These peace checks are only for masks */ + else if (IRCD->IsChannelValid(mask)) + ; + /* Check whether target nick has equal/higher access + * or whether the mask matches a user with higher/equal access - Viper */ + else if (ci->HasFieldS("PEACE") && nc) + { + ChanServ::AccessGroup nc_access = ci->AccessFor(nc), u_access = source.AccessFor(ci); + if (nc == ci->GetFounder() || nc_access >= u_access) + { + source.Reply(_("Access denied. You can not auto kick \002{0}\002 because they have more privileges than you on \002{1}\002 and \002{2}\002 is enabled."), nc->GetDisplay(), ci->GetName(), "PEACE"); + return; + } + } + else if (ci->HasFieldS("PEACE")) + { +#if 0 + /* Match against all currently online users with equal or + * higher access. - Viper */ + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + { + User *u2 = it->second; + + ChanServ::AccessGroup nc_access = ci->AccessFor(nc), u_access = source.AccessFor(ci); + Entry entry_mask("", mask); + + if ((ci->AccessFor(u2).HasPriv("FOUNDER") || nc_access >= u_access) && entry_mask.Matches(u2)) + { + source.Reply(ACCESS_DENIED); + return; + } + } + + /* Match against the lastusermask of all nickalias's with equal + * or higher access. - Viper */ + for (nickalias_map::const_iterator it = NickServ::NickList->begin(), it_end = NickServ::NickList->end(); it != it_end; ++it) + { + na = it->second; + + ChanServ::AccessGroup nc_access = ci->AccessFor(na->GetAccount()), u_access = source.AccessFor(ci); + if (na->GetAccount() && (na->GetAccount() == ci->GetFounder() || nc_access >= u_access)) + { + Anope::string buf = na->GetNick() + "!" + na->GetLastUsermask(); + if (Anope::Match(buf, mask)) + { + source.Reply(ACCESS_DENIED); + return; + } + } + } +#endif + } + + for (unsigned j = 0, end = ci->GetAkickCount(); j < end; ++j) + { + AutoKick *ak = ci->GetAkick(j); + if (ak->GetAccount() ? ak->GetAccount() == nc : mask.equals_ci(ak->GetMask())) + { + source.Reply(_("\002{0}\002 already exists on \002{1}\002 autokick list."), ak->GetAccount() ? ak->GetAccount()->GetDisplay() : ak->GetMask(), ci->GetName()); + return; + } + } + + if (ci->GetAkickCount() >= Config->GetModule(this->owner)->Get("autokickmax")) + { + source.Reply(_("Sorry, you can only have \002{0}\002 autokick masks on a channel."), Config->GetModule(this->owner)->Get("autokickmax")); + return; + } + + AutoKick *ak; + if (nc) + ak = ci->AddAkick(source.GetNick(), nc, reason); + else + ak = ci->AddAkick(source.GetNick(), mask, reason); + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to add " << mask << (reason == "" ? "" : ": ") << reason; + + this->akickevents(&Event::Akick::OnAkickAdd, source, ci, ak); + + source.Reply(_("\002{0}\002 added to \002{1}\002 autokick list."), mask, ci->GetName()); + + this->DoEnforce(source, ci); + } + + void DoDel(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + const Anope::string &mask = params[2]; + bool override = !source.AccessFor(ci).HasPriv("AKICK"); + + if (!ci->GetAkickCount()) + { + source.Reply(_("The autokick list of \002{0}\002 is empty."), ci->GetName()); + return; + } + + /* Special case: is it a number/list? Only do search if it isn't. */ + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + unsigned int deleted = 0; + + NumberList(mask, true, + [&](unsigned int number) + { + if (!number || number > ci->GetAkickCount()) + return; + + AutoKick *ak = ci->GetAkick(number - 1); + + this->akickevents(&Event::Akick::OnAkickDel, source, ci, ak); + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << (ak->GetAccount() ? ak->GetAccount()->GetDisplay() : ak->GetMask()); + + ++deleted; + delete ak; + }, + [&]() + { + if (!deleted) + source.Reply(_("There are no entries matching \002{0}\002 on the auto kick list of \002{1}\002."), + mask, ci->GetName()); + else if (deleted == 1) + source.Reply(_("Deleted \0021\002 entry from the autokick list of \002{0}\002."), ci->GetName()); + else + source.Reply(_("Deleted \002{0}\002 entries from \002{1}\002 autokick list."), deleted, ci->GetName()); + }); + } + else + { + NickServ::Nick *na = NickServ::FindNick(mask); + NickServ::Account *nc = na ? na->GetAccount() : nullptr; + + unsigned int i, end; + for (i = 0, end = ci->GetAkickCount(); i < end; ++i) + { + AutoKick *ak = ci->GetAkick(i); + + if (ak->GetAccount() ? ak->GetAccount() == nc : mask.equals_ci(ak->GetMask())) + break; + } + + if (i == ci->GetAkickCount()) + { + source.Reply(_("\002{0}\002 was not found on the auto kick list of \002{1}\002."), mask, ci->GetName()); + return; + } + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << mask; + + this->akickevents(&Event::Akick::OnAkickDel, source, ci, ci->GetAkick(i)); + + delete ci->GetAkick(i); + + source.Reply(_("\002{0}\002 deleted from the auto kick list of \002{1}\002."), mask, ci->GetName()); + } + } + + void ProcessList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms, ListFormatter &list) + { + const Anope::string &mask = params.size() > 2 ? params[2] : ""; + + if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + NumberList(mask, false, + [&](unsigned int number) + { + if (!number || number > ci->GetAkickCount()) + return; + + AutoKick *ak = ci->GetAkick(number - 1); + + Anope::string timebuf, lastused; + if (ak->GetAddTime()) + timebuf = Anope::strftime(ak->GetAddTime(), NULL, true); + else + timebuf = _(""); + if (ak->GetLastUsed()) + lastused = Anope::strftime(ak->GetLastUsed(), NULL, true); + else + lastused = _(""); + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(number); + if (ak->GetAccount()) + entry["Mask"] = ak->GetAccount()->GetDisplay(); + else + entry["Mask"] = ak->GetMask(); + entry["Creator"] = ak->GetCreator(); + entry["Created"] = timebuf; + entry["Last used"] = ak->GetLastUsed(); + entry["Reason"] = ak->GetReason(); + list.AddEntry(entry); + }, + []{}); + } + else + { + for (unsigned i = 0, end = ci->GetAkickCount(); i < end; ++i) + { + AutoKick *ak = ci->GetAkick(i); + + if (!mask.empty()) + { + if (!ak->GetAccount() && !Anope::Match(ak->GetMask(), mask)) + continue; + if (ak->GetAccount() && !Anope::Match(ak->GetAccount()->GetDisplay(), mask)) + continue; + } + + Anope::string timebuf, lastused; + if (ak->GetAddTime()) + timebuf = Anope::strftime(ak->GetAddTime(), NULL, true); + else + timebuf = _(""); + if (ak->GetLastUsed()) + lastused = Anope::strftime(ak->GetLastUsed(), NULL, true); + else + lastused = _(""); + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(i + 1); + if (ak->GetAccount()) + entry["Mask"] = ak->GetAccount()->GetDisplay(); + else + entry["Mask"] = ak->GetMask(); + entry["Creator"] = ak->GetCreator(); + entry["Created"] = timebuf; + entry["Last used"] = lastused; + entry["Reason"] = ak->GetReason(); + list.AddEntry(entry); + } + } + + if (list.IsEmpty()) + { + source.Reply(_("There are no entries matching \002{0}\002 on the autokick list of \002{1}\002."), mask, ci->GetName()); + return; + } + + std::vector replies; + list.Process(replies); + + source.Reply(_("Autokick list for \002{0}\002:"), ci->GetName()); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of autokick list.")); + } + + void DoList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + if (!ci->GetAkickCount()) + { + source.Reply(_("The autokick list of \002{0}\002 is empty."), ci->GetName()); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Reason")); + this->ProcessList(source, ci, params, list); + } + + void DoView(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + if (!ci->GetAkickCount()) + { + source.Reply(_("The autokick list of \002{0}\002 is empty."), ci->GetName()); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Last used")).AddColumn(_("Reason")); + this->ProcessList(source, ci, params, list); + } + + void DoEnforce(CommandSource &source, ChanServ::Channel *ci) + { + Channel *c = ci->c; + int count = 0; + + if (!c) + { + source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); + return; + } + + for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ) + { + ChanUserContainer *uc = it->second; + ++it; + + if (c->CheckKick(uc->user)) + ++count; + } + + bool override = !source.AccessFor(ci).HasPriv("AKICK"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "ENFORCE, affects " << count << " users"; + + source.Reply(_("Autokick enforce for \002{0}\002 complete; \002{1}\002 users were affected."), ci->GetName(), count); + } + + void DoClear(CommandSource &source, ChanServ::Channel *ci) + { + bool override = !source.AccessFor(ci).HasPriv("AKICK"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clear the akick list"; + + ci->ClearAkick(); + source.Reply(_("The autokick list of \002{0}\002 has been cleared."), ci->GetName()); + } + + EventHandlers &akickevents; + + public: + CommandCSAKick(Module *creator, EventHandlers &events) : Command(creator, "chanserv/akick", 2, 4), akickevents(events) + { + this->SetDesc(_("Maintain the AutoKick list")); + this->SetSyntax(_("\037channel\037 ADD {\037nick\037 | \037mask\037} [\037reason\037]")); + this->SetSyntax(_("\037channel\037 DEL {\037nick\037 | \037mask\037 | \037entry-num\037 | \037list\037}")); + this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037entry-num\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 VIEW [\037mask\037 | \037entry-num\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 ENFORCE")); + this->SetSyntax(_("\037channel\037 CLEAR")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &cmd = params[1]; + const Anope::string &mask = params.size() > 2 ? params[2] : ""; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + bool is_list = cmd.equals_ci("LIST") || cmd.equals_ci("VIEW"); + + if (mask.empty() && (cmd.equals_ci("ADD") || cmd.equals_ci("DEL"))) + { + this->OnSyntaxError(source, cmd); + return; + } + + if (!source.AccessFor(ci).HasPriv("AKICK") && !source.HasPriv("chanserv/access/modify") && (!is_list || source.HasPriv("chanserv/access/list"))) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "AKICK", ci->GetName()); + return; + } + + if (Anope::ReadOnly && (cmd.equals_ci("ADD") || cmd.equals_ci("DEL") || cmd.equals_ci("CLEAR"))) + { + source.Reply(_("Sorry, channel autokick list modification is temporarily disabled.")); + return; + } + + if (cmd.equals_ci("ADD")) + this->DoAdd(source, ci, params); + else if (cmd.equals_ci("DEL")) + this->DoDel(source, ci, params); + else if (cmd.equals_ci("LIST")) + this->DoList(source, ci, params); + else if (cmd.equals_ci("VIEW")) + this->DoView(source, ci, params); + else if (cmd.equals_ci("ENFORCE")) + this->DoEnforce(source, ci); + else if (cmd.equals_ci("CLEAR")) + this->DoClear(source, ci); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + source.Reply(_("The \002{0} ADD\002 command adds \037mask\037 to the auto kick list list of \037channel\037." + " If \037reason\037 is given, it is used when the user is kicked." + " If \037mask\037 is an account, then all users using the account will be affected.\n" + "\n" + "Examples:\n" + " {command} #anope ADD Cronus noob!\n" + " Adds the account \"Cronus\" to the auto kick list of \"#anope\" with the reason \"noob!\".\n" + "\n" + " {command} #anope ADD Guest*!*@*\n" + " Adds the mask \"Guest*!*@*\" to the auto kick list of \"#anope\"."), + source.command); + else if (subcommand.equals_ci("DEL")) + source.Reply(_("The \002{0} DEL\002 command removes \037mask\037 from the auto kick list." + " It does not, however, remove any bans placed by an auto kick; those must be removed manually.\n" + "\n" + "Example:\n" + " {command} #anope DEL Cronus!\n" + " Removes the auto kick for \"Cronus\" from the auto kick list of \"#anope\".\n"), + source.command); + else if (subcommand.equals_ci("LIST") || subcommand.equals_ci("VIEW")) + source.Reply(_("The \002{0} LIST\002 and \002{0} VIEW\002 command displays the auto kick list of \037channel\037." + " If a wildcard mask is given, only those entries matching the mask are displayed." + " If a list of entry numbers is given, only those entries are shown." + " \002VIEW\002 is similar to \002LIST\002 but also shows who created the auto kick entry, when it was created, and when it was last used." + "\n" + "Example:\n" + " \002{0} #anope LIST 2-5,7-9\002\n" + " Lists auto kick entries numbered 2 through 5 and 7 through 9 on #anope."), + source.command); + else if (subcommand.equals_ci("ENFORCE")) + source.Reply(_("The \002{0} ENFORCE\002 command enforces the auto kick list by forcibly removing users who match an entry on the auto kick list." + "This can be useful if someone does not authenticate or change their host mask until after joining.\n" + "\n" + "Example:\n" + " \002{0} #anope ENFORCE\002\n" + " Enforces the auto kick list of #anope."), + source.command); + else if (subcommand.equals_ci("CLEAR")) + source.Reply(_("The \002{0} CLEAR\002 command clears the auto kick list of \037channel\37. As with the \002DEL\002 command, existing bans placed by auto kick will not be removed.\n" + "\n" + "Example:\n" + " \002{0} #anope CLEAR\002\n" + " Clears the auto kick list of #anope."), + source.command); + else + { + source.Reply(_("Maintains the auto kick list for \037channel\037." + " If a user matching an entry on the auto kick list joins the channel, {0} will place a ban on the user and kick them.\n" + "\n" + "Use of this command requires the \002{1}\002 privilege on \037channel\037."), + source.service->nick, "AKICK"); + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("\n" + "The \002ADD\002 command adds \037nick\037 or \037mask\037 to the auto kick list of \037channel\037.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "The \002DEL\002 command removes \037mask\037 from the auto kick list of \037channel\037.\n" + "\002{msg}{service} {help} {command} DEL\002 for more information.\n" + "\n" + "The \002LIST\002 and \002VIEW\002 commands both display the auto kick list of \037channel\037, but \002VIEW\002 also displays who created the auto kick entry, when it was created, and when it was last used.\n" + "\002{msg}{service} {help} {command} [LIST | VIEW]\002 for more information.\n" + "\n" + "The \002ENFORCE\002 command enforces the auto kick list by forcibly removing users who match an entry on the auto kick list.\n" + "\002{msg}{service} {help} {command} ENFORCE\002 for more information.\n" + "" + "The \002CLEAR\002 clears the auto kick list of \037channel\037." + "\002{msg}{service} {help} {command} CLEAR\002 for more information.\n"), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "help"_kw = help->cname, "command"_kw = source.command); + } + + return true; + } +}; + +class CSAKick : public Module + , public EventHook +{ + CommandCSAKick commandcsakick; + EventHandlers akickevents; + AutoKickType akick_type; + + public: + CSAKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsakick(this, akickevents) + , akickevents(this) + , akick_type(this) + { + } + + EventReturn OnCheckKick(User *u, Channel *c, Anope::string &mask, Anope::string &reason) override + { + if (!c->ci || c->MatchesList(u, "EXCEPT")) + return EVENT_CONTINUE; + + for (unsigned j = 0, end = c->ci->GetAkickCount(); j < end; ++j) + { + AutoKick *ak = c->ci->GetAkick(j); + bool kick = false; + + if (ak->GetAccount()) + kick = ak->GetAccount() == u->Account(); + else if (IRCD->IsChannelValid(ak->GetMask())) + { + Channel *chan = Channel::Find(ak->GetMask()); + kick = chan != NULL && chan->FindUser(u); + } + else + kick = Entry("BAN", ak->GetMask()).Matches(u); + + if (kick) + { + Log(LOG_DEBUG_2) << u->nick << " matched akick " << (ak->GetAccount() ? ak->GetAccount()->GetDisplay() : ak->GetMask()); + ak->SetLastUsed(Anope::CurTime); + if (!ak->GetAccount() && ak->GetMask().find('#') == Anope::string::npos) + mask = ak->GetMask(); + reason = ak->GetReason(); + if (reason.empty()) + reason = Language::Translate(u, Config->GetModule(this)->Get("autokickreason").c_str()); + if (reason.empty()) + reason = Language::Translate(u, _("User has been banned from the channel")); + return EVENT_STOP; + } + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(CSAKick) diff --git a/modules/chanserv/ban.cpp b/modules/chanserv/ban.cpp new file mode 100644 index 000000000..f797eda0f --- /dev/null +++ b/modules/chanserv/ban.cpp @@ -0,0 +1,268 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + + +#include "module.h" + +static Module *me; + +class TempBan : public Timer +{ + private: + Anope::string channel; + Anope::string mask; + Anope::string mode; + + public: + TempBan(time_t seconds, Channel *c, const Anope::string &banmask, const Anope::string &mod) : Timer(me, seconds), channel(c->name), mask(banmask), mode(mod) { } + + void Tick(time_t ctime) override + { + Channel *c = Channel::Find(this->channel); + if (c) + c->RemoveMode(NULL, mode, this->mask); + } +}; + +class CommandCSBan : public Command +{ + public: + CommandCSBan(Module *creator) : Command(creator, "chanserv/ban", 2, 4) + { + this->SetDesc(_("Bans a given nick or mask on a channel")); + this->SetSyntax(_("\037channel\037 [+\037expiry\037] {\037nick\037 | \037mask\037} [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + Configuration::Block *block = Config->GetCommand(source); + const Anope::string &mode = block->Get("mode", "BAN"); + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + Channel *c = ci->c; + if (c == NULL) + { + source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); + return; + } + + if (IRCD->GetMaxListFor(c) && c->HasMode(mode) >= IRCD->GetMaxListFor(c)) + { + source.Reply(_("The %s list for %s is full."), mode.lower().c_str(), c->name.c_str()); + return; + } + + Anope::string expiry, target, reason; + time_t ban_time; + if (params[1][0] == '+') + { + ban_time = Anope::DoTime(params[1]); + if (ban_time == -1) + { + source.Reply(_("Invalid expiry time \002{0}\002."), params[1]); + return; + } + if (params.size() < 3) + { + this->SendSyntax(source); + return; + } + target = params[2]; + reason = "Requested"; + if (params.size() > 3) + reason = params[3]; + } + else + { + ban_time = 0; + target = params[1]; + reason = "Requested"; + if (params.size() > 2) + reason = params[2]; + if (params.size() > 3) + reason += " " + params[3]; + } + + unsigned reasonmax = Config->GetModule("chanserv")->Get("reasonmax", "200"); + if (reason.length() > reasonmax) + reason = reason.substr(0, reasonmax); + + Anope::string signkickformat = Config->GetModule("chanserv")->Get("signkickformat", "%m (%n)"); + signkickformat = signkickformat.replace_all_cs("%n", source.GetNick()); + + User *u = source.GetUser(); + User *u2 = User::Find(target, true); + + ChanServ::AccessGroup u_access = source.AccessFor(ci); + + if (!u_access.HasPriv("BAN") && !source.HasPriv("chanserv/kick")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "BAN", ci->GetName()); + return; + } + + if (u2) + { + ChanServ::AccessGroup u2_access = ci->AccessFor(u2); + + if (u != u2 && ci->HasFieldS("PEACE") && u2_access >= u_access && !source.HasPriv("chanserv/kick")) + { + source.Reply(_("Access denied. \002{0}\002 has the same or more privileges than you on \002{1}\002."), u2->nick, ci->GetName()); + return; + } + + /* + * Don't ban/kick the user on channels where he is excepted + * to prevent services <-> server wars. + */ + if (c->MatchesList(u2, "EXCEPT")) + { + source.Reply(_("\002{0}\002 matches an except on \002{1}\002 and can not be banned until the except has been removed."), u2->nick, ci->GetName()); + return; + } + + if (u2->IsProtected()) + { + source.Reply(_("Access denied. \002{0}\002 is protected and can not be kicked."), u2->nick); + return; + } + + Anope::string mask = ci->GetIdealBan(u2); + + bool override = !u_access.HasPriv("BAN") || (u != u2 && ci->HasFieldS("PEACE") && u2_access >= u_access); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << mask; + + if (!c->HasMode(mode, mask)) + { + c->SetMode(NULL, mode, mask); + if (ban_time) + { + new TempBan(ban_time, c, mask, mode); + source.Reply(_("Ban on \002{0}\002 expires in \002{1}\002."), mask, Anope::Duration(ban_time, source.GetAccount())); + } + } + + /* We still allow host banning while not allowing to kick */ + if (!c->FindUser(u2)) + return; + + if (block->Get("kick", "yes")) + { + if (ci->HasFieldS("SIGNKICK") || (ci->HasFieldS("SIGNKICK_LEVEL") && !source.AccessFor(ci).HasPriv("SIGNKICK"))) + { + signkickformat = signkickformat.replace_all_cs("%m", reason); + c->Kick(ci->WhoSends(), u2, "%s", signkickformat.c_str()); + } + else + c->Kick(ci->WhoSends(), u2, "%s", reason.c_str()); + } + } + else + { + bool founder = u_access.HasPriv("FOUNDER"); + bool override = !founder && !u_access.HasPriv("BAN"); + + Anope::string mask = IRCD->NormalizeMask(target); + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << mask; + + if (!c->HasMode(mode, mask)) + { + c->SetMode(NULL, mode, mask); + if (ban_time) + { + new TempBan(ban_time, c, mask, mode); + source.Reply(_("Ban on \002{0}\002 expires in \002{1}\002."), mask, Anope::Duration(ban_time, source.GetAccount())); + } + } + + int matched = 0, kicked = 0; + for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end;) + { + ChanUserContainer *uc = it->second; + ++it; + + Entry e(mode, mask); + if (e.Matches(uc->user)) + { + ++matched; + + ChanServ::AccessGroup u2_access = ci->AccessFor(uc->user); + + if (matched > 1 && !founder) + continue; + if (u != uc->user && ci->HasFieldS("PEACE") && u2_access >= u_access) + continue; + else if (ci->c->MatchesList(uc->user, "EXCEPT")) + continue; + else if (uc->user->IsProtected()) + continue; + + if (block->Get("kick", "yes")) + { + ++kicked; + + if (ci->HasFieldS("SIGNKICK") || (ci->HasFieldS("SIGNKICK_LEVEL") && !u_access.HasPriv("SIGNKICK"))) + { + reason += " (Matches " + mask + ")"; + signkickformat = signkickformat.replace_all_cs("%m", reason); + c->Kick(ci->WhoSends(), uc->user, "%s", signkickformat.c_str()); + } + else + c->Kick(ci->WhoSends(), uc->user, "%s (Matches %s)", reason.c_str(), mask.c_str()); + } + } + } + + if (matched) + source.Reply(_("Kicked \002{0}/{1}\002 users matching \002{2}\002 from \002{3}\002."), kicked, matched, mask, c->name); + else + source.Reply(_("No users on \002{0}\002 match \002{1}\002."), c->name, mask); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Bans a given \037nick\037 or \037mask\037 on \002channel\002. An optional expiry may be given to cause services to remove the ban after a set amount of time.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037.\n" + "\n" + "Examples:\n" + " {command} #anope Jobe\n" + " Bans the user \"Jobe\" from \"#anope\".\n" + "\n" + " {command} #anope Guest!*@*\n" + " Bans the mask \"Guest!*@*\" on \"#anope\"."), + "BAN"); + return true; + } +}; + +class CSBan : public Module +{ + CommandCSBan commandcsban; + + public: + CSBan(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsban(this) + { + me = this; + } +}; + +MODULE_INIT(CSBan) diff --git a/modules/chanserv/clone.cpp b/modules/chanserv/clone.cpp new file mode 100644 index 000000000..256446bd1 --- /dev/null +++ b/modules/chanserv/clone.cpp @@ -0,0 +1,221 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/bs_badwords.h" +#include "modules/cs_akick.h" + +class CommandCSClone : public Command +{ +#if 0 + void CopyLevels(CommandSource &source, ChannelInfo *ci, ChannelInfo *target_ci) + { + const Anope::map &cilevels = ci->GetLevelEntries(); + + for (Anope::map::const_iterator it = cilevels.begin(); it != cilevels.end(); ++it) + { + target_ci->SetLevel(it->first, it->second); + } + + source.Reply(_("All level entries from \002%s\002 have been cloned into \002%s\002."), ci->name.c_str(), target_ci->name.c_str()); + } +#endif + +public: + CommandCSClone(Module *creator) : Command(creator, "chanserv/clone", 2, 3) + { + this->SetDesc(_("Copy all settings from one channel to another")); + this->SetSyntax(_("\037channel\037 \037target\037 [\037what\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &channel = params[0]; + const Anope::string &target = params[1]; + Anope::string what = params.size() > 2 ? params[2] : ""; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + User *u = source.GetUser(); + ChanServ::Channel *ci = ChanServ::Find(channel); + bool override = false; + + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), channel); + return; + } + + ChanServ::Channel *target_ci = ChanServ::Find(target); + if (!target_ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), target); + return; + } + + if (ci == target_ci) + { + source.Reply(_("Cannot clone channel \002{0}\002 to itself!"), ci->GetName()); + return; + } + + if (!source.IsFounder(ci) || !source.IsFounder(target_ci)) + { + if (!source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have the privilege \002{0}\002 on \002{1}\002 and \002{2}\002."), "FOUNDER", ci->GetName(), target_ci->GetName()); + return; + } + else + override = true; + } + + if (what.equals_ci("ALL")) + what.clear(); + + if (what.empty()) + { + target_ci->Delete(); + target_ci = ChanServ::channel.Create(); + target_ci->SetName(target); + ChanServ::registered_channel_map& map = ChanServ::service->GetChannels(); + map[target_ci->GetName()] = target_ci; + target_ci->c = Channel::Find(target_ci->GetName()); + + if (ci->GetBot()) + ci->GetBot()->Assign(u, target_ci); + else + target_ci->SetBot(nullptr); + + if (target_ci->c) + { + target_ci->c->ci = target_ci; + + target_ci->c->CheckModes(); + + target_ci->c->SetCorrectModes(u, true); + } + + if (target_ci->c && !target_ci->c->topic.empty()) + { + target_ci->SetLastTopic(target_ci->GetLastTopic()); + target_ci->SetLastTopicSetter(target_ci->c->topic_setter); + target_ci->SetLastTopicTime(target_ci->c->topic_time); + } + else + target_ci->SetLastTopicSetter(source.service->nick); + + Event::OnChanRegistered(&Event::ChanRegistered::OnChanRegistered, target_ci); + + source.Reply(_("All settings from \002{0}\002 have been cloned to \002{0}\002."), channel, target); + } + else if (what.equals_ci("ACCESS")) + { + std::set masks; + unsigned access_max = Config->GetModule("chanserv")->Get("accessmax", "1024"); + unsigned count = 0; + + for (unsigned i = 0; i < target_ci->GetAccessCount(); ++i) + masks.insert(target_ci->GetAccess(i)->Mask()); + + for (unsigned i = 0; i < ci->GetAccessCount(); ++i) + { + ChanServ::ChanAccess *taccess = ci->GetAccess(i); + + if (access_max && target_ci->GetDeepAccessCount() >= access_max) + break; + + if (masks.count(taccess->Mask())) + continue; + masks.insert(taccess->Mask()); + + ChanServ::ChanAccess *newaccess = anope_dynamic_static_cast(taccess->GetSerializableType()->Create()); + newaccess->SetChannel(taccess->GetChannel()); + newaccess->SetMask(taccess->GetMask()); + newaccess->SetMask(taccess->GetMask()); + newaccess->SetCreator(taccess->GetCreator()); + newaccess->SetLastSeen(taccess->GetLastSeen()); + newaccess->SetCreated(taccess->GetCreated()); + newaccess->AccessUnserialize(taccess->AccessSerialize()); + + ++count; + } + + source.Reply(_("\002{0}\002 access entries from \002{1}\002 have been cloned to \002{2}\002."), count, channel, target); + } + else if (what.equals_ci("AKICK")) + { + target_ci->ClearAkick(); + for (unsigned i = 0; i < ci->GetAkickCount(); ++i) + { + AutoKick *ak = ci->GetAkick(i); + if (ak->GetAccount()) + target_ci->AddAkick(ak->GetCreator(), ak->GetAccount(), ak->GetReason(), ak->GetAddTime(), ak->GetLastUsed()); + else + target_ci->AddAkick(ak->GetCreator(), ak->GetMask(), ak->GetReason(), ak->GetAddTime(), ak->GetLastUsed()); + } + + source.Reply(_("All auto kick entries from \002{0}\002 have been cloned to \002{1}\002."), channel, target); + } + else if (what.equals_ci("BADWORDS")) + { + if (!badwords) + { + source.Reply(_("All badword entries from \002{0}\002 have been cloned to \002{1}\002."), channel, target); // BotServ doesn't exist/badwords isn't loaded + return; + } + + badwords->ClearBadWords(target_ci); + + for (unsigned i = 0; i < badwords->GetBadWordCount(ci); ++i) + { + BadWord *bw = badwords->GetBadWord(ci, i); + badwords->AddBadWord(target_ci, bw->GetWord(), bw->GetType()); + } + + source.Reply(_("All badword entries from \002{0}\002 have been cloned to \002{1}\002."), channel, target); + } + else + { + this->OnSyntaxError(source, ""); + return; + } + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clone " << (what.empty() ? "everything from it" : what) << " to " << target_ci->GetName(); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Copies all settings, access, akicks, etc. from \037channel\037 to the \037target\037 channel." + " If \037what\037 is \002ACCESS\002, \002AKICK\002, or \002BADWORDS\002 then only the respective settings are cloned.\n" + "\n" + "You must be the founder of \037channel\037 and \037target\037.")); + return true; + } +}; + +class CSClone : public Module +{ + CommandCSClone commandcsclone; + + public: + CSClone(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsclone(this) + { + + } +}; + +MODULE_INIT(CSClone) diff --git a/modules/chanserv/cs_access.old b/modules/chanserv/cs_access.old new file mode 100644 index 000000000..b363e9757 --- /dev/null +++ b/modules/chanserv/cs_access.old @@ -0,0 +1,941 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + + +#include "module.h" +#include "modules/chanserv.h" +#include "modules/cs_access.h" +#include "main/chanaccess.h" +#include "main/chanaccesstype.h" + +class AccessChanAccess : public ChanAccessImpl +{ + public: + AccessChanAccess(Serialize::TypeBase *type) : ChanAccessImpl(type) { } + AccessChanAccess(Serialize::TypeBase *type, Serialize::ID id) : ChanAccessImpl(type, id) { } + + int GetLevel(); + void SetLevel(const int &); + + bool HasPriv(const Anope::string &name) override + { + return this->GetChannel()->GetLevel(name) != ChanServ::ACCESS_INVALID && this->GetLevel() >= this->GetChannel()->GetLevel(name); + } + + Anope::string AccessSerialize() + { + return stringify(this->GetLevel()); + } + + void AccessUnserialize(const Anope::string &data) override + { + try + { + this->SetLevel(convertTo(data)); + } + catch (const ConvertException &) + { + } + } + +#if 0 + bool operator>(ChanServ::ChanAccess &other) override + { + if (this->GetSerializableType() != other.GetSerializableType()) + return ChanServ::ChanAccess::operator>(other); + else + return this->GetLevel() > anope_dynamic_static_cast(&other)->GetLevel(); + } + + bool operator<(ChanServ::ChanAccess &other) override + { + if (this->GetSerializableType() != other.GetSerializableType()) + return ChanAccess::operator<(other); + else + return this->GetLevel() < anope_dynamic_static_cast(&other)->GetLevel(); + } +#endif +}; + +class AccessChanAccessType : public Serialize::Type +{ + public: + Serialize::Field level; + + AccessChanAccessType(Module *me) : Serialize::Type(me, "AccessChanAccess") + , level(this, "level") + { + SetParent(ChanServ::chanaccess); + } +}; + +int AccessChanAccess::GetLevel() +{ + return Get(&AccessChanAccessType::level); +} + +void AccessChanAccess::SetLevel(const int &i) +{ + Object::Set(&AccessChanAccessType::level, i); +} + +class CommandCSAccess : public Command +{ + void DoAdd(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + Anope::string mask = params[2]; + ChanServ::Privilege *p = NULL; + int level = ChanServ::ACCESS_INVALID; + + try + { + level = convertTo(params[3]); + } + catch (const ConvertException &) + { + p = ChanServ::service ? ChanServ::service->FindPrivilege(params[3]) : nullptr; + if (p != NULL && p->level) + level = p->level; + } + + if (!level) + { + source.Reply(_("Access level must be non-zero.")); + return; + } + + if (level <= ChanServ::ACCESS_INVALID || level >= ChanServ::ACCESS_FOUNDER) + { + source.Reply(_("Access level must be between \002{0}\002 and \002{1}\002 inclusive."), ChanServ::ACCESS_INVALID + 1, ChanServ::ACCESS_FOUNDER - 1); + return; + } + + ChanServ::AccessGroup u_access = source.AccessFor(ci); + ChanServ::ChanAccess *highest = u_access.Highest(); + + AccessChanAccess tmp_access(nullptr); + tmp_access.SetChannel(ci); + tmp_access.SetLevel(level); + + bool override = false; + + if ((!highest || *highest <= tmp_access) && !u_access.founder) + { + if (source.HasPriv("chanserv/access/modify")) + override = true; + else + { + source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to add someone at level \002{1}\002."), ci->GetName(), level); + return; + } + } + + NickServ::Nick *na = nullptr; + ChanServ::Channel *targ_ci = nullptr; + + if (IRCD->IsChannelValid(mask)) + { + if (Config->GetModule("chanserv")->Get("disallow_channel_access")) + { + source.Reply(_("Channels may not be on access lists.")); + return; + } + + targ_ci = ChanServ::Find(mask); + if (targ_ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), mask); + return; + } + + if (ci == targ_ci) + { + source.Reply(_("You can't add a channel to its own access list.")); + return; + } + + mask = targ_ci->GetName(); + } + else + { + na = NickServ::FindNick(mask); + + if (!na && Config->GetModule("chanserv")->Get("disallow_hostmask_access")) + { + source.Reply(_("Masks and unregistered users may not be on access lists.")); + return; + } + + if (mask.find_first_of("!*@") == Anope::string::npos && !na) + { + User *targ = User::Find(mask, true); + if (targ != NULL) + mask = "*!*@" + targ->GetDisplayedHost(); + else + { + source.Reply(_("\002{0}\002 isn't registered."), mask); + return; + } + } + + if (na) + mask = na->GetNick(); + } + + for (unsigned i = ci->GetAccessCount(); i > 0; --i) + { + ChanServ::ChanAccess *access = ci->GetAccess(i - 1); + if (mask.equals_ci(access->Mask())) + { + /* Don't allow lowering from a level >= u_level */ + if ((!highest || *access >= *highest) && !u_access.founder && !source.HasPriv("chanserv/access/modify")) + { + source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to lower the access of \002{1}\002."), ci->GetName(), access->Mask()); + return; + } + delete access; + break; + } + } + + unsigned access_max = Config->GetModule("chanserv")->Get("accessmax", "1024"); + if (access_max && ci->GetDeepAccessCount() >= access_max) + { + source.Reply(_("Sorry, you can only have %d access entries on a channel, including access entries from other channels."), access_max); + return; + } + + AccessChanAccess *access = anope_dynamic_static_cast(accesschanaccess.Create()); + if (na) + access->SetObj(na->GetAccount()); + else if (targ_ci) + access->SetObj(targ_ci); + access->SetChannel(ci); + access->SetMask(mask); + access->SetCreator(source.GetNick()); + access->SetLevel(level); + access->SetLastSeen(0); + access->SetCreated(Anope::CurTime); + + Event::OnAccessAdd(&Event::AccessAdd::OnAccessAdd, ci, source, access); + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to add " << mask << " with level " << level; + if (p != NULL) + source.Reply(_("\002{0}\002 added to the access list of \002{1}\002 with privilege \002{2}\002 (level \002{3}\002)."), access->Mask(), ci->GetName(), p->name, level); + else + source.Reply(_("\002{0}\002 added to the access list of \002{1}\002 at level \002{2}\002."), access->Mask(), ci->GetName(), level); + } + + void DoDel(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + Anope::string mask = params[2]; + + if (!ci->GetAccessCount()) + { + source.Reply(_("The access list for \002{0}\002 is empty."), ci->GetName()); + return; + } + + if (!isdigit(mask[0]) && mask.find_first_of("#!*@") == Anope::string::npos && !NickServ::FindNick(mask)) + { + User *targ = User::Find(mask, true); + if (targ != NULL) + mask = "*!*@" + targ->GetDisplayedHost(); + else + { + source.Reply(_("\002{0}\002 isn't registered."), mask); + return; + } + } + + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + bool override = !source.AccessFor(ci).HasPriv("ACCESS_CHANGE") && source.HasPriv("chanserv/access/modify"); + Anope::string nicks; + bool denied = false; + unsigned int deleted = 0; + + NumberList(mask, true, + [&](unsigned int num) + { + if (!num || num > ci->GetAccessCount()) + return; + + ChanServ::ChanAccess *access = ci->GetAccess(num - 1); + + ChanServ::AccessGroup ag = source.AccessFor(ci); + ChanServ::ChanAccess *u_highest = ag.Highest(); + + if ((!u_highest || *u_highest <= *access) && !ag.founder && !override && access->GetObj() != source.nc) + { + denied = true; + return; + } + + ++deleted; + if (!nicks.empty()) + nicks += ", " + access->Mask(); + else + nicks = access->Mask(); + + Event::OnAccessDel(&Event::AccessDel::OnAccessDel, ci, source, access); + delete access; + }, + [&]() + { + if (denied && !deleted) + source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to remove any access entries matching \002{1}\002.")); + else if (!deleted) + source.Reply(_("There are no entries matching \002{0}\002 on the access list of \002{1}\002."), mask, ci->GetName()); + else + { + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << nicks; + + if (deleted == 1) + source.Reply(_("Deleted \0021\002 entry from the access list of \002{0}\002."), ci->GetName()); + else + source.Reply(_("Deleted \002{0}\002 entries from the access list of \002{1}\002."), deleted, ci->GetName()); + } + }); + } + else + { + ChanServ::AccessGroup u_access = source.AccessFor(ci); + ChanServ::ChanAccess *highest = u_access.Highest(); + + for (unsigned i = ci->GetAccessCount(); i > 0; --i) + { + ChanServ::ChanAccess *access = ci->GetAccess(i - 1); + if (mask.equals_ci(access->Mask())) + { + if (access->GetObj() != source.nc && !u_access.founder && (!highest || *highest <= *access) && !source.HasPriv("chanserv/access/modify")) + source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to remove the access of \002{1}\002."), ci->GetName(), access->Mask()); + else + { + source.Reply(_("\002{0}\002 deleted from the access list of \002{1}\002."), access->Mask(), ci->GetName()); + bool override = !u_access.founder && !u_access.HasPriv("ACCESS_CHANGE") && !access->Mask().equals_ci(source.nc->GetDisplay()); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << access->Mask(); + + Event::OnAccessDel(&Event::AccessDel::OnAccessDel, ci, source, access); + delete access; + } + return; + } + } + + source.Reply(_("\002{0}\002 was not found on the access list of \002{1}\002."), mask, ci->GetName()); + } + } + + void ProcessList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms, ListFormatter &list) + { + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + + if (!ci->GetAccessCount()) + { + source.Reply(_("The access list for \002{0}\002 is empty."), ci->GetName()); + return; + } + + if (!nick.empty() && nick.find_first_not_of("1234567890,-") == Anope::string::npos) + { + NumberList(nick, false, + [&](unsigned int number) + { + if (!number || number > ci->GetAccessCount()) + return; + + ChanServ::ChanAccess *access = ci->GetAccess(number - 1); + + Anope::string timebuf; + if (ci->c) + for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end; ++cit) + { + ChanServ::ChanAccess::Path p; + if (access->Matches(cit->second->user, cit->second->user->Account(), p)) + timebuf = "Now"; + } + if (timebuf.empty()) + { + if (access->GetLastSeen() == 0) + timebuf = "Never"; + else + timebuf = Anope::strftime(access->GetLastSeen(), NULL, true); + } + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(number); + entry["Level"] = access->AccessSerialize(); + entry["Mask"] = access->Mask(); + entry["By"] = access->GetCreator(); + entry["Last seen"] = timebuf; + list.AddEntry(entry); + }, + [&](){}); + } + else + { + for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i) + { + ChanServ::ChanAccess *access = ci->GetAccess(i); + + if (!nick.empty() && !Anope::Match(access->Mask(), nick)) + continue; + + Anope::string timebuf; + if (ci->c) + for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end; ++cit) + { + ChanServ::ChanAccess::Path p; + if (access->Matches(cit->second->user, cit->second->user->Account(), p)) + timebuf = "Now"; + } + if (timebuf.empty()) + { + if (access->GetLastSeen() == 0) + timebuf = "Never"; + else + timebuf = Anope::strftime(access->GetLastSeen(), NULL, true); + } + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(i + 1); + entry["Level"] = access->AccessSerialize(); + entry["Mask"] = access->Mask(); + entry["By"] = access->GetCreator(); + entry["Last seen"] = timebuf; + list.AddEntry(entry); + } + } + + if (list.IsEmpty()) + { + source.Reply(_("No matching entries on the access list of \002{0}\002."), ci->GetName()); + return; + } + + std::vector replies; + list.Process(replies); + + source.Reply(_("Access list for \002{0}\002:"), ci->GetName()); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of access list.")); + } + + void DoList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + if (!ci->GetAccessCount()) + { + source.Reply(_("The access list for \002{0}\002 is empty."), ci->GetName()); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Level")).AddColumn(_("Mask")); + this->ProcessList(source, ci, params, list); + } + + void DoView(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + if (!ci->GetAccessCount()) + { + source.Reply(_("The access list for \002{0}\002 is empty."), ci->GetName()); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Level")).AddColumn(_("Mask")).AddColumn(_("By")).AddColumn(_("Last seen")); + this->ProcessList(source, ci, params, list); + } + + void DoClear(CommandSource &source, ChanServ::Channel *ci) + { + if (!source.IsFounder(ci) && !source.HasPriv("chanserv/access/modify")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); + return; + } + + Event::OnAccessClear(&Event::AccessClear::OnAccessClear, ci, source); + + ci->ClearAccess(); + + source.Reply(_("The access list of \002{0}\002 has been cleared."), ci->GetName()); + + bool override = !source.IsFounder(ci); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clear the access list"; + } + + public: + CommandCSAccess(Module *creator) : Command(creator, "chanserv/access", 2, 4) + { + this->SetDesc(_("Modify the list of privileged users")); + this->SetSyntax(_("\037channel\037 ADD \037mask\037 \037level\037")); + this->SetSyntax(_("\037channel\037 DEL {\037mask\037 | \037entry-num\037 | \037list\037}")); + this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 VIEW [\037mask\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 CLEAR")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &cmd = params[1]; + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + const Anope::string &s = params.size() > 3 ? params[3] : ""; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + bool is_list = cmd.equals_ci("LIST") || cmd.equals_ci("VIEW"); + bool is_clear = cmd.equals_ci("CLEAR"); + bool is_del = cmd.equals_ci("DEL"); + + ChanServ::AccessGroup access = source.AccessFor(ci); + + bool has_access = false; + if (source.HasPriv("chanserv/access/modify")) + has_access = true; + else if (is_list && source.HasPriv("chanserv/access/list")) + has_access = true; + else if (is_list && source.AccessFor(ci).HasPriv("ACCESS_LIST")) + has_access = true; + else if (source.AccessFor(ci).HasPriv("ACCESS_CHANGE")) + has_access = true; + else if (is_del) + { + NickServ::Nick *na = NickServ::FindNick(nick); + if (na && na->GetAccount() == source.GetAccount()) + has_access = true; + } + + /* If LIST, we don't *require* any parameters, but we can take any. + * If DEL, we require a nick and no level. + * Else (ADD), we require a level (which implies a nick). */ + if (is_list || is_clear ? 0 : (cmd.equals_ci("DEL") ? (nick.empty() || !s.empty()) : s.empty())) + { + this->OnSyntaxError(source, cmd); + return; + } + + if (!has_access) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), is_list ? "ACCESS_LIST" : "ACCESS_CHANGE", ci->GetName()); + return; + } + + if (Anope::ReadOnly && !is_list) + { + source.Reply(_("Sorry, channel access list modification is temporarily disabled.")); + return; + } + + if (cmd.equals_ci("ADD")) + this->DoAdd(source, ci, params); + else if (cmd.equals_ci("DEL")) + this->DoDel(source, ci, params); + else if (cmd.equals_ci("LIST")) + this->DoList(source, ci, params); + else if (cmd.equals_ci("VIEW")) + this->DoView(source, ci, params); + else if (cmd.equals_ci("CLEAR")) + this->DoClear(source, ci); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + { + source.Reply(_("The \002{0} ADD\002 adds \037mask\037 to the access list of \037channel\037 at level \037level\037." + " If \037mask\037 is already present on the access list, the access level for it is changed to \037level\037." + " The \037level\037 may be a numerical level between \002{1}\002 and \002{2}\002 or the name of a privilege (eg. \002{3}\002)." + " The privilege set granted to a given user is the union of the privileges of access entries that match the user." + " Use of this command requires the \002{4}\002 privilege on \037channel\037."), + source.command, ChanServ::ACCESS_INVALID + 1, ChanServ::ACCESS_FOUNDER - 1, "AUTOOP", "ACCESS_CHANGE"); + + if (!Config->GetModule("chanserv")->Get("disallow_channel_access")) + source.Reply(_("The given \037mask\037 may also be a channel, which will use the access list from the other channel up to the given \037level\037.")); + + //XXX show def levels + + source.Reply(_("\n" + "Examples:\n" + " {command} #anope ADD Adam 9001\n" + " Adds \"Adam\" to the access list of \"#anope\" at level \"9001\".\n" + "\n" + " {command} #anope ADD *!*@anope.org AUTOOP\n" + " Adds the host mask \"*!*@anope.org\" to the access list of \"#anope\" with the privilege \"AUTOOP\".")); + } + else if (subcommand.equals_ci("DEL")) + source.Reply(_("The \002{0} DEL\002 command removes \037mask\037 from the access list of \037channel\037." + " If a list of entry numbers is given, those entries are deleted." + " You may remove yourself from an access list, even if you do not have access to modify that list otherwise." + " Use of this command requires the \002{1}\002 privilege on \037channel\037.\n" + "\n" + "Example:\n" + " {command} #anope del DukePyrolator\n" + " Removes the access of \"DukePyrolator\" from \"#anope\"."), + source.command, "ACCESS_CHANGE"); + else if (subcommand.equals_ci("LIST") || subcommand.equals_ci("VIEW")) + source.Reply(_("The \002{0} LIST\002 and \002{0} VIEW\002 command displays the access list of \037channel\037." + " If a wildcard mask is given, only those entries matching the mask are displayed." + " If a list of entry numbers is given, only those entries are shown." + " \002VIEW\002 is similar to \002LIST\002 but also shows who created the access entry, and when the access entry was last used." + " Use of these commands requires the \002{1}\002 privilege on \037channel\037.\n" + "\n" + "Example:\n" + " {0} #anope LIST 2-5,7-9\n" + " Lists access entries numbered 2 through 5 and 7 through 9 on #anope."), + source.command, "ACCESS_LIST"); + else if (subcommand.equals_ci("CLEAR")) + source.Reply(_("The \002{0} CLEAR\002 command clears the access list of \037channel\037." + " Use of this command requires the \002{1}\002 privilege on \037channel\037."), + source.command, "FOUNDER"); + + else + { + source.Reply(_("Maintains the access list for \037channel\037. The access list specifies which users are granted which privileges to the channel." + " The access system uses numerical levels to represent different sets of privileges. Users who are identified but do not match any entries on" + " the access list has a level of 0. Unregistered or unidentified users who do not match any entries have a user level of 0.")); + ServiceBot *bi; + Anope::string name; + CommandInfo *help = source.service->FindCommand("generic/help"); + if (Command::FindCommandFromService("chanserv/levels", bi, name) && help) + source.Reply(_("\n" + "Access levels can be configured via the \002{levels}\002 command. See \002{msg}{service} {help} {levels}\002 for more information."), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = bi->nick, "help"_kw = help->cname, "levels"_kw = name); + + if (help) + source.Reply(_("\n" + "The \002ADD\002 command adds \037mask\037 to the access list at level \037level\037.\n" + "Use of this command requires the \002{change}\002 privilege on \037channel\037.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "The \002DEL\002 command removes \037mask\037 from the access list.\n" + "Use of this command requires the \002{change}\002 privilege on \037channel\037.\n" + "\002{msg}{service} {help} {command} DEL\002 for more information.\n" + "\n" + "The \002LIST\002 and \002VIEW\002 commands both show the access list for \037channel\037, but \002VIEW\002 also shows who created the access entry, and when the user was last seen.\n" + "Use of these commands requires the \002{list}\002 privilege on \037channel\037.\n" + "\002{msg}{service} {help} {command} [LIST | VIEW]\002 for more information.\n" + "\n" + "The \002CLEAR\002 command clears the access list." + "Use of this command requires the \002{founder}\002 privilege on \037channel\037.\n" + "\002{msg}{service} {help} {command} CLEAR\002 for more information."), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "command"_kw = source.command, + "help"_kw = help->cname, "change"_kw = "ACCESS_CHANGE", "list"_kw = "ACCESS_LIST", "founder"_kw = "FOUNDER"); + } + + return true; + } +}; + +class CommandCSLevels : public Command +{ + void DoSet(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + const Anope::string &what = params[2]; + const Anope::string &lev = params[3]; + + int level; + + if (lev.equals_ci("FOUNDER")) + level = ChanServ::ACCESS_FOUNDER; + else + { + try + { + level = convertTo(lev); + } + catch (const ConvertException &) + { + this->OnSyntaxError(source, "SET"); + return; + } + } + + if (level <= ChanServ::ACCESS_INVALID || level > ChanServ::ACCESS_FOUNDER) + source.Reply(_("Level must be between \002{0}\002 and \002{1}\002 inclusive."), ChanServ::ACCESS_INVALID + 1, ChanServ::ACCESS_FOUNDER - 1); + else + { + ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(what) : nullptr; + if (p == NULL) + { + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("There is no such privilege \002{0}\002. See \002{0}{1} {2} {3}\002 for a list of valid settings."), + what, Config->StrictPrivmsg, source.service->nick, help->cname, source.command); + } + else + { + bool override = !source.AccessFor(ci).HasPriv("FOUNDER"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to set " << p->name << " to level " << level; + + ci->SetLevel(p->name, level); + this->onlevelchange(&Event::LevelChange::OnLevelChange, source, ci, p->name, level); + + if (level == ChanServ::ACCESS_FOUNDER) + source.Reply(_("Level for privilege \002{0}\002 on channel \002{1}\002 changed to \002founder only\002."), p->name, ci->GetName()); + else + source.Reply(_("Level for privilege \002{0}\002 on channel \002{1}\002 changed to \002{3}\002."), p->name, ci->GetName(), level); + } + } + } + + void DoDisable(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + const Anope::string &what = params[2]; + + /* Don't allow disabling of the founder level. It would be hard to change it back if you don't have access to use this command */ + if (what.equals_ci("FOUNDER")) + { + source.Reply(_("You can not disable the founder privilege because it would be impossible to reenable it at a later time.")); + return; + } + + ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(what) : nullptr; + if (p != NULL) + { + bool override = !source.AccessFor(ci).HasPriv("FOUNDER"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable " << p->name; + + ci->SetLevel(p->name, ChanServ::ACCESS_INVALID); + this->onlevelchange(&Event::LevelChange::OnLevelChange, source, ci, p->name, ChanServ::ACCESS_INVALID); + + source.Reply(_("Privileged \002{0}\002 disabled on channel \002{1}\002."), p->name, ci->GetName()); + return; + } + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("There is no such privilege \002{0}\002. See \002{0}{1} {2} {3}\002 for a list of valid settings."), + what, Config->StrictPrivmsg, source.service->nick, help->cname, source.command); + } + + void DoList(CommandSource &source, ChanServ::Channel *ci) + { + if (!ChanServ::service) + return; + + source.Reply(_("Access level settings for channel \002{0}\002"), ci->GetName()); + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Name")).AddColumn(_("Level")); + + const std::vector &privs = ChanServ::service->GetPrivileges(); + + for (unsigned i = 0; i < privs.size(); ++i) + { + const ChanServ::Privilege &p = privs[i]; + int16_t j = ci->GetLevel(p.name); + + ListFormatter::ListEntry entry; + entry["Name"] = p.name; + + if (j == ChanServ::ACCESS_INVALID) + entry["Level"] = Language::Translate(source.GetAccount(), _("(disabled)")); + else if (j == ChanServ::ACCESS_FOUNDER) + entry["Level"] = Language::Translate(source.GetAccount(), _("(founder only)")); + else + entry["Level"] = stringify(j); + + list.AddEntry(entry); + } + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + + void DoReset(CommandSource &source, ChanServ::Channel *ci) + { + bool override = !source.AccessFor(ci).HasPriv("FOUNDER"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to reset all levels"; + + ci->ClearLevels(); + this->onlevelchange(&Event::LevelChange::OnLevelChange, source, ci, "ALL", 0); + + source.Reply(_("Levels for \002{0}\002 reset to defaults."), ci->GetName()); + } + + EventHandlers &onlevelchange; + + public: + CommandCSLevels(Module *creator, EventHandlers &event) : Command(creator, "chanserv/levels", 2, 4), onlevelchange(event) + { + this->SetDesc(_("Redefine the meanings of access levels")); + this->SetSyntax(_("\037channel\037 SET \037privilege\037 \037level\037")); + this->SetSyntax(_("\037channel\037 {DIS | DISABLE} \037privilege\037")); + this->SetSyntax(_("\037channel\037 LIST")); + this->SetSyntax(_("\037channel\037 RESET")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &cmd = params[1]; + const Anope::string &what = params.size() > 2 ? params[2] : ""; + const Anope::string &s = params.size() > 3 ? params[3] : ""; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + bool has_access = false; + if (source.HasPriv("chanserv/access/modify")) + has_access = true; + else if (cmd.equals_ci("LIST") && source.HasPriv("chanserv/access/list")) + has_access = true; + else if (source.AccessFor(ci).HasPriv("FOUNDER")) + has_access = true; + + /* If SET, we want two extra parameters; if DIS[ABLE] or FOUNDER, we want only + * one; else, we want none. + */ + if (cmd.equals_ci("SET") ? s.empty() : (cmd.substr(0, 3).equals_ci("DIS") ? (what.empty() || !s.empty()) : !what.empty())) + { + this->OnSyntaxError(source, cmd); + return; + } + + if (!has_access) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); + return; + } + + if (Anope::ReadOnly && !cmd.equals_ci("LIST")) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + if (cmd.equals_ci("SET")) + this->DoSet(source, ci, params); + else if (cmd.equals_ci("DIS") || cmd.equals_ci("DISABLE")) + this->DoDisable(source, ci, params); + else if (cmd.equals_ci("LIST")) + this->DoList(source, ci); + else if (cmd.equals_ci("RESET")) + this->DoReset(source, ci); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("DESC")) + { + source.Reply(_("The following privileges are available:")); + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Name")).AddColumn(_("Description")); + + if (ChanServ::service) + { + const std::vector &privs = ChanServ::service->GetPrivileges(); + for (unsigned i = 0; i < privs.size(); ++i) + { + const ChanServ::Privilege &p = privs[i]; + ListFormatter::ListEntry entry; + entry["Name"] = p.name; + entry["Description"] = Language::Translate(source.nc, p.desc.c_str()); + list.AddEntry(entry); + } + } + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + else + { + ServiceBot *bi; + Anope::string name; + if (!Command::FindCommandFromService("chanserv/access", bi, name) || bi != source.service) + return false; + CommandInfo *help = source.service->FindCommand("generic/help"); + if (!help) + return false; + + source.Reply(_("The \002{0}\002 command allows fine control over the meaning of numeric access levels used in the \002{1}\001 command.\n" + "\n" + "\002{0} SET\002 allows changing which \037privilege\037 is included in a given \037level\37.\n" + "\n" + "\002{0} DISABLE\002 disables a privilege and prevents anyone from be granted it, even channel founders." + " The \002{2}\002 privilege can not be disabled.\n" + "\n" + "\002{0} LIST\002 shows the current level for each privilege.\n" + "\n" + "\002{0} RESET\002 resets the levels to the default levels for newly registered channels.\n" + "\n" + "For the list of privileges and their descriptions, see \002{3} {4} DESC\002."), + source.command, name, "FOUNDER", help->cname, source.command); + } + return true; + } +}; + +class CSAccess : public Module + , public EventHook +{ + CommandCSAccess commandcsaccess; + CommandCSLevels commandcslevels; + EventHandlers onlevelchange; + AccessChanAccessType accesschanaccesstype; + + public: + CSAccess(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsaccess(this) + , commandcslevels(this, onlevelchange) + , onlevelchange(this) + , accesschanaccesstype(this) + { + this->SetPermanent(true); + + } + + EventReturn OnGroupCheckPriv(const ChanServ::AccessGroup *group, const Anope::string &priv) override + { + if (group->ci == NULL) + return EVENT_CONTINUE; + /* Special case. Allows a level of < 0 to match anyone, and a level of 0 to match anyone identified. */ + int16_t level = group->ci->GetLevel(priv); + if (level < 0) + return EVENT_ALLOW; + else if (level == 0 && group->nc) + return EVENT_ALLOW; + return EVENT_CONTINUE; + } +}; + +template<> void ModuleInfo(ModuleDef *def) +{ + def->Depends("chanserv"); +} + +MODULE_INIT(CSAccess) diff --git a/modules/chanserv/cs_flags.old b/modules/chanserv/cs_flags.old new file mode 100644 index 000000000..9a29a491f --- /dev/null +++ b/modules/chanserv/cs_flags.old @@ -0,0 +1,528 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_access.h" +#include "main/chanaccess.h" +#include "main/chanaccesstype.h" + +static std::map defaultFlags; + +class FlagsChanAccess : public ChanAccessImpl +{ + public: + FlagsChanAccess(Serialize::TypeBase *type) : ChanAccessImpl(type) { } + FlagsChanAccess(Serialize::TypeBase *type, Serialize::ID id) : ChanAccessImpl(type, id) { } + + Anope::string GetFlags(); + void SetFlags(const Anope::string &); + + bool HasPriv(const Anope::string &priv) override + { + std::map::iterator it = defaultFlags.find(priv); + return it != defaultFlags.end() && GetFlags().find(it->second) != Anope::string::npos; + } + + Anope::string AccessSerialize() + { + return GetFlags(); + } + + void AccessUnserialize(const Anope::string &data) override + { + SetFlags(data); + } + + static Anope::string DetermineFlags(ChanServ::ChanAccess *access) + { + if (access->GetSerializableType()->GetName() != "FlagsChanAccess") + return access->AccessSerialize(); + + std::set buffer; + + for (std::map::iterator it = defaultFlags.begin(), it_end = defaultFlags.end(); it != it_end; ++it) + if (access->HasPriv(it->first)) + buffer.insert(it->second); + + if (buffer.empty()) + return "(none)"; + else + return Anope::string(buffer.begin(), buffer.end()); + } +}; + +class FlagsChanAccessType : public Serialize::Type +{ + public: + Serialize::Field flags; + //Serialize::Field>> flags; XXX? + + FlagsChanAccessType(Module *me) : Serialize::Type(me, "FlagsChanAccess") + , flags(this, "flags") + { + SetParent(ChanServ::chanaccess); + } +}; + +Anope::string FlagsChanAccess::GetFlags() +{ + return Get(&FlagsChanAccessType::flags); +} + +void FlagsChanAccess::SetFlags(const Anope::string &i) +{ + Object::Set(&FlagsChanAccessType::flags, i); +} + +class CommandCSFlags : public Command +{ + void DoModify(CommandSource &source, ChanServ::Channel *ci, Anope::string mask, const Anope::string &flags) + { + if (flags.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + ChanServ::AccessGroup u_access = source.AccessFor(ci); + ChanServ::ChanAccess *highest = u_access.Highest(); + + NickServ::Nick *na = nullptr; + ChanServ::Channel *targ_ci = nullptr; + + if (IRCD->IsChannelValid(mask)) + { + if (Config->GetModule("chanserv")->Get("disallow_channel_access")) + { + source.Reply(_("Channels may not be on access lists.")); + return; + } + + targ_ci = ChanServ::Find(mask); + if (targ_ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), mask); + return; + } + else if (ci == targ_ci) + { + source.Reply(_("You can't add a channel to its own access list.")); + return; + } + + mask = targ_ci->GetName(); + } + else + { + na = NickServ::FindNick(mask); + if (!na && Config->GetModule("chanserv")->Get("disallow_hostmask_access")) + { + source.Reply(_("Masks and unregistered users may not be on access lists.")); + return; + } + else if (mask.find_first_of("!*@") == Anope::string::npos && !na) + { + User *targ = User::Find(mask, true); + if (targ != NULL) + mask = "*!*@" + targ->GetDisplayedHost(); + else + { + source.Reply(_("\002{0}\002 isn't registered."), mask); + return; + } + } + + if (na) + mask = na->GetNick(); + } + + ChanServ::ChanAccess *current = NULL; + unsigned current_idx; + std::set current_flags; + bool override = false; + for (current_idx = ci->GetAccessCount(); current_idx > 0; --current_idx) + { + ChanServ::ChanAccess *access = ci->GetAccess(current_idx - 1); + if ((na && na->GetAccount() == access->GetAccount()) || mask.equals_ci(access->Mask())) + { + // Flags allows removing others that have the same access as you, + // but no other access system does. + if (highest && highest->GetSerializableType()->GetName() != "FlagsChanAccess" && !u_access.founder) + // operator<= on the non-me entry! + if (*highest <= *access) + { + if (source.HasPriv("chanserv/access/modify")) + override = true; + else + { + source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to modify the access of \002{1}\002."), ci->GetName(), access->Mask()); + return; + } + } + + current = access; + Anope::string cur_flags = FlagsChanAccess::DetermineFlags(access); + for (unsigned j = cur_flags.length(); j > 0; --j) + current_flags.insert(cur_flags[j - 1]); + break; + } + } + + unsigned access_max = Config->GetModule("chanserv")->Get("accessmax", "1024"); + if (access_max && ci->GetDeepAccessCount() >= access_max) + { + source.Reply(_("Sorry, you can only have \002{0}\002 access entries on a channel, including access entries from other channels."), access_max); + return; + } + + ChanServ::Privilege *p = NULL; + bool add = true; + for (size_t i = 0; i < flags.length(); ++i) + { + char f = flags[i]; + switch (f) + { + case '+': + add = true; + break; + case '-': + add = false; + break; + case '*': + for (std::map::iterator it = defaultFlags.begin(), it_end = defaultFlags.end(); it != it_end; ++it) + { + bool has = current_flags.count(it->second); + // If we are adding a flag they already have or removing one they don't have, don't bother + if (add == has) + continue; + + if (!u_access.HasPriv(it->first) && !u_access.founder) + { + if (source.HasPriv("chanserv/access/modify")) + override = true; + else + continue; + } + + if (add) + current_flags.insert(it->second); + else + current_flags.erase(it->second); + } + break; + default: + p = ChanServ::service ? ChanServ::service->FindPrivilege(flags.substr(i)) : nullptr; + if (p != NULL && defaultFlags[p->name]) + { + f = defaultFlags[p->name]; + i = flags.length(); + } + + for (std::map::iterator it = defaultFlags.begin(), it_end = defaultFlags.end(); it != it_end; ++it) + { + if (f != it->second) + continue; + else if (!u_access.HasPriv(it->first) && !u_access.founder) + { + if (source.HasPriv("chanserv/access/modify")) + override = true; + else + { + source.Reply(_("You can not set the \002{0}\002 flag."), f); + break; + } + } + if (add) + current_flags.insert(f); + else + current_flags.erase(f); + break; + } + } + } + if (current_flags.empty()) + { + if (current != NULL) + { + Event::OnAccessDel(&Event::AccessDel::OnAccessDel, ci, source, current); + delete current; + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << mask; + source.Reply(_("\002{0}\002 removed from the access list of \002{1}\002."), mask, ci->GetName()); + } + else + { + source.Reply(_("\002{0}\002 not found on the access list of \002{1}\002."), mask, ci->GetName()); + } + return; + } + + FlagsChanAccess *access = anope_dynamic_static_cast(flagschanaccess.Create()); + if (na) + access->SetObj(na->GetAccount()); + else if (targ_ci) + access->SetObj(targ_ci); + access->SetChannel(ci); + access->SetMask(mask); + access->SetCreator(source.GetNick()); + access->SetLastSeen(current ? current->GetLastSeen() : 0); + access->SetCreated(Anope::CurTime); + access->SetFlags(Anope::string(current_flags.begin(), current_flags.end())); + + if (current != NULL) + delete current; + + Event::OnAccessAdd(&Event::AccessAdd::OnAccessAdd, ci, source, access); + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to modify " << mask << "'s flags to " << access->AccessSerialize(); + if (p != NULL) + { + if (add) + source.Reply(_("Privilege \002{0}\002 added to \002{1}\002 on \002{2}\002, new flags are +\002{3}\002"), p->name, access->Mask(), ci->GetName(), access->AccessSerialize()); + else + source.Reply(_("Privilege \002{0}\002 removed from \002{1}\002 on \002{2}\002, new flags are +\002{3}\002"), p->name, access->Mask(), ci->GetName(), access->AccessSerialize()); + } + else + source.Reply(_("Flags for \002{0}\002 on \002{1}\002 set to +\002{2}\002"), access->Mask(), ci->GetName(), access->AccessSerialize()); + } + + void DoList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + const Anope::string &arg = params.size() > 2 ? params[2] : ""; + + if (!ci->GetAccessCount()) + { + source.Reply(_("The access list of \002{0}\002 is empty."), ci->GetName()); + return; + } + + ListFormatter list(source.GetAccount()); + + list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Flags")).AddColumn(_("Creator")).AddColumn(_("Created")); + + unsigned count = 0; + for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i) + { + ChanServ::ChanAccess *access = ci->GetAccess(i); + const Anope::string &flags = FlagsChanAccess::DetermineFlags(access); + + if (!arg.empty()) + { + if (arg[0] == '+') + { + bool pass = true; + for (size_t j = 1; j < arg.length(); ++j) + if (flags.find(arg[j]) == Anope::string::npos) + pass = false; + if (pass == false) + continue; + } + else if (!Anope::Match(access->Mask(), arg)) + continue; + } + + ListFormatter::ListEntry entry; + ++count; + entry["Number"] = stringify(i + 1); + entry["Mask"] = access->Mask(); + entry["Flags"] = flags; + entry["Creator"] = access->GetCreator(); + entry["Created"] = Anope::strftime(access->GetCreated(), source.nc, true); + list.AddEntry(entry); + } + + if (list.IsEmpty()) + source.Reply(_("No matching entries on the access list of \002{0}\002."), ci->GetName()); + else + { + std::vector replies; + list.Process(replies); + + source.Reply(_("Flags list for \002{0}\002:"), ci->GetName()); + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + if (count == ci->GetAccessCount()) + source.Reply(_("End of access list.")); + else + source.Reply(_("End of access list - \002{0}/{1}\002 entries shown."), count, ci->GetAccessCount()); + } + } + + void DoClear(CommandSource &source, ChanServ::Channel *ci) + { + if (!source.IsFounder(ci) && !source.HasPriv("chanserv/access/modify")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); + return; + } + + ci->ClearAccess(); + + Event::OnAccessClear(&Event::AccessClear::OnAccessClear, ci, source); + + source.Reply(_("The access list of \002{0}\002 has been cleared."), ci->GetName()); + + bool override = !source.IsFounder(ci); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clear the access list"; + } + + public: + CommandCSFlags(Module *creator) : Command(creator, "chanserv/flags", 1, 4) + { + this->SetDesc(_("Modify the list of privileged users")); + this->SetSyntax(_("\037channel\037 [MODIFY] \037mask\037 \037changes\037")); + this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | +\037flags\037]")); + this->SetSyntax(_("\037channel\037 CLEAR")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &cmd = params.size() > 1 ? params[1] : ""; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + bool is_list = cmd.empty() || cmd.equals_ci("LIST"); + bool has_access = false; + if (source.HasPriv("chanserv/access/modify")) + has_access = true; + else if (is_list && source.HasPriv("chanserv/access/list")) + has_access = true; + else if (is_list && source.AccessFor(ci).HasPriv("ACCESS_LIST")) + has_access = true; + else if (source.AccessFor(ci).HasPriv("ACCESS_CHANGE")) + has_access = true; + + if (!has_access) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), is_list ? "ACCESS_LIST" : "ACCESS_CHANGE", ci->GetName()); + return; + } + + if (Anope::ReadOnly && !is_list) + { + source.Reply(_("Sorry, channel access list modification is temporarily disabled.")); + return; + } + + if (is_list) + this->DoList(source, ci, params); + else if (cmd.equals_ci("CLEAR")) + this->DoClear(source, ci); + else + { + Anope::string mask, flags; + if (cmd.equals_ci("MODIFY")) + { + mask = params.size() > 2 ? params[2] : ""; + flags = params.size() > 3 ? params[3] : ""; + } + else + { + mask = cmd; + flags = params.size() > 2 ? params[2] : ""; + } + + this->DoModify(source, ci, mask, flags); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("{0} allow granularly granting privileges on channels to users by assigning them flags, which map to one or more privileges.\n" + "\n" + "The \002MODIFY\002 command allows you to modify the access list." + " If \037mask\037 is not already on the access list is it added, then the \037changes\037 are applied." + " If the mask has no more flags, then the mask is removed from the access list." + " Additionally, you may use +* or -* to add or remove all flags, respectively." + " You are only able to modify the access list if you have the \002{1}\002 privilege on the channel, and can only give privileges to up what you have."), + source.command, "ACCESS_CHANGE"); + source.Reply(" "); + source.Reply(_("The \002LIST\002 command allows you to list existing entries on the channel access list." + " If \037mask\037 is given, the \037mask\037 is wildcard matched against all existing entries on the access list, and only those entries are returned." + " If a set of flags is given, only those on the access list with the specified flags are returned.")); + source.Reply(" "); + source.Reply(_("The \002CLEAR\002 command clears the channel access list, which requires being the founder of \037channel\037.")); + source.Reply(" "); + source.Reply(_("\n" + "Examples:\n" + " {command} #anope MODIFY Attila +fHhu-i\n" + " Modifies the flags of \"Attila\" on the access list of \"#anope\" to \"+fHhu-i\".\n" + "\n" + " {command} #anope MODIFY *!*@anope.org +*\n" + " Modifies the flags of the host mask \"*!*@anope.org\" on the access list of \"#anope\" to have all flags.")); + source.Reply(" "); + source.Reply(_("The available flags are:")); + + typedef std::multimap reverse_map; + reverse_map reverse; + for (std::map::iterator it = defaultFlags.begin(), it_end = defaultFlags.end(); it != it_end; ++it) + reverse.insert(std::make_pair(it->second, it->first)); + + for (reverse_map::iterator it = reverse.begin(), it_end = reverse.end(); it != it_end; ++it) + { + ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(it->second) : nullptr; + if (p == NULL) + continue; + source.Reply(" %c - %s", it->first, Language::Translate(source.nc, p->desc.c_str())); + } + + return true; + } +}; + +class CSFlags : public Module +{ + CommandCSFlags commandcsflags; + FlagsChanAccessType flagsaccesstype; + + public: + CSFlags(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsflags(this) + , flagsaccesstype(this) + { + this->SetPermanent(true); + + } + + void OnReload(Configuration::Conf *conf) override + { + defaultFlags.clear(); + + for (int i = 0; i < conf->CountBlock("privilege"); ++i) + { + Configuration::Block *priv = conf->GetBlock("privilege", i); + + const Anope::string &pname = priv->Get("name"); + + ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(pname) : nullptr; + if (p == NULL) + continue; + + const Anope::string &value = priv->Get("flag"); + if (value.empty()) + continue; + + defaultFlags[p->name] = value[0]; + } + } +}; + +template<> void ModuleInfo(ModuleDef *def) +{ + def->Depends("chanserv"); +} + +MODULE_INIT(CSFlags) diff --git a/modules/chanserv/cs_xop.old b/modules/chanserv/cs_xop.old new file mode 100644 index 000000000..eae8c73a6 --- /dev/null +++ b/modules/chanserv/cs_xop.old @@ -0,0 +1,659 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + + +#include "module.h" +#include "modules/cs_access.h" +#include "main/chanaccess.h" +#include "main/chanaccesstype.h" + +namespace +{ + std::vector order; + std::map > permissions; +} + +class XOPChanAccess : public ChanAccessImpl +{ + public: + XOPChanAccess(Serialize::TypeBase *type) : ChanAccessImpl(type) { } + XOPChanAccess(Serialize::TypeBase *type, Serialize::ID id) : ChanAccessImpl(type, id) { } + + Anope::string GetType(); + void SetType(const Anope::string &); + + bool HasPriv(const Anope::string &priv) override + { + for (std::vector::iterator it = std::find(order.begin(), order.end(), this->GetType()); it != order.end(); ++it) + { + const std::vector &privs = permissions[*it]; + if (std::find(privs.begin(), privs.end(), priv) != privs.end()) + return true; + } + return false; + } + + Anope::string AccessSerialize() override + { + return this->GetType(); + } + + void AccessUnserialize(const Anope::string &data) override + { + this->SetType(data); + } + + static Anope::string DetermineLevel(ChanServ::ChanAccess *access) + { + if (access->GetSerializableType()->GetName() == "XOPChanAccess") + { + XOPChanAccess *xaccess = anope_dynamic_static_cast(access); + return xaccess->GetType(); + } + + std::map count; + + for (std::map >::const_iterator it = permissions.begin(), it_end = permissions.end(); it != it_end; ++it) + { + int &c = count[it->first]; + const std::vector &perms = it->second; + for (unsigned i = 0; i < perms.size(); ++i) + if (access->HasPriv(perms[i])) + ++c; + } + + Anope::string max; + int maxn = 0; + for (std::map::iterator it = count.begin(), it_end = count.end(); it != it_end; ++it) + if (it->second > maxn) + { + maxn = it->second; + max = it->first; + } + + return max; + } + +}; + +class XOPChanAccessType : public Serialize::Type +{ + public: + Serialize::Field type; + + XOPChanAccessType(Module *me) : Serialize::Type(me, "XOPChanAccess") + , type(this, "type") + { + SetParent(ChanServ::chanaccess); + } +}; + +Anope::string XOPChanAccess::GetType() +{ + return Get(&XOPChanAccessType::type); +} + +void XOPChanAccess::SetType(const Anope::string &i) +{ + Object::Set(&XOPChanAccessType::type, i); +} + +class CommandCSXOP : public Command +{ + private: + void DoAdd(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + Anope::string mask = params.size() > 2 ? params[2] : ""; + + if (mask.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, channel %s list modification is temporarily disabled."), source.command.c_str()); + return; + } + + XOPChanAccess tmp_access(NULL); + tmp_access.SetChannel(ci); + tmp_access.SetType(source.command.upper()); + + ChanServ::AccessGroup access = source.AccessFor(ci); + ChanServ::ChanAccess *highest = access.Highest(); + bool override = false; + + std::vector::iterator cmd_it = std::find(order.begin(), order.end(), source.command.upper()), + access_it = highest ? std::find(order.begin(), order.end(), XOPChanAccess::DetermineLevel(highest)) : order.end(); + + if (!access.founder && (!access.HasPriv("ACCESS_CHANGE") || cmd_it <= access_it)) + { + if (source.HasPriv("chanserv/access/modify")) + override = true; + else + { + source.Reply(_("Access denied. You do not have the \002{0}\002 privilege on \002{1}\002."), "ACCESS_CHANGE", ci->GetName()); + return; + } + } + + NickServ::Nick *na = nullptr; + ChanServ::Channel *targ_ci = nullptr; + + if (IRCD->IsChannelValid(mask)) + { + if (Config->GetModule("chanserv")->Get("disallow_channel_access")) + { + source.Reply(_("Channels may not be on access lists.")); + return; + } + + targ_ci = ChanServ::Find(mask); + if (targ_ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), mask); + return; + } + + if (ci == targ_ci) + { + source.Reply(_("You can't add a channel to its own access list.")); + return; + } + + mask = targ_ci->GetName(); + } + else + { + na = NickServ::FindNick(mask); + + if (!na && Config->GetModule("chanserv")->Get("disallow_hostmask_access")) + { + source.Reply(_("Masks and unregistered users may not be on access lists.")); + return; + } + + if (mask.find_first_of("!*@") == Anope::string::npos && !na) + { + User *targ = User::Find(mask, true); + if (targ != NULL) + mask = "*!*@" + targ->GetDisplayedHost(); + else + { + source.Reply(_("\002{0}\002 isn't registered."), mask); + return; + } + } + + if (na) + mask = na->GetNick(); + } + + for (unsigned i = 0; i < ci->GetAccessCount(); ++i) + { + ChanServ::ChanAccess *a = ci->GetAccess(i); + + if ((na && na->GetAccount() == a->GetAccount()) || mask.equals_ci(a->Mask())) + { + if ((!highest || *a >= *highest) && !access.founder && !source.HasPriv("chanserv/access/modify")) + { + source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to lower the access of \002{1}\002."), ci->GetName(), a->Mask()); + return; + } + + a->Delete(); + break; + } + } + + unsigned access_max = Config->GetModule("chanserv")->Get("accessmax", "1024"); + if (access_max && ci->GetDeepAccessCount() >= access_max) + { + source.Reply(_("Sorry, you can only have %d access entries on a channel, including access entries from other channels."), access_max); + return; + } + + XOPChanAccess *acc = anope_dynamic_static_cast(xopchanaccess.Create()); + if (na) + acc->SetObj(na->GetAccount()); + else if (targ_ci) + acc->SetObj(targ_ci); + acc->SetChannel(ci); + acc->SetMask(mask); + acc->SetCreator(source.GetNick()); + acc->SetType(source.command.upper()); + acc->SetLastSeen(0); + acc->SetCreated(Anope::CurTime); + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to add " << mask; + + Event::OnAccessAdd(&Event::AccessAdd::OnAccessAdd, ci, source, acc); + source.Reply(_("\002%s\002 added to %s %s list."), acc->Mask(), ci->GetName().c_str(), source.command.c_str()); + } + + void DoDel(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + NickServ::Account *nc = source.nc; + Anope::string mask = params.size() > 2 ? params[2] : ""; + + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, channel %s list modification is temporarily disabled."), source.command.c_str()); + return; + } + + if (!ci->GetAccessCount()) + { + source.Reply(_("%s %s list is empty."), ci->GetName().c_str(), source.command.c_str()); + return; + } + + ChanServ::AccessGroup access = source.AccessFor(ci); + ChanServ::ChanAccess *highest = access.Highest(); + bool override = false; + + if (!isdigit(mask[0]) && mask.find_first_of("#!*@") == Anope::string::npos && !NickServ::FindNick(mask)) + { + User *targ = User::Find(mask, true); + if (targ != NULL) + mask = "*!*@" + targ->GetDisplayedHost(); + else + { + source.Reply(_("\002{0}\002 isn't registered."), mask); + return; + } + } + + std::vector::iterator cmd_it = std::find(order.begin(), order.end(), source.command.upper()), + access_it = highest ? std::find(order.begin(), order.end(), XOPChanAccess::DetermineLevel(highest)) : order.end(); + + if (!mask.equals_ci(nc->GetDisplay()) && !access.founder && (!access.HasPriv("ACCESS_CHANGE") || cmd_it <= access_it)) + { + if (source.HasPriv("chanserv/access/modify")) + override = true; + else + { + source.Reply(_("Access denied. You do not have the \002{0}\002 privilege on \002{1}\002."), "ACCESS_CHANGE", ci->GetName()); + return; + } + } + + /* Special case: is it a number/list? Only do search if it isn't. */ + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + unsigned int deleted = 0; + Anope::string nicks; + + NumberList(mask, true, + [&](unsigned int number) + { + if (!number || number > ci->GetAccessCount()) + return; + + ChanServ::ChanAccess *caccess = ci->GetAccess(number - 1); + + if (caccess->GetSerializableType()->GetName() != "XOPChanAccess" || source.command.upper() != caccess->AccessSerialize()) + return; + + ++deleted; + if (!nicks.empty()) + nicks += ", " + caccess->Mask(); + else + nicks = caccess->Mask(); + + Event::OnAccessDel(&Event::AccessDel::OnAccessDel, ci, source, caccess); + delete caccess; + }, + [&]() + { + if (!deleted) + source.Reply(_("No matching entries on %s %s list."), ci->GetName().c_str(), source.command.c_str()); + else + { + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << nicks; + + if (deleted == 1) + source.Reply(_("Deleted one entry from %s %s list."), ci->GetName().c_str(), source.command.c_str()); + else + source.Reply(_("Deleted %d entries from %s %s list."), deleted, ci->GetName().c_str(), source.command.c_str()); + } + }); + } + else + { + for (unsigned i = 0; i < ci->GetAccessCount(); ++i) + { + ChanServ::ChanAccess *a = ci->GetAccess(i); + + if (a->GetSerializableType()->GetName() != "XOPChanAccess" || source.command.upper() != a->AccessSerialize()) + continue; + + if (a->Mask().equals_ci(mask)) + { + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << a->Mask(); + + source.Reply(_("\002%s\002 deleted from %s %s list."), a->Mask().c_str(), ci->GetName().c_str(), source.command.c_str()); + + Event::OnAccessDel(&Event::AccessDel::OnAccessDel, ci, source, a); + delete a; + + return; + } + } + + source.Reply(_("\002%s\002 not found on %s %s list."), mask.c_str(), ci->GetName().c_str(), source.command.c_str()); + } + } + + void DoList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + + ChanServ::AccessGroup access = source.AccessFor(ci); + + if (!access.HasPriv("ACCESS_LIST") && !source.HasPriv("chanserv/access/list")) + { + source.Reply(_("Access denied. You do not have the \002{0}\002 privilege on \002{1}\002."), "ACCESS_LIST", ci->GetName()); + return; + } + + if (!ci->GetAccessCount()) + { + source.Reply(_("%s %s list is empty."), ci->GetName().c_str(), source.command.c_str()); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Mask")); + + if (!nick.empty() && nick.find_first_not_of("1234567890,-") == Anope::string::npos) + { + NumberList(nick, false, + [&](unsigned int number) + { + if (!number || number > ci->GetAccessCount()) + return; + + ChanServ::ChanAccess *a = ci->GetAccess(number - 1); + + if (a->GetSerializableType()->GetName() != "XOPChanAccess" || source.command.upper() != a->AccessSerialize()) + return; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(number); + entry["Mask"] = a->Mask(); + list.AddEntry(entry); + }, + []{}); + } + else + { + for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i) + { + ChanServ::ChanAccess *a = ci->GetAccess(i); + + if (a->GetSerializableType()->GetName() != "XOPChanAccess" || source.command.upper() != a->AccessSerialize()) + continue; + if (!nick.empty() && !Anope::Match(a->Mask(), nick)) + continue; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(i + 1); + entry["Mask"] = a->Mask(); + list.AddEntry(entry); + } + } + + if (list.IsEmpty()) + source.Reply(_("No matching entries on %s access list."), ci->GetName().c_str()); + else + { + std::vector replies; + list.Process(replies); + + source.Reply(_("%s list for %s"), source.command.c_str(), ci->GetName().c_str()); + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + } + + void DoClear(CommandSource &source, ChanServ::Channel *ci) + { + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, channel %s list modification is temporarily disabled."), source.command.c_str()); + return; + } + + if (!ci->GetAccessCount()) + { + source.Reply(_("%s %s list is empty."), ci->GetName().c_str(), source.command.c_str()); + return; + } + + if (!source.AccessFor(ci).HasPriv("FOUNDER") && !source.HasPriv("chanserv/access/modify")) + { + source.Reply(_("Access denied. You do not have the \002{0}\002 privilege on \002{1}\002."), "FOUNDER", ci->GetName()); + return; + } + + bool override = !source.AccessFor(ci).HasPriv("FOUNDER"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clear the access list"; + + for (unsigned i = ci->GetAccessCount(); i > 0; --i) + { + ChanServ::ChanAccess *access = ci->GetAccess(i - 1); + + if (access->GetSerializableType()->GetName() != "XOPChanAccess" || source.command.upper() != access->AccessSerialize()) + continue; + + delete access; + } + + Event::OnAccessClear(&Event::AccessClear::OnAccessClear, ci, source); + + source.Reply(_("Channel %s %s list has been cleared."), ci->GetName().c_str(), source.command.c_str()); + } + + public: + CommandCSXOP(Module *modname) : Command(modname, "chanserv/xop", 2, 4) + { + this->SetSyntax(_("\037channel\037 ADD \037mask\037")); + this->SetSyntax(_("\037channel\037 DEL {\037mask\037 | \037entry-num\037 | \037list\037}")); + this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 CLEAR")); + } + + const Anope::string GetDesc(CommandSource &source) const override + { + return Anope::printf(Language::Translate(source.GetAccount(), _("Modify the list of %s users")), source.command.upper().c_str()); + } + + void Execute(CommandSource &source, const std::vector ¶ms) + { + ChanServ::Channel *ci = ChanServ::Find(params[0]); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), params[0]); + return; + } + + const Anope::string &cmd = params[1]; + + if (cmd.equals_ci("ADD")) + return this->DoAdd(source, ci, params); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, ci, params); + else if (cmd.equals_ci("LIST")) + return this->DoList(source, ci, params); + else if (cmd.equals_ci("CLEAR")) + return this->DoClear(source, ci); + else + this->OnSyntaxError(source, ""); + } + + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + { + source.Reply(_("The \002{0} ADD\002 command adds \037mask\037 to the {0} list of \037channel\037." + " If you have the privilege to use this command, you may use it if you have more privileges than it grants."), + source.command); + + source.Reply(_("Use of this command requires the \002{0}\002 privilege on \037channel\037.\n" + "Example:\n" + " {1} #anope ADD Adam" + " Adds \"Adam\" to the access list of \"#anope\" with the privilege set {1}"), + "ACCESS_CHANGE", source.command); + } + else if (subcommand.equals_ci("DEL")) + source.Reply(_("The \002{0} DEL\002 command removes the given \037mask\037 from the {0} list of \037channel\037." + " If a list of entry numbers is given, those entries are deleted.\n" + "\n" + "Use of this command requires the \002{1}\002 privilege on \037channel\037.\n" + "Example:\n" + " {0} #anope DEL Cronus" + " Removes \"Cronus\" from the {0} list of \"#anope\""), + source.command, "ACCESS_CHANGE"); + else if (subcommand.equals_ci("LIST")) + source.Reply(_("The \002{0} LIST\002 command displays the {0} list of \037channel\037." + " If a wildcard mask is given, only those entries matching the mask are displayed." + " If a list of entry numbers is given, only those entries are shown.\n" + "\n" + "Use of this command requires the \002{1}\002 privilege on \037channel\037.\n" + "\n" + "Example:" + " {0} #anope LIST 2-5,7-9\n" + " List entries numbered 2 through 5 and 7 through 9 on the {0} of \"#anope\""), + source.command, "ACCESS_LIST"); + else if (subcommand.equals_ci("CLEAR")) + source.Reply(_("The \002{0} CLEAR\002 command clears the {0} list of \037channel\037." + "\n" + "Use of this command requires the \002{1}\002 privilege on \037channel\037."), + source.command, "FOUNDER"); + + else + { + source.Reply(_("Maintains the \002{0} list\002 for a channel." + " Users who match an access entry on the {0} list receive the following privileges:\n" + "\n"), + source.command); + + Anope::string buf; + for (unsigned i = 0; i < permissions[source.command].size(); ++i) + buf += "," + permissions[source.command][i]; + + source.Reply(buf); + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + { + source.Reply(_("\n" + "The \002ADD\002 command adds \037mask\037 to the {0} list.\n" + "Use of this command requires the \002{change}\002 privilege on \037channel\037.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "The \002DEL\002 command removes \037mask\037 from the {0} list.\n" + "Use of this command requires the \002{change}\002 privilege on \037channel\037.\n" + "\002{msg}{service} {help} {command} DEL\002 for more information.\n" + "\n" + "The \002LIST\002 command shows the {0} list for \037channel\037.\n" + "Use of this commands requires the \002{list}\002 privilege on \037channel\037.\n" + "\002{msg}{service} {help} {command} LIST\002 for more information.\n" + "\n" + "The \002CLEAR\002 command clears the {0} list." + "Use of this command requires the \002{founder}\002 privilege on \037channel\037.\n" + "\002{msg}{service} {help} {command} CLEAR\002 for more information.)"), + source.command, "change"_kw = "ACCESS_CHANGE", "list"_kw = "ACCESS_LIST", "help"_kw = help->cname); + + Anope::string access_cmd, flags_cmd; + ServiceBot *access_bi, *flags_bi; + Command::FindCommandFromService("chanserv/access", access_bi, access_cmd); + Command::FindCommandFromService("chanserv/flags", flags_bi, access_cmd); + if (!access_cmd.empty() || !flags_cmd.empty()) + { + source.Reply(_("\n" + "Alternative methods of modifying channel access lists are available:\n")); + if (!access_cmd.empty()) + source.Reply(_("See \002{0}{1} {2} {3}\002 for more information about the access list."), Config->StrictPrivmsg, access_bi->nick, help->cname, access_cmd); + if (!flags_cmd.empty()) + source.Reply(_("See \002{0}{1} {2} {3}\002 for more information about the flags system."), Config->StrictPrivmsg, flags_bi->nick, help->cname, flags_cmd); + } + } + } + return true; + } +}; + +class CSXOP : public Module +{ + CommandCSXOP commandcsxop; + XOPChanAccessType xopaccesstype; + + public: + CSXOP(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsxop(this) + , xopaccesstype(this) + { + this->SetPermanent(true); + + } + + void OnReload(Configuration::Conf *conf) override + { + order.clear(); + permissions.clear(); + + for (int i = 0; i < conf->CountBlock("privilege"); ++i) + { + Configuration::Block *block = conf->GetBlock("privilege", i); + const Anope::string &pname = block->Get("name"); + + ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(pname) : nullptr; + if (p == NULL) + continue; + + const Anope::string &xop = block->Get("xop"); + if (pname.empty() || xop.empty()) + continue; + + permissions[xop].push_back(pname); + } + + for (int i = 0; i < conf->CountBlock("command"); ++i) + { + Configuration::Block *block = conf->GetBlock("command", i); + const Anope::string &cname = block->Get("name"), + &cserv = block->Get("command"); + if (cname.empty() || cserv != "chanserv/xop") + continue; + + order.push_back(cname); + } + } +}; + +template<> void ModuleInfo(ModuleDef *def) +{ + def->Depends("chanserv"); +} + +MODULE_INIT(CSXOP) diff --git a/modules/chanserv/drop.cpp b/modules/chanserv/drop.cpp new file mode 100644 index 000000000..a1020bcab --- /dev/null +++ b/modules/chanserv/drop.cpp @@ -0,0 +1,101 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_drop.h" + +class CommandCSDrop : public Command +{ + EventHandlers &onchandrop; + + public: + CommandCSDrop(Module *creator, EventHandlers &event) : Command(creator, "chanserv/drop", 1, 2), onchandrop(event) + { + this->SetDesc(_("Cancel the registration of a channel")); + this->SetSyntax(_("\037channel\037 \037channel\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + + if (Anope::ReadOnly && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Sorry, channel de-registration is temporarily disabled.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (params.size() < 2 || !chan.equals_ci(params[1])) + { + source.Reply(_("You must enter the channel name twice as a confirmation that you wish to drop \002{0}\002."), ci->GetName()); + return; + } + + if ((ci->HasFieldS("SECUREFOUNDER") ? !source.IsFounder(ci) : !source.AccessFor(ci).HasPriv("FOUNDER")) && !source.HasCommand("chanserv/drop")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); + return; + } + + EventReturn MOD_RESULT = this->onchandrop(&Event::ChanDrop::OnChanDrop, source, ci); + if (MOD_RESULT == EVENT_STOP) + return; + + bool override = (ci->HasFieldS("SECUREFOUNDER") ? !source.IsFounder(ci) : !source.AccessFor(ci).HasPriv("FOUNDER")); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "(founder was: " << (ci->GetFounder() ? ci->GetFounder()->GetDisplay() : "none") << ")"; + + Reference c = ci->c; + ci->Delete(); + + source.Reply(_("Channel \002{0}\002 has been dropped."), chan); + + if (c) + c->CheckModes(); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Unregisters \037channel\037. All channel settings will be deleted. As a precaution, you must give the \037channel\037 name twice.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037."), + "FOUNDER"); + + if (source.HasCommand("chanserv/drop")) + source.Reply(_("\n" + "As a Services Operator with the command \002{0}\002, you may drop any channel."), + "chanserv/drop"); + + return true; + } +}; + +class CSDrop : public Module +{ + CommandCSDrop commandcsdrop; + EventHandlers onchandrop; + + public: + CSDrop(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsdrop(this, onchandrop) + , onchandrop(this) + { + + } +}; + +MODULE_INIT(CSDrop) diff --git a/modules/chanserv/enforce.cpp b/modules/chanserv/enforce.cpp new file mode 100644 index 000000000..10069d3f8 --- /dev/null +++ b/modules/chanserv/enforce.cpp @@ -0,0 +1,307 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Original Coder: GeniusDex + * + * Please read COPYING and README for further details. + * + * Send any bug reports to the Anope Coder, as he will be able + * to deal with it best. + */ + +#include "module.h" + +class CommandCSEnforce : public Command +{ + private: + void DoSecureOps(CommandSource &source, ChanServ::Channel *ci) + { + bool override = !source.AccessFor(ci).HasPriv("AKICK") && source.HasPriv("chanserv/access/modify"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enforce secureops"; + + /* Dirty hack to allow Channel::SetCorrectModes to work ok. + * We pretend like SECUREOPS is on so it doesn't ignore that + * part of the code. This way we can enforce SECUREOPS even + * if it's off. + */ + bool hadsecureops = ci->HasFieldS("SECUREOPS"); + ci->SetS("SECUREOPS", true); + + for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + { + ChanUserContainer *uc = it->second; + + ci->c->SetCorrectModes(uc->user, false); + } + + if (!hadsecureops) + ci->UnsetS("SECUREOPS"); + + source.Reply(_("\002Secureops\002 enforced on \002{0}\002."), ci->GetName()); + } + + void DoRestricted(CommandSource &source, ChanServ::Channel *ci) + { + bool override = !source.AccessFor(ci).HasPriv("AKICK") && source.HasPriv("chanserv/access/modify"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enforce restricted"; + + std::vector users; + for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + { + ChanUserContainer *uc = it->second; + User *user = uc->user; + + if (user->IsProtected()) + continue; + + if (ci->AccessFor(user).empty()) + users.push_back(user); + } + + for (unsigned i = 0; i < users.size(); ++i) + { + User *user = users[i]; + + Anope::string mask = ci->GetIdealBan(user); + Anope::string reason = Language::Translate(user, _("RESTRICTED enforced by ")) + source.GetNick(); + ci->c->SetMode(NULL, "BAN", mask); + ci->c->Kick(NULL, user, "%s", reason.c_str()); + } + + source.Reply(_("\002Restricted\002 enforced on \002{0}\002."), ci->GetName()); + } + + void DoRegOnly(CommandSource &source, ChanServ::Channel *ci) + { + bool override = !source.AccessFor(ci).HasPriv("AKICK") && source.HasPriv("chanserv/access/modify"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enforce registered only"; + + std::vector users; + for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + { + ChanUserContainer *uc = it->second; + User *user = uc->user; + + if (user->IsProtected()) + continue; + + if (!user->IsIdentified()) + users.push_back(user); + } + + for (unsigned i = 0; i < users.size(); ++i) + { + User *user = users[i]; + + Anope::string mask = ci->GetIdealBan(user); + Anope::string reason = Language::Translate(user, _("REGONLY enforced by ")) + source.GetNick(); + if (!ci->c->HasMode("REGISTEREDONLY")) + ci->c->SetMode(NULL, "BAN", mask); + ci->c->Kick(NULL, user, "%s", reason.c_str()); + } + + source.Reply(_("\002Registered only\002 enforced on \002{0}\002."), ci->GetName()); + } + + void DoSSLOnly(CommandSource &source, ChanServ::Channel *ci) + { + bool override = !source.AccessFor(ci).HasPriv("AKICK") && source.HasPriv("chanserv/access/modify"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enforce SSL only"; + + std::vector users; + for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + { + ChanUserContainer *uc = it->second; + User *user = uc->user; + + if (user->IsProtected()) + continue; + + if (!user->HasMode("SSL") && !user->HasExtOK("ssl")) + users.push_back(user); + } + + for (unsigned i = 0; i < users.size(); ++i) + { + User *user = users[i]; + + Anope::string mask = ci->GetIdealBan(user); + Anope::string reason = Language::Translate(user, _("SSLONLY enforced by ")) + source.GetNick(); + if (!ci->c->HasMode("SSL")) + ci->c->SetMode(NULL, "BAN", mask); + ci->c->Kick(NULL, user, "%s", reason.c_str()); + } + + source.Reply(_("\002SSL only\002 enforced on %s."), ci->GetName().c_str()); + } + + void DoBans(CommandSource &source, ChanServ::Channel *ci) + { + bool override = !source.AccessFor(ci).HasPriv("AKICK") && source.HasPriv("chanserv/access/modify"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enforce bans"; + + std::vector users; + for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + { + ChanUserContainer *uc = it->second; + User *user = uc->user; + + if (user->IsProtected()) + continue; + + if (ci->c->MatchesList(user, "BAN") && !ci->c->MatchesList(user, "EXCEPT")) + users.push_back(user); + } + + for (unsigned i = 0; i < users.size(); ++i) + { + User *user = users[i]; + + Anope::string reason = Language::Translate(user, _("BANS enforced by ")) + source.GetNick(); + ci->c->Kick(NULL, user, "%s", reason.c_str()); + } + + source.Reply(_("\002Bans\002 enforced on %s."), ci->GetName().c_str()); + } + + void DoLimit(CommandSource &source, ChanServ::Channel *ci) + { + bool override = !source.AccessFor(ci).HasPriv("AKICK") && source.HasPriv("chanserv/access/modify"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enforce limit"; + + Anope::string l_str; + if (!ci->c->GetParam("LIMIT", l_str)) + { + source.Reply(_("There is no limit is set on \002{0}\002."), ci->GetName()); + return; + } + + int l; + try + { + l = convertTo(l_str); + if (l < 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + source.Reply(_("The limit on \002{0}\002 is not valid."), ci->GetName()); + return; + } + + std::vector users; + /* The newer users are at the end of the list, so kick users starting from the end */ + for (Channel::ChanUserList::reverse_iterator it = ci->c->users.rbegin(), it_end = ci->c->users.rend(); it != it_end; ++it) + { + ChanUserContainer *uc = it->second; + User *user = uc->user; + + if (user->IsProtected()) + continue; + + if (!ci->AccessFor(user).empty()) + continue; + + if (ci->c->users.size() - users.size() <= static_cast(l)) + continue; + + users.push_back(user); + } + + for (unsigned i = 0; i < users.size(); ++i) + { + User *user = users[i]; + + Anope::string reason = Language::Translate(user, _("LIMIT enforced by ")) + source.GetNick(); + ci->c->Kick(NULL, user, "%s", reason.c_str()); + } + + source.Reply(_("LIMIT enforced on \002{0}\002, \002{1]\002 users removed."), ci->GetName(), users.size()); + } + + public: + CommandCSEnforce(Module *creator) : Command(creator, "chanserv/enforce", 2, 2) + { + this->SetDesc(_("Enforce various channel modes and set options")); + this->SetSyntax(_("\037channel\037 \037what\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &what = params.size() > 1 ? params[1] : ""; + + ChanServ::Channel *ci = ChanServ::Find(params[0]); + + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), params[0]); + return; + } + + if (!ci->c) + { + source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); + return; + } + + if (!source.AccessFor(ci).HasPriv("AKICK") && !source.HasPriv("chanserv/access/modify")) + { + source.Reply("Access denied. You do not have the \002{0}\002 privilege on \002{1}\002.", "AKICK", ci->GetName()); + return; + } + + if (what.equals_ci("SECUREOPS")) + this->DoSecureOps(source, ci); + else if (what.equals_ci("RESTRICTED")) + this->DoRestricted(source, ci); + else if (what.equals_ci("REGONLY")) + this->DoRegOnly(source, ci); + else if (what.equals_ci("SSLONLY")) + this->DoSSLOnly(source, ci); + else if (what.equals_ci("BANS")) + this->DoBans(source, ci); + else if (what.equals_ci("LIMIT")) + this->DoLimit(source, ci); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Enforce various channel modes and set options on \037channel\037." + "\n" + "The \002SECUREOPS\002 option enforces SECUREOPS, even if it is not enabled.\n" + "See \002/msg ChanServ HELP SET SECUREOPS\002 for information about SECUREOPS.\n" //XXX + "\n" + "The \002RESTRICTED\002 option enforces RESTRICTED, even if it is not enabled.\n" + "See \002/msg ChanServ HELP SET RESTRICTED\002 for information about RESTRICTED.\n" //XXX + "\n" + "The \002REGONLY\002 option removes all unregistered users.\n" + "\n" + "The \002SSLONLY\002 option removes all users not using a secure connection.\n" + "\n" + "The \002BANS\002 option enforces bans on the channel by removing users affected by then.\n" + "\n" + "The \002LIMIT\002 option will remove users until the user count drops below the channel limit, if one is set.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege."), + "AKICK"); + return true; + } +}; + +class CSEnforce : public Module +{ + CommandCSEnforce commandcsenforce; + + public: + CSEnforce(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsenforce(this) + { + + } +}; + +MODULE_INIT(CSEnforce) diff --git a/modules/chanserv/entrymsg.cpp b/modules/chanserv/entrymsg.cpp new file mode 100644 index 000000000..31253599e --- /dev/null +++ b/modules/chanserv/entrymsg.cpp @@ -0,0 +1,270 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_entrymsg.h" + +class EntryMsgImpl : public EntryMsg +{ + public: + EntryMsgImpl(Serialize::TypeBase *type) : EntryMsg(type) { } + EntryMsgImpl(Serialize::TypeBase *type, Serialize::ID id) : EntryMsg(type, id) { } + + ChanServ::Channel *GetChannel() override; + void SetChannel(ChanServ::Channel *ci) override; + + Anope::string GetCreator() override; + void SetCreator(const Anope::string &c) override; + + Anope::string GetMessage() override; + void SetMessage(const Anope::string &m) override; + + time_t GetWhen() override; + void SetWhen(const time_t &t) override; +}; + +class EntryMsgType : public Serialize::Type +{ + public: + Serialize::ObjectField chan; + Serialize::Field creator, message; + Serialize::Field when; + + EntryMsgType(Module *me) : Serialize::Type(me, "EntryMsg") + , chan(this, "chan", true) + , creator(this, "creator") + , message(this, "message") + , when(this, "when") + { + } +}; + +ChanServ::Channel *EntryMsgImpl::GetChannel() +{ + return Get(&EntryMsgType::chan); +} + +void EntryMsgImpl::SetChannel(ChanServ::Channel *ci) +{ + Set(&EntryMsgType::chan, ci); +} + +Anope::string EntryMsgImpl::GetCreator() +{ + return Get(&EntryMsgType::creator); +} + +void EntryMsgImpl::SetCreator(const Anope::string &c) +{ + Set(&EntryMsgType::creator, c); +} + +Anope::string EntryMsgImpl::GetMessage() +{ + return Get(&EntryMsgType::message); +} + +void EntryMsgImpl::SetMessage(const Anope::string &m) +{ + Set(&EntryMsgType::message, m); +} + +time_t EntryMsgImpl::GetWhen() +{ + return Get(&EntryMsgType::when); +} + +void EntryMsgImpl::SetWhen(const time_t &t) +{ + Set(&EntryMsgType::when, t); +} + +class CommandEntryMessage : public Command +{ + private: + void DoList(CommandSource &source, ChanServ::Channel *ci) + { + std::vector messages = ci->GetRefs(entrymsg); + + if (messages.empty()) + { + source.Reply(_("Entry message list for \002{0}\002 is empty."), ci->GetName()); + return; + } + + source.Reply(_("Entry message list for \002{0}\002:"), ci->GetName()); + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Message")); + for (unsigned i = 0; i < messages.size(); ++i) + { + EntryMsg *msg = messages[i]; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(i + 1); + entry["Creator"] = msg->GetCreator(); + entry["Created"] = Anope::strftime(msg->GetWhen(), NULL, true); + entry["Message"] = msg->GetMessage(); + list.AddEntry(entry); + } + + std::vector replies; + list.Process(replies); + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of entry message list.")); + } + + void DoAdd(CommandSource &source, ChanServ::Channel *ci, const Anope::string &message) + { + std::vector messages = ci->GetRefs(entrymsg); + + if (messages.size() >= Config->GetModule(this->owner)->Get("maxentries")) + { + source.Reply(_("The entry message list for \002{0}\002 is full."), ci->GetName()); + return; + } + + EntryMsg *msg = entrymsg.Create(); + msg->SetChannel(ci); + msg->SetCreator(source.GetNick()); + msg->SetMessage(message); + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to add a message"; + source.Reply(_("Entry message added to \002{0}\002"), ci->GetName()); + } + + void DoDel(CommandSource &source, ChanServ::Channel *ci, const Anope::string &message) + { + std::vector messages = ci->GetRefs(entrymsg); + + if (!message.is_pos_number_only()) + source.Reply(("Entry message \002{0}\002 not found on channel \002{1}\002."), message, ci->GetName()); + else if (messages.empty()) + source.Reply(_("Entry message list for \002{0}\002 is empty."), ci->GetName()); + else + { + try + { + unsigned i = convertTo(message); + if (i > 0 && i <= messages.size()) + { + messages[i - 1]->Delete(); + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to remove a message"; + source.Reply(_("Entry message \002{0}\002 for \002{1]\002 deleted."), i, ci->GetName()); + } + else + throw ConvertException(); + } + catch (const ConvertException &) + { + source.Reply(_("Entry message \002{0}\002 not found on channel \002{1}\002."), message, ci->GetName()); + } + } + } + + void DoClear(CommandSource &source, ChanServ::Channel *ci) + { + for (EntryMsg *e : ci->GetRefs(entrymsg)) + delete e; + + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to remove all messages"; + source.Reply(_("Entry messages for \002{0}\002 have been cleared."), ci->GetName()); + } + + public: + CommandEntryMessage(Module *creator) : Command(creator, "chanserv/entrymsg", 2, 3) + { + this->SetDesc(_("Manage the channel's entry messages")); + this->SetSyntax(_("\037channel\037 ADD \037message\037")); + this->SetSyntax(_("\037channel\037 DEL \037num\037")); + this->SetSyntax(_("\037channel\037 LIST")); + this->SetSyntax(_("\037channel\037 CLEAR")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (Anope::ReadOnly && !params[1].equals_ci("LIST")) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + if (!source.AccessFor(ci).HasPriv("SET") && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); + return; + } + + if (params[1].equals_ci("LIST")) + this->DoList(source, ci); + else if (params[1].equals_ci("CLEAR")) + this->DoClear(source, ci); + else if (params.size() < 3) + this->OnSyntaxError(source, ""); + else if (params[1].equals_ci("ADD")) + this->DoAdd(source, ci, params[2]); + else if (params[1].equals_ci("DEL")) + this->DoDel(source, ci, params[2]); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Controls what messages will be sent to users when they join \037channel\037.\n" + "\n" + "The \002{0} ADD\002 command adds the given message to the list of messages to be shown to users when they join the channel.\n" + "\n" + "The \002{0} DEL\002 command removes the given message from the entry message list.\n" + " You can remove the message by specifying its number which you can get by listing the messages as explained below.\n" + "\n" + "The \002{0} LIST\002 command displays the list of messages on the entry message list.\n" + "\n" + "The \002{0} CLEAR\002 command clears the entry message list.\n" + "\n" + "Use of this command requires the \002SET\002 privilege on \037channel\037."), + source.command); + return true; + } +}; + +class CSEntryMessage : public Module + , public EventHook +{ + CommandEntryMessage commandentrymsg; + EntryMsgType entrymsg_type; + + public: + CSEntryMessage(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandentrymsg(this) + , entrymsg_type(this) + { + } + + void OnJoinChannel(User *u, Channel *c) override + { + if (u && c && c->ci && u->server->IsSynced()) + for (EntryMsg *msg : c->ci->GetRefs(entrymsg)) + u->SendMessage(c->ci->WhoSends(), "[{0}] {1}", c->ci->GetName(), msg->GetMessage()); + } +}; + +MODULE_INIT(CSEntryMessage) diff --git a/modules/chanserv/getkey.cpp b/modules/chanserv/getkey.cpp new file mode 100644 index 000000000..31fe56ec5 --- /dev/null +++ b/modules/chanserv/getkey.cpp @@ -0,0 +1,75 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandCSGetKey : public Command +{ + public: + CommandCSGetKey(Module *creator) : Command(creator, "chanserv/getkey", 1, 1) + { + this->SetDesc(_("Returns the key of the given channel")); + this->SetSyntax(_("\037channel\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!source.AccessFor(ci).HasPriv("GETKEY") && !source.HasCommand("chanserv/getkey")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "GETKEY", ci->GetName()); + return; + } + + Anope::string key; + if (!ci->c || !ci->c->GetParam("KEY", key)) + { + source.Reply(_("Channel \002{0}\002 does not have a key."), ci->GetName()); + return; + } + + bool override = !source.AccessFor(ci).HasPriv("GETKEY"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci); + + source.Reply(_("Key for channel \002{0}\002 is \002{1}\002."), ci->GetName(), key); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Returns the key of \037channel\037.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037."), + "GETKEY"); + return true; + } +}; + +class CSGetKey : public Module +{ + CommandCSGetKey commandcsgetkey; + + public: + CSGetKey(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsgetkey(this) + { + + } +}; + +MODULE_INIT(CSGetKey) diff --git a/modules/chanserv/info.cpp b/modules/chanserv/info.cpp new file mode 100644 index 000000000..267aee66a --- /dev/null +++ b/modules/chanserv/info.cpp @@ -0,0 +1,97 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_info.h" + +class CommandCSInfo : public Command +{ + EventHandlers &eventonchaninfo; + + public: + CommandCSInfo(Module *creator, EventHandlers &event) : Command(creator, "chanserv/info", 1, 2), eventonchaninfo(event) + { + this->SetDesc(_("Lists information about the specified registered channel")); + this->SetSyntax(_("\037channel\037")); + this->AllowUnregistered(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + + NickServ::Account *nc = source.nc; + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + bool has_auspex = source.HasPriv("chanserv/auspex"); + bool show_all = false; + + /* Should we show all fields? Only for sadmins and identified users */ + if (source.AccessFor(ci).HasPriv("INFO") || has_auspex) + show_all = true; + + InfoFormatter info(nc); + + source.Reply(_("Information for channel \002{0}\002:"), ci->GetName()); + if (ci->GetFounder()) + info[_("Founder")] = ci->GetFounder()->GetDisplay(); + + if (show_all && ci->GetSuccessor()) + info[_("Successor")] = ci->GetSuccessor()->GetDisplay(); + + if (!ci->GetDesc().empty()) + info[_("Description")] = ci->GetDesc(); + + info[_("Registered")] = Anope::strftime(ci->GetTimeRegistered(), source.GetAccount()); + info[_("Last used")] = Anope::strftime(ci->GetLastUsed(), source.GetAccount()); + + if (show_all) + { + info[_("Ban type")] = stringify(ci->GetBanType()); + } + + this->eventonchaninfo(&Event::ChanInfo::OnChanInfo, source, ci, info, show_all); + + std::vector replies; + info.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Shows information about \037channel\037, including its founder, description, time of registration, and last time used." + " If the user issuing the command has \002{0}\002 privilege on \037channel\037, then the successor, last topic set, settings and expiration time will also be displayed when applicable.")); + return true; + } +}; + +class CSInfo : public Module +{ + CommandCSInfo commandcsinfo; + EventHandlers eventonchaninfo; + + public: + CSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsinfo(this, eventonchaninfo) + , eventonchaninfo(this) + { + + } +}; + +MODULE_INIT(CSInfo) diff --git a/modules/chanserv/invite.cpp b/modules/chanserv/invite.cpp new file mode 100644 index 000000000..8e6f94d6c --- /dev/null +++ b/modules/chanserv/invite.cpp @@ -0,0 +1,109 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandCSInvite : public Command +{ + public: + CommandCSInvite(Module *creator) : Command(creator, "chanserv/invite", 1, 3) + { + this->SetDesc(_("Invites you or an optionally specified nick into a channel")); + this->SetSyntax(_("\037channel\037 [\037nick\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + + User *u = source.GetUser(); + Channel *c = Channel::Find(chan); + + if (!c) + { + source.Reply(_("Channel \002{0}\002 doesn't exist."), chan); + return; + } + + ChanServ::Channel *ci = c->ci; + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), c->name); + return; + } + + if (!source.AccessFor(ci).HasPriv("INVITE") && !source.HasCommand("chanserv/invite")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "INVITE", ci->GetName()); + return; + } + + User *u2; + if (params.size() == 1) + u2 = u; + else + u2 = User::Find(params[1], true); + + if (!u2) + { + source.Reply(_("\002{0}\002 isn't currently online."), params.size() > 1 ? params[1] : source.GetNick()); + return; + } + + if (c->FindUser(u2)) + { + if (u2 == u) + source.Reply(_("You are already in \002{0}\002!"), c->name); + else + source.Reply(_("\002{0}\002 is already in \002{1}\002!"), u2->nick, c->name); + + return; + } + + bool override = !source.AccessFor(ci).HasPriv("INVITE"); + + IRCD->SendInvite(ci->WhoSends(), c, u2); + if (u2 != u) + { + source.Reply(_("\002{0}\002 has been invited to \002{1}\002."), u2->nick, c->name); + u2->SendMessage(ci->WhoSends(), _("You have been invited to \002{0}\002 by \002{1}\002."), c->name, source.GetNick()); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << u2->nick; + } + else + { + u2->SendMessage(ci->WhoSends(), _("You have been invited to \002{0}\002."), c->name); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Causes \002{0}\002 to invite you or an optionally specified \037nick\037 to \037channel\037.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037."), + source.service->nick, "INVITE"); + return true; + } +}; + +class CSInvite : public Module +{ + CommandCSInvite commandcsinvite; + + public: + CSInvite(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsinvite(this) + { + + } +}; + +MODULE_INIT(CSInvite) diff --git a/modules/chanserv/kick.cpp b/modules/chanserv/kick.cpp new file mode 100644 index 000000000..89536df7f --- /dev/null +++ b/modules/chanserv/kick.cpp @@ -0,0 +1,152 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandCSKick : public Command +{ + public: + CommandCSKick(Module *creator) : Command(creator, "chanserv/kick", 2, 3) + { + this->SetDesc(_("Kicks a specified nick from a channel")); + this->SetSyntax(_("\037channel\037 \037user\037 [\037reason\037]")); + this->SetSyntax(_("\037channel\037 \037mask\037 [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &target = params[1]; + Anope::string reason = params.size() > 2 ? params[2] : "Requested"; + + User *u = source.GetUser(); + ChanServ::Channel *ci = ChanServ::Find(params[0]); + Channel *c = Channel::Find(params[0]); + User *u2 = User::Find(target, true); + + if (!c) + { + source.Reply(_("Channel \002{0}\002 doesn't exist."), chan); + return; + } + + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), c->name); + return; + } + + unsigned reasonmax = Config->GetModule("chanserv")->Get("reasonmax", "200"); + if (reason.length() > reasonmax) + reason = reason.substr(0, reasonmax); + + ChanServ::AccessGroup u_access = source.AccessFor(ci); + + Anope::string signkickformat = Config->GetModule("chanserv")->Get("signkickformat", "%m (%n)"); + signkickformat = signkickformat.replace_all_cs("%n", source.GetNick()); + + if (!u_access.HasPriv("KICK") && !source.HasPriv("chanserv/kick")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "KICK", ci->GetName()); + return; + } + + if (u2) + { + ChanServ::AccessGroup u2_access = ci->AccessFor(u2); + if (u != u2 && ci->HasFieldS("PEACE") && u2_access >= u_access && !source.HasPriv("chanserv/kick")) + source.Reply(_("Access denied. \002{0}\002 has the same or more privileges than you on \002{1}\002."), u2->nick, ci->GetName()); + else if (u2->IsProtected()) + source.Reply(_("Access denied. \002{0}\002 is protected and can not be kicked."), u2->nick); + else if (!c->FindUser(u2)) + source.Reply(_("User \002{0}\002 is not on channel \002{1}\002."), u2->nick, c->name); + else + { + bool override = !u_access.HasPriv("KICK") || (u != u2 && ci->HasFieldS("PEACE") && u2_access >= u_access); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << u2->nick; + + if (ci->HasFieldS("SIGNKICK") || (ci->HasFieldS("SIGNKICK_LEVEL") && !u_access.HasPriv("SIGNKICK"))) + { + signkickformat = signkickformat.replace_all_cs("%m", reason); + c->Kick(ci->WhoSends(), u2, "%s", signkickformat.c_str()); + } + else + c->Kick(ci->WhoSends(), u2, "%s", reason.c_str()); + } + } + else if (u_access.HasPriv("FOUNDER")) + { + Anope::string mask = IRCD->NormalizeMask(target); + + Log(LOG_COMMAND, source, this, ci) << "for " << mask; + + int matched = 0, kicked = 0; + for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end;) + { + ChanUserContainer *uc = it->second; + ++it; + + Entry e("", mask); + if (e.Matches(uc->user)) + { + ++matched; + + ChanServ::AccessGroup u2_access = ci->AccessFor(uc->user); + if (u != uc->user && ci->HasFieldS("PEACE") && u2_access >= u_access) + continue; + else if (uc->user->IsProtected()) + continue; + + ++kicked; + + if (ci->HasFieldS("SIGNKICK") || (ci->HasFieldS("SIGNKICK_LEVEL") && !u_access.HasPriv("SIGNKICK"))) + { + reason += " (Matches " + mask + ")"; + signkickformat = signkickformat.replace_all_cs("%m", reason); + c->Kick(ci->WhoSends(), uc->user, "%s", signkickformat.c_str()); + } + else + c->Kick(ci->WhoSends(), uc->user, "%s (Matches %s)", reason.c_str(), mask.c_str()); + } + } + + if (matched) + source.Reply(_("Kicked \002{0}/{1}\002 users matching \002{2}\002 from \002{3}\002."), kicked, matched, mask, c->name); + else + source.Reply(_("No users on\002{0}\002 match \002{1}\002."), c->name, mask); + } + else + source.Reply(_("\002{0}\002 isn't currently in use."), target); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Kicks \037user\037 from a \037channel\037. Users with the \002{0}\002 privilege on \037channel\037 may give a \037mask\037, which kicks all users who match the wildcard mask.\n" + " \n" + "Use of this command requires the \002{1}\002 privilege on \037channel\037."), + "FOUNDER", "KICK"); + return true; + } +}; + +class CSKick : public Module +{ + CommandCSKick commandcskick; + + public: + CSKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcskick(this) + { + + } +}; + +MODULE_INIT(CSKick) diff --git a/modules/chanserv/list.cpp b/modules/chanserv/list.cpp new file mode 100644 index 000000000..5a4c58bff --- /dev/null +++ b/modules/chanserv/list.cpp @@ -0,0 +1,263 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_info.h" +#include "modules/cs_set.h" +#include "modules/cs_mode.h" + +class CommandCSList : public Command +{ + public: + CommandCSList(Module *creator) : Command(creator, "chanserv/list", 1, 2) + { + this->SetDesc(_("Lists all registered channels matching the given pattern")); + this->SetSyntax(_("\037pattern\037 [SUSPENDED] [NOEXPIRE]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + Anope::string pattern = params[0]; + unsigned nchans; + bool is_servadmin = source.HasCommand("chanserv/list"); + int count = 0, from = 0, to = 0; + bool suspended = false, channoexpire = false; + + if (pattern[0] == '#') + { + Anope::string n1, n2; + sepstream(pattern.substr(1), '-').GetToken(n1, 0); + sepstream(pattern, '-').GetToken(n2, 1); + + try + { + from = convertTo(n1); + to = convertTo(n2); + } + catch (const ConvertException &) + { + source.Reply(_("Incorrect range specified. The correct syntax is \002#\037from\037-\037to\037\002.")); + source.Reply(_("To search for channels starting with #, search for the channel name without the #-sign prepended (\002anope\002 instead of \002#anope\002).")); + return; + } + + pattern = "*"; + } + + nchans = 0; + + if (is_servadmin && params.size() > 1) + { + Anope::string keyword; + spacesepstream keywords(params[1]); + while (keywords.GetToken(keyword)) + { + if (keyword.equals_ci("SUSPENDED")) + suspended = true; + if (keyword.equals_ci("NOEXPIRE")) + channoexpire = true; + } + } + + Anope::string spattern = "#" + pattern; + unsigned listmax = Config->GetModule(this->owner)->Get("listmax", "50"); + + source.Reply(_("List of entries matching \002{0}\002:"), pattern); + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Name")).AddColumn(_("Description")); + + // XXX wtf + Anope::map ordered_map; + if (ChanServ::service) + for (auto& it : ChanServ::service->GetChannels()) + ordered_map[it.first] = it.second; + + for (Anope::map::const_iterator it = ordered_map.begin(), it_end = ordered_map.end(); it != it_end; ++it) + { + ChanServ::Channel *ci = it->second; + + if (!is_servadmin) + { + if (ci->HasFieldS("CS_PRIVATE") || ci->HasFieldS("CS_SUSPENDED")) + continue; + if (ci->c && ci->c->HasMode("SECRET")) + continue; + + if (mlocks) + { + ModeLock *secret = mlocks->GetMLock(ci, "SECRET"); + if (secret && secret->GetSet()) + continue; + } + } + + if (suspended && !ci->HasFieldS("CS_SUSPENDED")) + continue; + + if (channoexpire && !ci->HasFieldS("CS_NO_EXPIRE")) + continue; + + if (pattern.equals_ci(ci->GetName()) || ci->GetName().equals_ci(spattern) || Anope::Match(ci->GetName(), pattern, false, true) || Anope::Match(ci->GetName(), spattern, false, true) || Anope::Match(ci->GetDesc(), pattern, false, true) || Anope::Match(ci->GetLastTopic(), pattern, false, true)) + { + if (((count + 1 >= from && count + 1 <= to) || (!from && !to)) && ++nchans <= listmax) + { + bool isnoexpire = false; + if (is_servadmin && (ci->HasFieldS("CS_NO_EXPIRE"))) + isnoexpire = true; + + ListFormatter::ListEntry entry; + entry["Name"] = (isnoexpire ? "!" : "") + ci->GetName(); + if (ci->HasFieldS("CS_SUSPENDED")) + entry["Description"] = Language::Translate(source.GetAccount(), _("[Suspended]")); + else + entry["Description"] = ci->GetDesc(); + list.AddEntry(entry); + } + ++count; + } + } + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of list - \002{0}/{1}\002 matches shown."), nchans > listmax ? listmax : nchans, nchans); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Lists all registered channels matching the given pattern." + " Channels with the \002PRIVATE\002 option set will only be displayed to Services Operators with the proper access." + " Channels with the \002NOEXPIRE\002 option set will have a \002!\002 prefixed to the channel for Services Operators to see." + "\n" + "Note that a preceding '#' specifies a range, channel names are to be written without '#'.\n" + "\n" + "If the SUSPENDED or NOEXPIRE options are given, only channels which, respectively, are SUSPENDED or have the NOEXPIRE flag set will be displayed." + " If multiple options are given, all channels matching at least one option will be displayed." + " Note that these options are limited to \037Services Operators\037.\n" + "\n" + "Examples:\n" + "\n" + " {0} *anope*\n" + " Lists all registered channels with \002anope\002 in their names (case insensitive).\n" + "\n" + " {0} * NOEXPIRE\n" + " Lists all registered channels which have been set to not expire.\n" + "\n" + " {0} #51-100\n" + " Lists all registered channels within the given range (51-100).")); + + if (!Config->GetBlock("options")->Get("regexengine").empty()) + { + source.Reply(" "); + source.Reply(_("Regex matches are also supported using the {0} engine. Enclose your pattern in // if this is desired."), Config->GetBlock("options")->Get("regexengine")); + } + + return true; + } +}; + +class CommandCSSetPrivate : public Command +{ + public: + CommandCSSetPrivate(Module *creator, const Anope::string &cname = "chanserv/set/private") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Hide channel from the LIST command")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, params[1]); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (params[1].equals_ci("ON")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable private"; + ci->SetS("CS_PRIVATE", true); + source.Reply(_("Private option for \002{0}\002 is now \002on\002."), ci->GetName()); + } + else if (params[1].equals_ci("OFF")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable private"; + ci->UnsetS("CS_PRIVATE"); + source.Reply(_("Private option for \002{0}\002 is now \002off\002."), ci->GetName()); + } + else + this->OnSyntaxError(source, "PRIVATE"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables the \002private\002 option for a channel.")); + + ServiceBot *bi; + Anope::string cmd; + if (Command::FindCommandFromService("chanserv/list", bi, cmd)) + source.Reply(_("When \002private\002 is set, the channel will not appear in the \002{0}\002 command of \002{1}\002."), cmd, bi->nick); + return true; + } +}; + +class CSList : public Module + , public EventHook +{ + CommandCSList commandcslist; + CommandCSSetPrivate commandcssetprivate; + + ExtensibleItem priv; // XXX + + public: + CSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcslist(this) + , commandcssetprivate(this) + , priv(this, "CS_PRIVATE") + { + } + + void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override + { + if (!show_all) + return; + + if (priv.HasExt(ci)) + info.AddOption(_("Private")); + } +}; + +MODULE_INIT(CSList) diff --git a/modules/chanserv/log.cpp b/modules/chanserv/log.cpp new file mode 100644 index 000000000..6a90115f5 --- /dev/null +++ b/modules/chanserv/log.cpp @@ -0,0 +1,448 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_log.h" +#include "modules/memoserv.h" + +class LogSettingImpl : public LogSetting +{ + public: + LogSettingImpl(Serialize::TypeBase *type) : LogSetting(type) { } + LogSettingImpl(Serialize::TypeBase *type, Serialize::ID id) : LogSetting(type, id) { } + + ChanServ::Channel *GetChannel() override; + void SetChannel(ChanServ::Channel *) override; + + Anope::string GetServiceName() override; + void SetServiceName(const Anope::string &) override; + + Anope::string GetCommandService() override; + void SetCommandService(const Anope::string &) override; + + Anope::string GetCommandName() override; + void SetCommandName(const Anope::string &) override; + + Anope::string GetMethod() override; + virtual void SetMethod(const Anope::string &) override; + + Anope::string GetExtra() override; + void SetExtra(const Anope::string &) override; + + Anope::string GetCreator() override; + void SetCreator(const Anope::string &) override; + + time_t GetCreated() override; + void SetCreated(const time_t &) override; +}; + +class LogSettingType : public Serialize::Type +{ + public: + Serialize::ObjectField ci; + Serialize::Field service_name, command_service, command_name, method, extra, creator; + Serialize::Field created; + + LogSettingType(Module *me) : Serialize::Type(me, "LogSetting") + , ci(this, "ci", true) + , service_name(this, "service_name") + , command_service(this, "command_service") + , command_name(this, "command_name") + , method(this, "method") + , extra(this, "extra") + , creator(this, "creator") + , created(this, "created") + { + } +}; + +ChanServ::Channel *LogSettingImpl::GetChannel() +{ + return Get(&LogSettingType::ci); +} + +void LogSettingImpl::SetChannel(ChanServ::Channel *ci) +{ + Set(&LogSettingType::ci, ci); +} + +Anope::string LogSettingImpl::GetServiceName() +{ + return Get(&LogSettingType::service_name); +} + +void LogSettingImpl::SetServiceName(const Anope::string &s) +{ + Set(&LogSettingType::service_name, s); +} + +Anope::string LogSettingImpl::GetCommandService() +{ + return Get(&LogSettingType::command_service); +} + +void LogSettingImpl::SetCommandService(const Anope::string &s) +{ + Set(&LogSettingType::command_service, s); +} + +Anope::string LogSettingImpl::GetCommandName() +{ + return Get(&LogSettingType::command_name); +} + +void LogSettingImpl::SetCommandName(const Anope::string &s) +{ + Set(&LogSettingType::command_name, s); +} + +Anope::string LogSettingImpl::GetMethod() +{ + return Get(&LogSettingType::method); +} + +void LogSettingImpl::SetMethod(const Anope::string &m) +{ + Set(&LogSettingType::method, m); +} + +Anope::string LogSettingImpl::GetExtra() +{ + return Get(&LogSettingType::extra); +} + +void LogSettingImpl::SetExtra(const Anope::string &e) +{ + Set(&LogSettingType::extra, e); +} + +Anope::string LogSettingImpl::GetCreator() +{ + return Get(&LogSettingType::creator); +} + +void LogSettingImpl::SetCreator(const Anope::string &creator) +{ + Set(&LogSettingType::extra, creator); +} + +time_t LogSettingImpl::GetCreated() +{ + return Get(&LogSettingType::created); +} + +void LogSettingImpl::SetCreated(const time_t &t) +{ + Set(&LogSettingType::created, t); +} + +class CommandCSLog : public Command +{ +public: + CommandCSLog(Module *creator) : Command(creator, "chanserv/log", 1, 4) + { + this->SetDesc(_("Configures channel logging settings")); + this->SetSyntax(_("\037channel\037")); + this->SetSyntax(_("\037channel\037 \037command\037 \037method\037 [\037status\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &channel = params[0]; + + ChanServ::Channel *ci = ChanServ::Find(channel); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), channel); + return; + } + + if (!source.AccessFor(ci).HasPriv("SET") && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (params.size() == 1) + { + std::vector ls = ci->GetRefs(logsetting); + if (ls.empty()) + { + source.Reply(_("There currently are no logging configurations for \002{0}\002."), ci->GetName()); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Service")).AddColumn(_("Command")).AddColumn(_("Method")).AddColumn(""); + + for (unsigned i = 0; i < ls.size(); ++i) + { + LogSetting *log = ls[i]; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(i + 1); + entry["Service"] = log->GetCommandService(); + entry["Command"] = !log->GetCommandName().empty() ? log->GetCommandName() : log->GetServiceName(); + entry["Method"] = log->GetMethod(); + entry[""] = log->GetExtra(); + list.AddEntry(entry); + } + + source.Reply(_("Log list for \002{0}\002:"), ci->GetName()); + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + else if (params.size() > 2) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + const Anope::string &command = params[1]; + const Anope::string &method = params[2]; + const Anope::string &extra = params.size() > 3 ? params[3] : ""; + + size_t sl = command.find('/'); + if (sl == Anope::string::npos) + { + source.Reply(_("\002{0}\002 is not a valid command."), command); + return; + } + + Anope::string service = command.substr(0, sl), + command_name = command.substr(sl + 1); + ServiceBot *bi = ServiceBot::Find(service, true); + + Anope::string service_name; + + /* Allow either a command name or a service name. */ + if (bi && bi->commands.count(command_name)) + { + /* Get service name from command */ + service_name = bi->commands[command_name].name; + } + else if (ServiceReference("Command", command.lower())) + { + /* This is the service name, don't use any specific command */ + service_name = command; + bi = NULL; + command_name.clear(); + } + else + { + source.Reply(_("\002{0}\002 is not a valid command."), command); + return; + } + + if (!method.equals_ci("MESSAGE") && !method.equals_ci("NOTICE") && !method.equals_ci("MEMO")) + { + source.Reply(_("\002%s\002 is not a valid logging method.")); + return; + } + + for (unsigned i = 0; i < extra.length(); ++i) + if (ModeManager::GetStatusChar(extra[i]) == 0) + { + source.Reply(_("\002%c\002 is an unknown status mode."), extra[i]); + return; + } + + bool override = !source.AccessFor(ci).HasPriv("SET"); + + std::vector ls = ci->GetRefs(logsetting); + for (unsigned i = ls.size(); i > 0; --i) + { + LogSetting *log = ls[i - 1]; + + if (log->GetServiceName() == service_name && log->GetMethod().equals_ci(method) && command_name.equals_ci(log->GetCommandName())) + { + if (log->GetExtra() == extra) + { + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to remove logging for " << command << " with method " << method << (extra == "" ? "" : " ") << extra; + source.Reply(_("Logging for command \002{0}\002 on \002{1}\002 with log method \002{2}{3}{4}\002 has been removed."), !log->GetCommandName().empty() ? log->GetCommandName() : log->GetServiceName(), !log->GetCommandService().empty() ? log->GetCommandService() : "any service", method, extra.empty() ? "" : " ", extra); + delete log; + } + else + { + log->SetExtra(extra); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to change logging for " << command << " to method " << method << (extra == "" ? "" : " ") << extra; + source.Reply(_("Logging changed for command \002{0}\002 on \002{1}\002, now using log method \002{2}{3}{4]\002."), !log->GetCommandName().empty() ? log->GetCommandName() : log->GetServiceName(), !log->GetCommandService().empty() ? log->GetCommandService() : "any service", method, extra.empty() ? "" : " ", extra); + } + return; + } + } + + LogSetting *log = logsetting.Create(); + log->SetChannel(ci); + log->SetServiceName(service_name); + if (bi) + log->SetCommandService(bi->nick); + log->SetCommandName(command_name); + log->SetMethod(method); + log->SetExtra(extra); + log->SetCreated(Anope::CurTime); + log->SetCreator(source.GetNick()); + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to log " << command << " with method " << method << (extra == "" ? "" : " ") << extra; + + source.Reply(_("Logging is now active for command \002{0}\002 on \002{1}\002, using log method \002{2}{3}{4}\002."), !command_name.empty() ? command_name : service_name, bi ? bi->nick : "any service", method, extra.empty() ? "" : " ", extra); + } + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("The {0} command allows users to configure logging settings for \037channel\037." + " If no parameters are given this command lists the current logging methods in place on \037channel\037." + " Otherwise, \037command\037 must be a command name, and \037method\037 must be one of the following logging methods:\n" + "\n" + " MESSAGE [status], NOTICE [status], MEMO\n" + "\n" + "Which are used to message, notice, and memo the channel respectively." + " With MESSAGE or NOTICE you must have a service bot assigned to and joined to your channel." + " Status may be a channel status such as @ or +.\n" + "\n" + "To remove a logging method use the same syntax as you would to add it.\n" + "\n" + "Use of this command requires the \002{1}\002 privilege on \037channel\037." + "\n" + "Example:\n" + " {command} #anope chanserv/access MESSAGE @\n" + " Would message any channel operators of \"#anope\" whenever someone used the \"ACCESS\" command on ChanServ for \"#anope\"."), + source.command, "SET"); + return true; + } +}; + +class CSLog : public Module + , public EventHook + , public EventHook +{ + CommandCSLog commandcslog; + LogSettingType logtype; + + struct LogDefault + { + Anope::string service, command, method; + }; + + std::vector defaults; + + public: + CSLog(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcslog(this) + , logtype(this) + { + + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + defaults.clear(); + + for (int i = 0; i < block->CountBlock("default"); ++i) + { + Configuration::Block *def = block->GetBlock("default", i); + + LogDefault ld; + + ld.service = def->Get("service"); + ld.command = def->Get("command"); + ld.method = def->Get("method"); + + defaults.push_back(ld); + } + } + + void OnChanRegistered(ChanServ::Channel *ci) override + { + if (defaults.empty()) + return; + + for (unsigned i = 0; i < defaults.size(); ++i) + { + LogDefault &d = defaults[i]; + + LogSetting *log = logsetting.Create(); + log->SetChannel(ci); + + if (!d.service.empty()) + { + log->SetServiceName(d.service.lower() + "/" + d.command.lower()); + log->SetCommandService(d.service); + log->SetCommandName(d.command); + } + else + log->SetServiceName(d.command); + + spacesepstream sep(d.method); + Anope::string method, extra; + sep.GetToken(method); + extra = sep.GetRemaining(); + + log->SetMethod(method); + log->SetExtra(extra); + log->SetCreated(Anope::CurTime); + log->SetCreator(ci->GetFounder() ? ci->GetFounder()->GetDisplay() : "(default)"); + } + } + + void OnLog(::Log *l) override + { + if (l->type != LOG_COMMAND || l->u == NULL || l->c == NULL || l->ci == NULL || !Me || !Me->IsSynced()) + return; + + std::vector ls = l->ci->GetRefs(logsetting); + for (unsigned i = 0; i < ls.size(); ++i) + { + LogSetting *log = ls[i]; + + /* wrong command */ + if (log->GetServiceName() != l->c->name) + continue; + + /* if a command name is given check the service and the command */ + if (!log->GetCommandName().empty()) + { + /* wrong service (only check if not a fantasy command, though) */ + if (!l->source->c && log->GetCommandService() != l->source->service->nick) + continue; + + if (!log->GetCommandName().equals_ci(l->source->command)) + continue; + } + + Anope::string buffer = l->u->nick + " used " + l->source->command.upper() + " " + l->buf.str(); + + if (log->GetMethod().equals_ci("MEMO") && MemoServ::service && l->ci->WhoSends() != NULL) + MemoServ::service->Send(l->ci->WhoSends()->nick, l->ci->GetName(), buffer, true); + else if (l->source->c) + /* Sending a channel message or notice in response to a fantasy command */; + else if (log->GetMethod().equals_ci("MESSAGE") && l->ci->c) + { + IRCD->SendPrivmsg(l->ci->WhoSends(), log->GetExtra() + l->ci->c->name, "%s", buffer.c_str()); + //l->ci->WhoSends()->lastmsg = Anope::CurTime; XXX + } + else if (log->GetMethod().equals_ci("NOTICE") && l->ci->c) + IRCD->SendNotice(l->ci->WhoSends(), log->GetExtra() + l->ci->c->name, "%s", buffer.c_str()); + } + } +}; + +MODULE_INIT(CSLog) diff --git a/modules/chanserv/main/CMakeLists.txt b/modules/chanserv/main/CMakeLists.txt new file mode 100644 index 000000000..781f0ef1f --- /dev/null +++ b/modules/chanserv/main/CMakeLists.txt @@ -0,0 +1 @@ +build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/chanserv/main/chanaccess.cpp b/modules/chanserv/main/chanaccess.cpp new file mode 100644 index 000000000..1601260c2 --- /dev/null +++ b/modules/chanserv/main/chanaccess.cpp @@ -0,0 +1,127 @@ +#include "module.h" +#include "chanaccess.h" +#include "chanaccesstype.h" + +ChanServ::Channel *ChanAccessImpl::GetChannel() +{ + return Get(&ChanAccessType::ci); +} + +void ChanAccessImpl::SetChannel(ChanServ::Channel *ci) +{ + Object::Set(&ChanAccessType::ci, ci); +} + +Anope::string ChanAccessImpl::GetCreator() +{ + return Get(&ChanAccessType::creator); +} + +void ChanAccessImpl::SetCreator(const Anope::string &c) +{ + Object::Set(&ChanAccessType::creator, c); +} + +time_t ChanAccessImpl::GetLastSeen() +{ + return Get(&ChanAccessType::last_seen); +} + +void ChanAccessImpl::SetLastSeen(const time_t &t) +{ + Object::Set(&ChanAccessType::last_seen, t); +} + +time_t ChanAccessImpl::GetCreated() +{ + return Get(&ChanAccessType::created); +} + +void ChanAccessImpl::SetCreated(const time_t &t) +{ + Object::Set(&ChanAccessType::created, t); +} + +Anope::string ChanAccessImpl::GetMask() +{ + return Get(&ChanAccessType::mask); +} + +void ChanAccessImpl::SetMask(const Anope::string &n) +{ + Object::Set(&ChanAccessType::mask, n); +} + +Serialize::Object *ChanAccessImpl::GetObj() +{ + return Get(&ChanAccessType::obj); +} + +void ChanAccessImpl::SetObj(Serialize::Object *o) +{ + Object::Set(&ChanAccessType::obj, o); +} + +Anope::string ChanAccessImpl::Mask() +{ + if (NickServ::Account *acc = GetAccount()) + return acc->GetDisplay(); + + return GetMask(); +} + +NickServ::Account *ChanAccessImpl::GetAccount() +{ + if (!GetObj() || GetObj()->GetSerializableType() != NickServ::account) + return nullptr; + + return anope_dynamic_static_cast(GetObj()); +} + +bool ChanAccessImpl::Matches(const User *u, NickServ::Account *acc, Path &p) +{ + if (this->GetAccount()) + return this->GetAccount() == acc; + + if (u) + { + bool is_mask = this->Mask().find_first_of("!@?*") != Anope::string::npos; + if (is_mask && Anope::Match(u->nick, this->Mask())) + return true; + else if (Anope::Match(u->GetDisplayedMask(), this->Mask())) + return true; + } + + if (acc) + for (NickServ::Nick *na : acc->GetRefs(NickServ::nick)) + if (Anope::Match(na->GetNick(), this->Mask())) + return true; + + if (IRCD->IsChannelValid(this->Mask())) + { + ChanServ::Channel *tci = ChanServ::Find(this->Mask()); + if (tci) + { + for (unsigned i = 0; i < tci->GetAccessCount(); ++i) + { + ChanServ::ChanAccess *a = tci->GetAccess(i); + std::pair pair = std::make_pair(this, a); + + std::pair range = p.first.equal_range(this); + for (; range.first != range.second; ++range.first) + if (range.first->first == pair.first && range.first->second == pair.second) + goto cont; + + p.first.insert(pair); + if (a->Matches(u, acc, p)) + p.second.insert(pair); + + cont:; + } + + return p.second.count(this) > 0; + } + } + + return false; +} diff --git a/modules/chanserv/main/chanaccess.h b/modules/chanserv/main/chanaccess.h new file mode 100644 index 000000000..bb65f79c5 --- /dev/null +++ b/modules/chanserv/main/chanaccess.h @@ -0,0 +1,30 @@ + +class ChanAccessImpl : public ChanServ::ChanAccess +{ + public: + ChanAccessImpl(Serialize::TypeBase *type) : ChanServ::ChanAccess(type) { } + ChanAccessImpl(Serialize::TypeBase *type, Serialize::ID id) : ChanServ::ChanAccess(type, id) { } + + ChanServ::Channel *GetChannel() override; + void SetChannel(ChanServ::Channel *ci) override; + + Anope::string GetCreator() override; + void SetCreator(const Anope::string &c) override; + + time_t GetLastSeen() override; + void SetLastSeen(const time_t &t) override; + + time_t GetCreated() override; + void SetCreated(const time_t &t) override; + + Anope::string GetMask() override; + void SetMask(const Anope::string &) override; + + Serialize::Object *GetObj() override; + void SetObj(Serialize::Object *) override; + + Anope::string Mask() override; + NickServ::Account *GetAccount() override; + + bool Matches(const User *u, NickServ::Account *acc, Path &p) override; +}; diff --git a/modules/chanserv/main/chanaccesstype.h b/modules/chanserv/main/chanaccesstype.h new file mode 100644 index 000000000..ca8dc1b79 --- /dev/null +++ b/modules/chanserv/main/chanaccesstype.h @@ -0,0 +1,21 @@ + +class ChanAccessType : public Serialize::AbstractType +{ + public: + Serialize::ObjectField ci; + Serialize::Field mask; + Serialize::ObjectField obj; + Serialize::Field creator; + Serialize::Field last_seen; + Serialize::Field created; + + ChanAccessType(Module *me, const Anope::string &name) : Serialize::AbstractType(me, name) + , ci(this, "ci", true) + , mask(this, "mask") + , obj(this, "obj", true) + , creator(this, "creator") + , last_seen(this, "last_seen") + , created(this, "created") + { + } +}; diff --git a/modules/chanserv/main/channel.cpp b/modules/chanserv/main/channel.cpp new file mode 100644 index 000000000..b24edcc0c --- /dev/null +++ b/modules/chanserv/main/channel.cpp @@ -0,0 +1,429 @@ +/* Registered channel functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + * + */ + +#include "module.h" +#include "channeltype.h" +#include "modules/cs_akick.h" + +ChannelImpl::ChannelImpl(Serialize::TypeBase *type, const Anope::string &chname) : ChanServ::Channel(type) +{ + SetName(chname); + SetTimeRegistered(Anope::CurTime); + SetLastUsed(Anope::CurTime); +} + +ChannelImpl::~ChannelImpl() +{ + ChanServ::registered_channel_map& map = ChanServ::service->GetChannels(); + map.erase(this->GetName()); +} + +void ChannelImpl::Delete() +{ + Event::OnDelChan(&Event::DelChan::OnDelChan, this); + + Log(LOG_DEBUG) << "Deleting channel " << this->GetName(); + + if (this->c) + { + if (this->GetBot() && this->c->FindUser(this->GetBot())) + this->GetBot()->Part(this->c); + + /* Parting the service bot can cause the channel to go away */ + + if (this->c) + { + if (this->c && this->c->CheckDelete()) + delete this->c; + + this->c = NULL; + } + } + + return Serialize::Object::Delete(); +} + +Anope::string ChannelImpl::GetName() +{ + return Get(&ChannelType::name); +} + +void ChannelImpl::SetName(const Anope::string &name) +{ + Set(&ChannelType::name, name); +} + +Anope::string ChannelImpl::GetDesc() +{ + return Get(&ChannelType::desc); +} + +void ChannelImpl::SetDesc(const Anope::string &desc) +{ + Set(&ChannelType::desc, desc); +} + +time_t ChannelImpl::GetTimeRegistered() +{ + return Get(&ChannelType::time_registered); +} + +void ChannelImpl::SetTimeRegistered(const time_t &t) +{ + Set(&ChannelType::time_registered, t); +} + +time_t ChannelImpl::GetLastUsed() +{ + return Get(&ChannelType::last_used); +} + +void ChannelImpl::SetLastUsed(const time_t &t) +{ + Set(&ChannelType::last_used, t); +} + +Anope::string ChannelImpl::GetLastTopic() +{ + return Get(&ChannelType::last_topic); +} + +void ChannelImpl::SetLastTopic(const Anope::string &topic) +{ + Set(&ChannelType::last_topic, topic); +} + +Anope::string ChannelImpl::GetLastTopicSetter() +{ + return Get(&ChannelType::last_topic_setter); +} + +void ChannelImpl::SetLastTopicSetter(const Anope::string &setter) +{ + Set(&ChannelType::last_topic_setter, setter); +} + +time_t ChannelImpl::GetLastTopicTime() +{ + return Get(&ChannelType::last_topic_time); +} + +void ChannelImpl::SetLastTopicTime(const time_t &t) +{ + Set(&ChannelType::last_topic_time, t); +} + +int16_t ChannelImpl::GetBanType() +{ + return Get(&ChannelType::bantype); +} + +void ChannelImpl::SetBanType(const int16_t &i) +{ + Set(&ChannelType::bantype, i); +} + +time_t ChannelImpl::GetBanExpire() +{ + return Get(&ChannelType::banexpire); +} + +void ChannelImpl::SetBanExpire(const time_t &t) +{ + Set(&ChannelType::banexpire, t); +} + +BotInfo *ChannelImpl::GetBI() +{ + return Get(&ChannelType::bi); +} + +void ChannelImpl::SetBI(BotInfo *bi) +{ + Set(&ChannelType::bi, bi); +} + +ServiceBot *ChannelImpl::GetBot() +{ + BotInfo *bi = GetBI(); + return bi ? bi->bot : nullptr; +} + +void ChannelImpl::SetBot(ServiceBot *bi) +{ + SetBI(bi->bi); +} + +MemoServ::MemoInfo *ChannelImpl::GetMemos() +{ + return GetRef(MemoServ::memoinfo); +} + +void ChannelImpl::SetFounder(NickServ::Account *nc) +{ + Set(&ChannelType::founder, nc); +} + +NickServ::Account *ChannelImpl::GetFounder() +{ + return Get(&ChannelType::founder); +} + +void ChannelImpl::SetSuccessor(NickServ::Account *nc) +{ + Set(&ChannelType::successor, nc); +} + +NickServ::Account *ChannelImpl::GetSuccessor() +{ + return Get(&ChannelType::successor); +} + +ChanServ::ChanAccess *ChannelImpl::GetAccess(unsigned index) +{ + std::vector a = GetRefs(ChanServ::chanaccess); + return a.size() > index ? a[index] : nullptr; +} + +ChanServ::AccessGroup ChannelImpl::AccessFor(const User *u, bool updateLastUsed) +{ + ChanServ::AccessGroup group; + + if (u == NULL) + return group; + + const NickServ::Account *nc = u->Account(); + if (nc == NULL && !this->HasFieldS("NS_SECURE") && u->IsRecognized()) + { + NickServ::Nick *na = NickServ::FindNick(u->nick); + if (na != NULL) + nc = na->GetAccount(); + } + + group.super_admin = u->super_admin; + group.founder = IsFounder(u); + group.ci = this; + group.nc = nc; + + for (unsigned i = 0, end = this->GetAccessCount(); i < end; ++i) + { + ChanServ::ChanAccess *a = this->GetAccess(i); + if (a->Matches(u, u->Account(), group.path)) + group.push_back(a); + } + + if (group.founder || !group.empty()) + { + if (updateLastUsed) + this->SetLastUsed(Anope::CurTime); + + for (unsigned i = 0; i < group.size(); ++i) + group[i]->SetLastSeen(Anope::CurTime); + } + + return group; +} + +ChanServ::AccessGroup ChannelImpl::AccessFor(NickServ::Account *nc, bool updateLastUsed) +{ + ChanServ::AccessGroup group; + + group.founder = GetFounder() && GetFounder() == nc; + group.ci = this; + group.nc = nc; + + for (unsigned i = 0, end = this->GetAccessCount(); i < end; ++i) + { + ChanServ::ChanAccess *a = this->GetAccess(i); + if (a->Matches(NULL, nc, group.path)) + group.push_back(a); + } + + if (group.founder || !group.empty()) + if (updateLastUsed) + this->SetLastUsed(Anope::CurTime); + + /* don't update access last seen here, this isn't the user requesting access */ + + return group; +} + +unsigned ChannelImpl::GetAccessCount() +{ + return GetRefs(ChanServ::chanaccess).size(); +} + +unsigned ChannelImpl::GetDeepAccessCount() const +{ + return 0; +#if 0 + ChanServ::ChanAccess::Path path; + for (unsigned i = 0, end = this->GetAccessCount(); i < end; ++i) + { + ChanServ::ChanAccess *a = this->GetAccess(i); + a->Matches(NULL, NULL, path); + } + + unsigned count = this->GetAccessCount(); + std::set channels; + channels.insert(this); + for (ChanServ::ChanAccess::Set::iterator it = path.first.begin(); it != path.first.end(); ++it) + { + const ChanServ::Channel *ci = it->first->GetChannel(); + if (!channels.count(ci)) + { + channels.count(ci); + count += ci->GetAccessCount(); + } + } + return count; +#endif +} + +void ChannelImpl::ClearAccess() +{ + for (ChanServ::ChanAccess *a : GetRefs(ChanServ::chanaccess)) + a->Delete(); +} + +AutoKick *ChannelImpl::AddAkick(const Anope::string &user, NickServ::Account *akicknc, const Anope::string &reason, time_t t, time_t lu) +{ + AutoKick *ak = ::autokick.Create(); + ak->SetChannel(this); + ak->SetAccount(akicknc); + ak->SetReason(reason); + ak->SetCreator(user); + ak->SetAddTime(t); + ak->SetLastUsed(lu); + + return ak; +} + +AutoKick *ChannelImpl::AddAkick(const Anope::string &user, const Anope::string &mask, const Anope::string &reason, time_t t, time_t lu) +{ + AutoKick *ak = ::autokick.Create(); + ak->SetChannel(this); + ak->SetMask(mask); + ak->SetReason(reason); + ak->SetCreator(user); + ak->SetAddTime(t); + ak->SetLastUsed(lu); + + return ak; +} + +AutoKick *ChannelImpl::GetAkick(unsigned index) +{ + std::vector a = GetRefs(autokick); + return a.size() > index ? a[index] : nullptr; +} + +unsigned ChannelImpl::GetAkickCount() +{ + std::vector t = GetRefs(autokick); + return t.size(); +} + +void ChannelImpl::ClearAkick() +{ + for (AutoKick *ak : GetRefs(autokick)) + ak->Delete(); +} + +int16_t ChannelImpl::GetLevel(const Anope::string &priv) +{ + for (ChanServ::Level *l : this->GetRefs(ChanServ::level)) + if (l->GetName() == priv) + return l->GetLevel(); + + ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(priv) : nullptr; + if (!p) + { + Log(LOG_DEBUG) << "Unknown privilege " + priv; + return ChanServ::ACCESS_INVALID; + } + + return p->level; +} + +void ChannelImpl::SetLevel(const Anope::string &priv, int16_t level) +{ + for (ChanServ::Level *l : this->GetRefs(ChanServ::level)) + if (l->GetName() == priv) + { + l->SetLevel(level); + return; + } + + ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(priv) : nullptr; + if (!p) + { + Log(LOG_DEBUG) << "Unknown privilege " + priv; + return; + } + + ChanServ::Level *l = ChanServ::level.Create(); + l->SetChannel(this); + l->SetName(priv); + l->SetLevel(level); +} + +void ChannelImpl::RemoveLevel(const Anope::string &priv) +{ + for (ChanServ::Level *l : this->GetRefs(ChanServ::level)) + if (l->GetName() == priv) + { + l->Delete(); + return; + } +} + +void ChannelImpl::ClearLevels() +{ + for (ChanServ::Level *l : this->GetRefs(ChanServ::level)) + l->Delete(); +} + +Anope::string ChannelImpl::GetIdealBan(User *u) +{ + int bt = this->GetBanType(); + switch (bt) + { + case 0: + return "*!" + u->GetVIdent() + "@" + u->GetDisplayedHost(); + case 1: + if (u->GetVIdent()[0] == '~') + return "*!*" + u->GetVIdent() + "@" + u->GetDisplayedHost(); + else + return "*!" + u->GetVIdent() + "@" + u->GetDisplayedHost(); + case 3: + return "*!" + u->Mask(); + case 2: + default: + return "*!*@" + u->GetDisplayedHost(); + } +} + +bool ChannelImpl::IsFounder(const User *user) +{ + if (!user) + return false; + + if (user->super_admin) + return true; + + if (user->Account() && user->Account() == this->GetFounder()) + return true; + + return false; +} + diff --git a/modules/chanserv/main/channel.h b/modules/chanserv/main/channel.h new file mode 100644 index 000000000..86e42a8f9 --- /dev/null +++ b/modules/chanserv/main/channel.h @@ -0,0 +1,68 @@ + + +class ChannelImpl : public ChanServ::Channel +{ + public: + ChannelImpl(Serialize::TypeBase *type) : ChanServ::Channel(type) { } + ChannelImpl(Serialize::TypeBase *type, Serialize::ID id) : ChanServ::Channel(type, id) { } + ChannelImpl(Serialize::TypeBase *type, const Anope::string &chname); + ~ChannelImpl(); + void Delete() override; + + Anope::string GetName() override; + void SetName(const Anope::string &) override; + + Anope::string GetDesc() override; + void SetDesc(const Anope::string &) override; + + time_t GetTimeRegistered() override; + void SetTimeRegistered(const time_t &) override; + + time_t GetLastUsed() override; + void SetLastUsed(const time_t &) override; + + Anope::string GetLastTopic() override; + void SetLastTopic(const Anope::string &) override; + + Anope::string GetLastTopicSetter() override; + void SetLastTopicSetter(const Anope::string &) override; + + time_t GetLastTopicTime() override; + void SetLastTopicTime(const time_t &) override; + + int16_t GetBanType() override; + void SetBanType(const int16_t &) override; + + time_t GetBanExpire() override; + void SetBanExpire(const time_t &) override; + + BotInfo *GetBI() override; + void SetBI(BotInfo *) override; + + ServiceBot *GetBot() override; + void SetBot(ServiceBot *) override; + + MemoServ::MemoInfo *GetMemos() override; + + bool IsFounder(const User *user) override; + void SetFounder(NickServ::Account *nc) override; + NickServ::Account *GetFounder() override; + void SetSuccessor(NickServ::Account *nc) override; + NickServ::Account *GetSuccessor() override; + ChanServ::ChanAccess *GetAccess(unsigned index) /*const*/ override; + ChanServ::AccessGroup AccessFor(const User *u, bool = true) override; + ChanServ::AccessGroup AccessFor(NickServ::Account *nc, bool = true) override; + unsigned GetAccessCount()/* const*/ override; + unsigned GetDeepAccessCount() const override; + void ClearAccess() override; + AutoKick* AddAkick(const Anope::string &user, NickServ::Account *akicknc, const Anope::string &reason, time_t t = Anope::CurTime, time_t lu = 0) override; + AutoKick* AddAkick(const Anope::string &user, const Anope::string &mask, const Anope::string &reason, time_t t = Anope::CurTime, time_t lu = 0) override; + AutoKick* GetAkick(unsigned index) override; + unsigned GetAkickCount() override; + void ClearAkick() override; + int16_t GetLevel(const Anope::string &priv) override; + void SetLevel(const Anope::string &priv, int16_t level) override; + void RemoveLevel(const Anope::string &priv) override; + void ClearLevels() override; + Anope::string GetIdealBan(User *u) override; +}; diff --git a/modules/chanserv/main/channeltype.cpp b/modules/chanserv/main/channeltype.cpp new file mode 100644 index 000000000..4143285a8 --- /dev/null +++ b/modules/chanserv/main/channeltype.cpp @@ -0,0 +1,45 @@ +#include "module.h" +#include "channeltype.h" + +ChannelType::ChannelType(Module *me) : Serialize::Type(me, "ChannelInfo") + , name(this, "name") + , desc(this, "desc") + , time_registered(this, "time_registered") + , last_used(this, "last_used") + , last_topic(this, "last_topic") + , last_topic_setter(this, "last_topic_setter") + , last_topic_time(this, "last_topic_time") + , bantype(this, "bantype") + , banexpire(this, "banexpire") + , founder(this, "founder") + , successor(this, "successor") + , bi(this, "bi") +{ + +} + +void ChannelType::Name::SetField(ChanServ::Channel *c, const Anope::string &value) +{ + ChanServ::registered_channel_map& map = ChanServ::service->GetChannels(); + map.erase(GetField(c)); + + Serialize::Field::SetField(c, value); + + map[value] = c; +} + +ChanServ::Channel *ChannelType::FindChannel(const Anope::string &chan) +{ + Serialize::ID id; + EventReturn result = Event::OnSerialize(&Event::SerializeEvents::OnSerializeFind, this, &this->name, chan, id); + if (result == EVENT_ALLOW) + return RequireID(id); + + // fall back + ChanServ::registered_channel_map& map = ChanServ::service->GetChannels(); + auto it = map.find(chan); + if (it != map.end()) + return it->second; + return nullptr; +} + diff --git a/modules/chanserv/main/channeltype.h b/modules/chanserv/main/channeltype.h new file mode 100644 index 000000000..7bc22a8f6 --- /dev/null +++ b/modules/chanserv/main/channeltype.h @@ -0,0 +1,34 @@ +#include "channel.h" + +class ChannelType : public Serialize::Type +{ + public: + /* Channel name */ + struct Name : Serialize::Field + { + using Serialize::Field::Field; + + void SetField(ChanServ::Channel *c, const Anope::string &value) override; + } name; + Serialize::Field desc; + Serialize::Field time_registered; + Serialize::Field last_used; + + Serialize::Field last_topic; + Serialize::Field last_topic_setter; + Serialize::Field last_topic_time; + + Serialize::Field bantype; + Serialize::Field banexpire; + + /* Channel founder */ + Serialize::ObjectField founder; + /* Who gets the channel if the founder nick is dropped or expires */ + Serialize::ObjectField successor; + + Serialize::ObjectField bi; + + ChannelType(Module *); + + ChanServ::Channel *FindChannel(const Anope::string &); +}; diff --git a/modules/chanserv/main/chanserv.cpp b/modules/chanserv/main/chanserv.cpp new file mode 100644 index 000000000..420051252 --- /dev/null +++ b/modules/chanserv/main/chanserv.cpp @@ -0,0 +1,587 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_mode.h" +#include "modules/help.h" +#include "modules/bs_bot.h" +#include "modules/chanserv.h" +#include "modules/cs_info.h" +#include "modules/cs_akick.h" +#include "channeltype.h" +#include "leveltype.h" +#include "modetype.h" +#include "chanaccesstype.h" +#include "chanaccess.h" + +class ChanServCore : public Module + , public ChanServ::ChanServService + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + Reference ChanServ; + std::vector defaults; + ExtensibleItem inhabit; + ExtensibleRef persist;//XXX? + bool always_lower; + EventHandlers OnPreChanExpire; + EventHandlers OnChanExpire; + std::vector Privileges; + ChanServ::registered_channel_map registered_channels; + ChannelType channel_type; + ChanAccessType chanaccess_type; + LevelType level_type; + CSModeType mode_type; + + public: + ChanServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) + , ChanServService(this) + , inhabit(this, "inhabit") + , persist("PERSIST") + , always_lower(false) + , OnPreChanExpire(this) + , OnChanExpire(this) + , channel_type(this) + , chanaccess_type(this, "ChanAccess") + , level_type(this) + , mode_type(this) + { + } + + ChanServ::Channel *Find(const Anope::string &name) override + { + return channel_type.FindChannel(name); + } + + ChanServ::registered_channel_map& GetChannels() override + { + return registered_channels; + } + + void Hold(Channel *c) override + { + /** A timer used to keep the BotServ bot/ChanServ in the channel + * after kicking the last user in a channel + */ + class ChanServTimer : public Timer + { + Reference &ChanServ; + ExtensibleItem &inhabit; + Reference c; + + public: + /** Constructor + * @param chan The channel + */ + ChanServTimer(Reference &cs, ExtensibleItem &i, Module *m, Channel *chan) : Timer(m, Config->GetModule(m)->Get("inhabit", "15s")), ChanServ(cs), inhabit(i), c(chan) + { + if (!ChanServ || !c) + return; + inhabit.Set(c, true); + if (!c->ci || !c->ci->GetBot()) + ChanServ->Join(c); + else if (!c->FindUser(c->ci->GetBot())) + c->ci->GetBot()->Join(c); + + /* Set +ntsi to prevent rejoin */ + c->SetMode(NULL, "NOEXTERNAL"); + c->SetMode(NULL, "TOPIC"); + c->SetMode(NULL, "SECRET"); + c->SetMode(NULL, "INVITE"); + } + + /** Called when the delay is up + * @param The current time + */ + void Tick(time_t) override + { + if (!c) + return; + + inhabit.Unset(c); + + /* In the event we don't part */ + c->RemoveMode(NULL, "SECRET"); + c->RemoveMode(NULL, "INVITE"); + + if (!c->ci || !c->ci->GetBot()) + { + if (ChanServ) + ChanServ->Part(c); + } + /* If someone has rejoined this channel in the meantime, don't part the bot */ + else if (c->users.size() <= 1) + c->ci->GetBot()->Part(c); + } + }; + + if (inhabit.HasExt(c)) + return; + + new ChanServTimer(ChanServ, inhabit, this->owner, c); + } + + void AddPrivilege(ChanServ::Privilege p) override + { + unsigned i; + for (i = 0; i < Privileges.size(); ++i) + { + ChanServ::Privilege &priv = Privileges[i]; + + if (priv.rank > p.rank) + break; + } + + Privileges.insert(Privileges.begin() + i, p); + } + + void RemovePrivilege(ChanServ::Privilege &p) override + { + std::vector::iterator it = std::find(Privileges.begin(), Privileges.end(), p); + if (it != Privileges.end()) + Privileges.erase(it); + + for (auto& cit : GetChannels()) + { + ChanServ::Channel *ci = cit.second; + ci->RemoveLevel(p.name); + } + } + + ChanServ::Privilege *FindPrivilege(const Anope::string &name) override + { + for (unsigned i = Privileges.size(); i > 0; --i) + if (Privileges[i - 1].name.equals_ci(name)) + return &Privileges[i - 1]; + return NULL; + } + + std::vector &GetPrivileges() override + { + return Privileges; + } + + void ClearPrivileges() override + { + Privileges.clear(); + } + + void OnReload(Configuration::Conf *conf) override + { + const Anope::string &channick = conf->GetModule(this)->Get("client"); + + if (channick.empty()) + throw ConfigException(Module::name + ": must be defined"); + + ServiceBot *bi = ServiceBot::Find(channick, true); + if (!bi) + throw ConfigException(Module::name + ": no bot named " + channick); + + ChanServ = bi; + + ClearPrivileges(); + for (int i = 0; i < conf->CountBlock("privilege"); ++i) + { + Configuration::Block *privilege = conf->GetBlock("privilege", i); + + const Anope::string &nname = privilege->Get("name"), + &desc = privilege->Get("desc"); + int rank = privilege->Get("rank"); + Anope::string value = privilege->Get("level"); + int level; + if (value.equals_ci("founder")) + level = ChanServ::ACCESS_FOUNDER; + else if (value.equals_ci("disabled")) + level = ChanServ::ACCESS_INVALID; + else + level = privilege->Get("level"); + + AddPrivilege(ChanServ::Privilege(nname, desc, rank, level)); + } + + spacesepstream(conf->GetModule(this)->Get("defaults", "greet fantasy")).GetTokens(defaults); + if (defaults.empty()) + { + defaults.push_back("KEEPTOPIC"); + defaults.push_back("CS_SECURE"); + defaults.push_back("SECUREFOUNDER"); + defaults.push_back("SIGNKICK"); + } + else if (defaults[0].equals_ci("none")) + defaults.clear(); + + always_lower = conf->GetModule(this)->Get("always_lower_ts"); + } + + void OnChannelCreate(Channel *c) override + { + c->ci = Find(c->name); + if (c->ci) + c->ci->c = c; + } + + void OnBotDelete(ServiceBot *bi) override + { + if (bi == ChanServ) + ChanServ = NULL; + } + + EventReturn OnBotPrivmsg(User *u, ServiceBot *bi, Anope::string &message) override + { + if (bi == ChanServ && Config->GetModule(this)->Get("opersonly") && !u->HasMode("OPER")) + { + u->SendMessage(bi, _("Access denied.")); + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + void OnDelCore(NickServ::Account *nc) override + { + unsigned int max_reg = Config->GetModule(this)->Get("maxregistered"); + for (ChanServ::Channel *ci : nc->GetRefs(ChanServ::channel)) + { + if (ci->GetFounder() == nc) + { + NickServ::Account *newowner = NULL; + if (ci->GetSuccessor() && ci->GetSuccessor() != nc && (ci->GetSuccessor()->IsServicesOper() || !max_reg || ci->GetSuccessor()->GetChannelCount() < max_reg)) + newowner = ci->GetSuccessor(); + else + { + ChanServ::ChanAccess *highest = NULL; + for (unsigned j = 0; j < ci->GetAccessCount(); ++j) + { + ChanServ::ChanAccess *ca = ci->GetAccess(j); + NickServ::Account *anc = ca->GetAccount(); + + if (!anc || (!anc->IsServicesOper() && max_reg && anc->GetChannelCount() >= max_reg) || (anc == nc)) + continue; + if (!highest || *ca > *highest) + highest = ca; + } + if (highest) + newowner = highest->GetAccount(); + } + + if (newowner) + { + ::Log(LOG_NORMAL, "chanserv/drop", ChanServ) << "Transferring foundership of " << ci->GetName() << " from deleted nick " << nc->GetDisplay() << " to " << newowner->GetDisplay(); + ci->SetFounder(newowner); + ci->SetSuccessor(NULL); + } + else + { + ::Log(LOG_NORMAL, "chanserv/drop", ChanServ) << "Deleting channel " << ci->GetName() << " owned by deleted nick " << nc->GetDisplay(); + + delete ci; + continue; + } + } + + if (ci->GetSuccessor() == nc) + ci->SetSuccessor(NULL); + + /* are these necessary? */ + for (unsigned j = 0; j < ci->GetAccessCount(); ++j) + { + ChanServ::ChanAccess *ca = ci->GetAccess(j); + NickServ::Account *anc = ca->GetAccount(); + + if (anc && anc == nc) + { + delete ca; + break; + } + } + + for (unsigned j = 0; j < ci->GetAkickCount(); ++j) + { + AutoKick *ak = ci->GetAkick(j); + if (ak->GetAccount() == nc) + { + delete ak; + break; + } + } + } + } + + void OnDelChan(ChanServ::Channel *ci) override + { + /* remove access entries that are this channel */ + + for (ChanServ::Channel *c : ci->GetRefs(ChanServ::channel)) + { + for (unsigned j = 0; j < c->GetAccessCount(); ++j) + { + ChanServ::ChanAccess *a = c->GetAccess(j); + + if (a->Mask().equals_ci(ci->GetName())) + { + delete a; + break; + } + } + } + + if (ci->c) + { + ci->c->RemoveMode(ci->WhoSends(), "REGISTERED", "", false); + + const Anope::string &require = Config->GetModule(this)->Get("require"); + if (!require.empty()) + ci->c->SetModes(ci->WhoSends(), false, "-%s", require.c_str()); + } + } + + EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override + { + if (!params.empty() || source.c || source.service != *ChanServ) + return EVENT_CONTINUE; + source.Reply(_("\002%s\002 allows you to register and control various\n" + "aspects of channels. %s can often prevent\n" + "malicious users from \"taking over\" channels by limiting\n" + "who is allowed channel operator privileges. Available\n" + "commands are listed below; to use them, type\n" + "\002%s%s \037command\037\002. For more information on a\n" + "specific command, type \002%s%s HELP \037command\037\002.\n"), + ChanServ->nick.c_str(), ChanServ->nick.c_str(), Config->StrictPrivmsg.c_str(), ChanServ->nick.c_str(), Config->StrictPrivmsg.c_str(), ChanServ->nick.c_str(), ChanServ->nick.c_str(), source.command.c_str()); + return EVENT_CONTINUE; + } + + void OnPostHelp(CommandSource &source, const std::vector ¶ms) override + { + if (!params.empty() || source.c || source.service != *ChanServ) + return; + time_t chanserv_expire = Config->GetModule(this)->Get("expire", "14d"); + if (chanserv_expire >= 86400) + source.Reply(_(" \n" + "Note that any channel which is not used for %d days\n" + "(i.e. which no user on the channel's access list enters\n" + "for that period of time) will be automatically dropped."), chanserv_expire / 86400); + if (source.IsServicesOper()) + source.Reply(_(" \n" + "Services Operators can also, depending on their access drop\n" + "any channel, view (and modify) the access, levels and akick\n" + "lists and settings for any channel.")); + } + + void OnCheckModes(Reference &c) override + { + if (!c) + return; + + if (c->ci) + c->SetMode(c->ci->WhoSends(), "REGISTERED", "", false); + else + c->RemoveMode(c->ci->WhoSends(), "REGISTERED", "", false); + + const Anope::string &require = Config->GetModule(this)->Get("require"); + if (!require.empty()) + { + if (c->ci) + c->SetModes(c->ci->WhoSends(), false, "+%s", require.c_str()); + else + c->SetModes(c->ci->WhoSends(), false, "-%s", require.c_str()); + } + } + + void OnCreateChan(ChanServ::Channel *ci) override + { + /* Set default chan flags */ + for (unsigned i = 0; i < defaults.size(); ++i) + ci->SetS(defaults[i].upper(), true); + } + + EventReturn OnCanSet(User *u, const ChannelMode *cm) override + { + if (Config->GetModule(this)->Get("nomlock").find(cm->mchar) != Anope::string::npos + || Config->GetModule(this)->Get("require").find(cm->mchar) != Anope::string::npos) + return EVENT_STOP; + return EVENT_CONTINUE; + } + + void OnChannelSync(Channel *c) override + { + bool perm = c->HasMode("PERM") || (c->ci && persist && persist->HasExt(c->ci)); + if (!perm && !c->botchannel && (c->users.empty() || (c->users.size() == 1 && c->users.begin()->second->user->server == Me))) + { + this->Hold(c); + } + } + + void OnLog(::Log *l) override + { + if (l->type == LOG_CHANNEL) + l->bi = ChanServ; + } + + void OnExpireTick() override + { + time_t chanserv_expire = Config->GetModule(this)->Get("expire", "14d"); + + if (!chanserv_expire || Anope::NoExpire || Anope::ReadOnly) + return; + + for (ChanServ::Channel *ci : channel_type.List()) + { + bool expire = false; + + if (Anope::CurTime - ci->GetLastUsed() >= chanserv_expire) + { + if (ci->c) + { + time_t last_used = ci->GetLastUsed(); + for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end && last_used == ci->GetLastUsed(); ++cit) + ci->AccessFor(cit->second->user); + expire = last_used == ci->GetLastUsed(); + } + else + expire = true; + } + + this->OnPreChanExpire(&ChanServ::Event::PreChanExpire::OnPreChanExpire, ci, expire); + + if (expire) + { + ::Log(LOG_NORMAL, "chanserv/expire", ChanServ) << "Expiring channel " << ci->GetName() << " (founder: " << (ci->GetFounder() ? ci->GetFounder()->GetDisplay() : "(none)") << ")"; + this->OnChanExpire(&ChanServ::Event::ChanExpire::OnChanExpire, ci); + delete ci; + } + } + } + + EventReturn OnCheckDelete(Channel *c) override + { + /* Do not delete this channel if ChanServ/a BotServ bot is inhabiting it */ + if (inhabit.HasExt(c)) + return EVENT_STOP; + + return EVENT_CONTINUE; + } + + void OnPreUplinkSync(Server *serv) override + { + if (!persist) + return; + /* Find all persistent channels and create them, as we are about to finish burst to our uplink */ + for (ChanServ::Channel *ci : channel_type.List()) + { + if (persist->HasExt(ci)) + { + bool c; + ci->c = Channel::FindOrCreate(ci->GetName(), c, ci->GetTimeRegistered()); + + if (ModeManager::FindChannelModeByName("PERM") != NULL) + { + if (c) + IRCD->SendChannel(ci->c); + ci->c->SetMode(NULL, "PERM"); + } + else + { + if (!ci->GetBot()) + ci->WhoSends()->Assign(NULL, ci); + if (ci->c->FindUser(ci->GetBot()) == NULL) + { + ChannelStatus status(Config->GetModule("botserv")->Get("botmodes")); + ci->GetBot()->Join(ci->c, &status); + } + } + } + } + + } + + void OnChanRegistered(ChanServ::Channel *ci) override + { + if (!persist || !ci->c) + return; + /* Mark the channel as persistent */ + if (ci->c->HasMode("PERM")) + persist->Set(ci, true); + /* Persist may be in def cflags, set it here */ + else if (persist->HasExt(ci)) + ci->c->SetMode(NULL, "PERM"); + } + + void OnJoinChannel(User *u, Channel *c) override + { + if (always_lower && c->ci && c->creation_time > c->ci->GetTimeRegistered()) + { + ::Log(LOG_DEBUG) << "Changing TS of " << c->name << " from " << c->creation_time << " to " << c->ci->GetTimeRegistered(); + c->creation_time = c->ci->GetTimeRegistered(); + IRCD->SendChannel(c); + c->Reset(); + } + } + + EventReturn OnChannelModeSet(Channel *c, const MessageSource &setter, ChannelMode *mode, const Anope::string ¶m) override + { + if (!always_lower && Anope::CurTime == c->creation_time && c->ci && setter.GetUser() && !setter.GetUser()->server->IsULined()) + { + ChanUserContainer *cu = c->FindUser(setter.GetUser()); + ChannelMode *cm = ModeManager::FindChannelModeByName("OP"); + if (cu && cm && !cu->status.HasMode(cm->mchar)) + { + /* Our -o and their mode change crossing, bounce their mode */ + c->RemoveMode(c->ci->WhoSends(), mode, param); + /* We don't set mlocks until after the join has finished processing, it will stack with this change, + * so there isn't much for the user to remove except -nt etc which is likely locked anyway. + */ + } + } + + return EVENT_CONTINUE; + } + + void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override + { + if (!show_all) + return; + + time_t chanserv_expire = Config->GetModule(this)->Get("expire", "14d"); + if (!ci->HasFieldS("CS_NO_EXPIRE") && chanserv_expire && !Anope::NoExpire && ci->GetLastUsed() != Anope::CurTime) + info[_("Expires")] = Anope::strftime(ci->GetLastUsed() + chanserv_expire, source.GetAccount()); + } + + void OnSetCorrectModes(User *user, Channel *chan, ChanServ::AccessGroup &access, bool &give_modes, bool &take_modes) override + { + if (always_lower) + // Since we always lower the TS, the other side will remove the modes if the channel ts lowers, so we don't + // have to worry about it + take_modes = false; + else if (ModeManager::FindChannelModeByName("REGISTERED")) + // Otherwise if the registered channel mode exists, we should remove modes if the channel is not +r + take_modes = !chan->HasMode("REGISTERED"); + } +}; + +MODULE_INIT(ChanServCore) + diff --git a/modules/chanserv/main/level.cpp b/modules/chanserv/main/level.cpp new file mode 100644 index 000000000..d31c98d64 --- /dev/null +++ b/modules/chanserv/main/level.cpp @@ -0,0 +1,33 @@ +#include "modules/chanserv.h" +#include "leveltype.h" + +ChanServ::Channel *LevelImpl::GetChannel() +{ + return Get(&LevelType::channel); +} + +void LevelImpl::SetChannel(ChanServ::Channel *c) +{ + Set(&LevelType::channel, c); +} + +Anope::string LevelImpl::GetName() +{ + return Get(&LevelType::name); +} + +void LevelImpl::SetName(const Anope::string &n) +{ + Set(&LevelType::name, n); +} + +int LevelImpl::GetLevel() +{ + return Get(&LevelType::level); +} + +void LevelImpl::SetLevel(const int &i) +{ + Set(&LevelType::level, i); +} + diff --git a/modules/chanserv/main/level.h b/modules/chanserv/main/level.h new file mode 100644 index 000000000..c9ffd0dfb --- /dev/null +++ b/modules/chanserv/main/level.h @@ -0,0 +1,16 @@ + +class LevelImpl : public ChanServ::Level +{ + public: + LevelImpl(Serialize::TypeBase *type) : ChanServ::Level(type) { } + LevelImpl(Serialize::TypeBase *type, Serialize::ID id) : ChanServ::Level(type, id) { } + + ChanServ::Channel *GetChannel(); + void SetChannel(ChanServ::Channel *); + + Anope::string GetName(); + void SetName(const Anope::string &); + + int GetLevel(); + void SetLevel(const int &); +}; diff --git a/modules/chanserv/main/leveltype.h b/modules/chanserv/main/leveltype.h new file mode 100644 index 000000000..12e4c132f --- /dev/null +++ b/modules/chanserv/main/leveltype.h @@ -0,0 +1,16 @@ +#include "level.h" + +class LevelType : public Serialize::Type +{ + public: + Serialize::ObjectField channel; + Serialize::Field name; + Serialize::Field level; + + LevelType(Module *creator) : Serialize::Type(creator, "Level") + , channel(this, "channel", true) + , name(this, "name") + , level(this, "level") + { + } +}; diff --git a/modules/chanserv/main/mode.cpp b/modules/chanserv/main/mode.cpp new file mode 100644 index 000000000..066e2cfd5 --- /dev/null +++ b/modules/chanserv/main/mode.cpp @@ -0,0 +1,33 @@ +#include "modules/chanserv.h" +#include "modetype.h" + +ChanServ::Channel *ModeImpl::GetChannel() +{ + return Get(&CSModeType::channel); +} + +void ModeImpl::SetChannel(ChanServ::Channel *c) +{ + Set(&CSModeType::channel, c); +} + +Anope::string ModeImpl::GetMode() +{ + return Get(&CSModeType::mode); +} + +void ModeImpl::SetMode(const Anope::string &m) +{ + Set(&CSModeType::mode, m); +} + +Anope::string ModeImpl::GetParam() +{ + return Get(&CSModeType::param); +} + +void ModeImpl::SetParam(const Anope::string &p) +{ + Set(&CSModeType::param, p); +} + diff --git a/modules/chanserv/main/mode.h b/modules/chanserv/main/mode.h new file mode 100644 index 000000000..cd94e5ad1 --- /dev/null +++ b/modules/chanserv/main/mode.h @@ -0,0 +1,17 @@ + +class ModeImpl : public ChanServ::Mode +{ + public: + ModeImpl(Serialize::TypeBase *type) : ChanServ::Mode(type) { } + ModeImpl(Serialize::TypeBase *type, Serialize::ID id) : ChanServ::Mode(type, id) { } + + ChanServ::Channel *GetChannel() override; + void SetChannel(ChanServ::Channel *) override; + + Anope::string GetMode() override; + void SetMode(const Anope::string &) override; + + Anope::string GetParam() override; + void SetParam(const Anope::string &) override; +}; + diff --git a/modules/chanserv/main/modetype.h b/modules/chanserv/main/modetype.h new file mode 100644 index 000000000..b30c0491f --- /dev/null +++ b/modules/chanserv/main/modetype.h @@ -0,0 +1,15 @@ +#include "mode.h" + +class CSModeType : public Serialize::Type +{ + public: + Serialize::ObjectField channel; + Serialize::Field mode, param; + + CSModeType(Module *creator) : Serialize::Type(creator, "CSKeepMode") + , channel(this, "channel", true) + , mode(this, "mode") + , param(this, "param") + { + } +}; diff --git a/modules/chanserv/mode.cpp b/modules/chanserv/mode.cpp new file mode 100644 index 000000000..121179a58 --- /dev/null +++ b/modules/chanserv/mode.cpp @@ -0,0 +1,999 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_mode.h" +#include "modules/cs_info.h" + +static EventHandlers *events; + +class ModeLockImpl : public ModeLock +{ + public: + ModeLockImpl(Serialize::TypeBase *type) : ModeLock(type) { } + ModeLockImpl(Serialize::TypeBase *type, Serialize::ID id) : ModeLock(type, id) { } + + ChanServ::Channel *GetChannel() override; + void SetChannel(ChanServ::Channel *ci) override; + + bool GetSet() override; + void SetSet(const bool &) override; + + Anope::string GetName() override; + void SetName(const Anope::string &name) override; + + Anope::string GetParam() override; + void SetParam(const Anope::string &p) override; + + Anope::string GetSetter() override; + void SetSetter(const Anope::string &s) override; + + time_t GetCreated() override; + void SetCreated(const time_t &) override; +}; + +class ModeLockType : public Serialize::Type +{ + public: + Serialize::ObjectField ci; + Serialize::Field set; + Serialize::Field name, param, setter; + Serialize::Field created; + + ModeLockType(Module *me) : Serialize::Type(me, "ModeLock") + , ci(this, "ci", true) + , set(this, "set") + , name(this, "name") + , param(this, "param") + , setter(this, "setter") + , created(this, "created") + { + } +}; + +ChanServ::Channel *ModeLockImpl::GetChannel() +{ + return Get(&ModeLockType::ci); +} + +void ModeLockImpl::SetChannel(ChanServ::Channel *ci) +{ + Set(&ModeLockType::ci, ci); +} + +bool ModeLockImpl::GetSet() +{ + return Get(&ModeLockType::set); +} + +void ModeLockImpl::SetSet(const bool &b) +{ + Set(&ModeLockType::set, b); +} + +Anope::string ModeLockImpl::GetName() +{ + return Get(&ModeLockType::name); +} + +void ModeLockImpl::SetName(const Anope::string &name) +{ + Set(&ModeLockType::name, name); +} + +Anope::string ModeLockImpl::GetParam() +{ + return Get(&ModeLockType::param); +} + +void ModeLockImpl::SetParam(const Anope::string &p) +{ + Set(&ModeLockType::name, p); +} + +Anope::string ModeLockImpl::GetSetter() +{ + return Get(&ModeLockType::setter); +} + +void ModeLockImpl::SetSetter(const Anope::string &s) +{ + Set(&ModeLockType::name, s); +} + +time_t ModeLockImpl::GetCreated() +{ + return Get(&ModeLockType::created); +} + +void ModeLockImpl::SetCreated(const time_t &c) +{ + Set(&ModeLockType::created, c); +} + +class ModeLocksImpl : public ModeLocks +{ + public: + using ModeLocks::ModeLocks; + + bool HasMLock(ChanServ::Channel *ci, ChannelMode *mode, const Anope::string ¶m, bool status) const override + { + if (!mode) + return false; + + for (ModeLock *ml : ci->GetRefs(modelock)) + if (ml->GetName() == mode->name && ml->GetSet() == status && ml->GetParam() == param) + return true; + + return false; + } + + bool SetMLock(ChanServ::Channel *ci, ChannelMode *mode, bool status, const Anope::string ¶m, Anope::string setter, time_t created = Anope::CurTime) override + { + if (!mode) + return false; + + RemoveMLock(ci, mode, status, param); + + if (setter.empty()) + setter = ci->GetFounder() ? ci->GetFounder()->GetDisplay() : "Unknown"; + + ModeLock *ml = modelock.Create(); + ml->SetChannel(ci); + ml->SetSet(status); + ml->SetName(name); + ml->SetParam(param); + ml->SetSetter(setter); + ml->SetCreated(created); + + EventReturn MOD_RESULT; + MOD_RESULT = (*events)(&Event::MLockEvents::OnMLock, ci, ml); + if (MOD_RESULT == EVENT_STOP) + { + delete ml; + return false; + } + + return true; + } + + bool RemoveMLock(ChanServ::Channel *ci, ChannelMode *mode, bool status, const Anope::string ¶m = "") override + { + if (!mode) + return false; + + for (ModeLock *m : ci->GetRefs(modelock)) + if (m->GetName() == mode->name) + { + // For list or status modes, we must check the parameter + if (mode->type == MODE_LIST || mode->type == MODE_STATUS) + if (m->GetParam() != param) + continue; + + EventReturn MOD_RESULT; + MOD_RESULT = (*events)(&Event::MLockEvents::OnUnMLock, ci, m); + if (MOD_RESULT == EVENT_STOP) + break; + + delete m; + return true; + } + + return false; + } + + void ClearMLock(ChanServ::Channel *ci) override + { + for (ModeLock *m : ci->GetRefs(modelock)) + delete m; + } + + ModeList GetMLock(ChanServ::Channel *ci) const override + { + return ci->GetRefs(modelock); + } + + std::list GetModeLockList(ChanServ::Channel *ci, const Anope::string &name) override + { + std::list mlist; + for (ModeLock *m : ci->GetRefs(modelock)) + if (m->GetName() == name) + mlist.push_back(m); + return mlist; + } + + ModeLock *GetMLock(ChanServ::Channel *ci, const Anope::string &mname, const Anope::string ¶m = "") override + { + for (ModeLock *m : ci->GetRefs(modelock)) + if (m->GetName() == mname && m->GetParam() == param) + return m; + + return NULL; + } + + Anope::string GetMLockAsString(ChanServ::Channel *ci, bool complete) const override + { + Anope::string pos = "+", neg = "-", params; + + for (ModeLock *ml : ci->GetRefs(modelock)) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(ml->GetName()); + + if (!cm || cm->type == MODE_LIST || cm->type == MODE_STATUS) + continue; + + if (ml->GetSet()) + pos += cm->mchar; + else + neg += cm->mchar; + + if (complete && ml->GetSet() && !ml->GetParam().empty() && cm->type == MODE_PARAM) + params += " " + ml->GetParam(); + } + + if (pos.length() == 1) + pos.clear(); + if (neg.length() == 1) + neg.clear(); + + return pos + neg + params; + } +}; + +class CommandCSMode : public Command +{ + bool CanSet(CommandSource &source, ChanServ::Channel *ci, ChannelMode *cm, bool self) + { + if (!ci || !cm || cm->type != MODE_STATUS) + return false; + + return source.AccessFor(ci).HasPriv(cm->name + (self ? "ME" : "")); + } + + void DoLock(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + User *u = source.GetUser(); + const Anope::string &subcommand = params[2]; + const Anope::string ¶m = params.size() > 3 ? params[3] : ""; + + bool override = !source.AccessFor(ci).HasPriv("MODE"); + + if (Anope::ReadOnly && !subcommand.equals_ci("LIST")) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + if ((subcommand.equals_ci("ADD") || subcommand.equals_ci("SET")) && !param.empty()) + { + /* If setting, remove the existing locks */ + if (subcommand.equals_ci("SET")) + for (ModeLock *ml : mlocks->GetMLock(ci)) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(ml->GetName()); + if (cm && cm->CanSet(source.GetUser())) + mlocks->RemoveMLock(ci, cm, ml->GetSet(), ml->GetParam()); + } + + spacesepstream sep(param); + Anope::string modes; + + sep.GetToken(modes); + + Anope::string pos = "+", neg = "-", pos_params, neg_params; + + int adding = 1; + bool needreply = true; + for (size_t i = 0; i < modes.length(); ++i) + { + switch (modes[i]) + { + case '+': + adding = 1; + break; + case '-': + adding = 0; + break; + default: + needreply = false; + ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); + if (!cm) + { + source.Reply(_("Unknown mode character \002{0}\002 ignored."), modes[i]); + break; + } + else if (u && !cm->CanSet(u)) + { + source.Reply(_("You may not (un)lock mode \002{0}\002."), modes[i]); + break; + } + + Anope::string mode_param; + if (((cm->type == MODE_STATUS || cm->type == MODE_LIST) && !sep.GetToken(mode_param)) || (cm->type == MODE_PARAM && adding && !sep.GetToken(mode_param))) + source.Reply(_("Missing parameter for mode \002{0}\002."), cm->mchar); + else if (cm->type == MODE_LIST && ci->c && IRCD->GetMaxListFor(ci->c) && ci->c->HasMode(cm->name) >= IRCD->GetMaxListFor(ci->c)) + source.Reply(_("List for mode \002{0}\002 is full."), cm->mchar); + else if (ci->GetRefs(modelock).size() >= Config->GetModule(this->owner)->Get("max", "32")) + source.Reply(_("The mode lock list of \002{0}\002 is full."), ci->GetName()); + else + { + mlocks->SetMLock(ci, cm, adding, mode_param, source.GetNick()); + + if (adding) + { + pos += cm->mchar; + if (!mode_param.empty()) + pos_params += " " + mode_param; + } + else + { + neg += cm->mchar; + if (!mode_param.empty()) + neg_params += " " + mode_param; + } + } + } + } + + if (pos == "+") + pos.clear(); + if (neg == "-") + neg.clear(); + Anope::string reply = pos + neg + pos_params + neg_params; + + if (!reply.empty()) + { + source.Reply(_("\002{0}\002 locked on \002{1}\002."), reply, ci->GetName()); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to lock " << reply; + } + else if (needreply) + source.Reply(_("Nothing to do.")); + + if (ci->c) + ci->c->CheckModes(); + } + else if (subcommand.equals_ci("DEL") && !param.empty()) + { + spacesepstream sep(param); + Anope::string modes; + + sep.GetToken(modes); + + int adding = 1; + bool needreply = true; + for (size_t i = 0; i < modes.length(); ++i) + { + switch (modes[i]) + { + case '+': + adding = 1; + break; + case '-': + adding = 0; + break; + default: + needreply = false; + ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); + if (!cm) + { + source.Reply(_("Unknown mode character \002{0}\002 ignored."), modes[i]); + break; + } + else if (u && !cm->CanSet(u)) + { + source.Reply(_("You may not (un)lock mode \002{0}\002."), modes[i]); + break; + } + + Anope::string mode_param; + if (cm->type != MODE_REGULAR && !sep.GetToken(mode_param)) + source.Reply(_("Missing parameter for mode \002{0}\002."), cm->mchar); + else + { + if (mlocks->RemoveMLock(ci, cm, adding, mode_param)) + { + if (!mode_param.empty()) + mode_param = " " + mode_param; + source.Reply(_("\002{0}{1}{2}\002 has been unlocked from \002{3}\002."), adding == 1 ? '+' : '-', cm->mchar, mode_param, ci->GetName()); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to unlock " << (adding ? '+' : '-') << cm->mchar << mode_param; + } + else + source.Reply(_("\002{0}{1}\002 is not locked on \002{2}\002."), adding == 1 ? '+' : '-', cm->mchar, ci->GetName()); + } + } + } + + if (needreply) + source.Reply(_("Nothing to do.")); + } + else if (subcommand.equals_ci("LIST")) + { + ModeLocks::ModeList locks = mlocks->GetMLock(ci); + if (locks.empty()) + { + source.Reply(_("Channel \002{0}\002 has no mode locks."), ci->GetName()); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Mode")).AddColumn(_("Param")).AddColumn(_("Creator")).AddColumn(_("Created")); + + for (ModeLock *ml : locks) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(ml->GetName()); + if (!cm) + continue; + + ListFormatter::ListEntry entry; + entry["Mode"] = Anope::printf("%c%c", ml->GetSet() ? '+' : '-', cm->mchar); + entry["Param"] = ml->GetParam(); + entry["Creator"] = ml->GetSetter(); + entry["Created"] = Anope::strftime(ml->GetCreated(), NULL, true); + list.AddEntry(entry); + } + + source.Reply(_("Mode locks for \002{0}\002:"), ci->GetName()); + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + else + this->OnSyntaxError(source, subcommand); + } + + void DoSet(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + User *u = source.GetUser(); + + bool has_access = source.AccessFor(ci).HasPriv("MODE") || source.HasPriv("chanserv/administration"); + bool can_override = source.HasPriv("chanserv/administration"); + + spacesepstream sep(params.size() > 3 ? params[3] : ""); + Anope::string modes = params[2], param; + + bool override = !source.AccessFor(ci).HasPriv("MODE") && source.HasPriv("chanserv/administration"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to set " << params[2] << (params.size() > 3 ? " " + params[3] : ""); + + int adding = -1; + for (size_t i = 0; i < modes.length(); ++i) + { + switch (modes[i]) + { + case '+': + adding = 1; + break; + case '-': + adding = 0; + break; + case '*': + if (adding == -1 || !has_access) + break; + for (unsigned j = 0; j < ModeManager::GetChannelModes().size() && ci->c; ++j) + { + ChannelMode *cm = ModeManager::GetChannelModes()[j]; + + if (!u || cm->CanSet(u) || can_override) + { + if (cm->type == MODE_REGULAR || (!adding && cm->type == MODE_PARAM)) + { + if (adding) + ci->c->SetMode(NULL, cm); + else + ci->c->RemoveMode(NULL, cm); + } + } + } + break; + default: + if (adding == -1) + break; + ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); + if (!cm || (u && !cm->CanSet(u) && !can_override)) + continue; + switch (cm->type) + { + case MODE_REGULAR: + if (!has_access) + break; + if (adding) + ci->c->SetMode(NULL, cm); + else + ci->c->RemoveMode(NULL, cm); + break; + case MODE_PARAM: + if (!has_access) + break; + if (adding && !sep.GetToken(param)) + break; + if (adding) + ci->c->SetMode(NULL, cm, param); + else + ci->c->RemoveMode(NULL, cm); + break; + case MODE_STATUS: + { + if (!sep.GetToken(param)) + param = source.GetNick(); + + ChanServ::AccessGroup u_access = source.AccessFor(ci); + + if (param.find_first_of("*?") != Anope::string::npos) + { + if (!this->CanSet(source, ci, cm, false) && !can_override) + { + source.Reply(_("You do not have access to set mode \002{0}\002."), cm->mchar); + break; + } + + for (Channel::ChanUserList::const_iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end;) + { + ChanUserContainer *uc = it->second; + ++it; + + ChanServ::AccessGroup targ_access = ci->AccessFor(uc->user); + + if (uc->user->IsProtected() || (ci->HasFieldS("PEACE") && targ_access >= u_access && !can_override)) + { + source.Reply(_("You do not have the access to change the modes of \002{0}\002."), uc->user->nick.c_str()); + continue; + } + + if (Anope::Match(uc->user->GetMask(), param)) + { + if (adding) + ci->c->SetMode(NULL, cm, uc->user->GetUID()); + else + ci->c->RemoveMode(NULL, cm, uc->user->GetUID()); + } + } + } + else + { + User *target = User::Find(param, true); + if (target == NULL) + { + source.Reply(_("User \002{0}\002 isn't currently online."), param); + break; + } + + if (!this->CanSet(source, ci, cm, source.GetUser() == target) && !can_override) + { + source.Reply(_("You do not have access to set mode \002{0}\002."), cm->mchar); + break; + } + + if (source.GetUser() != target) + { + ChanServ::AccessGroup targ_access = ci->AccessFor(target); + if (ci->HasFieldS("PEACE") && targ_access >= u_access && !can_override) + { + source.Reply(_("You do not have the access to change the modes of \002{0}\002"), target->nick); + break; + } + else if (target->IsProtected()) + { + source.Reply(_("Access denied. \002{0}\002 is protected and can not have their modes changed."), target->nick); + break; + } + } + + if (adding) + ci->c->SetMode(NULL, cm, target->GetUID()); + else + ci->c->RemoveMode(NULL, cm, target->GetUID()); + } + break; + } + case MODE_LIST: + if (!has_access) + break; + if (!sep.GetToken(param)) + break; + if (adding) + { + if (IRCD->GetMaxListFor(ci->c) && ci->c->HasMode(cm->name) < IRCD->GetMaxListFor(ci->c)) + ci->c->SetMode(NULL, cm, param); + } + else + { + std::vector v = ci->c->GetModeList(cm->name); + for (unsigned j = 0; j < v.size(); ++j) + if (Anope::Match(v[j], param)) + ci->c->RemoveMode(NULL, cm, v[j]); + } + } + } + } + } + + void DoClear(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + const Anope::string ¶m = params.size() > 2 ? params[2] : ""; + + if (param.empty()) + { + std::vector new_params; + new_params.push_back(params[0]); + new_params.push_back("SET"); + new_params.push_back("-*"); + this->DoSet(source, ci, new_params); + return; + } + + ChannelMode *cm; + if (param.length() == 1) + cm = ModeManager::FindChannelModeByChar(param[0]); + else + { + cm = ModeManager::FindChannelModeByName(param.upper()); + if (!cm) + cm = ModeManager::FindChannelModeByName(param.substr(0, param.length() - 1).upper()); + } + + if (!cm) + { + source.Reply(_("There is no such mode \002{0}\002."), param); + return; + } + + if (cm->type != MODE_STATUS && cm->type != MODE_LIST) + { + source.Reply(_("Mode \002{0}\002 is not a status or list mode."), param); + return; + } + + std::vector new_params; + new_params.push_back(params[0]); + new_params.push_back("SET"); + new_params.push_back("-" + stringify(cm->mchar)); + new_params.push_back("*"); + this->DoSet(source, ci, new_params); + } + + public: + CommandCSMode(Module *creator) : Command(creator, "chanserv/mode", 2, 4) + { + this->SetDesc(_("Control modes and mode locks on a channel")); + this->SetSyntax(_("\037channel\037 LOCK {ADD|DEL|SET|LIST} [\037what\037]")); + this->SetSyntax(_("\037channel\037 SET \037modes\037")); + this->SetSyntax(_("\037channel\037 CLEAR [\037what\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &subcommand = params[1]; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (subcommand.equals_ci("LOCK") && params.size() > 2) + { + if (!source.AccessFor(ci).HasPriv("MODE") && !source.HasPriv("chanserv/administration")) + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MODE", ci->GetName()); + else + this->DoLock(source, ci, params); + } + else if (!ci->c) + source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); + else if (subcommand.equals_ci("SET") && params.size() > 2) + this->DoSet(source, ci, params); + else if (subcommand.equals_ci("CLEAR")) + { + if (!source.AccessFor(ci).HasPriv("MODE") && !source.HasPriv("chanserv/administration")) + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MODE", ci->GetName()); + else + this->DoClear(source, ci, params); + } + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Controls mode locks and allows changing modes on a channel." + "\n" + "The \002{0} LOCK\002 command allows you to add, delete, and view mode locks on a channel." + " If a mode is locked on or off, services will not allow that mode to be changed." + " The \002SET\002 command will clear all existing mode locks and set the new one given, while \002ADD\002 and \002DEL\002 modify the existing mode lock.\n" + "\n" + "Example:\n" + " {0} #channel LOCK ADD +bmnt *!*@*aol*\n" + "\n" + "The \002{0} SET\002 command allows you to set modes through services. Wildcards * and ? may be given as parameters for list and status modes.\n" + "Example:\n" + " {0} #channel SET +v *\n" + " Sets voice status to all users in the channel.\n" + "\n" + " {0} #channel SET -b ~c:*\n" + " Clears all extended bans that start with ~c:\n" + "\n" + "The \002{0} CLEAR\002 command is an easy way to clear modes on a channel. \037what\037 may be any mode name. Examples include bans, excepts, inviteoverrides, ops, halfops, and voices." + " If \037what\037 is not given then all basic modes are removed."), + source.command.upper()); + return true; + } +}; + +static Anope::map > modes; + +class CommandCSModes : public Command +{ + public: + CommandCSModes(Module *creator) : Command(creator, "chanserv/modes", 1, 2) + { + this->SetSyntax(_("\037channel\037 [\037user\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + User *u = source.GetUser(), + *targ = params.size() > 1 ? User::Find(params[1], true) : u; + + if (!targ) + { + source.Reply(_("User \002{0}\002 isn't currently online."), params.size() > 1 ? params[1] : source.GetNick()); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!ci->c) + { + source.Reply(_("Channel \002%s\002 doesn't exist."), ci->GetName()); + return; + } + + ChanServ::AccessGroup u_access = source.AccessFor(ci), targ_access = ci->AccessFor(targ); + const std::pair &m = modes[source.command]; + + bool can_override = source.HasPriv("chanserv/administration"); + bool override = false; + + if (m.second.empty()) + return; // Configuration issue + + const Anope::string &want = u == targ ? m.second + "ME" : m.second; + if (!u_access.HasPriv(want)) + { + if (!can_override) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), want, ci->GetName()); + return; + } + else + override = true; + } + + if (!override && !m.first && u != targ && (targ->IsProtected() || (ci->HasFieldS("PEACE") && targ_access >= u_access))) + { + if (!can_override) + { + source.Reply(_("Access denied. \002{0}\002 has the same or more privileges than you on \002{1}\002."), targ->nick, ci->GetName()); + return; + } + else + override = true; + } + + if (!ci->c->FindUser(targ)) + { + source.Reply(_("User \002{0}\002 is not on channel \002{1}\002."), targ->nick, ci->GetName()); + return; + } + + if (m.first) + ci->c->SetMode(NULL, m.second, targ->GetUID()); + else + ci->c->RemoveMode(NULL, m.second, targ->GetUID()); + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "on " << targ->nick; + } + + const Anope::string GetDesc(CommandSource &source) const override + { + const std::pair &m = modes[source.command]; + if (!m.second.empty()) + { + if (m.first) + return Anope::printf(Language::Translate(source.GetAccount(), _("Gives you or the specified nick %s status on a channel")), m.second.c_str()); + else + return Anope::printf(Language::Translate(source.GetAccount(), _("Removes %s status from you or the specified nick on a channel")), m.second.c_str()); + } + else + return ""; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + const std::pair &m = modes[source.command]; + if (m.second.empty()) + return false; + + if (m.first) + source.Reply(_("Gives {0} status to the selected \037user\037 on \037channel\037. If \037user\037 is not given, it will give the status to you."), + m.second.upper()); + else + source.Reply(_("Removes {0} status from the selected \037user\037 on \037channel\037. If \037user\037 is not given, it will remove the status from you."), + m.second.upper()); + source.Reply(" "); + source.Reply(_("Use of this command requires the \002{1}(ME)\002 privilege on \037channel\037."), m.second.upper()); + + return true; + } +}; + +class CSMode : public Module + , public EventHook + , public EventHook + , public EventHook +{ + CommandCSMode commandcsmode; + CommandCSModes commandcsmodes; + ModeLocksImpl modelock; + ModeLockType modelock_type; + + EventHandlers modelockevents; + + public: + CSMode(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsmode(this) + , commandcsmodes(this) + , modelock(this) + , modelock_type(this) + , modelockevents(this) + { + events = &modelockevents; + } + + void OnReload(Configuration::Conf *conf) override + { + modes.clear(); + + for (int i = 0; i < conf->CountBlock("command"); ++i) + { + Configuration::Block *block = conf->GetBlock("command", i); + + const Anope::string &cname = block->Get("name"), + &cmd = block->Get("command"); + + if (cname.empty() || cmd != "chanserv/modes") + continue; + + const Anope::string &set = block->Get("set"), + &unset = block->Get("unset"); + + if (set.empty() && unset.empty()) + continue; + + modes[cname] = std::make_pair(!set.empty(), !set.empty() ? set : unset); + } + } + + void OnCheckModes(Reference &c) override + { + if (!c || !c->ci) + return; + + ModeLocks::ModeList locks = mlocks->GetMLock(c->ci); + for (ModeLock *ml : locks) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(ml->GetName()); + if (!cm) + continue; + + if (cm->type == MODE_REGULAR) + { + if (!c->HasMode(cm->name) && ml->GetSet()) + c->SetMode(NULL, cm, "", false); + else if (c->HasMode(cm->name) && !ml->GetSet()) + c->RemoveMode(NULL, cm, "", false); + } + else if (cm->type == MODE_PARAM) + { + /* If the channel doesn't have the mode, or it does and it isn't set correctly */ + if (ml->GetSet()) + { + Anope::string param; + c->GetParam(cm->name, param); + + if (!c->HasMode(cm->name) || (!param.empty() && !ml->GetParam().empty() && !param.equals_cs(ml->GetParam()))) + c->SetMode(NULL, cm, ml->GetParam(), false); + } + else + { + if (c->HasMode(cm->name)) + c->RemoveMode(NULL, cm, "", false); + } + } + else if (cm->type == MODE_LIST || cm->type == MODE_STATUS) + { + if (ml->GetSet()) + c->SetMode(NULL, cm, ml->GetParam(), false); + else + c->RemoveMode(NULL, cm, ml->GetParam(), false); + } + } + } + + void OnChanRegistered(ChanServ::Channel *ci) override + { + Anope::string mlock; + spacesepstream sep(Config->GetModule(this)->Get("mlock", "+nt")); + if (sep.GetToken(mlock)) + { + bool add = true; + for (unsigned i = 0; i < mlock.length(); ++i) + { + if (mlock[i] == '+') + { + add = true; + continue; + } + + if (mlock[i] == '-') + { + add = false; + continue; + } + + ChannelMode *cm = ModeManager::FindChannelModeByChar(mlock[i]); + if (!cm) + continue; + + Anope::string param; + if (cm->type == MODE_PARAM) + { + ChannelModeParam *cmp = anope_dynamic_static_cast(cm); + if (add || !cmp->minus_no_arg) + { + sep.GetToken(param); + if (param.empty() || !cmp->IsValid(param)) + continue; + } + } + else if (cm->type != MODE_REGULAR) + { + sep.GetToken(param); + if (param.empty()) + continue; + } + + mlocks->SetMLock(ci, cm, add, param); + } + } + } + + void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_hidden) override + { + if (!show_hidden) + return; + + const Anope::string &m = mlocks->GetMLockAsString(ci, true); + if (!m.empty()) + info[_("Mode lock")] = m; + } +}; + +MODULE_INIT(CSMode) diff --git a/modules/chanserv/register.cpp b/modules/chanserv/register.cpp new file mode 100644 index 000000000..eb2832074 --- /dev/null +++ b/modules/chanserv/register.cpp @@ -0,0 +1,157 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandCSRegister : public Command +{ + public: + CommandCSRegister(Module *creator) : Command(creator, "chanserv/register", 1, 2) + { + this->SetDesc(_("Register a channel")); + this->SetSyntax(_("\037channel\037 [\037description\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &chdesc = params.size() > 1 ? params[1] : ""; + + User *u = source.GetUser(); + NickServ::Account *nc = source.nc; + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, channel registration is temporarily disabled.")); + return; + } + + if (nc->HasFieldS("UNCONFIRMED")) + { + source.Reply(_("You must confirm your account before you can register a channel.")); + return; + } + + if (chan[0] == '&') + { + source.Reply(_("Local channels can not be registered.")); + return; + } + + if (chan[0] != '#') + { + source.Reply(_("Please use the symbol of \002#\002 when attempting to register.")); + return; + } + + if (!IRCD->IsChannelValid(chan)) + { + source.Reply(_("Channel \002{0}\002 is not a valid channel."), chan); + return; + } + + Channel *c = Channel::Find(params[0]); + if (!c && u) + { + source.Reply(_("Channel \002{0}\002 doesn't exist."), chan); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci) + { + source.Reply(_("Channel \002{0}\002 is already registered!"), chan); + return; + } + + if (c && u && !c->HasUserStatus(u, "OP")) + { + source.Reply(_("You must be a channel operator to register the channel.")); + return; + } + + unsigned maxregistered = Config->GetModule("chanserv")->Get("maxregistered"); + if (maxregistered && nc->GetChannelCount() >= maxregistered && !source.HasPriv("chanserv/no-register-limit")) + { + if (nc->GetChannelCount() > maxregistered) + source.Reply(_("Sorry, you have already exceeded your limit of \002{0}\002 channels."), maxregistered); + else + source.Reply(_("Sorry, you have already reached your limit of \002{0}\002 channels."), maxregistered); + return; + } + + if (!ChanServ::service) + return; + ci = ChanServ::channel.Create(); + ci->SetName(chan); + ci->SetFounder(nc); + ci->SetDesc(chdesc); + + if (c && !c->topic.empty()) + { + ci->SetLastTopic(c->topic); + ci->SetLastTopicSetter(c->topic_setter); + ci->SetLastTopicTime(c->topic_time); + } + else + ci->SetLastTopicSetter(source.service->nick); + + Log(LOG_COMMAND, source, this, ci); + source.Reply(_("Channel \002{0}\002 registered under your account: \002{1}\002"), chan, nc->GetDisplay()); + + /* Implement new mode lock */ + if (c) + { + c->CheckModes(); + if (u) + c->SetCorrectModes(u, true); + + Event::OnChanRegistered(&Event::ChanRegistered::OnChanRegistered, ci); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Registers a channel, which sets you as the founder and prevents other users from gaining unauthorized access in the channel." + " To use this command, you must first be a channel operator on \037channel\037." + " The description, which is optional, is a general description of the channel's purpose.\n" + "\n" + "When you register a channel, you are recorded as the founder of the channel." + " The channel founder is allowed to change all of the channel settings for the channel," + " and will automatically be given channel operator status when entering the channel.")); + + ServiceBot *bi; + Anope::string cmd; + CommandInfo *help = source.service->FindCommand("generic/help"); + if (Command::FindCommandFromService("chanserv/access", bi, cmd) && help) + source.Reply(_("\n" + "See the \002{0}\002 command (\002{1}{2} {3} {0}\002) for information on giving a subset of these privileges to other users."), + cmd, Config->StrictPrivmsg, bi->nick, help->cname); + + return true; + } +}; + + +class CSRegister : public Module +{ + CommandCSRegister commandcsregister; + + public: + CSRegister(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsregister(this) + { + } +}; + +MODULE_INIT(CSRegister) diff --git a/modules/chanserv/seen.cpp b/modules/chanserv/seen.cpp new file mode 100644 index 000000000..b97ffe238 --- /dev/null +++ b/modules/chanserv/seen.cpp @@ -0,0 +1,477 @@ +/* cs_seen: provides a seen command by tracking all users + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + + +#if 0 +#include "module.h" + +enum TypeInfo +{ + NEW, NICK_TO, NICK_FROM, JOIN, PART, QUIT, KICK +}; + +static bool simple; +struct SeenInfo; +static SeenInfo *FindInfo(const Anope::string &nick); +typedef Anope::hash_map database_map; +database_map database; + +struct SeenInfo : Serialize::Object +{ + Anope::string nick; + Anope::string vhost; + TypeInfo type; + Anope::string nick2; // for nickchanges and kicks + Anope::string channel; // for join/part/kick + Anope::string message; // for part/kick/quit + time_t last; // the time when the user was last seen + + SeenInfo() : Serialize::Object("SeenInfo") + { + } + + ~SeenInfo() + { + database_map::iterator iter = database.find(nick); + if (iter != database.end() && iter->second == this) + database.erase(iter); + } + +#if 0 + void Serialize(Serialize::Data &data) const override + { + data["nick"] << nick; + data["vhost"] << vhost; + data["type"] << type; + data["nick2"] << nick2; + data["channel"] << channel; + data["message"] << message; + data.SetType("last", Serialize::Data::DT_INT); data["last"] << last; + } + + static Serializable* Unserialize(Serializable *obj, Serialize::Data &data) + { + Anope::string snick; + + data["nick"] >> snick; + + SeenInfo *s; + if (obj) + s = anope_dynamic_static_cast(obj); + else + { + SeenInfo* &info = database[snick]; + if (!info) + info = new SeenInfo(); + s = info; + } + + s->nick = snick; + data["vhost"] >> s->vhost; + unsigned int n; + data["type"] >> n; + s->type = static_cast(n); + data["nick2"] >> s->nick2; + data["channel"] >> s->channel; + data["message"] >> s->message; + data["last"] >> s->last; + + if (!obj) + database[s->nick] = s; + return s; + } +#endif +}; + +static SeenInfo *FindInfo(const Anope::string &nick) +{ + database_map::iterator iter = database.find(nick); + if (iter != database.end()) + return iter->second; + return NULL; +} + +static bool ShouldHide(const Anope::string &channel, User *u) +{ + Channel *targetchan = Channel::Find(channel); + const ChanServ::Channel *targetchan_ci = targetchan ? *targetchan->ci : ChanServ::Find(channel); + + if (targetchan && targetchan->HasMode("SECRET")) + return true; + else if (targetchan_ci && targetchan_ci->HasExt("CS_PRIVATE")) + return true; + else if (u && u->HasMode("PRIV")) + return true; + return false; +} + +class CommandOSSeen : public Command +{ + public: + CommandOSSeen(Module *creator) : Command(creator, "operserv/seen", 1, 2) + { + this->SetDesc(_("Statistics and maintenance for seen data")); + this->SetSyntax("STATS"); + this->SetSyntax(_("CLEAR \037time\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (params[0].equals_ci("STATS")) + { + size_t mem_counter; + mem_counter = sizeof(database_map); + for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end; ++it) + { + mem_counter += (5 * sizeof(Anope::string)) + sizeof(TypeInfo) + sizeof(time_t); + mem_counter += it->first.capacity(); + mem_counter += it->second->vhost.capacity(); + mem_counter += it->second->nick2.capacity(); + mem_counter += it->second->channel.capacity(); + mem_counter += it->second->message.capacity(); + } + source.Reply(_("%lu nicks are stored in the database, using %.2Lf kB of memory."), database.size(), static_cast(mem_counter) / 1024); + } + else if (params[0].equals_ci("CLEAR")) + { + time_t time = 0; + if ((params.size() < 2) || (0 >= (time = Anope::DoTime(params[1])))) + { + this->OnSyntaxError(source, params[0]); + return; + } + time = Anope::CurTime - time; + database_map::iterator buf; + size_t counter = 0; + for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end;) + { + buf = it; + ++it; + if (time < buf->second->last) + { + Log(LOG_DEBUG) << buf->first << " was last seen " << Anope::strftime(buf->second->last) << ", deleting entry"; + delete buf->second; + counter++; + } + } + Log(LOG_ADMIN, source, this) << "CLEAR and removed " << counter << " nicks that were added after " << Anope::strftime(time, NULL, true); + source.Reply(_("Database cleared, removed %lu nicks that were added after %s."), counter, Anope::strftime(time, source.nc, true).c_str()); + } + else + this->SendSyntax(source); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { +<<<<<<< HEAD + source.Reply(_("The \002STATS\002 command prints out statistics about stored nicks and memory usage.\n" + "The \002CLEAR\002 command lets you clean the database by removing all entries from the entries from the database that were added within \037time\037.\n" + "\n" + "Example:\n" + " {0} CLEAR 30m\n" + " Will remove all entries that were added within the last 30 minutes."), + source.command); +======= + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("The \002STATS\002 command prints out statistics about stored nicks and memory usage.")); + source.Reply(_("The \002CLEAR\002 command lets you clean the database by removing all entries from the\n" + "database that were added within \037time\037.\n" + " \n" + "Example:\n" + " %s CLEAR 30m\n" + " Will remove all entries that were added within the last 30 minutes."), source.command.c_str()); +>>>>>>> 2.0 + return true; + } +}; + +class CommandSeen : public Command +{ + void SimpleSeen(CommandSource &source, const std::vector ¶ms) + { + if (!source.c || !source.c->ci) + { + if (source.IsOper()) + source.Reply("Seen in simple mode is designed as a fantasy command only!"); + return; + } + + ServiceBot *bi = ServiceBot::Find(params[0], true); + if (bi) + { + if (bi == source.c->ci->GetBot()) + source.Reply(_("You found me, %s!"), source.GetNick().c_str()); + else + source.Reply(_("%s is a network service."), bi->nick.c_str()); + return; + } + + NickServ::Nick *na = NickServ::FindNick(params[0]); + if (!na) + { + source.Reply(_("I don't know who %s is."), params[0].c_str()); + return; + } + + if (source.GetAccount() == na->GetAccount()) + { + source.Reply(_("Looking for yourself, eh %s?"), source.GetNick().c_str()); + return; + } + + User *target = User::Find(params[0], true); + + if (target && source.c->FindUser(target)) + { + source.Reply(_("%s is on the channel right now!"), target->nick.c_str()); + return; + } + + for (Channel::ChanUserList::const_iterator it = source.c->users.begin(), it_end = source.c->users.end(); it != it_end; ++it) + { + ChanUserContainer *uc = it->second; + User *u = uc->user; + + if (u->Account() == na->GetAccount()) + { + source.Reply(_("%s is on the channel right now (as %s)!"), params[0].c_str(), u->nick.c_str()); + return; + } + } + + ChanServ::AccessGroup ag = source.c->ci->AccessFor(na->GetAccount()); + time_t last = 0; + for (unsigned i = 0; i < ag.size(); ++i) + { + ChanServ::ChanAccess *a = ag[i]; + + if (a->GetAccount() == na->GetAccount() && a->GetLastSeen() > last) + last = a->GetLastSeen(); + } + + if (last > Anope::CurTime || !last) + source.Reply(_("I've never seen %s on this channel."), na->GetNick().c_str()); + else + source.Reply(_("%s was last seen here %s ago."), na->GetNick().c_str(), Anope::Duration(Anope::CurTime - last, source.GetAccount()).c_str()); + } + + public: + CommandSeen(Module *creator) : Command(creator, "chanserv/seen", 1, 2) + { + this->SetDesc(_("Tells you about the last time a user was seen")); + this->SetSyntax(_("\037nick\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &target = params[0]; + + if (simple) + return this->SimpleSeen(source, params); + + if (target.length() > Config->GetBlock("networkinfo")->Get("nicklen")) + { + source.Reply(_("Nick too long, max length is \002{0}\002 characters."), Config->GetBlock("networkinfo")->Get("nicklen")); + return; + } + + if (ServiceBot::Find(target, true) != NULL) + { + source.Reply(_("\002{0}\002 is a service bot."), target); + return; + } + + if (target.equals_ci(source.GetNick())) + { + source.Reply(_("You might see yourself in the mirror, \002{0}\002."), source.GetNick()); + return; + } + + SeenInfo *info = FindInfo(target); + if (!info) + { + source.Reply(_("Sorry, I have not seen \002{0}\002."), target); + return; + } + + User *u2 = User::Find(target, true); + Anope::string onlinestatus; + if (u2) + onlinestatus = "."; + else + onlinestatus = Anope::printf(_(" but %s mysteriously dematerialized."), target.c_str()); + + Anope::string timebuf = Anope::Duration(Anope::CurTime - info->last, source.nc); + Anope::string timebuf2 = Anope::strftime(info->last, source.nc, true); + + if (info->type == NEW) + { + source.Reply(_("\002{0}\002 (\002{1}\002) was last seen connecting \002{2}\002 ago (\002{3}\002){4}"), + target, info->vhost, timebuf, timebuf2, onlinestatus); + } + else if (info->type == NICK_TO) + { + u2 = User::Find(info->nick2, true); + if (u2) + onlinestatus = Anope::printf( _(". %s is still online."), u2->nick.c_str()); + else + onlinestatus = Anope::printf(_(", but %s mysteriously dematerialized."), info->nick2.c_str()); + + source.Reply(_("\002{0}\002 (\002{1}\002) was last seen changing nick to \002{2}\002 \002{3}\002 ago{4}"), + target, info->vhost, info->nick2, timebuf, onlinestatus); + } + else if (info->type == NICK_FROM) + { + source.Reply(_("\002{0}\002 (\002{1}\002) was last seen changing nick from \002{2}\002 to \002{3} {4}\002 ago{5}"), + target, info->vhost, info->nick2, target, timebuf, onlinestatus); + } + else if (info->type == JOIN) + { + if (ShouldHide(info->channel, u2)) + source.Reply(_("\002{0}\002 (\002{1}\002) was last seen joining a secret channel \002{2}\002 ago{3}"), + target, info->vhost, timebuf, onlinestatus); + else + source.Reply(_("\002{0}\002 (\002{1}\002) was last seen joining \002{2} {3}\002 ago{4}"), + target, info->vhost, info->channel, timebuf, onlinestatus); + } + else if (info->type == PART) + { + if (ShouldHide(info->channel, u2)) + source.Reply(_("\002{0}\002 (\002{1}\002) was last seen parting a secret channel \002{2}\002 ago{3}"), + target, info->vhost, timebuf, onlinestatus); + else + source.Reply(_("\002{0}\002 (\002{1}\002) was last seen parting \002{2} {3}\002 ago{4}"), + target, info->vhost, info->channel, timebuf, onlinestatus); + } + else if (info->type == QUIT) + { + source.Reply(_("\002{0}\002 (\002{1}\002) was last seen quitting (\002{2}\002) \002{3}\002 ago (\002{4}\\2)."), + target, info->vhost, info->message, timebuf, timebuf2); + } + else if (info->type == KICK) + { + if (ShouldHide(info->channel, u2)) + source.Reply(_("\002{0}\002 (\002{1}\002) was kicked from a secret channel \002{2}\002 ago{3}"), + target, info->vhost, timebuf, onlinestatus); + else + source.Reply(_("\002{0}\002 (\002{1}\002) was kicked from \002{2}\002 (\"{3}\") {4} ago{5}"), + target, info->vhost, info->channel, info->message, timebuf, onlinestatus); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Checks for the last time \037nick\037 was seen joining, leaving, or changing nick on the network and tells you when and, depending on channel or user settings, where it was.")); + return true; + } +}; + +class CSSeen : public Module + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + //Serialize::TypeBase seeninfo_type; + CommandSeen commandseen; + CommandOSSeen commandosseen; + + public: + CSSeen(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + // , seeninfo_type("SeenInfo", SeenInfo::Unserialize) + , commandseen(this) + , commandosseen(this) + { + } + + void OnReload(Configuration::Conf *conf) override + { + simple = conf->GetModule(this)->Get("simple"); + } + + void OnExpireTick() override + { + size_t previous_size = database.size(); + time_t purgetime = Config->GetModule(this)->Get("purgetime"); + if (!purgetime) + purgetime = Anope::DoTime("30d"); + for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end;) + { + database_map::iterator cur = it; + ++it; + + if ((Anope::CurTime - cur->second->last) > purgetime) + { + Log(LOG_DEBUG) << cur->first << " was last seen " << Anope::strftime(cur->second->last) << ", purging entries"; + delete cur->second; + } + } + Log(LOG_DEBUG) << "cs_seen: Purged database, checked " << previous_size << " nicks and removed " << (previous_size - database.size()) << " old entries."; + } + + void OnUserConnect(User *u, bool &exempt) override + { + if (!u->Quitting()) + UpdateUser(u, NEW, u->nick, "", "", ""); + } + + void OnUserNickChange(User *u, const Anope::string &oldnick) override + { + UpdateUser(u, NICK_TO, oldnick, u->nick, "", ""); + UpdateUser(u, NICK_FROM, u->nick, oldnick, "", ""); + } + + void OnUserQuit(User *u, const Anope::string &msg) override + { + UpdateUser(u, QUIT, u->nick, "", "", msg); + } + + void OnJoinChannel(User *u, Channel *c) override + { + UpdateUser(u, JOIN, u->nick, "", c->name, ""); + } + + void OnPartChannel(User *u, Channel *c, const Anope::string &channel, const Anope::string &msg) override + { + UpdateUser(u, PART, u->nick, "", channel, msg); + } + + EventReturn OnPreUserKicked(const MessageSource &source, ChanUserContainer *cu, const Anope::string &msg) override + { + UpdateUser(cu->user, KICK, cu->user->nick, source.GetSource(), cu->chan->name, msg); + return EVENT_CONTINUE; + } + + private: + void UpdateUser(const User *u, const TypeInfo Type, const Anope::string &nick, const Anope::string &nick2, const Anope::string &channel, const Anope::string &message) + { + if (simple || !u->server->IsSynced()) + return; + + SeenInfo* &info = database[nick]; + if (!info) + info = new SeenInfo(); + info->nick = nick; + info->vhost = u->GetVIdent() + "@" + u->GetDisplayedHost(); + info->type = Type; + info->last = Anope::CurTime; + info->nick2 = nick2; + info->channel = channel; + info->message = message; + } +}; + +MODULE_INIT(CSSeen) +#endif diff --git a/modules/chanserv/set.cpp b/modules/chanserv/set.cpp new file mode 100644 index 000000000..06c996f52 --- /dev/null +++ b/modules/chanserv/set.cpp @@ -0,0 +1,1265 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_mode.h" +#include "modules/chanserv.h" +#include "modules/cs_info.h" +#include "modules/cs_set.h" + +class CommandCSSet : public Command +{ + public: + CommandCSSet(Module *creator) : Command(creator, "chanserv/set", 2, 3) + { + this->SetDesc(_("Set channel options and information")); + this->SetSyntax(_("\037option\037 \037channel\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows the channel founder to set various channel options and other information.\n" + "\n" + "Available options:")); + Anope::string this_name = source.command; + bool hide_privileged_commands = Config->GetBlock("options")->Get("hideprivilegedcommands"), + hide_registered_commands = Config->GetBlock("options")->Get("hideregisteredcommands"); + for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + const CommandInfo &info = it->second; + if (c_name.find_ci(this_name + " ") == 0) + { + ServiceReference c("Command", info.name); + + // XXX dup + if (!c) + continue; + else if (hide_registered_commands && !c->AllowUnregistered() && !source.GetAccount()) + continue; + else if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission)) + continue; + + source.command = it->first; + c->OnServHelp(source); + } + } + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("Type \002{0}{1} {2} {3} \037option\037\002 for more information on a particular option."), + Config->StrictPrivmsg, source.service->nick, help->cname, this_name); + + return true; + } +}; + +class CommandCSSetAutoOp : public Command +{ + public: + CommandCSSetAutoOp(Module *creator, const Anope::string &cname = "chanserv/set/autoop") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Should services automatically give status to users")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, params[1]); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (params[1].equals_ci("ON")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable autoop"; + ci->UnsetS("NOAUTOOP"); + source.Reply(_("Services will now automatically give modes to users in \002{0}\002."), ci->GetName()); + } + else if (params[1].equals_ci("OFF")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable autoop"; + ci->SetS("NOAUTOOP", true); + source.Reply(_("Services will no longer automatically give modes to users in \002{0}\002."), ci->GetName()); + } + else + this->OnSyntaxError(source, "AUTOOP"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables AUTOOP for a \037channel\037. When disabled, users who join \037channel\037 will not automatically gain any status from {0}."), source.service->nick); + return true; + } +}; + +class CommandCSSetBanType : public Command +{ + public: + CommandCSSetBanType(Module *creator, const Anope::string &cname = "chanserv/set/bantype") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Set how Services make bans on the channel")); + this->SetSyntax(_("\037channel\037 \037bantype\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, params[1]); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + try + { + int16_t new_type = convertTo(params[1]); + if (new_type < 0 || new_type > 3) + throw ConvertException("Invalid range"); + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change the ban type to " << new_type; + ci->SetBanType(new_type); + source.Reply(_("Ban type for channel \002{0}\002 is now \002#{1}\002."), ci->GetName(), new_type); + } + catch (const ConvertException &) + { + source.Reply(_("\002{0}\002 is not a valid ban type."), params[1]); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Sets the ban type that will be used by services whenever they need to ban someone from your channel.\n" + "\n" + "Bantype is a number between 0 and 3 that means:\n" + "\n" + "0: ban in the form *!user@host\n" + "1: ban in the form *!*user@host\n" + "2: ban in the form *!*@host\n" + "3: ban in the form *!*user@*.domain")); + return true; + } +}; + +class CommandCSSetDescription : public Command +{ + public: + CommandCSSetDescription(Module *creator, const Anope::string &cname = "chanserv/set/description") : Command(creator, cname, 1, 2) + { + this->SetDesc(_("Set the channel description")); + this->SetSyntax(_("\037channel\037 [\037description\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params.size() > 1 ? params[1] : ""; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + ci->SetDesc(param); + if (!param.empty()) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change the description to " << ci->GetDesc(); + source.Reply(_("Description of \002{0}\002 changed to \002{1}\002."), ci->GetName(), ci->GetDesc()); + } + else + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to unset the description"; + source.Reply(_("Description of \002{0}\002 unset."), ci->GetName()); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Sets the description for the channel, which shows up with the \002LIST\002 and \002INFO\002 commands.")); + return true; + } +}; + +class CommandCSSetFounder : public Command +{ + public: + CommandCSSetFounder(Module *creator, const Anope::string &cname = "chanserv/set/founder") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Set the founder of a channel")); + this->SetSyntax(_("\037channel\037 \037user\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && (ci->HasFieldS("SECUREFOUNDER") ? !source.IsFounder(ci) : !source.AccessFor(ci).HasPriv("FOUNDER")) && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); + return; + } + + NickServ::Nick *na = NickServ::FindNick(param); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), param); + return; + } + + NickServ::Account *nc = na->GetAccount(); + unsigned max_reg = Config->GetModule("chanserv")->Get("maxregistered"); + if (max_reg && nc->GetChannelCount() >= max_reg && !source.HasPriv("chanserv/no-register-limit")) + { + source.Reply(_("\002{0}\002 has too many channels registered."), na->GetNick()); + return; + } + + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change the founder from " << (ci->GetFounder() ? ci->GetFounder()->GetDisplay() : "(none)") << " to " << nc->GetDisplay(); + + ci->SetFounder(nc); + + source.Reply(_("Founder of \002{0}\002 changed to \002{1}\002."), ci->GetName(), na->GetNick()); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the founder of \037channel\037 to \037user\037. Using this command will cause you to lose your founder access to \037channel\037, and cannot be undone." + "\n" + "Use of this command requires being the founder or \037channel\037 or having the \002{0}\002 privilege, if secure founder is enabled or not, respectively.")); + return true; + } +}; + +class CommandCSSetKeepModes : public Command +{ + public: + CommandCSSetKeepModes(Module *creator, const Anope::string &cname = "chanserv/set/keepmodes") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Retain modes when channel is not in use")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (param.equals_ci("ON")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable keep modes"; + ci->SetS("CS_KEEP_MODES", true); + source.Reply(_("Keep modes for \002{0}\002 is now \002on\002."), ci->GetName()); + if (ci->c) + for (const std::pair &p : ci->c->GetModes()) + { + ChanServ::Mode *mode = ChanServ::mode.Create(); + mode->SetChannel(ci); + mode->SetMode(p.first); + mode->SetParam(p.second); + } + } + else if (param.equals_ci("OFF")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable keep modes"; + ci->UnsetS("CS_KEEP_MODES"); + source.Reply(_("Keep modes for \002{0}\002 is now \002off\002."), ci->GetName()); + for (ChanServ::Mode *m : ci->GetRefs(ChanServ::mode)) + m->Delete(); + } + else + this->OnSyntaxError(source, "KEEPMODES"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables keepmodes for \037channel\037. If keepmodes is enabled, services will remember modes set on the channel and re-set them the next time the channel is created.")); + return true; + } +}; + +class CommandCSSetPeace : public Command +{ + public: + CommandCSSetPeace(Module *creator, const Anope::string &cname = "chanserv/set/peace") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Regulate the use of critical commands")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (param.equals_ci("ON")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable peace"; + ci->SetS("PEACE", true); + source.Reply(_("Peace option for \002{0}\002 is now \002on\002."), ci->GetName()); + } + else if (param.equals_ci("OFF")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable peace"; + ci->UnsetS("PEACE"); + source.Reply(_("Peace option for \002{0}\002 is now \002off\002."), ci->GetName()); + } + else + this->OnSyntaxError(source, "PEACE"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables the \002peace\002 option for \037channel\037." + " When \002peace\002 is set, a user won't be able to kick, ban or remove a channel status of a user that has a level superior or equal to his via services."), + source.service->nick); + return true; + } +}; + +inline static Anope::string BotModes() +{ + return Config->GetModule("botserv")->Get("botmodes", + Config->GetModule("chanserv")->Get("botmodes", "o") + ); +} + +class CommandCSSetPersist : public Command +{ + public: + CommandCSSetPersist(Module *creator, const Anope::string &cname = "chanserv/set/persist") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Set the channel as permanent")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + ChannelMode *cm = ModeManager::FindChannelModeByName("PERM"); + + if (params[1].equals_ci("ON")) + { + if (!ci->HasFieldS("PERSIST")) + { + ci->SetS("PERSIST", true); + + /* Channel doesn't exist, create it */ + if (!ci->c) + { + bool created; + Channel *c = Channel::FindOrCreate(ci->GetName(), created); + if (ci->GetBot()) + { + ChannelStatus status(BotModes()); + ci->GetBot()->Join(c, &status); + } + if (created) + c->Sync(); + } + + /* Set the perm mode */ + if (cm) + { + if (ci->c && !ci->c->HasMode("PERM")) + ci->c->SetMode(NULL, cm); + /* Add it to the channels mlock */ + if (mlocks) + mlocks->SetMLock(ci, cm, true, "", source.GetNick()); + } + /* No botserv bot, no channel mode, give them ChanServ. + * Yes, this works fine with no BotServ. + */ + else if (!ci->GetBot()) + { + ServiceBot *ChanServ = Config->GetClient("ChanServ"); + if (!ChanServ) + { + source.Reply(_("ChanServ is required to enable persist on this network.")); + return; + } + + ChanServ->Assign(NULL, ci); + if (!ci->c->FindUser(ChanServ)) + { + ChannelStatus status(BotModes()); + ChanServ->Join(ci->c, &status); + } + } + } + + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable persist"; + source.Reply(_("Channel \002{0}\002 is now persistent."), ci->GetName()); + } + else if (params[1].equals_ci("OFF")) + { + if (ci->HasFieldS("PERSIST")) + { + ci->UnsetS("PERSIST"); + + ServiceBot *ChanServ = Config->GetClient("ChanServ"), + *BotServ = Config->GetClient("BotServ"); + + /* Unset perm mode */ + if (cm) + { + if (ci->c && ci->c->HasMode("PERM")) + ci->c->RemoveMode(NULL, cm); + /* Remove from mlock */ + if (mlocks) + mlocks->RemoveMLock(ci, cm, true); + } + /* No channel mode, no BotServ, but using ChanServ as the botserv bot + * which was assigned when persist was set on + */ + else if (!cm && !BotServ && ci->GetBot()) + { + if (!ChanServ) + { + source.Reply(_("ChanServ is required to enable persist on this network.")); + return; + } + + /* Unassign bot */ + ChanServ->UnAssign(NULL, ci); + } + } + + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable persist"; + source.Reply(_("Channel \002{0}\002 is no longer persistent."), ci->GetName()); + } + else + this->OnSyntaxError(source, "PERSIST"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables the persistent channel setting." + " When persistent is set, the service bot will remain in the channel when it has emptied of users.")); + return true; + } +}; + +class CommandCSSetRestricted : public Command +{ + public: + CommandCSSetRestricted(Module *creator, const Anope::string &cname = "chanserv/set/restricted") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Restrict access to the channel")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (param.equals_ci("ON")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable restricted"; + ci->SetS("RESTRICTED", true); + source.Reply(_("Restricted access option for \002{0}\002 is now \002on\002."), ci->GetName()); + } + else if (param.equals_ci("OFF")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable restricted"; + ci->UnsetS("RESTRICTED"); + source.Reply(_("Restricted access option for \002{0}\002 is now \002off\002."), ci->GetName()); + } + else + this->OnSyntaxError(source, "RESTRICTED"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables the \002restricted access\002 option for \037channel\037. When \002restricted access\002 is set, users not on the access list will not be permitted to join the channel.")); + return true; + } +}; + +class CommandCSSetSecure : public Command +{ + public: + CommandCSSetSecure(Module *creator, const Anope::string &cname = "chanserv/set/secure") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Activate security features")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (param.equals_ci("ON")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable secure"; + ci->SetS("CS_SECURE", true); + source.Reply(_("Secure option for \002{0}\002 is now \002on\002."), ci->GetName()); + } + else if (param.equals_ci("OFF")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable secure"; + ci->UnsetS("CS_SECURE"); + source.Reply(_("Secure option for \002{0}\002 is now \002off\002."), ci->GetName()); + } + else + this->OnSyntaxError(source, "SECURE"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables security features for a channel." + " When \002secure\002 is set, only users who have logged in (eg. not recognized based on their hostmask) will be given access to the channel as controlled by the access list.")); + return true; + } +}; + +class CommandCSSetSecureFounder : public Command +{ + public: + CommandCSSetSecureFounder(Module *creator, const Anope::string &cname = "chanserv/set/securefounder") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Stricter control of channel founder status")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && (ci->HasFieldS("SECUREFOUNDER") ? !source.IsFounder(ci) : !source.AccessFor(ci).HasPriv("FOUNDER")) && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); + return; + } + + if (param.equals_ci("ON")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable secure founder"; + ci->SetS("SECUREFOUNDER", true); + source.Reply(_("Secure founder option for \002{0}\002 is now \002on\002."), ci->GetName()); + } + else if (param.equals_ci("OFF")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable secure founder"; + ci->UnsetS("SECUREFOUNDER"); + source.Reply(_("Secure founder option for \002{0}\002 is now \002off\002."), ci->GetName()); + } + else + this->OnSyntaxError(source, "SECUREFOUNDER"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables the \002secure founder\002 option for a channel." + " When \002secure founder\002 is set, only the real founder will be able to drop the channel, change its founder, and change its successor." + " Otherwise, anyone with the \002{0}\002 privilege will be able to use these commands."), + "FOUNDER"); + return true; + } +}; + +class CommandCSSetSecureOps : public Command +{ + public: + CommandCSSetSecureOps(Module *creator, const Anope::string &cname = "chanserv/set/secureops") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Stricter control of chanop status")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (param.equals_ci("ON")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable secure ops"; + ci->SetS("SECUREOPS", true); + source.Reply(_("Secure ops option for \002{0}\002 is now \002on\002."), ci->GetName()); + } + else if (param.equals_ci("OFF")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable secure ops"; + ci->UnsetS("SECUREOPS"); + source.Reply(_("Secure ops option for \002{0}\002 is now \002off\002."), ci->GetName()); + } + else + this->OnSyntaxError(source, "SECUREOPS"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables the \002secure ops\002 option for \037channel\037." + " When \002secure ops\002 is set, users will not be allowed to have channel operator status if they do not have the privileges for it.")); + return true; + } +}; + +class CommandCSSetSignKick : public Command +{ + public: + CommandCSSetSignKick(Module *creator, const Anope::string &cname = "chanserv/set/signkick") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Sign kicks that are done with the KICK command")); + this->SetSyntax(_("\037channel\037 {ON | LEVEL | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (param.equals_ci("ON")) + { + ci->SetS("SIGNKICK", true); + ci->UnsetS("SIGNKICK_LEVEL"); + source.Reply(_("Signed kick option for \002{0}\002 is now \002on\002."), ci->GetName()); + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable sign kick"; + } + else if (param.equals_ci("LEVEL")) + { + ci->SetS("SIGNKICK_LEVEL", true); + ci->UnsetS("SIGNKICK"); + source.Reply(_("Signed kick option for \002{0}\002 is now \002on\002, but depends of the privileges of the user that is using the command."), ci->GetName()); + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable sign kick level"; + } + else if (param.equals_ci("OFF")) + { + ci->UnsetS("SIGNKICK"); + ci->UnsetS("SIGNKICK_LEVEL"); + source.Reply(_("Signed kick option for \002{0}\002 is now \002off\002."), ci->GetName()); + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable sign kick"; + } + else + this->OnSyntaxError(source, "SIGNKICK"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables \002signed kicks\002 for \037channel\037." + " When \002signed kicks\002 is enabled, kicks issued through services will have the nickname of the user who performed the kick included in the kick reason." + " If you use \002LEVEL\002 setting, then only users who do not have the \002{0}\002 privilege will have their kicks signed."), + "SIGNKICK"); + return true; + } +}; + +class CommandCSSetSuccessor : public Command +{ + public: + CommandCSSetSuccessor(Module *creator, const Anope::string &cname = "chanserv/set/successor") : Command(creator, cname, 1, 2) + { + this->SetDesc(_("Set the successor for a channel")); + this->SetSyntax(_("\037channel\037 \037nick\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && (ci->HasFieldS("SECUREFOUNDER") ? !source.IsFounder(ci) : !source.AccessFor(ci).HasPriv("FOUNDER")) && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); + return; + } + + NickServ::Account *nc; + + if (!param.empty()) + { + NickServ::Nick *na = NickServ::FindNick(param); + + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), param); + return; + } + + if (na->GetAccount() == ci->GetFounder()) + { + source.Reply(_("\002{0}\002 cannot be the successor of channel \002{1}\002 as they are the founder."), na->GetNick(), ci->GetName()); + return; + } + + nc = na->GetAccount(); + } + else + nc = NULL; + + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change the successor from " << (ci->GetSuccessor() ? ci->GetSuccessor()->GetDisplay() : "(none)") << " to " << (nc ? nc->GetDisplay() : "(none)"); + + ci->SetSuccessor(nc); + + if (nc) + source.Reply(_("Successor for \002{0}\002 changed to \002{1}\002."), ci->GetName(), nc->GetDisplay()); + else + source.Reply(_("Successor for \002{0}\002 unset."), ci->GetName()); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the successor of \037channel\037." + " The successor of a channel is automatically given ownership of the channel if the founder's account drops of expires." + " If the success has too many registered channels or there is no successor, the channel may instead be given to one of the users on the channel access list with the most privileges.")); + return true; + } +}; + +class CommandCSSetNoexpire : public Command +{ + public: + CommandCSSetNoexpire(Module *creator) : Command(creator, "chanserv/saset/noexpire", 2, 2) + { + this->SetDesc(_("Prevent the channel from expiring")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (source.permission.empty() && !source.AccessFor(ci).HasPriv("SET")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (param.equals_ci("ON")) + { + Log(LOG_ADMIN, source, this, ci) << "to enable noexpire"; + ci->SetS("CS_NO_EXPIRE", true); + source.Reply(_("Channel \002{0} will not\002 expire."), ci->GetName()); + } + else if (param.equals_ci("OFF")) + { + Log(LOG_ADMIN, source, this, ci) << "to disable noexpire"; + ci->UnsetS("CS_NO_EXPIRE"); + source.Reply(_("Channel \002{0} will\002 expire."), ci->GetName()); + } + else + this->OnSyntaxError(source, "NOEXPIRE"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Sets whether the given \037channel\037 will expire. Setting \002 noexpire\002 to \002on\002 prevents the channel from expiring.")); + return true; + } +}; + +class CSSet : public Module + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + Serialize::Field noautoop, peace, securefounder, + restricted, secure, secureops, signkick, signkick_level, noexpire, keep_modes, persist; + + CommandCSSet commandcsset; + CommandCSSetAutoOp commandcssetautoop; + CommandCSSetBanType commandcssetbantype; + CommandCSSetDescription commandcssetdescription; + CommandCSSetFounder commandcssetfounder; + CommandCSSetKeepModes commandcssetkeepmodes; + CommandCSSetPeace commandcssetpeace; + CommandCSSetPersist commandcssetpersist; + CommandCSSetRestricted commandcssetrestricted; + CommandCSSetSecure commandcssetsecure; + CommandCSSetSecureFounder commandcssetsecurefounder; + CommandCSSetSecureOps commandcssetsecureops; + CommandCSSetSignKick commandcssetsignkick; + CommandCSSetSuccessor commandcssetsuccessor; + CommandCSSetNoexpire commandcssetnoexpire; + + ExtensibleRef inhabit; + + bool persist_lower_ts; + + public: + CSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + + , noautoop(this, ChanServ::channel, "NOAUTOOP") + , peace(this, ChanServ::channel, "PEACE") + , securefounder(this, ChanServ::channel, "SECUREFOUNDER") + , restricted(this, ChanServ::channel, "RESTRICTED") + , secure(this, ChanServ::channel, "CS_SECURE") + , secureops(this, ChanServ::channel, "SECUREOPS") + , signkick(this, ChanServ::channel, "SIGNKICK") + , signkick_level(this, ChanServ::channel, "SIGNKICK_LEVEL") + , noexpire(this, ChanServ::channel, "CS_NO_EXPIRE") + , keep_modes(this, ChanServ::channel, "CS_KEEP_MODES") + , persist(this, ChanServ::channel, "PERSIST") + + , commandcsset(this) + , commandcssetautoop(this) + , commandcssetbantype(this) + , commandcssetdescription(this) + , commandcssetfounder(this) + , commandcssetkeepmodes(this) + , commandcssetpeace(this) + , commandcssetpersist(this) + , commandcssetrestricted(this) + , commandcssetsecure(this) + , commandcssetsecurefounder(this) + , commandcssetsecureops(this) + , commandcssetsignkick(this) + , commandcssetsuccessor(this) + , commandcssetnoexpire(this) + + , inhabit("inhabit") + { + } + + void OnReload(Configuration::Conf *conf) override + { + persist_lower_ts = conf->GetModule(this)->Get("persist_lower_ts"); + } + + void OnCreateChan(ChanServ::Channel *ci) override + { + ci->SetBanType(Config->GetModule(this)->Get("defbantype", "2")); + } + + void OnChannelSync(Channel *c) override + { + if (c->ci && keep_modes.HasExt(c->ci)) + for (ChanServ::Mode *m : c->ci->GetRefs(ChanServ::mode)) + c->SetMode(c->ci->WhoSends(), m->GetMode(), m->GetParam()); + } + + EventReturn OnCheckKick(User *u, Channel *c, Anope::string &mask, Anope::string &reason) override + { + if (!c->ci || !restricted.HasExt(c->ci) || c->MatchesList(u, "EXCEPT")) + return EVENT_CONTINUE; + + if (c->ci->AccessFor(u).empty() && (!c->ci->GetFounder() || u->Account() != c->ci->GetFounder())) + return EVENT_STOP; + + return EVENT_CONTINUE; + } + + void OnDelChan(ChanServ::Channel *ci) override + { + if (ci->c && persist.HasExt(ci)) + ci->c->RemoveMode(ci->WhoSends(), "PERM", "", false); + persist.Unset(ci); + } + + EventReturn OnChannelModeSet(Channel *c, const MessageSource &setter, ChannelMode *mode, const Anope::string ¶m) override + { + if (c->ci) + { + /* Channel mode +P or so was set, mark this channel as persistent */ + if (mode->name == "PERM") + persist.Set(c->ci, true); + + if (mode->type != MODE_STATUS && !c->syncing && Me->IsSynced() && ChanServ::mode && (!inhabit || !inhabit->HasExt(c))) + { + ChanServ::Mode *m = ChanServ::mode.Create(); + m->SetChannel(c->ci); + m->SetMode(mode->name); + m->SetParam(param); + } + } + + return EVENT_CONTINUE; + } + + EventReturn OnChannelModeUnset(Channel *c, const MessageSource &setter, ChannelMode *mode, const Anope::string ¶m) override + { + if (mode->name == "PERM") + { + if (c->ci) + persist.Unset(c->ci); + } + + if (c->ci && mode->type != MODE_STATUS && !c->syncing && Me->IsSynced() && (!inhabit || !inhabit->HasExt(c))) + for (ChanServ::Mode *m : c->ci->GetRefs(ChanServ::mode)) + if (m->GetMode() == mode->name && m->GetParam().equals_ci(param)) + m->Delete(); + + return EVENT_CONTINUE; + } + + void OnJoinChannel(User *u, Channel *c) override + { + if (persist_lower_ts && c->ci && persist.HasExt(c->ci) && c->creation_time > c->ci->GetTimeRegistered()) + { + Log(LOG_DEBUG) << "Changing TS of " << c->name << " from " << c->creation_time << " to " << c->ci->GetTimeRegistered(); + c->creation_time = c->ci->GetTimeRegistered(); + IRCD->SendChannel(c); + c->Reset(); + } + } + + void OnSetCorrectModes(User *user, Channel *chan, ChanServ::AccessGroup &access, bool &give_modes, bool &take_modes) override + { + if (chan->ci) + { + if (noautoop.HasExt(chan->ci)) + give_modes = false; + if (secureops.HasExt(chan->ci)) + // This overrides what chanserv does because it is loaded after chanserv + take_modes = true; + } + } + + void OnPreChanExpire(ChanServ::Channel *ci, bool &expire) override + { + if (noexpire.HasExt(ci)) + expire = false; + } + + void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override + { + if (!show_all) + return; + + if (peace.HasExt(ci)) + info.AddOption(_("Peace")); + if (restricted.HasExt(ci)) + info.AddOption(_("Restricted access")); + if (secure.HasExt(ci)) + info.AddOption(_("Security")); + if (securefounder.HasExt(ci)) + info.AddOption(_("Secure founder")); + if (secureops.HasExt(ci)) + info.AddOption(_("Secure ops")); + if (signkick.HasExt(ci) || signkick_level.HasExt(ci)) + info.AddOption(_("Signed kicks")); + if (persist.HasExt(ci)) + info.AddOption(_("Persistent")); + if (noexpire.HasExt(ci)) + info.AddOption(_("No expire")); + if (keep_modes.HasExt(ci)) + info.AddOption(_("Keep modes")); + if (noautoop.HasExt(ci)) + info.AddOption(_("No auto-op")); + } +}; + +MODULE_INIT(CSSet) diff --git a/modules/chanserv/set_misc.cpp b/modules/chanserv/set_misc.cpp new file mode 100644 index 000000000..7af347087 --- /dev/null +++ b/modules/chanserv/set_misc.cpp @@ -0,0 +1,211 @@ +/* + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_set_misc.h" +#include "modules/cs_info.h" +#include "modules/cs_set.h" + +static Anope::map descriptions; + +class CSMiscDataImpl : public CSMiscData +{ + public: + CSMiscDataImpl(Serialize::TypeBase *type) : CSMiscData(type) { } + CSMiscDataImpl(Serialize::TypeBase *type, Serialize::ID id) : CSMiscData(type, id) { } + + ChanServ::Channel *GetChannel() override; + void SetChannel(ChanServ::Channel *s) override; + + Anope::string GetName() override; + void SetName(const Anope::string &n) override; + + Anope::string GetData() override; + void SetData(const Anope::string &d) override; +}; + +class CSMiscDataType : public Serialize::Type +{ + public: + Serialize::ObjectField owner; + Serialize::Field name, data; + + CSMiscDataType(Module *me) : Serialize::Type(me, "CSMiscData") + , owner(this, "owner", true) + , name(this, "name") + , data(this, "data") + { + } +}; + +ChanServ::Channel *CSMiscDataImpl::GetChannel() +{ + return Get(&CSMiscDataType::owner); +} + +void CSMiscDataImpl::SetChannel(ChanServ::Channel *s) +{ + Set(&CSMiscDataType::owner, s); +} + +Anope::string CSMiscDataImpl::GetName() +{ + return Get(&CSMiscDataType::name); +} + +void CSMiscDataImpl::SetName(const Anope::string &n) +{ + Set(&CSMiscDataType::name, n); +} + +Anope::string CSMiscDataImpl::GetData() +{ + return Get(&CSMiscDataType::data); +} + +void CSMiscDataImpl::SetData(const Anope::string &d) +{ + Set(&CSMiscDataType::data, d); +} + +class CommandCSSetMisc : public Command +{ + Anope::string GetAttribute(const Anope::string &command) + { + size_t sp = command.rfind(' '); + if (sp != Anope::string::npos) + return command.substr(sp + 1); + return command; + } + + public: + CommandCSSetMisc(Module *creator, const Anope::string &cname = "chanserv/set/misc") : Command(creator, cname, 1, 2) + { + this->SetSyntax(_("\037channel\037 [\037parameters\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + Anope::string scommand = GetAttribute(source.command); + + /* remove existing */ + for (CSMiscData *data : ci->GetRefs(csmiscdata)) + if (data->GetName() == scommand) + { + data->Delete(); + break; + } + + if (!param.empty()) + { + CSMiscData *data = csmiscdata.Create(); + data->SetChannel(ci); + data->SetName(scommand); + data->SetData(param); + + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change it to " << param; + source.Reply(_("\002{0}\002 for \002{1}\002 set to \002{2}\002."), scommand, ci->GetName(), param); + } + else + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to unset it"; + source.Reply(_("\002{0}\002 for \002{1}\002 unset."), scommand, ci->GetName()); + } + } + + void OnServHelp(CommandSource &source) override + { + if (descriptions.count(source.command)) + { + this->SetDesc(descriptions[source.command]); + Command::OnServHelp(source); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (descriptions.count(source.command)) + { + source.Reply("%s", Language::Translate(source.nc, descriptions[source.command].c_str())); + return true; + } + return false; + } +}; + +class CSSetMisc : public Module + , public EventHook +{ + CommandCSSetMisc commandcssetmisc; + CSMiscDataType type; + + public: + CSSetMisc(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcssetmisc(this) + , type(this) + { + } + + void OnReload(Configuration::Conf *conf) override + { + descriptions.clear(); + + for (int i = 0; i < conf->CountBlock("command"); ++i) + { + Configuration::Block *block = conf->GetBlock("command", i); + + if (block->Get("command") != "chanserv/set/misc") + continue; + + Anope::string cname = block->Get("name"); + Anope::string desc = block->Get("misc_description"); + + if (cname.empty() || desc.empty()) + continue; + + descriptions[cname] = desc; + } + } + + void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool) override + { + for (CSMiscData *data : ci->GetRefs(csmiscdata)) + info[data->GetName()] = data->GetData(); + } +}; + +MODULE_INIT(CSSetMisc) diff --git a/modules/chanserv/status.cpp b/modules/chanserv/status.cpp new file mode 100644 index 000000000..35676fa53 --- /dev/null +++ b/modules/chanserv/status.cpp @@ -0,0 +1,115 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_akick.h" + +class CommandCSStatus : public Command +{ +public: + CommandCSStatus(Module *creator) : Command(creator, "chanserv/status", 1, 2) + { + this->SetDesc(_("Find a user's status on a channel")); + this->SetSyntax(_("\037channel\037 [\037user\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &channel = params[0]; + + ChanServ::Channel *ci = ChanServ::Find(channel); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), channel); + return; + } + + if (!source.AccessFor(ci).HasPriv("ACCESS_CHANGE") && !source.HasPriv("chanserv/auspex")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "ACCESS_CHANGE", ci->GetName()); + return; + } + + Anope::string nick = source.GetNick(); + if (params.size() > 1) + nick = params[1]; + + ChanServ::AccessGroup ag; + User *u = User::Find(nick, true); + NickServ::Nick *na = NULL; + if (u != NULL) + ag = ci->AccessFor(u); + else + { + na = NickServ::FindNick(nick); + if (na != NULL) + ag = ci->AccessFor(na->GetAccount()); + } + + if (ag.super_admin) + source.Reply(_("\002{0}\002 is a super administrator."), nick); + else if (ag.founder) + source.Reply(_("\002{0}\002 is the founder of \002{1}\002."), nick, ci->GetName()); + else if (ag.empty()) + source.Reply(_("\002{0}\002 has no access on \002{1}\002."), nick, ci->GetName()); + else + { + source.Reply(_("Access for \002{0}\002 on \002{1}\002:"), nick, ci->GetName()); + + for (unsigned i = 0; i < ag.size(); ++i) + { + ChanServ::ChanAccess *acc = ag[i]; + + source.Reply(_("\002{0}\002 matches access entry \002{1}\002, which has privilege \002{2}\002."), nick, acc->Mask(), acc->AccessSerialize()); + } + } + + for (unsigned j = 0, end = ci->GetAkickCount(); j < end; ++j) + { + AutoKick *ak = ci->GetAkick(j); + + if (ak->GetAccount()) + { + if (na && ak->GetAccount() == na->GetAccount()) + source.Reply(_("\002{0}\002 is on the auto kick list of \002{1}\002 ({2})."), na->GetAccount()->GetDisplay(), ci->GetName(), ak->GetReason()); + } + else if (u != NULL) + { + Entry akick_mask("", ak->GetMask()); + if (akick_mask.Matches(u)) + source.Reply(_("\002{0}\002 matches auto kick entry \002{1}\002 on \002{2}\002 ({3})."), u->nick, ak->GetMask(), ci->GetName(), ak->GetReason()); + } + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command tells you what access \037user\037 has on \037channel\037." + "It will also tell you which access and auto kick entries match \037user\037.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037."), + "ACCESS_CHANGE"); + return true; + } +}; + +class CSStatus : public Module +{ + CommandCSStatus commandcsstatus; + + public: + CSStatus(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsstatus(this) + { + } +}; + +MODULE_INIT(CSStatus) diff --git a/modules/chanserv/statusupdate.cpp b/modules/chanserv/statusupdate.cpp new file mode 100644 index 000000000..e2e37c60d --- /dev/null +++ b/modules/chanserv/statusupdate.cpp @@ -0,0 +1,67 @@ +/* + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" + +class StatusUpdate : public Module + , public EventHook + , public EventHook +{ + public: + StatusUpdate(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + { + + } + + void OnAccessAdd(ChanServ::Channel *ci, CommandSource &, ChanServ::ChanAccess *access) override + { + if (ci->c) + for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + { + User *user = it->second->user; + + ChanServ::ChanAccess::Path p; + if (user->server != Me && access->Matches(user, user->Account(), p)) + { + ChanServ::AccessGroup ag = ci->AccessFor(user); + + for (unsigned i = 0; i < ModeManager::GetStatusChannelModesByRank().size(); ++i) + { + ChannelModeStatus *cms = ModeManager::GetStatusChannelModesByRank()[i]; + if (!ag.HasPriv("AUTO" + cms->name)) + ci->c->RemoveMode(NULL, cms, user->GetUID()); + } + ci->c->SetCorrectModes(user, true); + } + } + } + + // XXX this relies on the access entry already being removed from the list? + void OnAccessDel(ChanServ::Channel *ci, CommandSource &, ChanServ::ChanAccess *access) override + { + if (ci->c) + for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + { + User *user = it->second->user; + + ChanServ::ChanAccess::Path p; + if (user->server != Me && access->Matches(user, user->Account(), p)) + { + ChanServ::AccessGroup ag = ci->AccessFor(user); + + for (unsigned i = 0; i < ModeManager::GetStatusChannelModesByRank().size(); ++i) + { + ChannelModeStatus *cms = ModeManager::GetStatusChannelModesByRank()[i]; + if (!ag.HasPriv("AUTO" + cms->name)) + ci->c->RemoveMode(NULL, cms, user->GetUID()); + } + } + } + } +}; + +MODULE_INIT(StatusUpdate) diff --git a/modules/chanserv/suspend.cpp b/modules/chanserv/suspend.cpp new file mode 100644 index 000000000..73a034192 --- /dev/null +++ b/modules/chanserv/suspend.cpp @@ -0,0 +1,339 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_suspend.h" +#include "modules/cs_drop.h" +#include "modules/chanserv.h" +#include "modules/cs_info.h" + +class CSSuspendInfoImpl : public CSSuspendInfo +{ + public: + CSSuspendInfoImpl(Serialize::TypeBase *type) : CSSuspendInfo(type) { } + CSSuspendInfoImpl(Serialize::TypeBase *type, Serialize::ID id) : CSSuspendInfo(type, id) { } + + ChanServ::Channel *GetChannel() override; + void SetChannel(ChanServ::Channel *s) override; + + Anope::string GetBy() override; + void SetBy(const Anope::string &by) override; + + Anope::string GetReason() override; + void SetReason(const Anope::string &reason) override; + + time_t GetWhen() override; + void SetWhen(const time_t &w) override; + + time_t GetExpires() override; + void SetExpires(const time_t &e) override; +}; + +class CSSuspendType : public Serialize::Type +{ + public: + Serialize::ObjectField channel; + Serialize::Field by, reason; + Serialize::Field when, expires; + + CSSuspendType(Module *me) : Serialize::Type(me, "CSSuspendInfo") + , channel(this, "chan", true) + , by(this, "by") + , reason(this, "reason") + , when(this, "time") + , expires(this, "expires") + { + } +}; + +ChanServ::Channel *CSSuspendInfoImpl::GetChannel() +{ + return Get(&CSSuspendType::channel); +} + +void CSSuspendInfoImpl::SetChannel(ChanServ::Channel *s) +{ + Set(&CSSuspendType::channel, s); +} + +Anope::string CSSuspendInfoImpl::GetBy() +{ + return Get(&CSSuspendType::by); +} + +void CSSuspendInfoImpl::SetBy(const Anope::string &by) +{ + Set(&CSSuspendType::by, by); +} + +Anope::string CSSuspendInfoImpl::GetReason() +{ + return Get(&CSSuspendType::reason); +} + +void CSSuspendInfoImpl::SetReason(const Anope::string &reason) +{ + Set(&CSSuspendType::reason, reason); +} + +time_t CSSuspendInfoImpl::GetWhen() +{ + return Get(&CSSuspendType::when); +} + +void CSSuspendInfoImpl::SetWhen(const time_t &w) +{ + Set(&CSSuspendType::when, w); +} + +time_t CSSuspendInfoImpl::GetExpires() +{ + return Get(&CSSuspendType::expires); +} + +void CSSuspendInfoImpl::SetExpires(const time_t &e) +{ + Set(&CSSuspendType::expires, e); +} + +class CommandCSSuspend : public Command +{ + EventHandlers &onchansuspend; + public: + CommandCSSuspend(Module *creator, EventHandlers &event) : Command(creator, "chanserv/suspend", 2, 3), onchansuspend(event) + { + this->SetDesc(_("Prevent a channel from being used preserving channel data and settings")); + this->SetSyntax(_("\037channel\037 [+\037expiry\037] [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + Anope::string expiry = params[1]; + Anope::string reason = params.size() > 2 ? params[2] : ""; + time_t expiry_secs = Config->GetModule(this->owner)->Get("expire"); + + if (!expiry.empty() && expiry[0] != '+') + { + reason = expiry + " " + reason; + reason.trim(); + expiry.clear(); + } + else + { + expiry_secs = Anope::DoTime(expiry); + if (expiry_secs == -1) + { + source.Reply(_("Invalid expiry time \002{0}\002."), expiry); + return; + } + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + CSSuspendInfo *si = ci->GetRef(cssuspendinfo); + if (si) + { + source.Reply(_("\002{0}\002 is already suspended."), ci->GetName()); + return; + } + + si = cssuspendinfo.Create(); + si->SetChannel(ci); + si->SetBy(source.GetNick()); + si->SetReason(reason); + si->SetWhen(Anope::CurTime); + si->SetExpires(expiry_secs ? expiry_secs + Anope::CurTime : 0); + + if (ci->c) + { + std::vector users; + + for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + { + ChanUserContainer *uc = it->second; + User *user = uc->user; + if (!user->HasMode("OPER") && user->server != Me) + users.push_back(user); + } + + for (unsigned i = 0; i < users.size(); ++i) + ci->c->Kick(NULL, users[i], "%s", !reason.empty() ? reason.c_str() : Language::Translate(users[i], _("This channel has been suspended."))); + } + + Log(LOG_ADMIN, source, this, ci) << "(" << (!reason.empty() ? reason : "No reason") << "), expires on " << (expiry_secs ? Anope::strftime(Anope::CurTime + expiry_secs) : "never"); + source.Reply(_("Channel \002{0}\002 is now suspended."), ci->GetName()); + + this->onchansuspend(&Event::ChanSuspend::OnChanSuspend, ci); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Disallows anyone from using the given channel." + " All channel settings are preserved while the channel is suspended." + "If \037expiry\037 is given the channel will be unsuspended after that period of time.")); + return true; + } +}; + +class CommandCSUnSuspend : public Command +{ + EventHandlers &onchanunsuspend; + + public: + CommandCSUnSuspend(Module *creator, EventHandlers &event) : Command(creator, "chanserv/unsuspend", 1, 1), onchanunsuspend(event) + { + this->SetDesc(_("Releases a suspended channel")); + this->SetSyntax(_("\037channel\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + CSSuspendInfo *si = ci->GetRef(cssuspendinfo); + if (!si) + { + source.Reply(_("Channel \002{0}\002 isn't suspended."), ci->GetName()); + return; + } + + Log(LOG_ADMIN, source, this, ci) << "which was suspended by " << si->GetBy() << " for: " << (!si->GetReason().empty() ? si->GetReason() : "No reason"); + + si->Delete(); + + source.Reply(_("Channel \002%s\002 is now released."), ci->GetName().c_str()); + + this->onchanunsuspend(&Event::ChanUnsuspend::OnChanUnsuspend, ci); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Releases a suspended channel. All data and settings are preserved from before the suspension.")); + return true; + } +}; + +class CSSuspend : public Module + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + CommandCSSuspend commandcssuspend; + CommandCSUnSuspend commandcsunsuspend; + EventHandlers onchansuspend; + EventHandlers onchanunsuspend; + std::vector show; + CSSuspendType cst; + + struct trim + { + Anope::string operator()(Anope::string s) const + { + return s.trim(); + } + }; + + bool Show(CommandSource &source, const Anope::string &what) const + { + return source.IsOper() || std::find(show.begin(), show.end(), what) != show.end(); + } + + public: + CSSuspend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcssuspend(this, onchansuspend) + , commandcsunsuspend(this, onchanunsuspend) + , onchansuspend(this) + , onchanunsuspend(this) + , cst(this) + { + } + + void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_hidden) override + { + CSSuspendInfo *si = ci->GetRef(cssuspendinfo); + if (!si) + return; + + if (show_hidden || Show(source, "suspended")) + info[_("Suspended")] = _("This channel is \002suspended\002."); + if (!si->GetBy().empty() && (show_hidden || Show(source, "by"))) + info[_("Suspended by")] = si->GetBy(); + if (!si->GetReason().empty() && (show_hidden || Show(source, "reason"))) + info[_("Suspend reason")] = si->GetReason(); + if (si->GetWhen() && (show_hidden || Show(source, "on"))) + info[_("Suspended on")] = Anope::strftime(si->GetWhen(), source.GetAccount()); + if (si->GetExpires() && (show_hidden || Show(source, "expires"))) + info[_("Suspension expires")] = Anope::strftime(si->GetExpires(), source.GetAccount()); + } + + void OnPreChanExpire(ChanServ::Channel *ci, bool &expire) override + { + CSSuspendInfo *si = ci->GetRef(cssuspendinfo); + if (!si) + return; + + expire = false; + + if (!si->GetExpires()) + return; + + if (si->GetExpires() < Anope::CurTime) + { + ci->SetLastUsed(Anope::CurTime); + si->Delete(); + + Log(this) << "Expiring suspend for " << ci->GetName(); + } + } + + EventReturn OnCheckKick(User *u, Channel *c, Anope::string &mask, Anope::string &reason) override + { + if (u->HasMode("OPER") || !c->ci || !c->ci->GetRef(cssuspendinfo)) + return EVENT_CONTINUE; + + reason = Language::Translate(u, _("This channel may not be used.")); + return EVENT_STOP; + } + + EventReturn OnChanDrop(CommandSource &source, ChanServ::Channel *ci) override + { + CSSuspendInfo *si = ci->GetRef(cssuspendinfo); + if (si && !source.HasCommand("chanserv/drop")) + { + source.Reply(_("Channel \002{0}\002 is currently suspended."), ci->GetName()); + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(CSSuspend) diff --git a/modules/chanserv/sync.cpp b/modules/chanserv/sync.cpp new file mode 100644 index 000000000..37b56798c --- /dev/null +++ b/modules/chanserv/sync.cpp @@ -0,0 +1,77 @@ +/* + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandCSSync : public Command +{ + public: + CommandCSSync(Module *creator) : Command(creator, "chanserv/sync", 1, 1) + { + this->SetDesc(_("Sync users channel modes")); + this->SetSyntax(_("\037channel\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (ci->c == NULL) + { + source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); + return; + } + + if (!source.AccessFor(ci).HasPriv("ACCESS_CHANGE") && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "ACCESS_CHANGE", ci->GetName()); + return; + } + + bool override = !source.AccessFor(ci).HasPriv("ACCESS_CHANGE") && source.HasPriv("chanserv/administration"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci); + + for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + ci->c->SetCorrectModes(it->second->user, true); + + source.Reply(_("All user modes on \002{0}\002 have been synced.")); + } + + bool OnHelp(CommandSource &source, const Anope::string ¶ms) override + { + source.Reply(_("Syncs all channel status modes on all users on \037channel\037 with the modes they should have based on the channel access list.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037."), + "ACCESS_CHNAGE"); + return true; + } +}; + +class CSSync : public Module +{ + CommandCSSync commandcssync; + + public: + CSSync(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcssync(this) + { + + } +}; + +MODULE_INIT(CSSync) diff --git a/modules/chanserv/topic.cpp b/modules/chanserv/topic.cpp new file mode 100644 index 000000000..a132900a6 --- /dev/null +++ b/modules/chanserv/topic.cpp @@ -0,0 +1,276 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/cs_mode.h" +#include "modules/cs_info.h" +#include "modules/cs_set.h" + +class CommandCSSetKeepTopic : public Command +{ + public: + CommandCSSetKeepTopic(Module *creator, const Anope::string &cname = "chanserv/set/keeptopic") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Retain topic when channel is not in use")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string ¶m = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (param.equals_ci("ON")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable keeptopic"; + ci->SetS("KEEPTOPIC", true); + source.Reply(_("Topic retention option for \002{0}\002 is now \002on\002."), ci->GetName()); + } + else if (param.equals_ci("OFF")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable keeptopic"; + ci->UnsetS("KEEPTOPIC"); + source.Reply(_("Topic retention option for \002{0}\002 is now \002off\002."), ci->GetName()); + } + else + this->OnSyntaxError(source, "KEEPTOPIC"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables the \002topic retention\002 option for a \037channel\037." + " When \002topic retention\002 is set, the topic for the channel will be remembered by {0} even after the last user leaves the channel, and will be restored the next time the channel is created."), + source.service->nick); + return true; + } +}; + +class CommandCSTopic : public Command +{ + ExtensibleRef topiclock; + + void Lock(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, "topiclock on"); + if (MOD_RESULT == EVENT_STOP) + return; + + topiclock->Set(ci, true); + source.Reply(_("Topic lock option for \002{0}\002 is now \002on\002."), ci->GetName()); + } + + void Unlock(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, "topiclock off"); + if (MOD_RESULT == EVENT_STOP) + return; + + topiclock->Unset(ci); + source.Reply(_("Topic lock option for \002{0}\002 is now \002off\002."), ci->GetName()); + } + + void Set(CommandSource &source, ChanServ::Channel *ci, const Anope::string &topic) + { + bool has_topiclock = topiclock->HasExt(ci); + topiclock->Unset(ci); + ci->c->ChangeTopic(source.GetNick(), topic, Anope::CurTime); + if (has_topiclock) + topiclock->Set(ci, true); + + bool override = !source.AccessFor(ci).HasPriv("TOPIC"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << (!topic.empty() ? "to change the topic to: " : "to unset the topic") << (!topic.empty() ? topic : ""); + } + + void Append(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + const Anope::string &topic = params[2]; + + Anope::string new_topic; + if (!ci->c->topic.empty()) + { + new_topic = ci->c->topic + " " + topic; + ci->SetLastTopic(""); + } + else + new_topic = topic; + + this->Set(source, ci, new_topic); + } + + public: + CommandCSTopic(Module *creator) : Command(creator, "chanserv/topic", 2, 3), + topiclock("TOPICLOCK") + { + this->SetDesc(_("Manipulate the topic of the specified channel")); + this->SetSyntax(_("\037channel\037 [SET] [\037topic\037]")); + this->SetSyntax(_("\037channel\037 APPEND \037topic\037")); + this->SetSyntax(_("\037channel\037 [UNLOCK|LOCK]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &channel = params[0]; + const Anope::string &subcmd = params[1]; + + ChanServ::Channel *ci = ChanServ::Find(channel); + if (ci == NULL) + source.Reply(_("Channel \002{0}\002 isn't registered."), channel); + else if (!source.AccessFor(ci).HasPriv("TOPIC") && !source.HasCommand("chanserv/topic")) + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "TOPIC", ci->GetName()); + else if (subcmd.equals_ci("LOCK")) + this->Lock(source, ci, params); + else if (subcmd.equals_ci("UNLOCK")) + this->Unlock(source, ci, params); + else if (!ci->c) + source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); + else if (subcmd.equals_ci("APPEND") && params.size() > 2) + this->Append(source, ci, params); + else + { + Anope::string topic; + if (subcmd.equals_ci("SET")) + { + topic = params.size() > 2 ? params[2] : ""; + } + else + { + topic = subcmd; + if (params.size() > 2) + topic += " " + params[2]; + } + this->Set(source, ci, topic); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows manipulating the topic of the specified channel." + " The \002SET\002 command changes the topic of the channel to the given topic or unsets the topic if no topic is given." + " The \002APPEND\002 command appends the given topic to the existing topic.\n" + "\n" + "\002LOCK\002 and \002UNLOCK\002 may be used to enable and disable topic lock." + " When topic lock is set, the channel topic will be unchangeable by users who do not have the \002TOPIC\002 privilege.\n" + "\n" + "Use of this command requires the \002{0}\002 privilege on \037channel\037."), + "TOPIC"); + return true; + } +}; + +class CSTopic : public Module + , public EventHook + , public EventHook + , public EventHook +{ + CommandCSTopic commandcstopic; + CommandCSSetKeepTopic commandcssetkeeptopic; + + /*Serializable*/ExtensibleItem topiclock, keeptopic; + + public: + CSTopic(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcstopic(this) + , commandcssetkeeptopic(this) + , topiclock(this, "TOPICLOCK") + , keeptopic(this, "KEEPTOPIC") + { + + } + + void OnChannelSync(Channel *c) override + { + if (c->ci) + { + /* Update channel topic */ + if ((topiclock.HasExt(c->ci) || keeptopic.HasExt(c->ci)) && c->ci->GetLastTopic() != c->topic) + { + c->ChangeTopic(!c->ci->GetLastTopicSetter().empty() ? c->ci->GetLastTopicSetter() : c->ci->WhoSends()->nick, c->ci->GetLastTopic(), c->ci->GetLastTopicTime() ? c->ci->GetLastTopicTime() : Anope::CurTime); + } + } + } + + void OnTopicUpdated(User *source, Channel *c, const Anope::string &user, const Anope::string &topic) override + { + if (!c->ci) + return; + + /* We only compare the topics here, not the time or setter. This is because some (old) IRCds do not + * allow us to set the topic as someone else, meaning we have to bump the TS and change the setter to us. + * This desyncs what is really set with what we have stored, and we end up resetting the topic often when + * it is not required + */ + if (topiclock.HasExt(c->ci) && c->ci->GetLastTopic() != c->topic && (!source || !c->ci->AccessFor(source).HasPriv("TOPIC"))) + { + c->ChangeTopic(c->ci->GetLastTopicSetter(), c->ci->GetLastTopic(), c->ci->GetLastTopicTime()); + } + else + { + c->ci->SetLastTopic(c->topic); + c->ci->SetLastTopicSetter(c->topic_setter); + c->ci->SetLastTopicTime(c->topic_ts); + } + } + + void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override + { + if (keeptopic.HasExt(ci)) + info.AddOption(_("Topic retention")); + if (topiclock.HasExt(ci)) + info.AddOption(_("Topic lock")); + + ModeLock *secret = mlocks ? mlocks->GetMLock(ci, "SECRET") : nullptr; + if (!ci->GetLastTopic().empty() && (show_all || ((!secret || secret->GetSet() == false) && (!ci->c || !ci->c->HasMode("SECRET"))))) + { + info[_("Last topic")] = ci->GetLastTopic(); + info[_("Topic set by")] = ci->GetLastTopicSetter(); + } + } +}; + +MODULE_INIT(CSTopic) diff --git a/modules/chanserv/unban.cpp b/modules/chanserv/unban.cpp new file mode 100644 index 000000000..73391ac30 --- /dev/null +++ b/modules/chanserv/unban.cpp @@ -0,0 +1,120 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandCSUnban : public Command +{ + public: + CommandCSUnban(Module *creator) : Command(creator, "chanserv/unban", 0, 2) + { + this->SetDesc(_("Remove all bans preventing a user from entering a channel")); + this->SetSyntax(_("\037channel\037 [\037nick\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChannelMode *cm = ModeManager::FindChannelModeByName("BAN"); + if (!cm) + return; + + std::vector modes = cm->listeners; + modes.push_back(cm); + + if (params.empty()) + { + if (!source.GetUser()) + return; + + unsigned count = 0; + + for (ChanServ::Channel *ci : source.GetAccount()->GetRefs(ChanServ::channel)) + { + if (!ci->c || !source.AccessFor(ci).HasPriv("UNBAN")) + continue; + + for (unsigned j = 0; j < modes.size(); ++j) + if (ci->c->Unban(source.GetUser(), modes[j]->name, true)) + ++count; + } + + Log(LOG_COMMAND, source, this, NULL) << "on all channels"; + source.Reply(_("You have been unbanned from %d channels."), count); + + return; + } + + const Anope::string &chan = params[0]; + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (ci->c == NULL) + { + source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); + return; + } + + if (!source.AccessFor(ci).HasPriv("UNBAN") && !source.HasPriv("chanserv/kick")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "UNBAN", ci->GetName()); + return; + } + + User *u2 = source.GetUser(); + if (params.size() > 1) + u2 = User::Find(params[1], true); + + if (!u2) + { + if (params.size() > 1) + source.Reply(_("User \002{0}\002 isn't currently online."), params[1]); + return; + } + + bool override = !source.AccessFor(ci).HasPriv("UNBAN") && source.HasPriv("chanserv/kick"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to unban " << u2->nick; + + for (unsigned i = 0; i < modes.size(); ++i) + ci->c->Unban(u2, modes[i]->name, source.GetUser() == u2); + if (u2 == source.GetUser()) + source.Reply(_("You have been unbanned from \002{0}\002."), ci->c->name); + else + source.Reply(_("\002{0}\002 has been unbanned from \002{1}\002."), u2->nick, ci->c->name); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Tells {0} to remove all bans preventing you or the given \037user\037 from entering \037channel\037." + " If no channel is given, all bans affecting you in channels you have access in are removed.\n" + "\n" + "Use of this command requires the \002{1}\002 privilege on \037channel\037."), + source.service->nick, "UNBAN"); + return true; + } +}; + +class CSUnban : public Module +{ + CommandCSUnban commandcsunban; + + public: + CSUnban(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsunban(this) + { + + } +}; + +MODULE_INIT(CSUnban) diff --git a/modules/chanserv/updown.cpp b/modules/chanserv/updown.cpp new file mode 100644 index 000000000..383cc6e17 --- /dev/null +++ b/modules/chanserv/updown.cpp @@ -0,0 +1,261 @@ +/* ChanServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandCSUp : public Command +{ + void SetModes(User *u, Channel *c) + { + if (!c->ci) + return; + + /* whether or not we are giving modes */ + bool giving = true; + /* whether or not we have given a mode */ + bool given = false; + ChanServ::AccessGroup u_access = c->ci->AccessFor(u); + + for (unsigned i = 0; i < ModeManager::GetStatusChannelModesByRank().size(); ++i) + { + ChannelModeStatus *cm = ModeManager::GetStatusChannelModesByRank()[i]; + bool has_priv = u_access.HasPriv("AUTO" + cm->name) || u_access.HasPriv(cm->name); + + if (has_priv) + { + /* Always give op. If we have already given one mode, don't give more until it has a symbol */ + if (cm->name == "OP" || !given || (giving && cm->symbol)) + { + c->SetMode(NULL, cm, u->GetUID(), false); + /* Now if this contains a symbol don't give any more modes, to prevent setting +qaohv etc on users */ + giving = !cm->symbol; + given = true; + } + } + } + } + public: + CommandCSUp(Module *creator) : Command(creator, "chanserv/up", 0, 2) + { + this->SetDesc(_("Updates a selected nicks status on a channel")); + this->SetSyntax(_("[\037channel\037 [\037user\037]]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (params.empty()) + { + if (!source.GetUser()) + return; + for (User::ChanUserList::iterator it = source.GetUser()->chans.begin(); it != source.GetUser()->chans.end(); ++it) + { + Channel *c = it->second->chan; + SetModes(source.GetUser(), c); + } + Log(LOG_COMMAND, source, this, NULL) << "on all channels to update their status modes"; + } + else + { + const Anope::string &chan = params[0]; + const Anope::string &nick = params.size() > 1 ? params[1] : source.GetNick(); + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (ci->c == NULL) + { + source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); + return; + } + + User *u = User::Find(nick, true); + User *srcu = source.GetUser(); + Channel *c = ci->c; + bool override = false; + + if (u == NULL) + { + source.Reply(_("User \002{0}\002 isn't currently online."), nick); + return; + } + + if (srcu && !srcu->FindChannel(c)) + { + source.Reply(_("You must be in \002%s\002 to use this command."), c->name.c_str()); + return; + } + + if (!u->FindChannel(c)) + { + source.Reply(_("You must be on channel \002{0}\002 to use this command."), c->name); + return; + } + + if (!u->FindChannel(c)) + { + source.Reply(_("\002{0}\002 is not on channel \002{1}\002."), u->nick, c->name); + return; + } + + if (source.GetUser() && u != source.GetUser() && c->ci->HasFieldS("PEACE")) + { + if (c->ci->AccessFor(u) > c->ci->AccessFor(source.GetUser())) + { + if (source.HasPriv("chanserv/administration")) + override = true; + else + { + source.Reply(_("Access denied. \002{0}\002 has more privileges than you on \002{1}\002."), u->nick, ci->GetName()); + return; + } + } + } + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, c->ci) << "to update the status modes of " << u->nick; + SetModes(u, c); + } + + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Updates the status modes of \037user\037 on \037channel\037." + " If \037user\037 is omitted then your status is updated." + " If \037channel\037 is omitted then your status is updated on every channel you are in.")); + return true; + } +}; + +class CommandCSDown : public Command +{ + void RemoveAll(User *u, Channel *c) + { + ChanUserContainer *cu = c->FindUser(u); + if (cu != NULL) + for (size_t i = cu->status.Modes().length(); i > 0;) + c->RemoveMode(NULL, ModeManager::FindChannelModeByChar(cu->status.Modes()[--i]), u->GetUID()); + } + + public: + CommandCSDown(Module *creator) : Command(creator, "chanserv/down", 0, 2) + { + this->SetDesc(_("Removes a selected nicks status from a channel")); + this->SetSyntax(_("[\037channel\037 [\037nick\037]]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (params.empty()) + { + if (!source.GetUser()) + return; + for (User::ChanUserList::iterator it = source.GetUser()->chans.begin(); it != source.GetUser()->chans.end(); ++it) + { + Channel *c = it->second->chan; + RemoveAll(source.GetUser(), c); + } + Log(LOG_COMMAND, source, this, NULL) << "on all channels to remove their status modes"; + } + else + { + const Anope::string &channel = params[0]; + const Anope::string &nick = params.size() > 1 ? params[1] : source.GetNick(); + + ChanServ::Channel *ci = ChanServ::Find(channel); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), channel); + return; + } + + if (ci->c == NULL) + { + source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); + return; + } + + User *u = User::Find(nick, true); + Channel *c = ci->c; + + User *srcu = source.GetUser(); + bool override = false; + + if (u == NULL) + { + source.Reply(_("User \002{0}\002 isn't currently online."), nick); + return; + } + + if (srcu && !srcu->FindChannel(c)) + { + source.Reply(_("You must be on channel \002{0}\002 to use this command."), c->name); + return; + } + + if (srcu && !srcu->FindChannel(c)) + { + source.Reply(_("You must be in \002%s\002 to use this command."), c->name.c_str()); + return; + } + + if (!u->FindChannel(c)) + { + source.Reply(_("\002%s\002 is not on channel %s."), u->nick, c->name); + return; + } + + if (source.GetUser() && u != source.GetUser() && c->ci->HasFieldS("PEACE")) + { + if (c->ci->AccessFor(u) > c->ci->AccessFor(source.GetUser())) + { + if (source.HasPriv("chanserv/administration")) + override = true; + else + { + source.Reply(_("Access denied. \002{0}\002 has more privileges than you on \002{1}\002."), u->nick, ci->GetName()); + return; + } + } + } + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, c->ci) << "to remove the status modes from " << u->nick; + RemoveAll(u, c); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Removes a selected nicks status modes on a channel." + " If \037nick\037 is ommited then your status is removed." + " If \037channel\037 is ommited then channel status is removed on every channel you are in.")); + return true; + } +}; + +class CSUpDown : public Module +{ + CommandCSUp commandcsup; + CommandCSDown commandcsdown; + + public: + CSUpDown(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandcsup(this) + , commandcsdown(this) + { + + } +}; + +MODULE_INIT(CSUpDown) diff --git a/modules/commands/bs_assign.cpp b/modules/commands/bs_assign.cpp deleted file mode 100644 index 840651dfc..000000000 --- a/modules/commands/bs_assign.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/* BotServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/bs_info.h" - -class CommandBSAssign : public Command -{ - public: - CommandBSAssign(Module *creator) : Command(creator, "botserv/assign", 2, 2) - { - this->SetDesc(_("Assigns a bot to a channel")); - this->SetSyntax(_("\037channel\037 \037nickname\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &nick = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Sorry, bot assignment is temporarily disabled.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - ServiceBot *bi = ServiceBot::Find(nick, true); - if (!bi) - { - source.Reply(_("Bot \002{0}\002 does not exist."), nick); - return; - } - - ChanServ::AccessGroup access = source.AccessFor(ci); - if (!access.HasPriv("ASSIGN") && !source.HasPriv("botserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "ASSIGN", ci->GetName()); - return; - } - - if (ci->HasFieldS("BS_NOBOT")) - { - source.Reply(_("Access denied. \002{0}\002 may not have a bot assigned to it because a Services Operator has disallowed it."), ci->GetName()); - return; - } - - if (bi->bi->GetOperOnly() && !source.HasPriv("botserv/administration")) - { - source.Reply(_("Access denied. Bot \002{0}\002 is for operators only."), bi->nick); - return; - } - - if (ci->GetBot() == bi) - { - source.Reply(_("Bot \002{0}\002 is already assigned to \002{1}\002."), ci->GetBot()->nick, ci->GetName()); - return; - } - - bool override = !access.HasPriv("ASSIGN"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << bi->nick; - - bi->Assign(source.GetUser(), ci); - source.Reply(_("Bot \002{0}\002 has been assigned to \002{1}\002."), bi->nick, ci->GetName()); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Assigns the bot \037nickname\037 to \037channel\037." - " You can then configure the bot for the channel so it fits your needs.\n" - "\n" - "Use of this command requires the \002{0}\002 privilege on \037channel\037." - "\n" - "Example:\n" - " {command} #anope Botox\n" - " Assigns the bot Botox to #anope.\n"), - "ASSIGN", "command"_kw = source.command); - return true; - } -}; - -class CommandBSUnassign : public Command -{ - public: - CommandBSUnassign(Module *creator) : Command(creator, "botserv/unassign", 1, 1) - { - this->SetDesc(_("Unassigns a bot from a channel")); - this->SetSyntax(_("\037channel\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - - if (Anope::ReadOnly) - { - source.Reply(_("Sorry, bot assignment is temporarily disabled.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - ChanServ::AccessGroup access = source.AccessFor(ci); - if (!source.HasPriv("botserv/administration") && !access.HasPriv("ASSIGN")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "ASSIGN", ci->GetName()); - return; - } - - if (!ci->GetBot()) - { - source.Reply(_("There is no bot assigned to \002{0}\002."), ci->GetName()); - return; - } - - if (ci->HasFieldS("PERSIST") && !ModeManager::FindChannelModeByName("PERM")) - { - source.Reply(_("You cannot unassign bots while persist is set on the channel.")); - return; - } - - bool override = !access.HasPriv("ASSIGN"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << ci->GetBot()->nick; - - ServiceBot *bi = ci->GetBot(); - bi->UnAssign(source.GetUser(), ci); - source.Reply(_("Bot \002{0}\002 has been unassigned from \002{1}\002."), bi->nick, ci->GetName()); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Unassigns a bot from \037channel\037." - "Bot configuration is kept, so you will always be able to reassign a bot later without losing your settings.\n" - "\n" - "Use of this command requires the \002{0}\002 privilege on \037channel\037." - "\n" - "Example:\n" - " {command} #anope\n" - " Unassigns the current bot from #anope.\n"), - "ASSIGN", "command"_kw = source.command); - return true; - } -}; - -class CommandBSSetNoBot : public Command -{ - public: - CommandBSSetNoBot(Module *creator, const Anope::string &sname = "botserv/set/nobot") : Command(creator, sname, 2, 2) - { - this->SetDesc(_("Prevent a bot from being assigned to a channel")); - this->SetSyntax(_("\037channel\037 {\037ON|OFF\037}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &value = params[1]; - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (value.equals_ci("ON")) - { - Log(LOG_ADMIN, source, this, ci) << "to enable nobot"; - - ci->SetS("BS_NOBOT", true); - if (ci->GetBot()) - ci->GetBot()->UnAssign(source.GetUser(), ci); - source.Reply(_("No-bot mode is now \002on\002 for \002{0}\002."), ci->GetName()); - } - else if (value.equals_ci("OFF")) - { - Log(LOG_ADMIN, source, this, ci) << "to disable nobot"; - - ci->UnsetS("BS_NOBOT"); - source.Reply(_("No-bot mode is now \002off\002 for \002{0}\002."), ci->GetName()); - } - else - this->OnSyntaxError(source, source.command); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("If no-bot is set on a channel, then the channel will be unabled to have a bot assigned to it." - " If a bot is already assigned to the channel, it is unassigned automatically when no-bot is enabled." - "\n" - "Example:\n" - " {command} #anope on\n" - " Prevents a service bot from being assigned to #anope.\n"), - "command"_kw = source.command); - return true; - } -}; - -class BSAssign : public Module - , public EventHook - , public EventHook -{ - Serialize::Field nobot; - - CommandBSAssign commandbsassign; - CommandBSUnassign commandbsunassign; - CommandBSSetNoBot commandbssetnobot; - - public: - BSAssign(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - - , nobot(this, botinfo, "BS_NOBOT") - - , commandbsassign(this) - , commandbsunassign(this) - , commandbssetnobot(this) - { - } - - void OnInvite(User *source, Channel *c, User *targ) override - { - ServiceBot *bi; - if (Anope::ReadOnly || !c->ci || targ->server != Me || !(bi = ServiceBot::Find(targ->nick, true))) - return; - - ChanServ::AccessGroup access = c->ci->AccessFor(source); - if (!access.HasPriv("ASSIGN") && !source->HasPriv("botserv/administration")) - { - targ->SendMessage(bi, _("Access denied. You do not have privilege \002ASSIGN\002 on \002{0}\002."), c->ci->GetName()); - return; - } - - if (nobot.HasExt(c->ci)) - { - targ->SendMessage(bi, _("Access denied. \002{0}\002 may not have a bot assigned to it because a Services Operator has disallowed it."), c->ci->GetName()); - return; - } - - if (bi->bi->GetOperOnly() && !source->HasPriv("botserv/administration")) - { - targ->SendMessage(bi, _("Access denied. Bot \002{0}\002 is for operators only."), bi->nick); - return; - } - - if (c->ci->GetBot() == bi) - { - targ->SendMessage(bi, _("Bot \002{0}\002 is already assigned to \002{1}\002."), bi->nick, c->ci->GetName()); - return; - } - - bi->Assign(source, c->ci); - targ->SendMessage(bi, _("Bot \002{0}\002 has been assigned to \002{1}\002."), bi->nick, c->ci->GetName()); - } - - void OnServiceBot(CommandSource &source, ServiceBot *bi, ChanServ::Channel *ci, InfoFormatter &info) override - { - if (nobot.HasExt(ci)) - info.AddOption(_("No bot")); - } -}; - -MODULE_INIT(BSAssign) diff --git a/modules/commands/bs_badwords.cpp b/modules/commands/bs_badwords.cpp deleted file mode 100644 index 0f8e9e7d0..000000000 --- a/modules/commands/bs_badwords.cpp +++ /dev/null @@ -1,465 +0,0 @@ -/* BotServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/bs_badwords.h" - -static EventHandlers *bwevents; - -class BadWordImpl : public BadWord -{ - public: - BadWordImpl(Serialize::TypeBase *type) : BadWord(type) { } - BadWordImpl(Serialize::TypeBase *type, Serialize::ID id) : BadWord(type, id) { } - - ChanServ::Channel *GetChannel() override; - - void SetChannel(ChanServ::Channel *c) override; - - Anope::string GetWord() override; - - void SetWord(const Anope::string &w) override; - - BadWordType GetType() override; - - void SetType(const BadWordType &t) override; -}; - -class BadWordsType : public Serialize::Type -{ - public: - Serialize::ObjectField channel; - Serialize::Field word; - Serialize::Field type; - - BadWordsType(Module *me) : Serialize::Type(me, "BadWord") - , channel(this, "ci", true) - , word(this, "word") - , type(this, "type") - { - } -}; - -ChanServ::Channel *BadWordImpl::GetChannel() -{ - return Get(&BadWordsType::channel); -} - -void BadWordImpl::SetChannel(ChanServ::Channel *c) -{ - Set(&BadWordsType::channel, c); -} - -Anope::string BadWordImpl::GetWord() -{ - return Get(&BadWordsType::word); -} - -void BadWordImpl::SetWord(const Anope::string &w) -{ - Set(&BadWordsType::word, w); -} - -BadWordType BadWordImpl::GetType() -{ - return Get(&BadWordsType::type); -} - -void BadWordImpl::SetType(const BadWordType &t) -{ - Set(&BadWordsType::type, t); -} - -struct BadWordsImpl : BadWords -{ - BadWordsImpl(Module *me) : BadWords(me) { } - - BadWord* AddBadWord(ChanServ::Channel *ci, const Anope::string &word, BadWordType type) override - { - BadWord *bw = badword.Create(); - bw->SetChannel(ci); - bw->SetWord(word); - bw->SetType(type); - - (*bwevents)(&Event::BadWordEvents::OnBadWordAdd, ci, bw); - - return bw; - } - - std::vector GetBadWords(ChanServ::Channel *ci) - { - return ci->GetRefs(badword); - } - - BadWord* GetBadWord(ChanServ::Channel *ci, unsigned index) override - { - auto bw = GetBadWords(ci); - return index < bw.size() ? bw[index] : nullptr; - } - - unsigned GetBadWordCount(ChanServ::Channel *ci) override - { - return GetBadWords(ci).size(); - } - - void EraseBadWord(ChanServ::Channel *ci, unsigned index) override - { - auto bws = GetBadWords(ci); - if (index >= bws.size()) - return; - - BadWord *bw = bws[index]; - (*bwevents)(&Event::BadWordEvents::OnBadWordDel, ci, bw); - - delete bw; - } - - void ClearBadWords(ChanServ::Channel *ci) override - { - for (BadWord *bw : GetBadWords(ci)) - delete bw; - } -}; - -class CommandBSBadwords : public Command -{ - private: - void DoList(CommandSource &source, ChanServ::Channel *ci, const Anope::string &word) - { - bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "LIST"; - ListFormatter list(source.GetAccount()); - - list.AddColumn(_("Number")).AddColumn(_("Word")).AddColumn(_("Type")); - - if (!badwords->GetBadWordCount(ci)) - { - source.Reply(_("The bad word list of \002{0}\002 is empty."), ci->GetName()); - return; - } - - if (!word.empty() && word.find_first_not_of("1234567890,-") == Anope::string::npos) - { - NumberList(word, false, - [&](unsigned int num) - { - if (!num || num > badwords->GetBadWordCount(ci)) - return; - - BadWord *b = badwords->GetBadWord(ci, num - 1); - ListFormatter::ListEntry entry; - entry["Number"] = stringify(num); - entry["Word"] = b->GetWord(); - entry["Type"] = b->GetType() == BW_SINGLE ? "(SINGLE)" : (b->GetType() == BW_START ? "(START)" : (b->GetType() == BW_END ? "(END)" : "")); - list.AddEntry(entry); - }, - [](){}); - } - else - { - for (unsigned i = 0, end = badwords->GetBadWordCount(ci); i < end; ++i) - { - BadWord *b = badwords->GetBadWord(ci, i); - - if (!word.empty() && !Anope::Match(b->GetWord(), word)) - continue; - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(i + 1); - entry["Word"] = b->GetWord(); - entry["Type"] = b->GetType() == BW_SINGLE ? "(SINGLE)" : (b->GetType() == BW_START ? "(START)" : (b->GetType() == BW_END ? "(END)" : "")); - list.AddEntry(entry); - } - } - - if (list.IsEmpty()) - { - source.Reply(_("No matching entries on the bad word list of \002{0}\002."), ci->GetName()); - return; - } - - std::vector replies; - list.Process(replies); - - source.Reply(_("Bad words list for \002{0}\002:"), ci->GetName()); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("End of bad words list.")); - } - - void DoAdd(CommandSource &source, ChanServ::Channel *ci, const Anope::string &word) - { - size_t pos = word.rfind(' '); - BadWordType bwtype = BW_ANY; - Anope::string realword = word; - - if (pos != Anope::string::npos) - { - Anope::string opt = word.substr(pos + 1); - if (!opt.empty()) - { - if (opt.equals_ci("SINGLE")) - bwtype = BW_SINGLE; - else if (opt.equals_ci("START")) - bwtype = BW_START; - else if (opt.equals_ci("END")) - bwtype = BW_END; - } - realword = word.substr(0, pos); - } - - unsigned badwordsmax = Config->GetModule(this->module)->Get("badwordsmax"); - if (badwords->GetBadWordCount(ci) >= badwordsmax) - { - source.Reply(_("Sorry, you can only have \002{0}\002 bad words entries on a channel."), badwordsmax); - return; - } - - bool casesensitive = Config->GetModule(this->module)->Get("casesensitive"); - - for (BadWord *bw : badwords->GetBadWords(ci)) - if ((casesensitive && realword.equals_cs(bw->GetWord())) || (!casesensitive && realword.equals_ci(bw->GetWord()))) - { - source.Reply(_("\002{0}\002 already exists in \002{1}\002 bad words list."), bw->GetWord(), ci->GetName()); - return; - } - - bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "ADD " << realword; - badwords->AddBadWord(ci, realword, bwtype); - - source.Reply(_("\002{0}\002 added to \002{1}\002 bad words list."), realword, ci->GetName()); - } - - void DoDelete(CommandSource &source, ChanServ::Channel *ci, const Anope::string &word) - { - if (!badwords->GetBadWordCount(ci)) - { - source.Reply(_("Bad word list for \002{0}\002 is empty."), ci->GetName()); - return; - } - - bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); - - /* Special case: is it a number/list? Only do search if it isn't. */ - if (!word.empty() && isdigit(word[0]) && word.find_first_not_of("1234567890,-") == Anope::string::npos) - { - unsigned int deleted = 0; - - NumberList(word, true, - [&](unsigned int num) - { - if (!num || num > badwords->GetBadWordCount(ci)) - return; - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "DEL " << badwords->GetBadWord(ci, num - 1)->GetWord(); - ++deleted; - badwords->EraseBadWord(ci, num - 1); - }, - [&]() - { - if (!deleted) - source.Reply(_("No matching entries on the bad word list of \002{0}\002."), ci->GetName()); - else if (deleted == 1) - source.Reply(_("Deleted \0021\002 entry from bad word list of \002{0}\002."), ci->GetName()); - else - source.Reply(_("Deleted \002{0}\002 entries from the bad word list of \002{1}\002."), deleted, ci->GetName()); - }); - } - else - { - unsigned i, end; - BadWord *bw; - - for (i = 0, end = badwords->GetBadWordCount(ci); i < end; ++i) - { - bw = badwords->GetBadWord(ci, i); - - if (word.equals_ci(bw->GetWord())) - break; - } - - if (i == end) - { - source.Reply(_("\002{0}\002 was not found on the bad word list of \002{1}\002."), word, ci->GetName()); - return; - } - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "DEL " << bw->GetWord(); - - source.Reply(_("\002{0}\002 deleted from \002{1}\002 bad words list."), bw->GetWord(), ci->GetName()); - - bw->Delete(); - } - } - - void DoClear(CommandSource &source, ChanServ::Channel *ci) - { - bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "CLEAR"; - - badwords->ClearBadWords(ci); - source.Reply(_("Bad words list is now empty.")); - } - - public: - CommandBSBadwords(Module *creator) : Command(creator, "botserv/badwords", 2, 3) - { - this->SetDesc(_("Maintains the bad words list")); - this->SetSyntax(_("\037channel\037 ADD \037word\037 [\037SINGLE\037 | \037START\037 | \037END\037]")); - this->SetSyntax(_("\037channel\037 DEL {\037word\037 | \037entry-num\037 | \037list\037}")); - this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]")); - this->SetSyntax(_("\037channel\037 CLEAR")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &cmd = params[1]; - const Anope::string &word = params.size() > 2 ? params[2] : ""; - bool need_args = cmd.equals_ci("LIST") || cmd.equals_ci("CLEAR"); - - if (!need_args && word.empty()) - { - this->OnSyntaxError(source, cmd); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (!source.AccessFor(ci).HasPriv("BADWORDS") && (!need_args || !source.HasPriv("botserv/administration"))) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "BADWORDS", ci->GetName()); - return; - } - - if (Anope::ReadOnly) - { - source.Reply(_("Sorry, bad words list modification is temporarily disabled.")); - return; - } - - if (cmd.equals_ci("ADD")) - this->DoAdd(source, ci, word); - else if (cmd.equals_ci("DEL")) - this->DoDelete(source, ci, word); - else if (cmd.equals_ci("LIST")) - this->DoList(source, ci, word); - else if (cmd.equals_ci("CLEAR")) - this->DoClear(source, ci); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (subcommand.equals_ci("ADD")) - { - source.Reply(_("Adds \037word\037 to the bad words list for \037channel\037. If \002SINGLE\002 is specified then the user must say the enire word for it to be considered a match." - " If \002START\002 is specified then the user only must say a word that starts with \037word\037." - " Likewise if \002END\002 is specified then the user must only say a word that ends with \037word\037." - " If no argument is specified, then any word which contains \037word\037 will be considered a match.\n" - "\n" - "Examples:\n" - " {command} #anope ADD raw SINGLE\n" - " Adds the bad word \"raw\" to the bad word list of #anope."), - "command"_kw = source.command); - } - else if (subcommand.equals_ci("DEL")) - { - source.Reply(_("Removes \037word\037 from the bad words list for \037channel\037. If a number or list of numbers is given, those entries are deleted.\n" - "\n" - "Examples:\n" - " {command} #anope DEL raw\n" - " Removes the bad word \"raw\" from the bad word list of #anope.\n" - "\n" - " {command} #anope DEL 2-5,7-9\n" - " Removes bad words entries numbered 2 through 5 and 7 through 9 on #anope."), - "command"_kw = source.command); - - } - else if (subcommand.equals_ci("LIST")) - { - source.Reply(_("Lists the badwords for \037channel\037." - " If a wildcard mask is given, only those entries matching the mask are displayed." - " If a list of entry numbers is given, only those entries are shown.\n" - "\n" - "Examples:\n" - " {command} #anope LIST\n" - " Lists the bad words for #anope.\n" - "\n" - " {command} #anope LIST 2-5,7-9\n" - " Lists bad words entries numbered 2 thorough 5 and 7 through 9 on #anope."), - "command"_kw = source.command); - } - else if (subcommand.equals_ci("CLEAR")) - { - source.Reply(_("Clears the bad words for \037channel\037." - "\n" - "\n" - "Example:\n" - " {command} #anope CLEAR\n" - " Clears the bad word list for #anope."), - "command"_kw = source.command); - } - else - { - source.Reply(_("Maintains the bad words list for a channel." - " When a word on the bad words list is said, action may be taken against the offending user.\n" - "\n" - "Use of this command requires the \002{0}\002 privilege on \037channel\037."), - "BADWORDS"); - - CommandInfo *help = source.service->FindCommand("generic/help"); - if (help) - source.Reply(_("\n" - "For help configuring the bad word kicker use \002{msg}{service} HELP KICK BADWORDS\002.\n"//XXX - "\n" - "The \002ADD\002 command adds \037word\037 to the badwords list.\n" - "\002{msg}{service} {help} {command} ADD\002 for more information.\n" - "\n" - "The \002DEL\002 command removes \037word\037 from the badwords list.\n" - "\002{msg}{service} {help} {command} DEL\002 for more information.\n" - "\n" - "The \002LIST\002 and \002CLEAR\002 commands show and clear the bad words list, respectively.\n" - "\002{msg}{service} {help} {command} LIST\002 and \002{msg}{service} {help} {command} CLEAR\002 for more information.\n"), - "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "command"_kw = source.command, "help"_kw = help->cname); - } - return true; - } -}; - -class BSBadwords : public Module -{ - CommandBSBadwords commandbsbadwords; - BadWordsImpl badwords; - EventHandlers events; - BadWordsType bwtype; - - public: - BSBadwords(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandbsbadwords(this) - , badwords(this) - , events(this) - , bwtype(this) - { - bwevents = &events; - } -}; - -MODULE_INIT(BSBadwords) diff --git a/modules/commands/bs_bot.cpp b/modules/commands/bs_bot.cpp deleted file mode 100644 index 726c02ab2..000000000 --- a/modules/commands/bs_bot.cpp +++ /dev/null @@ -1,426 +0,0 @@ -/* BotServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/bs_bot.h" - -class CommandBSBot : public Command -{ - EventHandlers OnBotCreate; - EventHandlers OnBotChange; - EventHandlers OnBotDelete; - - void DoAdd(CommandSource &source, const std::vector ¶ms) - { - const Anope::string &nick = params[1]; - const Anope::string &user = params[2]; - const Anope::string &host = params[3]; - const Anope::string &real = params[4]; - - if (ServiceBot::Find(nick, true)) - { - source.Reply(_("Bot \002{0}\002 already exists."), nick); - return; - } - - Configuration::Block *networkinfo = Config->GetBlock("networkinfo"); - - if (nick.length() > networkinfo->Get("nicklen")) - { - source.Reply(_("Bot nicks may only be \002{0}\002 characters long."), networkinfo->Get("nicklen")); - return; - } - - if (user.length() > networkinfo->Get("userlen")) - { - source.Reply(_("Bot idents may only be \002{0}\002 characters long."), networkinfo->Get("userlen")); - return; - } - - if (host.length() > networkinfo->Get("hostlen")) - { - source.Reply(_("Bot hosts may only be \002{0}\002 characters long."), networkinfo->Get("hostlen")); - return; - } - - if (!IRCD->IsNickValid(nick)) - { - source.Reply(_("Bot nicks may only contain valid nick characters.")); - return; - } - - if (!IRCD->IsIdentValid(user)) - { - source.Reply(_("Bot idents may only contain valid ident characters.")); - return; - } - - if (!IRCD->IsHostValid(host)) - { - source.Reply(_("Bot hosts may only contain valid host characters.")); - return; - } - - /* We check whether the nick is registered, and inform the user - * if so. You need to drop the nick manually before you can use - * it as a bot nick from now on -GD - */ - NickServ::Nick *na = NickServ::FindNick(nick); - if (na) - { - source.Reply(_("\002{0}\002 is already registered!"), na->GetNick()); - return; - } - - User *targ = User::Find(nick, true); - if (targ) - { - source.Reply(_("\002{0}\002 is currently in use."), targ->nick); - return; - } - - ServiceBot *bi = new ServiceBot(nick, user, host, real); - - Log(LOG_ADMIN, source, this) << "ADD " << bi->GetMask() << " " << bi->realname; - - source.Reply(_("\002{0}!{1}@{2}\002 (\002{3}\002) added to the bot list."), bi->nick, bi->GetIdent(), bi->host, bi->realname); - - this->OnBotCreate(&Event::BotCreate::OnBotCreate, bi); - } - - void DoChange(CommandSource &source, const std::vector ¶ms) - { - const Anope::string &oldnick = params[1]; - const Anope::string &nick = params.size() > 2 ? params[2] : ""; - const Anope::string &user = params.size() > 3 ? params[3] : ""; - const Anope::string &host = params.size() > 4 ? params[4] : ""; - const Anope::string &real = params.size() > 5 ? params[5] : ""; - - if (oldnick.empty() || nick.empty()) - { - this->OnSyntaxError(source, "CHANGE"); - return; - } - - ServiceBot *bi = ServiceBot::Find(oldnick, true); - if (!bi) - { - source.Reply(_("Bot \002{0}\002 does not exist."), oldnick); - return; - } - - if (bi->bi->conf) - { - source.Reply(_("Bot \002{0}\002 is not changeable because it is configured in services configuration."), bi->nick.c_str()); - return; - } - - Configuration::Block *networkinfo = Config->GetBlock("networkinfo"); - - if (nick.length() > networkinfo->Get("nicklen")) - { - source.Reply(_("Bot nicknames may only be \002{0}\002 characters long."), networkinfo->Get("nicklen")); - return; - } - - if (user.length() > networkinfo->Get("userlen")) - { - source.Reply(_("Bot usernames may only be \002{0}\002 characters long."), networkinfo->Get("userlen")); - return; - } - - if (host.length() > networkinfo->Get("hostlen")) - { - source.Reply(_("Bot hostnames may only be \002{0}\002 characters long."), networkinfo->Get("hostlen")); - return; - } - - /* Checks whether there *are* changes. - * Case sensitive because we may want to change just the case. - * And we must finally check that the nick is not already - * taken by another bot. - */ - if (nick.equals_cs(bi->nick) - && (user.empty() || user.equals_cs(bi->GetIdent())) - && (host.empty() || host.equals_cs(bi->host)) - && (real.empty() || real.equals_cs(bi->realname))) - { - source.Reply(_("There is no difference between the current settings and the new settings.")); - return; - } - - if (!IRCD->IsNickValid(nick)) - { - source.Reply(_("Bot nicknames may only contain valid nickname characters.")); - return; - } - - if (!user.empty() && !IRCD->IsIdentValid(user)) - { - source.Reply(_("Bot uesrnames may only contain valid username characters.")); - return; - } - - if (!host.empty() && !IRCD->IsHostValid(host)) - { - source.Reply(_("Bot hostnames may only contain valid hostname characters.")); - return; - } - - ServiceBot *newbi = ServiceBot::Find(nick, true); - if (newbi && bi != newbi) - { - source.Reply(_("Bot \002{0}\002 already exists."), newbi->nick); - return; - } - - User *target = User::Find(nick, true); - if (target) - { - source.Reply(_("\002{0}\002 is currently in use."), target->nick); - return; - } - - if (!nick.equals_ci(bi->nick)) - { - /* We check whether the nick is registered, and inform the user - * if so. You need to drop the nick manually before you can use - * it as a bot nick from now on -GD - */ - NickServ::Nick *na = NickServ::FindNick(nick); - if (na) - { - source.Reply(_("\002{0}\002 is already registered."), na->GetNick()); - return; - } - - /* The new nick is really different, so we remove the Q line for the old nick. */ - //XLine x_del(bi->nick); - //IRCD->SendSQLineDel(&x_del); - - /* Add a Q line for the new nick */ - //XLine x(nick, "Reserved for services"); - //IRCD->SendSQLine(NULL, &x); - } - - if (!user.empty()) - { - IRCD->SendQuit(bi, "Quit: Be right back"); - bi->introduced = false; - } - else - IRCD->SendNickChange(bi, nick); - - if (!nick.equals_cs(bi->nick)) - { - bi->SetNewNick(nick); - bi->bi->SetNick(nick); - } - - if (!user.equals_cs(bi->GetIdent())) - { - bi->SetIdent(user); - bi->bi->SetUser(user); - } - if (!host.equals_cs(bi->host)) - { - bi->host = host; - bi->bi->SetHost(host); - } - if (real.equals_cs(bi->realname)) - { - bi->realname = real; - bi->bi->SetRealName(real); - } - - if (!user.empty()) - bi->OnKill(); - - source.Reply(_("Bot \002{0}\002 has been changed to \002{1}!{2}@{3}\002 (\002{4}\002)."), oldnick, bi->nick, bi->GetIdent(), bi->host, bi->realname); - Log(LOG_ADMIN, source, this) << "CHANGE " << oldnick << " to " << bi->GetMask() << " " << bi->realname; - - this->OnBotChange(&Event::BotChange::OnBotChange, bi); - } - - void DoDel(CommandSource &source, const std::vector ¶ms) - { - const Anope::string &nick = params[1]; - - if (nick.empty()) - { - this->OnSyntaxError(source, "DEL"); - return; - } - - ServiceBot *bi = ServiceBot::Find(nick, true); - if (!bi) - { - source.Reply(_("Bot \002{0}\002 does not exist."), nick); - return; - } - - if (bi->bi->conf) - { - source.Reply(_("Bot \002{0}\002 is can not be deleted because it is configured in services configuration."), bi->nick); - return; - } - - this->OnBotDelete(&Event::BotDelete::OnBotDelete, bi); - - Log(LOG_ADMIN, source, this) << "DEL " << bi->nick; - - source.Reply(_("Bot \002{0}\002 has been deleted."), bi->nick); - delete bi; - } - - public: - CommandBSBot(Module *creator) : Command(creator, "botserv/bot", 1, 6) - , OnBotCreate(creator) - , OnBotChange(creator) - , OnBotDelete(creator) - { - this->SetDesc(_("Maintains network bot list")); - this->SetSyntax(_("\002ADD \037nicknae\037 \037username\037 \037hostname\037 \037realname\037\002")); - this->SetSyntax(_("\002CHANGE \037oldnickname\037 \037newnickname\037 [\037username\037 [\037hostname\037 [\037realname\037]]]\002")); - this->SetSyntax(_("\002DEL \037nickname\037\002")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &cmd = params[0]; - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - if (cmd.equals_ci("ADD")) - { - if (!source.HasCommand("botserv/bot/add")) - { - source.Reply(_("Access denied. You do not have access to the operator command \002{0}\002."), "botserv/bot/add"); - return; - } - - // ADD nick user host real - 5 - if (params.size() < 5) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - std::vector tempparams = params; - // ADD takes less params than CHANGE, so we need to take 6 if given and append it with a space to 5. - if (tempparams.size() >= 6) - tempparams[4] = tempparams[4] + " " + tempparams[5]; - - this->DoAdd(source, tempparams); - } - else if (cmd.equals_ci("CHANGE")) - { - // CHANGE oldn newn user host real - 6 - // but only oldn and newn are required - if (!source.HasCommand("botserv/bot/change")) - { - source.Reply(_("Access denied. You do not have access to the operator command \002{0}\002."), "botserv/bot/change"); - return; - } - - if (params.size() < 3) - { - this->OnSyntaxError(source, "CHANGE"); - return; - } - - this->DoChange(source, params); - } - else if (cmd.equals_ci("DEL")) - { - // DEL nick - if (!source.HasCommand("botserv/bot/del")) - { - source.Reply(_("Access denied. You do not have access to the operator command \002{0}\002."), "botserv/bot/del"); - return; - } - - if (params.size() < 1) - { - this->OnSyntaxError(source, "DEL"); - return; - } - - this->DoDel(source, params); - } - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (subcommand.equals_ci("ADD")) - source.Reply(_("\002{command} ADD\002 adds a bot with the given \037nickname\037, \037username\037, \037hostname\037 and \037realname\037." - " You can not create a bot with a nickname that is currently registered. If an unregistered user is currently using the nick, they will be killed.\n" - " Once a bot is created, users will be able to assign the bot to their channels. This command requires the opererator privilege for command \002{0}\002." - "\n" - "Example:\n" - " {command} ADD Botox Botox services.anope.org Botox\n" - " Adds a service bot with nickname \"Botox\", username \"Botox\", hostname \"services.anope.org\", and realname \"Botox\" to the bot list."), - "botserv/bot/add", "command"_kw = source.command); - else if (subcommand.equals_ci("CHANGE")) - source.Reply(_("\002{command} CHANGE\002 allows changing the \037nickname\037, \037username\037, \037hostname\037 and \037realname\037 of bot \037oldnickname\037." - " If a new username, hostname, or realname is specified, then the bot \037nickname\037 will quit and rejoin all of its channels using the new mask." - " Otherwise, the bot simply change nick to \037newnickname\037. All settings on the bot, such as channels and no-bot, are retained." - " This command requires the operator privilege for command \002{0}\002." - "\n" - "Example:\n" - " {command} CHANGE Botox peer connection reset.by peer\n" - " Renames the bot \"Botox\" to \"peer\" with the given username, hostname, and realname."), - "botserv/bot/change", "command"_kw = source.command); - else if (subcommand.equals_ci("DEL")) - source.Reply(_("\002{command} DEL\002 removes the bot \037nickname\037 from the bot list. The bot will quit from any channels it is in, and will not be replaced." - " This command requires the operator privilege for command \002{0}\002.\n" - "\n" - "Example:\n" - " {command} DEL peer\n" - " Removes the bot \"peer\" from the bot list."), - "botserv/bot/del", "command"_kw = source.command); - else - { - source.Reply(_("Allows Services Operators to create, modify, and delete bots that users will be able to use on their channels.")); - - CommandInfo *help = source.service->FindCommand("generic/help"); - if (help) - source.Reply(_("\n" - "The \002ADD\002 command adds a bot with the given \037nickname\037, \037username\037, \037hostname\037 and \037realname\037 to the bot list.\n" - "\002{msg}{service} {help} {command} ADD\002 for more information.\n" - "\n" - "The \002CHANGE\002 command allows changing the \037nickname\037, \037username\037, \037hostname\037 and \037realname\037 of bot \037oldnickname\037.\n" - "\002{msg}{service} {help} {command} CHANGE\002 for more information.\n" - "\n" - "The \002{command} DEL\002 removes the bot \037nickname\037 from the bot list.\n" - "\002{msg}{service} {help} {command} DEL\002 for more information."), - "msg"_kw = Config->StrictPrivmsg, "help"_kw = help->cname, "command"_kw = source.command); - } - return true; - } -}; - -class BSBot : public Module -{ - CommandBSBot commandbsbot; - - public: - BSBot(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandbsbot(this) - { - - } -}; - -MODULE_INIT(BSBot) diff --git a/modules/commands/bs_botlist.cpp b/modules/commands/bs_botlist.cpp deleted file mode 100644 index ce899719e..000000000 --- a/modules/commands/bs_botlist.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* BotServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandBSBotList : public Command -{ - public: - CommandBSBotList(Module *creator) : Command(creator, "botserv/botlist", 0, 0) - { - this->SetDesc(_("Lists available bots")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - unsigned count = 0; - ListFormatter list(source.GetAccount()); - - list.AddColumn(_("Nick")).AddColumn(_("Mask")); - - for (BotInfo *bi : Serialize::GetObjects(botinfo)) - { - if (source.HasPriv("botserv/administration") || !bi->GetOperOnly()) - { - ++count; - ListFormatter::ListEntry entry; - entry["Nick"] = (bi->GetOperOnly() ? "* " : "") + bi->GetNick(); - entry["Mask"] = bi->GetUser() + "@" + bi->GetHost(); - list.AddEntry(entry); - } - } - - std::vector replies; - list.Process(replies); - - if (!count) - { - source.Reply(_("There are no bots available")); - return; - } - - source.Reply(_("Bot list:")); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("{0} bots available."), count); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - ServiceBot *bi; - Anope::string name; - Command::FindCommandFromService("botserv/assign", bi, name); - if (!bi) - return false; - - source.Reply(_("Lists all available bots. You may use the \002{msg}{service} {assign}\002 command to assign a bot to your channel." - "The bot names are vanity; they all proviate the same commands and features."), - "msg"_kw = Config->StrictPrivmsg, "service"_kw = bi->nick, "assign"_kw = name); - if (source.HasPriv("botserv/administration")) - source.Reply(_("Bots prefixed by a * are reserved for Services Operators with the privilege \002{0}\002."), - "botserv/administration"); - source.Reply(_("\n" - "Example:\n" - " {command} BOTLIST"), - "command"_kw = source.command); - return true; - } -}; - -class BSBotList : public Module -{ - CommandBSBotList commandbsbotlist; - - public: - BSBotList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandbsbotlist(this) - { - - } -}; - -MODULE_INIT(BSBotList) diff --git a/modules/commands/bs_control.cpp b/modules/commands/bs_control.cpp deleted file mode 100644 index 52c0933c5..000000000 --- a/modules/commands/bs_control.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* BotServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandBSSay : public Command -{ - public: - CommandBSSay(Module *creator) : Command(creator, "botserv/say", 2, 2) - { - this->SetDesc(_("Makes the bot say the specified text on the specified channel")); - this->SetSyntax(_("\037channel\037 \037text\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &text = params[1]; - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (!source.AccessFor(ci).HasPriv("SAY") && !source.HasPriv("botserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SAY", ci->GetName()); - return; - } - - if (!ci->GetBot()) - { - source.Reply(_("There is no bot assigned to \002{0}\002. One must be assigned to the channel before this command can be used."), ci->GetName()); - ServiceBot *bi; - Anope::string name; - Command::FindCommandFromService("botserv/assign", bi, name); - CommandInfo *help = source.service->FindCommand("generic/help"); - if (bi && help) - source.Reply(_("See \002{msg}{service} {help} {command}\002 for information on assigning bots."), - "msg"_kw = Config->StrictPrivmsg, "service"_kw = bi->nick, "help"_kw = help->cname, "command"_kw = name); - return; - } - - if (!ci->c || !ci->c->FindUser(ci->GetBot())) - { - source.Reply(_("Bot \002{0}\002 is not on channel \002{1}\002."), ci->GetBot()->nick, ci->GetName()); - return; - } - - if (text[0] == '\001') - { - this->OnSyntaxError(source, ""); - return; - } - - IRCD->SendPrivmsg(ci->GetBot(), ci->GetName(), "%s", text.c_str()); - ci->GetBot()->lastmsg = Anope::CurTime; - - bool override = !source.AccessFor(ci).HasPriv("SAY"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to say: " << text; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Makes the bot say the given \037text\037 on \037channel\037.\n" - "\n" - "Example:\n" - " {command} #anope mmm pie\n" - " Makes the assigned service bot say \"mmm pie\".")); - return true; - } -}; - -class CommandBSAct : public Command -{ - public: - CommandBSAct(Module *creator) : Command(creator, "botserv/act", 2, 2) - { - this->SetDesc(_("Makes the bot do the equivalent of a \"/me\" command")); - this->SetSyntax(_("\037channel\037 \037text\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - Anope::string message = params[1]; - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (!source.AccessFor(ci).HasPriv("SAY") && !source.HasPriv("botserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SAY", ci->GetName()); - return; - } - - if (!ci->GetBot()) - { - source.Reply(_("There is no bot assigned to \002{0}\002. One must be assigned to the channel before this command can be used."), ci->GetName()); - ServiceBot *bi; - Anope::string name; - Command::FindCommandFromService("botserv/assign", bi, name); - CommandInfo *help = source.service->FindCommand("generic/help"); - if (bi && help) - source.Reply(_("See \002{msg}{service} {help} {command}\002 for information on assigning bots."), - "msg"_kw = Config->StrictPrivmsg, "service"_kw = bi->nick, "help"_kw = help->cname, "command"_kw = name); - return; - } - - if (!ci->c || !ci->c->FindUser(ci->GetBot())) - { - source.Reply(_("Bot \002{0}\002 is not on channel \002{1}\002."), ci->GetBot()->nick, ci->GetName()); - return; - } - - message = message.replace_all_cs("\1", ""); - if (message.empty()) - return; - - IRCD->SendAction(ci->GetBot(), ci->GetName(), "%s", message.c_str()); - ci->GetBot()->lastmsg = Anope::CurTime; - - bool override = !source.AccessFor(ci).HasPriv("SAY"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to say: " << message; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Makes the assigned bot do the equivalent of a \"/me\" command on \037channel\037 using the given \037text\037.\n" - "\n" - "Example:\n" - " {command} #anope slaps Cronus\n" - " Shows the assigned service bot \"slapping\" Cronus."), - "command"_kw = source.command); - return true; - } -}; - -class BSControl : public Module -{ - CommandBSSay commandbssay; - CommandBSAct commandbsact; - - public: - BSControl(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandbssay(this), commandbsact(this) - { - - } -}; - -MODULE_INIT(BSControl) diff --git a/modules/commands/bs_info.cpp b/modules/commands/bs_info.cpp deleted file mode 100644 index 6d74c308e..000000000 --- a/modules/commands/bs_info.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/* BotServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - * - * - */ - -#include "module.h" -#include "modules/bs_info.h" - -class CommandBSInfo : public Command -{ - EventHandlers &onbotinfo; - - public: - CommandBSInfo(Module *creator, EventHandlers &event) : Command(creator, "botserv/info", 1, 1), onbotinfo(event) - { - this->SetSyntax(_("{\037channel\037 | \037nickname\037}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &query = params[0]; - - ServiceBot *bi = ServiceBot::Find(query, true); - ChanServ::Channel *ci = ChanServ::Find(query); - InfoFormatter info(source.nc); - - if (bi) - { - source.Reply(_("Information for bot \002%s\002:"), bi->nick.c_str()); - info[_("Mask")] = bi->GetIdent() + "@" + bi->host; - info[_("Real name")] = bi->realname; - info[_("Created")] = Anope::strftime(bi->bi->GetCreated(), source.GetAccount()); - info[_("Options")] = bi->bi->GetOperOnly() ? _("Private") : _("None"); - info[_("Used on")] = stringify(bi->GetChannelCount()) + " channel(s)"; - - this->onbotinfo(&Event::ServiceBotEvent::OnServiceBot, source, bi, ci, info); - - std::vector replies; - info.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - if (source.HasPriv("botserv/administration")) - { - Anope::string buf; - for (ChanServ::Channel *ci2 : bi->GetChannels()) - buf += " " + ci2->GetName(); - source.Reply(buf); - } - - } - else if (ci) - { - if (!source.AccessFor(ci).HasPriv("INFO") && !source.HasPriv("botserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "INFO", ci->GetName()); - return; - } - - source.Reply(_("Information for channel \002{0}\002:"), ci->GetName()); - info[_("Bot nick")] = ci->GetBot() ? ci->GetBot()->nick : _("not assigned yet"); - - Anope::string enabled = Language::Translate(source.nc, _("Enabled")); - Anope::string disabled = Language::Translate(source.nc, _("Disabled")); - - this->onbotinfo(&Event::ServiceBotEvent::OnServiceBot, source, bi, ci, info); - - std::vector replies; - info.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - else - source.Reply(_("\002{0}\002 is not a valid bot or registered channel."), query); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Allows you to see {0} information about a channel or a bot." - " If the parameter is a channel, then you'll get information such as enabled kickers." - " If the parameter is a bot nickname, you'll get information about a bot, such as creation time and number of channels it is on."), - source.service->nick); - return true; - } - - const Anope::string GetDesc(CommandSource &source) const override - { - return Anope::printf(Language::Translate(source.GetAccount(), _("Allows you to see %s information about a channel or a bot")), source.service->nick.c_str()); - } -}; - -class BSInfo : public Module -{ - CommandBSInfo commandbsinfo; - EventHandlers onbotinfo; - - public: - BSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandbsinfo(this, onbotinfo) - , onbotinfo(this) - { - - } -}; - -MODULE_INIT(BSInfo) diff --git a/modules/commands/bs_kick.cpp b/modules/commands/bs_kick.cpp deleted file mode 100644 index 70ed5b2ad..000000000 --- a/modules/commands/bs_kick.cpp +++ /dev/null @@ -1,1664 +0,0 @@ -/* BotServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - * - * - */ - -#include "module.h" -#include "modules/bs_kick.h" -#include "modules/bs_badwords.h" -#include "modules/bs_info.h" - -static Module *me; - -enum TTBType -{ - TTB_BOLDS, - TTB_COLORS, - TTB_REVERSES, - TTB_UNDERLINES, - TTB_BADWORDS, - TTB_CAPS, - TTB_FLOOD, - TTB_REPEAT, - TTB_ITALICS, - TTB_AMSGS, - TTB_SIZE -}; - -class KickerDataImpl : public KickerData -{ - public: - KickerDataImpl(Serialize::TypeBase *type) : KickerData(type) { } - KickerDataImpl(Serialize::TypeBase *type, Serialize::ID id) : KickerData(type, id) { } - - ChanServ::Channel *GetChannel() override; - void SetChannel(ChanServ::Channel *) override; - - bool GetAmsgs() override; - void SetAmsgs(const bool &) override; - - bool GetBadwords() override; - void SetBadwords(const bool &) override; - - bool GetBolds() override; - void SetBolds(const bool &) override; - - bool GetCaps() override; - void SetCaps(const bool &) override; - - bool GetColors() override; - void SetColors(const bool &) override; - - bool GetFlood() override; - void SetFlood(const bool &) override; - - bool GetItalics() override; - void SetItalics(const bool &) override; - - bool GetRepeat() override; - void SetRepeat(const bool &) override; - - bool GetReverses() override; - void SetReverses(const bool &) override; - - bool GetUnderlines() override; - void SetUnderlines(const bool &) override; - - int16_t GetTTBBolds() override; - void SetTTBBolds(const int16_t &) override; - - int16_t GetTTBColors() override; - void SetTTBColors(const int16_t &) override; - - int16_t GetTTBReverses() override; - void SetTTBReverses(const int16_t &) override; - - int16_t GetTTBUnderlines() override; - void SetTTBUnderlines(const int16_t &) override; - - int16_t GetTTBBadwords() override; - void SetTTBBadwords(const int16_t &) override; - - int16_t GetTTBCaps() override; - void SetTTBCaps(const int16_t &) override; - - int16_t GetTTBFlood() override; - void SetTTBFlood(const int16_t &) override; - - int16_t GetTTBRepeat() override; - void SetTTBRepeat(const int16_t &) override; - - int16_t GetTTBItalics() override; - void SetTTBItalics(const int16_t &) override; - - int16_t GetTTBAmsgs() override; - void SetTTBAmsgs(const int16_t &) override; - - int16_t GetCapsMin() override; - void SetCapsMin(const int16_t &) override; - - int16_t GetCapsPercent() override; - void SetCapsPercent(const int16_t &) override; - - int16_t GetFloodLines() override; - void SetFloodLines(const int16_t &) override; - - int16_t GetFloodSecs() override; - void SetFloodSecs(const int16_t &) override; - - int16_t GetRepeatTimes() override; - void SetRepeatTimes(const int16_t &) override; - - bool GetDontKickOps() override; - void SetDontKickOps(const bool &) override; - - bool GetDontKickVoices() override; - void SetDontKickVoices(const bool &) override; -}; - -class KickerDataType : public Serialize::Type -{ - public: - Serialize::ObjectField channel; - Serialize::Field amsgs, badwords, bolds, caps, colors, flood, italics, repeat, reverses, underlines; - Serialize::Field ttb_bolds, ttb_colors, ttb_reverses, ttb_underlines, ttb_badwords, ttb_caps, ttb_flood, ttb_repeat, ttb_italics, ttb_amsgs, - capsmin, capspercent, - floodlines, floodsecs, - repeattimes; - Serialize::Field dontkickops, dontkickvoices; - - KickerDataType(Module *owner) : Serialize::Type(owner, "KickerData") - , channel(this, "channel", true) - , amsgs(this, "amsgs") - , badwords(this, "badwords") - , bolds(this, "bolds") - , caps(this, "caps") - , colors(this, "colors") - , flood(this, "flood") - , italics(this, "italics") - , repeat(this, "repeat") - , reverses(this, "reverses") - , underlines(this, "underlines") - , ttb_bolds(this, "ttb_bolds") - , ttb_colors(this, "ttb_colors") - , ttb_reverses(this, "ttb_reverses") - , ttb_underlines(this, "ttb_underlines") - , ttb_badwords(this, "ttb_badwords") - , ttb_caps(this, "ttb_caps") - , ttb_flood(this, "ttb_flood") - , ttb_repeat(this, "ttb_repeat") - , ttb_italics(this, "ttb_italics") - , ttb_amsgs(this, "ttb_amsgs") - , capsmin(this, "capsmin") - , capspercent(this, "capspercent") - , floodlines(this, "floodlines") - , floodsecs(this, "floodsecs") - , repeattimes(this, "repeattimes") - , dontkickops(this, "dontkickops") - , dontkickvoices(this, "dontkickvoices") - { - } -}; - -ChanServ::Channel *KickerDataImpl::GetChannel() -{ - return Get(&KickerDataType::channel); -} - -void KickerDataImpl::SetChannel(ChanServ::Channel *channel) -{ - Set(&KickerDataType::amsgs, channel); -} - -bool KickerDataImpl::GetAmsgs() -{ - return Get(&KickerDataType::amsgs); -} - -void KickerDataImpl::SetAmsgs(const bool &b) -{ - Set(&KickerDataType::amsgs, b); -} - -bool KickerDataImpl::GetBadwords() -{ - return Get(&KickerDataType::badwords); -} - -void KickerDataImpl::SetBadwords(const bool &b) -{ - Set(&KickerDataType::badwords, b); -} - -bool KickerDataImpl::GetBolds() -{ - return Get(&KickerDataType::bolds); -} - -void KickerDataImpl::SetBolds(const bool &b) -{ - Set(&KickerDataType::bolds, b); -} - -bool KickerDataImpl::GetCaps() -{ - return Get(&KickerDataType::caps); -} - -void KickerDataImpl::SetCaps(const bool &b) -{ - Set(&KickerDataType::caps, b); -} - -bool KickerDataImpl::GetColors() -{ - return Get(&KickerDataType::colors); -} - -void KickerDataImpl::SetColors(const bool &b) -{ - Set(&KickerDataType::colors, b); -} - -bool KickerDataImpl::GetFlood() -{ - return Get(&KickerDataType::flood); -} - -void KickerDataImpl::SetFlood(const bool &b) -{ - Set(&KickerDataType::flood, b); -} - -bool KickerDataImpl::GetItalics() -{ - return Get(&KickerDataType::italics); -} - -void KickerDataImpl::SetItalics(const bool &b) -{ - Set(&KickerDataType::italics, b); -} - -bool KickerDataImpl::GetRepeat() -{ - return Get(&KickerDataType::repeat); -} - -void KickerDataImpl::SetRepeat(const bool &b) -{ - Set(&KickerDataType::repeat, b); -} - -bool KickerDataImpl::GetReverses() -{ - return Get(&KickerDataType::reverses); -} - -void KickerDataImpl::SetReverses(const bool &b) -{ - Set(&KickerDataType::reverses, b); -} - -bool KickerDataImpl::GetUnderlines() -{ - return Get(&KickerDataType::underlines); -} - -void KickerDataImpl::SetUnderlines(const bool &b) -{ - Set(&KickerDataType::underlines, b); -} - -int16_t KickerDataImpl::GetTTBBolds() -{ - return Get(&KickerDataType::ttb_bolds); -} - -void KickerDataImpl::SetTTBBolds(const int16_t &i) -{ - Set(&KickerDataType::ttb_bolds, i); -} - -int16_t KickerDataImpl::GetTTBColors() -{ - return Get(&KickerDataType::ttb_colors); -} - -void KickerDataImpl::SetTTBColors(const int16_t &i) -{ - Set(&KickerDataType::ttb_colors, i); -} - -int16_t KickerDataImpl::GetTTBReverses() -{ - return Get(&KickerDataType::ttb_reverses); -} - -void KickerDataImpl::SetTTBReverses(const int16_t &i) -{ - Set(&KickerDataType::ttb_reverses, i); -} - -int16_t KickerDataImpl::GetTTBUnderlines() -{ - return Get(&KickerDataType::ttb_underlines); -} - -void KickerDataImpl::SetTTBUnderlines(const int16_t &i) -{ - Set(&KickerDataType::ttb_underlines, i); -} - -int16_t KickerDataImpl::GetTTBBadwords() -{ - return Get(&KickerDataType::ttb_badwords); -} - -void KickerDataImpl::SetTTBBadwords(const int16_t &i) -{ - Set(&KickerDataType::ttb_badwords, i); -} - -int16_t KickerDataImpl::GetTTBCaps() -{ - return Get(&KickerDataType::ttb_caps); -} - -void KickerDataImpl::SetTTBCaps(const int16_t &i) -{ - Set(&KickerDataType::ttb_caps, i); -} - -int16_t KickerDataImpl::GetTTBFlood() -{ - return Get(&KickerDataType::ttb_flood); -} - -void KickerDataImpl::SetTTBFlood(const int16_t &i) -{ - Set(&KickerDataType::ttb_flood, i); -} - -int16_t KickerDataImpl::GetTTBRepeat() -{ - return Get(&KickerDataType::ttb_repeat); -} - -void KickerDataImpl::SetTTBRepeat(const int16_t &i) -{ - Set(&KickerDataType::ttb_repeat, i); -} - -int16_t KickerDataImpl::GetTTBItalics() -{ - return Get(&KickerDataType::ttb_italics); -} - -void KickerDataImpl::SetTTBItalics(const int16_t &i) -{ - Set(&KickerDataType::ttb_italics, i); -} - -int16_t KickerDataImpl::GetTTBAmsgs() -{ - return Get(&KickerDataType::ttb_amsgs); -} - -void KickerDataImpl::SetTTBAmsgs(const int16_t &i) -{ - Set(&KickerDataType::ttb_amsgs, i); -} - -int16_t KickerDataImpl::GetCapsMin() -{ - return Get(&KickerDataType::capsmin); -} - -void KickerDataImpl::SetCapsMin(const int16_t &i) -{ - Set(&KickerDataType::capsmin, i); -} - -int16_t KickerDataImpl::GetCapsPercent() -{ - return Get(&KickerDataType::capspercent); -} - -void KickerDataImpl::SetCapsPercent(const int16_t &i) -{ - Set(&KickerDataType::capspercent, i); -} - -int16_t KickerDataImpl::GetFloodLines() -{ - return Get(&KickerDataType::floodlines); -} - -void KickerDataImpl::SetFloodLines(const int16_t &i) -{ - Set(&KickerDataType::floodlines, i); -} - -int16_t KickerDataImpl::GetFloodSecs() -{ - return Get(&KickerDataType::floodsecs); -} - -void KickerDataImpl::SetFloodSecs(const int16_t &i) -{ - Set(&KickerDataType::floodsecs, i); -} - -int16_t KickerDataImpl::GetRepeatTimes() -{ - return Get(&KickerDataType::repeattimes); -} - -void KickerDataImpl::SetRepeatTimes(const int16_t &i) -{ - Set(&KickerDataType::repeattimes, i); -} - -bool KickerDataImpl::GetDontKickOps() -{ - return Get(&KickerDataType::dontkickops); -} - -void KickerDataImpl::SetDontKickOps(const bool &b) -{ - Set(&KickerDataType::dontkickops, b); -} - -bool KickerDataImpl::GetDontKickVoices() -{ - return Get(&KickerDataType::dontkickvoices); -} - -void KickerDataImpl::SetDontKickVoices(const bool &b) -{ - Set(&KickerDataType::dontkickvoices, b); -} - -class CommandBSKick : public Command -{ - public: - CommandBSKick(Module *creator) : Command(creator, "botserv/kick", 0) - { - this->SetDesc(_("Configures kickers")); - this->SetSyntax(_("\037option\037 \037channel\037 {\037ON|OFF\037} [\037settings\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Configures bot kickers." - " Use of this command requires the \002SET\002 privilege on \037channel\037.\n" - "\n" - "Available kickers:")); - - Anope::string this_name = source.command; - for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) - { - const Anope::string &c_name = it->first; - const CommandInfo &info = it->second; - - if (c_name.find_ci(this_name + " ") == 0) - { - ServiceReference command("Command", info.name); - if (command) - { - source.command = c_name; - command->OnServHelp(source); - } - } - } - - CommandInfo *help = source.service->FindCommand("generic/help"); - if (help) - source.Reply(_("See \002{0}{1} {2} {3} \037option\037\002 for more information on a specific option."), - Config->StrictPrivmsg, source.service->nick, help->cname, this_name); - - return true; - } -}; - -class CommandBSKickBase : public Command -{ - public: - CommandBSKickBase(Module *creator, const Anope::string &cname, int minarg, int maxarg) : Command(creator, cname, minarg, maxarg) - { - } - - virtual void Execute(CommandSource &source, const std::vector ¶ms) override anope_abstract; - - virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) override anope_abstract; - - protected: - bool CheckArguments(CommandSource &source, const std::vector ¶ms, ChanServ::Channel* &ci) - { - const Anope::string &chan = params[0]; - const Anope::string &option = params[1]; - - ci = ChanServ::Find(chan); - - if (Anope::ReadOnly) - source.Reply(_("Sorry, kicker configuration is temporarily disabled.")); - else if (ci == NULL) - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - else if (option.empty()) - this->OnSyntaxError(source, ""); - else if (!option.equals_ci("ON") && !option.equals_ci("OFF")) - this->OnSyntaxError(source, ""); - else if (!source.AccessFor(ci).HasPriv("SET") && !source.HasPriv("botserv/administration")) - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - else if (!ci->GetBot()) - source.Reply(_("There is no bot assigned to \002{0}\002."), ci->GetName()); - else - return true; - - return false; - } - - bool CheckTTB(CommandSource &source, const Anope::string &ttb, int16_t &i) - { - i = 0; - if (ttb.empty()) - return true; - - try - { - i = convertTo(ttb); - if (i < 0) - throw ConvertException(); - } - catch (const ConvertException &) - { - i = 0; - source.Reply(_("\002{0}\002 can not be taken as times to ban. Times to ban must be a positive integer."), ttb); - return false; - } - - return true; - } - - void Process(CommandSource &source, ChanServ::Channel *ci, const Anope::string ¶m, const Anope::string &ttb, void (KickerData::*setter)(const bool &), void (KickerData::*ttbsetter)(const int16_t &), const Anope::string &optname) - { - KickerData *kd = ci->GetRef(kickerdata); - - if (param.equals_ci("ON")) - { - int16_t i; - if (!CheckTTB(source, ttb, i)) - return; - - (kd->*setter)(true); - (kd->*ttbsetter)(i); - - if (i) - source.Reply(_("Bot will now kick for \002{0}\002, and will place a ban after \002{1}\002 kicks for the same user."), optname, i); - else - source.Reply(_("Bot will now kick for \002{0}\002."), optname); - - bool override = !source.AccessFor(ci).HasPriv("SET"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enable the " << optname << " kicker"; - } - else if (param.equals_ci("OFF")) - { - bool override = !source.AccessFor(ci).HasPriv("SET"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable the " << optname << " kicker"; - - (kd->*setter)(false); - (kd->*ttbsetter)(0); - - source.Reply(_("Bot won't kick for \002{0}\002 anymore."), optname); - } - else - this->OnSyntaxError(source, ""); - } -}; - -class CommandBSKickAMSG : public CommandBSKickBase -{ - public: - CommandBSKickAMSG(Module *creator) : CommandBSKickBase(creator, "botserv/kick/amsg", 2, 3) - { - this->SetDesc(_("Configures AMSG kicker")); - this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci; - if (CheckArguments(source, params, ci)) - Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetAmsgs, &KickerData::SetTTBAmsgs, "amsgs"); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets the AMSG kicker on or off on \037channel\037. When enabled, the bot will kick users who send the same message to multiple channels where {0} bots are.\n" - "\n" - "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system."), - source.service->nick); - return true; - } -}; - -class CommandBSKickBadwords : public CommandBSKickBase -{ - public: - CommandBSKickBadwords(Module *creator) : CommandBSKickBase(creator, "botserv/kick/badwords", 2, 3) - { - this->SetDesc(_("Configures badwords kicker")); - this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci; - if (CheckArguments(source, params, ci)) - Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetBadwords, &KickerData::SetTTBBadwords, "badwords"); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - ServiceBot *bi; - Anope::string name; - CommandInfo *help; - source.Reply(_("Sets the bad words kicker on or off on \037channel\037. When enabled, the bot will kick users who say certain words on the channel.")); - if (Command::FindCommandFromService("botserv/badwords", bi, name) && (help = bi->FindCommand("generic/help"))) - source.Reply(_("You can define bad words for your channel using the \002{0}\002 command. See \002{1}{2} {3} {4}\002 for more information."), - name, Config->StrictPrivmsg, bi->nick, help->cname, name); - source.Reply(_("\n" - "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); - return true; - } -}; - -class CommandBSKickBolds : public CommandBSKickBase -{ - public: - CommandBSKickBolds(Module *creator) : CommandBSKickBase(creator, "botserv/kick/bolds", 2, 3) - { - this->SetDesc(_("Configures bolds kicker")); - this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci; - if (CheckArguments(source, params, ci)) - Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetBolds, &KickerData::SetTTBBolds, "bolds"); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets the bolds kicker on or off on \037channel\037. When enabled, the bot will kick users who use \002bolds\002.\n" - "\n" - "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); - return true; - } -}; - -class CommandBSKickCaps : public CommandBSKickBase -{ - public: - CommandBSKickCaps(Module *creator) : CommandBSKickBase(creator, "botserv/kick/caps", 2, 5) - { - this->SetDesc(_("Configures caps kicker")); - this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037 [\037min\037 [\037percent\037]]]\002")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci; - if (!CheckArguments(source, params, ci)) - return; - - KickerData *kd = GetKickerData(ci); - - if (params[1].equals_ci("ON")) - { - const Anope::string &ttb = params.size() > 2 ? params[2] : "", - &min = params.size() > 3 ? params[3] : "", - &percent = params.size() > 4 ? params[4] : ""; - - int16_t i; - if (!CheckTTB(source, ttb, i)) - return; - - kd->SetCaps(true); - kd->SetTTBCaps(i); - - kd->SetCapsMin(10); - try - { - kd->SetCapsMin(convertTo(min)); - } - catch (const ConvertException &) { } - if (kd->GetCapsMin() < 1) - kd->SetCapsMin(10); - - try - { - kd->SetCapsPercent(convertTo(percent)); - } - catch (const ConvertException &) { } - if (kd->GetCapsPercent() < 1 || kd->GetCapsPercent() > 100) - kd->SetCapsPercent(25); - - if (i) - source.Reply(_("Bot will now kick for \002caps\002 if they constitute at least {0} characters and {1}% of the entire message, and will place a ban after {2} kicks for the same user."), kd->GetCapsMin(), kd->GetCapsPercent(), i); - else - source.Reply(_("Bot will now kick for \002caps\002 if they constitute at least {0} characters and {1}% of the entire message."), kd->GetCapsMin(), kd->GetCapsPercent()); - } - else - { - kd->SetCaps(false); - kd->SetTTBCaps(0); - kd->SetCapsMin(0); - kd->SetCapsPercent(0); - source.Reply(_("Bot won't kick for \002caps\002 anymore.")); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets the caps kicker on or off on \037channel\037. When enabled, the bot will kick users who talk in CAPS." - " The bot kicks only if there are at least \002min\002 caps and they constitute at least \002percent\002% of the total text line." - " (if not given, it defaults to 10 characters and 25%).\n" - "\n" - "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); - return true; - } -}; - -class CommandBSKickColors : public CommandBSKickBase -{ - public: - CommandBSKickColors(Module *creator) : CommandBSKickBase(creator, "botserv/kick/colors", 2, 3) - { - this->SetDesc(_("Configures color kicker")); - this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci; - if (CheckArguments(source, params, ci)) - Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetColors, &KickerData::SetTTBColors, "colors"); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets the colors kicker on or off on \037channel\037. When enabled, the bot will kick users who use \00304c\00307o\00308l\00303o\00306r\00312s\017.\n" - "\n" - "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); - return true; - } -}; - -class CommandBSKickFlood : public CommandBSKickBase -{ - public: - CommandBSKickFlood(Module *creator) : CommandBSKickBase(creator, "botserv/kick/flood", 2, 5) - { - this->SetDesc(_("Configures flood kicker")); - this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037 [\037lines\037 [\037seconds\037]]]\002")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci; - if (!CheckArguments(source, params, ci)) - return; - - KickerData *kd = GetKickerData(ci); - - if (params[1].equals_ci("ON")) - { - const Anope::string &ttb = params.size() > 2 ? params[2] : "", - &lines = params.size() > 3 ? params[3] : "", - &secs = params.size() > 4 ? params[4] : ""; - - int16_t i; - if (!CheckTTB(source, ttb, i)) - return; - - kd->SetFlood(true); - kd->SetTTBFlood(i); - - kd->SetFloodLines(6); - try - { - kd->SetFloodLines(convertTo(lines)); - } - catch (const ConvertException &) { } - if (kd->GetFloodLines() < 2) - kd->SetFloodLines(6); - - kd->SetFloodSecs(10); - try - { - kd->SetFloodSecs(convertTo(secs)); - } - catch (const ConvertException &) { } - if (kd->GetFloodSecs() < 1) - kd->SetFloodSecs(10); - if (kd->GetFloodSecs() > Config->GetModule(me)->Get("keepdata")) - kd->SetFloodSecs(Config->GetModule(me)->Get("keepdata")); - - if (i) - source.Reply(_("Bot will now kick for \002flood\002 ({0} lines in {1} seconds and will place a ban after {2} kicks for the same user."), kd->GetFloodLines(), kd->GetFloodSecs(), i); - else - source.Reply(_("Bot will now kick for \002flood\002 ({0} lines in {1} seconds)."), kd->GetFloodLines(), kd->GetFloodSecs()); - } - else if (params[1].equals_ci("OFF")) - { - kd->SetFlood(false); - kd->SetFloodLines(0); - kd->SetFloodSecs(0); - source.Reply(_("Bot won't kick for \002flood\002 anymore.")); - } - else - this->OnSyntaxError(source, params[1]); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets the flood kicker on or off on \037channel\037. When enabled, the bot to kick users who are flooding the channel with at least \037lines\037 lines in \037seconds\037 seconds. If not given, it defaults to 6 lines in 10 seconds.\n" - "\n" - "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); - return true; - } -}; - -class CommandBSKickItalics : public CommandBSKickBase -{ - public: - CommandBSKickItalics(Module *creator) : CommandBSKickBase(creator, "botserv/kick/italics", 2, 3) - { - this->SetDesc(_("Configures italics kicker")); - this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci; - if (CheckArguments(source, params, ci)) - Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetItalics, &KickerData::SetTTBItalics, "itlaics"); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets the italics kicker on or off on \037channel\037. When enabled, the bot will kick users who use \035italics\035." - "\n" - "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); - return true; - } -}; - -class CommandBSKickRepeat : public CommandBSKickBase -{ - public: - CommandBSKickRepeat(Module *creator) : CommandBSKickBase(creator, "botserv/kick/repeat", 2, 4) - { - this->SetDesc(_("Configures repeat kicker")); - this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037 [\037num\037]]\002")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci; - if (!CheckArguments(source, params, ci)) - return; - - KickerData *kd = GetKickerData(ci); - - if (params[1].equals_ci("ON")) - { - const Anope::string &ttb = params.size() > 2 ? params[2] : "", - × = params.size() > 3 ? params[3] : ""; - - int16_t i; - if (!CheckTTB(source, ttb, i)) - return; - - kd->SetRepeat(true); - kd->SetTTBRepeat(i); - kd->SetRepeatTimes(3); - try - { - kd->SetRepeatTimes(convertTo(times)); - } - catch (const ConvertException &) { } - if (kd->GetRepeatTimes() < 2) - kd->SetRepeatTimes(3); - - if (i) - source.Reply(_("Bot will now kick for \002repeats\002 (users that say the same thing {0} times), and will place a ban after {1} kicks for the same user."), kd->GetRepeatTimes(), i); - else - source.Reply(_("Bot will now kick for \002repeats\002 (users that say the same thing {0} times)."), kd->GetRepeatTimes() + 1); - } - else if (params[1].equals_ci("OFF")) - { - kd->SetRepeat(false); - kd->SetTTBRepeat(0); - kd->SetRepeatTimes(0); - source.Reply(_("Bot won't kick for \002repeats\002 anymore.")); - } - else - this->OnSyntaxError(source, params[1]); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets the repeat kicker on or off. When enabled, the bot will kick users who repeat themselves \037num\037 times. If \037num\037 is not given, it defaults to 3.\n" - "\n" - "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); - return true; - } -}; - -class CommandBSKickReverses : public CommandBSKickBase -{ - public: - CommandBSKickReverses(Module *creator) : CommandBSKickBase(creator, "botserv/kick/reverses", 2, 3) - { - this->SetDesc(_("Configures reverses kicker")); - this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci; - if (CheckArguments(source, params, ci)) - Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetReverses, &KickerData::SetTTBReverses, "reverses"); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets the reverses kicker on or off. When enabled, the bot will kick users who use \026reverses\026.\n" - "\n" - "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); - return true; - } -}; - -class CommandBSKickUnderlines : public CommandBSKickBase -{ - public: - CommandBSKickUnderlines(Module *creator) : CommandBSKickBase(creator, "botserv/kick/underlines", 2, 3) - { - this->SetDesc(_("Configures underlines kicker")); - this->SetSyntax(_("\037channel\037 {\037ON|OFF\037} [\037ttb\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci; - if (CheckArguments(source, params, ci)) - Process(source, ci, params[1], params.size() > 2 ? params[2] : "", &KickerData::SetUnderlines, &KickerData::SetTTBUnderlines, "underlines"); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets the underlines kicker on or off. When enabled, the bot will kick users who use \037underlines\037\n" - "\n" - "\037ttb\037 is the number of times a user can be kicked before they get banned. Don't give ttb to disable the ban system.")); - return true; - } -}; - -class CommandBSSetDontKickOps : public Command -{ - public: - CommandBSSetDontKickOps(Module *creator, const Anope::string &sname = "botserv/set/dontkickops") : Command(creator, sname, 2, 2) - { - this->SetDesc(_("To protect ops against bot kicks")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci = ChanServ::Find(params[0]); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), params[0]); - return; - } - - ChanServ::AccessGroup access = source.AccessFor(ci); - if (!source.HasPriv("botserv/administration") && !access.HasPriv("SET")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (Anope::ReadOnly) - { - source.Reply(_("Sorry, bot option setting is temporarily disabled.")); - return; - } - - KickerData *kd = GetKickerData(ci); - - if (params[1].equals_ci("ON")) - { - bool override = !access.HasPriv("SET"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enable dontkickops"; - - kd->SetDontKickOps(true); - source.Reply(_("Bot \002won't kick ops\002 on channel \002{0}\002."), ci->GetName()); - } - else if (params[1].equals_ci("OFF")) - { - bool override = !access.HasPriv("SET"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable dontkickops"; - - kd->SetDontKickOps(false); - source.Reply(_("Bot \002will kick ops\002 on channel \002{0}\002."), ci->GetName()); - } - else - this->OnSyntaxError(source, source.command); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables \002op protection\002 mode on a channel." - " When it is enabled, ops won't be kicked by the bot, even if they don't have the \002{0}\002 privilege.\n" - "\n" - "Use of this command requires the \002{1}\002 privilege on \037channel\037."), - "NOKICK", "SET"); - return true; - } -}; - -class CommandBSSetDontKickVoices : public Command -{ - public: - CommandBSSetDontKickVoices(Module *creator, const Anope::string &sname = "botserv/set/dontkickvoices") : Command(creator, sname, 2, 2) - { - this->SetDesc(_("To protect voices against bot kicks")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci = ChanServ::Find(params[0]); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), params[0]); - return; - } - - ChanServ::AccessGroup access = source.AccessFor(ci); - if (!source.HasPriv("botserv/administration") && !access.HasPriv("SET")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (Anope::ReadOnly) - { - source.Reply(_("Sorry, bot option setting is temporarily disabled.")); - return; - } - - KickerData *kd = GetKickerData(ci); - - if (params[1].equals_ci("ON")) - { - bool override = !access.HasPriv("SET"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enable dontkickvoices"; - - kd->SetDontKickVoices(true); - source.Reply(_("Bot \002won't kick voices\002 on channel %s."), ci->GetName().c_str()); - } - else if (params[1].equals_ci("OFF")) - { - bool override = !access.HasPriv("SET"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable dontkickvoices"; - - kd->SetDontKickVoices(false); - source.Reply(_("Bot \002will kick voices\002 on channel %s."), ci->GetName().c_str()); - } - else - this->OnSyntaxError(source, source.command); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables \002voice protection\002 mode on a channel." - " When it is enabled, ops won't be kicked by the bot, even if they don't have the \002{0}\002 privilege.\n" - "\n" - "Use of this command requires the \002{1}\002 privilege on \037channel\037."), - "NOKICK", "SET"); - return true; - } -}; - -struct BanData -{ - struct Data - { - Anope::string mask; - time_t last_use; - int16_t ttb[TTB_SIZE]; - - Data() - { - last_use = 0; - for (int i = 0; i < TTB_SIZE; ++i) - this->ttb[i] = 0; - } - }; - - private: - typedef Anope::map data_type; - data_type data_map; - - public: - Data &get(const Anope::string &key) - { - return this->data_map[key]; - } - - bool empty() const - { - return this->data_map.empty(); - } - - void purge() - { - time_t keepdata = Config->GetModule(me)->Get("keepdata"); - for (data_type::iterator it = data_map.begin(), it_end = data_map.end(); it != it_end;) - { - const Anope::string &user = it->first; - Data &bd = it->second; - ++it; - - if (Anope::CurTime - bd.last_use > keepdata) - data_map.erase(user); - } - } -}; - -struct UserData -{ - UserData() - { - last_use = last_start = Anope::CurTime; - } - - /* Data validity */ - time_t last_use; - - /* for flood kicker */ - int16_t lines = 0; - time_t last_start; - - /* for repeat kicker */ - Anope::string lasttarget; - int16_t times = 0; - - Anope::string lastline; -}; - -class BanDataPurger : public Timer -{ - public: - BanDataPurger(Module *o) : Timer(o, 300, Anope::CurTime, true) { } - - void Tick(time_t) override - { - Log(LOG_DEBUG) << "bs_main: Running bandata purger"; - - for (channel_map::iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) - { - Channel *c = it->second; - - BanData *bd = c->GetExt("bandata"); - if (bd != NULL) - { - bd->purge(); - if (bd->empty()) - c->ShrinkOK("bandata"); - } - } - } -}; - -class BSKick : public Module - , public EventHook - , public EventHook -{ - ExtensibleItem bandata; - ExtensibleItem userdata; - - KickerDataType kdtype; - - CommandBSKick commandbskick; - CommandBSKickAMSG commandbskickamsg; - CommandBSKickBadwords commandbskickbadwords; - CommandBSKickBolds commandbskickbolds; - CommandBSKickCaps commandbskickcaps; - CommandBSKickColors commandbskickcolors; - CommandBSKickFlood commandbskickflood; - CommandBSKickItalics commandbskickitalics; - CommandBSKickRepeat commandbskickrepeat; - CommandBSKickReverses commandbskickreverse; - CommandBSKickUnderlines commandbskickunderlines; - - CommandBSSetDontKickOps commandbssetdontkickops; - CommandBSSetDontKickVoices commandbssetdontkickvoices; - - BanDataPurger purger; - - EventHandlers onbotban; - - BanData::Data &GetBanData(User *u, Channel *c) - { - BanData *bd = bandata.Require(c); - return bd->get(u->GetMask()); - } - - UserData *GetUserData(User *u, Channel *c) - { - ChanUserContainer *uc = c->FindUser(u); - if (uc == nullptr) - return nullptr; - - return userdata.Require(uc); - } - - void TakeAction(ChanServ::Channel *ci, User *u, int ttb, TTBType ttbtype, const char *message, ...) - { - /* Don't ban ulines or protected users */ - if (u->IsProtected()) - return; - - BanData::Data &bd = this->GetBanData(u, ci->c); - - ++bd.ttb[ttbtype]; - if (ttb && bd.ttb[ttbtype] >= ttb) - { - bd.ttb[ttbtype] = 0; - - Anope::string mask = ci->GetIdealBan(u); - - ci->c->SetMode(NULL, "BAN", mask); - this->onbotban(&Event::BotBan::OnBotBan, u, ci, mask); - } - - if (!ci->c->FindUser(u)) - return; - - va_list args; - char buf[1024]; - - Anope::string fmt = Language::Translate(u, message); - va_start(args, message); - vsnprintf(buf, sizeof(buf), fmt.c_str(), args); - va_end(args); - - ci->c->Kick(ci->GetBot(), u, "%s", buf); - } - - public: - BSKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , bandata(this, "bandata") - , userdata(this, "userdata") - - , kdtype(this) - - , commandbskick(this) - , commandbskickamsg(this) - , commandbskickbadwords(this) - , commandbskickbolds(this) - , commandbskickcaps(this) - , commandbskickcolors(this) - , commandbskickflood(this) - , commandbskickitalics(this) - , commandbskickrepeat(this) - , commandbskickreverse(this) - , commandbskickunderlines(this) - - , commandbssetdontkickops(this) - , commandbssetdontkickvoices(this) - - , purger(this) - - , onbotban(this) - { - me = this; - } - - void OnServiceBot(CommandSource &source, ServiceBot *bi, ChanServ::Channel *ci, InfoFormatter &info) override - { - if (!ci) - return; - - Anope::string enabled = Language::Translate(source.nc, _("Enabled")); - Anope::string disabled = Language::Translate(source.nc, _("Disabled")); - KickerData *kd = ci->GetRef(&kdtype); - - if (kd && kd->GetBadwords()) - { - if (kd->GetTTBBadwords()) - info[_("Bad words kicker")] = Anope::printf("%s (%d kick(s) to ban)", enabled.c_str(), kd->GetBadwords()); - else - info[_("Bad words kicker")] = enabled; - } - else - info[_("Bad words kicker")] = disabled; - - if (kd && kd->GetBolds()) - { - if (kd->GetTTBBolds()) - info[_("Bolds kicker")] = Anope::printf("%s (%d kick(s) to ban)", enabled.c_str(), kd->GetTTBBolds()); - else - info[_("Bolds kicker")] = enabled; - } - else - info[_("Bolds kicker")] = disabled; - - if (kd && kd->GetCaps()) - { - if (kd->GetTTBCaps()) - info[_("Caps kicker")] = Anope::printf(_("%s (%d kick(s) to ban; minimum %d/%d%%)"), enabled.c_str(), kd->GetTTBCaps(), kd->GetCapsMin(), kd->GetCapsPercent()); - else - info[_("Caps kicker")] = Anope::printf(_("%s (minimum %d/%d%%)"), enabled.c_str(), kd->GetCapsMin(), kd->GetCapsPercent()); - } - else - info[_("Caps kicker")] = disabled; - - if (kd && kd->GetColors()) - { - if (kd->GetTTBColors()) - info[_("Colors kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->GetTTBColors()); - else - info[_("Colors kicker")] = enabled; - } - else - info[_("Colors kicker")] = disabled; - - if (kd && kd->GetFlood()) - { - if (kd->GetTTBFlood()) - info[_("Flood kicker")] = Anope::printf(_("%s (%d kick(s) to ban; %d lines in %ds)"), enabled.c_str(), kd->GetTTBFlood(), kd->GetFloodLines(), kd->GetFloodSecs()); - else - info[_("Flood kicker")] = Anope::printf(_("%s (%d lines in %ds)"), enabled.c_str(), kd->GetFloodLines(), kd->GetFloodSecs()); - } - else - info[_("Flood kicker")] = disabled; - - if (kd && kd->GetRepeat()) - { - if (kd->GetTTBRepeat()) - info[_("Repeat kicker")] = Anope::printf(_("%s (%d kick(s) to ban; %d times)"), enabled.c_str(), kd->GetTTBRepeat(), kd->GetRepeatTimes()); - else - info[_("Repeat kicker")] = Anope::printf(_("%s (%d times)"), enabled.c_str(), kd->GetRepeatTimes()); - } - else - info[_("Repeat kicker")] = disabled; - - if (kd && kd->GetReverses()) - { - if (kd->GetTTBReverses()) - info[_("Reverses kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->GetTTBReverses()); - else - info[_("Reverses kicker")] = enabled; - } - else - info[_("Reverses kicker")] = disabled; - - if (kd && kd->GetUnderlines()) - { - if (kd->GetTTBUnderlines()) - info[_("Underlines kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->GetTTBUnderlines()); - else - info[_("Underlines kicker")] = enabled; - } - else - info[_("Underlines kicker")] = disabled; - - if (kd && kd->GetItalics()) - { - if (kd->GetTTBItalics()) - info[_("Italics kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->GetTTBItalics()); - else - info[_("Italics kicker")] = enabled; - } - else - info[_("Italics kicker")] = disabled; - - if (kd && kd->GetAmsgs()) - { - if (kd->GetTTBAmsgs()) - info[_("AMSG kicker")] = Anope::printf(_("%s (%d kick(s) to ban)"), enabled.c_str(), kd->GetTTBAmsgs()); - else - info[_("AMSG kicker")] = enabled; - } - else - info[_("AMSG kicker")] = disabled; - - if (kd && kd->GetDontKickOps()) - info.AddOption(_("Ops protection")); - if (kd && kd->GetDontKickVoices()) - info.AddOption(_("Voices protection")); - } - - void OnPrivmsg(User *u, Channel *c, Anope::string &msg) override - { - /* Now we can make kicker stuff. We try to order the checks - * from the fastest one to the slowest one, since there's - * no need to process other kickers if a user is kicked before - * the last kicker check. - * - * But FIRST we check whether the user is protected in any - * way. - */ - ChanServ::Channel *ci = c->ci; - if (ci == NULL) - return; - KickerData *kd = c->ci->GetRef(&kdtype); - if (kd == NULL) - return; - - if (ci->AccessFor(u).HasPriv("NOKICK")) - return; - if (kd->GetDontKickOps() && (c->HasUserStatus(u, "HALFOP") || c->HasUserStatus(u, "OP") || c->HasUserStatus(u, "PROTECT") || c->HasUserStatus(u, "OWNER"))) - return; - if (kd->GetDontKickVoices() && c->HasUserStatus(u, "VOICE")) - return; - - Anope::string realbuf = msg; - - /* If it's a /me, cut the CTCP part because the ACTION will cause - * problems with the caps or badwords kicker - */ - if (realbuf.substr(0, 8).equals_ci("\1ACTION ") && realbuf[realbuf.length() - 1] == '\1') - { - realbuf.erase(0, 8); - realbuf.erase(realbuf.length() - 1); - } - - if (realbuf.empty()) - return; - - /* Bolds kicker */ - if (kd->GetBolds() && realbuf.find(2) != Anope::string::npos) - { - TakeAction(ci, u, kd->GetTTBBolds(), TTB_BOLDS, _("Don't use bolds on this channel!")); - return; - } - - /* Color kicker */ - if (kd->GetColors() && realbuf.find(3) != Anope::string::npos) - { - TakeAction(ci, u, kd->GetTTBColors(), TTB_COLORS, _("Don't use colors on this channel!")); - return; - } - - /* Reverses kicker */ - if (kd->GetReverses() && realbuf.find(22) != Anope::string::npos) - { - TakeAction(ci, u, kd->GetTTBReverses(), TTB_REVERSES, _("Don't use reverses on this channel!")); - return; - } - - /* Italics kicker */ - if (kd->GetItalics() && realbuf.find(29) != Anope::string::npos) - { - TakeAction(ci, u, kd->GetTTBItalics(), TTB_ITALICS, _("Don't use italics on this channel!")); - return; - } - - /* Underlines kicker */ - if (kd->GetUnderlines() && realbuf.find(31) != Anope::string::npos) - { - TakeAction(ci, u, kd->GetTTBUnderlines(), TTB_UNDERLINES, _("Don't use underlines on this channel!")); - return; - } - - /* Caps kicker */ - if (kd->GetCaps() && realbuf.length() >= static_cast(kd->GetCapsMin())) - { - int i = 0, l = 0; - - for (unsigned j = 0, end = realbuf.length(); j < end; ++j) - { - if (isupper(realbuf[j])) - ++i; - else if (islower(realbuf[j])) - ++l; - } - - /* i counts uppercase chars, l counts lowercase chars. Only - * alphabetic chars (so islower || isupper) qualify for the - * percentage of caps to kick for; the rest is ignored. -GD - */ - - if ((i || l) && i >= kd->GetCapsMin() && i * 100 / (i + l) >= kd->GetCapsPercent()) - { - TakeAction(ci, u, kd->GetTTBCaps(), TTB_CAPS, _("Turn caps lock OFF!")); - return; - } - } - - /* Bad words kicker */ - if (kd->GetBadwords()) - { - bool mustkick = false; - - /* Normalize the buffer */ - Anope::string nbuf = Anope::NormalizeBuffer(realbuf); - bool casesensitive = Config->GetModule("botserv")->Get("casesensitive"); - - /* Normalize can return an empty string if this only conains control codes etc */ - if (badwords && !nbuf.empty()) - for (unsigned i = 0; i < badwords->GetBadWordCount(ci); ++i) - { - BadWord *bw = badwords->GetBadWord(ci, i); - - if (bw->GetWord().empty()) - continue; // Shouldn't happen - - if (bw->GetWord().length() > nbuf.length()) - continue; // This can't ever match - - if (bw->GetType() == BW_ANY && ((casesensitive && nbuf.find(bw->GetWord()) != Anope::string::npos) || (!casesensitive && nbuf.find_ci(bw->GetWord()) != Anope::string::npos))) - mustkick = true; - else if (bw->GetType() == BW_SINGLE) - { - size_t len = bw->GetWord().length(); - - if ((casesensitive && bw->GetWord().equals_cs(nbuf)) || (!casesensitive && bw->GetWord().equals_ci(nbuf))) - mustkick = true; - else if (nbuf.find(' ') == len && ((casesensitive && bw->GetWord().equals_cs(nbuf.substr(0, len))) || (!casesensitive && bw->GetWord().equals_ci(nbuf.substr(0, len))))) - mustkick = true; - else - { - if (len < nbuf.length() && nbuf.rfind(' ') == nbuf.length() - len - 1 && ((casesensitive && nbuf.find(bw->GetWord()) == nbuf.length() - len) || (!casesensitive && nbuf.find_ci(bw->GetWord()) == nbuf.length() - len))) - mustkick = true; - else - { - Anope::string wordbuf = " " + bw->GetWord() + " "; - - if ((casesensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!casesensitive && nbuf.find_ci(wordbuf) != Anope::string::npos)) - mustkick = true; - } - } - } - else if (bw->GetType() == BW_START) - { - size_t len = bw->GetWord().length(); - - if ((casesensitive && nbuf.substr(0, len).equals_cs(bw->GetWord())) || (!casesensitive && nbuf.substr(0, len).equals_ci(bw->GetWord()))) - mustkick = true; - else - { - Anope::string wordbuf = " " + bw->GetWord(); - - if ((casesensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!casesensitive && nbuf.find_ci(wordbuf) != Anope::string::npos)) - mustkick = true; - } - } - else if (bw->GetType() == BW_END) - { - size_t len = bw->GetWord().length(); - - if ((casesensitive && nbuf.substr(nbuf.length() - len).equals_cs(bw->GetWord())) || (!casesensitive && nbuf.substr(nbuf.length() - len).equals_ci(bw->GetWord()))) - mustkick = true; - else - { - Anope::string wordbuf = bw->GetWord() + " "; - - if ((casesensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!casesensitive && nbuf.find_ci(wordbuf) != Anope::string::npos)) - mustkick = true; - } - } - - if (mustkick) - { - if (Config->GetModule(me)->Get("gentlebadwordreason")) - TakeAction(ci, u, kd->GetTTBBadwords(), TTB_BADWORDS, _("Watch your language!")); - else - TakeAction(ci, u, kd->GetTTBBadwords(), TTB_BADWORDS, _("Don't use the word \"%s\" on this channel!"), bw->GetWord().c_str()); - - return; - } - } /* for */ - } /* if badwords */ - - UserData *ud = GetUserData(u, c); - - if (ud) - { - /* Flood kicker */ - if (kd->GetFlood()) - { - if (Anope::CurTime - ud->last_start > kd->GetFloodSecs()) - { - ud->last_start = Anope::CurTime; - ud->lines = 0; - } - - ++ud->lines; - if (ud->lines >= kd->GetFloodLines()) - { - TakeAction(ci, u, kd->GetTTBFlood(), TTB_FLOOD, _("Stop flooding!")); - return; - } - } - - /* Repeat kicker */ - if (kd->GetRepeat()) - { - if (!ud->lastline.equals_ci(realbuf)) - ud->times = 0; - else - ++ud->times; - - if (ud->times >= kd->GetRepeatTimes()) - { - TakeAction(ci, u, kd->GetTTBRepeat(), TTB_REPEAT, _("Stop repeating yourself!")); - return; - } - } - - if (ud->lastline.equals_ci(realbuf) && !ud->lasttarget.empty() && !ud->lasttarget.equals_ci(ci->GetName())) - { - for (User::ChanUserList::iterator it = u->chans.begin(); it != u->chans.end();) - { - Channel *chan = it->second->chan; - ++it; - - if (chan->ci && kd->GetAmsgs() && !chan->ci->AccessFor(u).HasPriv("NOKICK")) - { - TakeAction(ci, u, kd->GetTTBAmsgs(), TTB_AMSGS, _("Don't use AMSGs!")); - return; - } - } - } - - ud->lasttarget = ci->GetName(); - ud->lastline = realbuf; - } - } -}; - -MODULE_INIT(BSKick) diff --git a/modules/commands/bs_set.cpp b/modules/commands/bs_set.cpp deleted file mode 100644 index 6e2ae998f..000000000 --- a/modules/commands/bs_set.cpp +++ /dev/null @@ -1,221 +0,0 @@ -/* BotServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/bs_kick.h" - -class CommandBSSet : public Command -{ - public: - CommandBSSet(Module *creator) : Command(creator, "botserv/set", 3, 3) - { - this->SetDesc(_("Configures bot options")); - this->SetSyntax(_("\037option\037 \037(channel | bot)\037 \037settings\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Configures bot options.\n" - "\n" - "Available options:")); - bool hide_privileged_commands = Config->GetBlock("options")->Get("hideprivilegedcommands"), - hide_registered_commands = Config->GetBlock("options")->Get("hideregisteredcommands"); - Anope::string this_name = source.command; - for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) - { - const Anope::string &c_name = it->first; - const CommandInfo &info = it->second; - if (c_name.find_ci(this_name + " ") == 0) - { - ServiceReference command("Command", info.name); - if (command) - { - // XXX dup - if (hide_registered_commands && !command->AllowUnregistered() && !source.GetAccount()) - continue; - - if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission)) - continue; - - source.command = it->first; - command->OnServHelp(source); - } - } - } - - CommandInfo *help = source.service->FindCommand("generic/help"); - if (help) - source.Reply(_("Type \002{0}{1} {2} {3} \037option\037\002 for more information on a particular option."), - Config->StrictPrivmsg, source.service->nick, help->cname, this_name); - - return true; - } -}; - -class CommandBSSetBanExpire : public Command -{ - public: - class UnbanTimer : public Timer - { - Anope::string chname; - Anope::string mask; - - public: - UnbanTimer(Module *creator, const Anope::string &ch, const Anope::string &bmask, time_t t) : Timer(creator, t), chname(ch), mask(bmask) { } - - void Tick(time_t) override - { - Channel *c = Channel::Find(chname); - if (c) - c->RemoveMode(NULL, "BAN", mask); - } - }; - - CommandBSSetBanExpire(Module *creator, const Anope::string &sname = "botserv/set/banexpire") : Command(creator, sname, 2, 2) - { - this->SetDesc(_("Configures the time bot bans expire in")); - this->SetSyntax(_("\037channel\037 \037time\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &arg = params[1]; - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - ChanServ::AccessGroup access = source.AccessFor(ci); - if (!source.HasPriv("botserv/administration") && !access.HasPriv("SET")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (Anope::ReadOnly) - { - source.Reply(_("Sorry, changing bot options is temporarily disabled.")); - return; - } - - time_t t = Anope::DoTime(arg); - if (t == -1) - { - source.Reply(_("Invalid expiry time \002{0}\002."), arg); - return; - } - - /* cap at 1 day */ - if (t > 86400) - { - source.Reply(_("Ban expiry may not be longer than 1 day.")); - return; - } - - ci->SetBanExpire(t); - - bool override = !access.HasPriv("SET"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to change banexpire to " << arg; - - if (!t) - source.Reply(_("Bot bans will no longer automatically expire.")); - else - source.Reply(_("Bot bans will automatically expire after \002{0}\002."), Anope::Duration(t, source.GetAccount())); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Sets the time bot bans expire in. If enabled, any bans placed by bots, such as by the flood kicker, badwords kicker, etc. will automatically be removed after the given time." - " Set to 0 to disable bans from automatically expiring.")); - return true; - } -}; - -class CommandBSSetPrivate : public Command -{ - public: - CommandBSSetPrivate(Module *creator, const Anope::string &sname = "botserv/set/private") : Command(creator, sname, 2, 2) - { - this->SetDesc(_("Prevent a bot from being assigned by non Services Operators")); - this->SetSyntax(_("\037botname\037 {\037ON|OFF\037}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &nick = params[9]; - const Anope::string &value = params[1]; - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - ServiceBot *bi = ServiceBot::Find(nick, true); - if (bi == NULL) - { - source.Reply(_("Bot \002{0}\002 does not exist."), nick); - return; - } - - if (value.equals_ci("ON")) - { - bi->bi->SetOperOnly(true); - source.Reply(_("Private mode of bot \002{0}\002 is now \002on\002."), bi->nick); - } - else if (value.equals_ci("OFF")) - { - bi->bi->SetOperOnly(false); - source.Reply(_("Private mode of bot \002{0}\002 is now \002off\002."), bi->nick); - } - else - this->OnSyntaxError(source, source.command); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("This option prevents a bot from being assigned to channels by users who do not have the \002{0}\002 privilege."), - "botserv/administration"); - return true; - } -}; - -class BSSet : public Module - , public EventHook -{ - CommandBSSet commandbsset; - CommandBSSetBanExpire commandbssetbanexpire; - CommandBSSetPrivate commandbssetprivate; - - public: - BSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandbsset(this) - , commandbssetbanexpire(this) - , commandbssetprivate(this) - { - } - - void OnBotBan(User *u, ChanServ::Channel *ci, const Anope::string &mask) override - { - if (!ci->GetBanExpire()) - return; - - new CommandBSSetBanExpire::UnbanTimer(this, ci->GetName(), mask, ci->GetBanExpire()); - } -}; - -MODULE_INIT(BSSet) diff --git a/modules/commands/cs_access.cpp b/modules/commands/cs_access.cpp deleted file mode 100644 index f8ecc6c38..000000000 --- a/modules/commands/cs_access.cpp +++ /dev/null @@ -1,942 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -/* Dependencies: chanserv */ - -#include "module.h" -#include "modules/chanserv.h" -#include "modules/cs_access.h" -#include "../pseudoclients/chanserv/chanaccess.h" -#include "../pseudoclients/chanserv/chanaccesstype.h" - -class AccessChanAccess : public ChanAccessImpl -{ - public: - AccessChanAccess(Serialize::TypeBase *type) : ChanAccessImpl(type) { } - AccessChanAccess(Serialize::TypeBase *type, Serialize::ID id) : ChanAccessImpl(type, id) { } - - int GetLevel(); - void SetLevel(const int &); - - bool HasPriv(const Anope::string &name) override - { - return this->GetChannel()->GetLevel(name) != ChanServ::ACCESS_INVALID && this->GetLevel() >= this->GetChannel()->GetLevel(name); - } - - Anope::string AccessSerialize() - { - return stringify(this->GetLevel()); - } - - void AccessUnserialize(const Anope::string &data) override - { - try - { - this->SetLevel(convertTo(data)); - } - catch (const ConvertException &) - { - } - } - -#if 0 - bool operator>(ChanServ::ChanAccess &other) override - { - if (this->GetSerializableType() != other.GetSerializableType()) - return ChanServ::ChanAccess::operator>(other); - else - return this->GetLevel() > anope_dynamic_static_cast(&other)->GetLevel(); - } - - bool operator<(ChanServ::ChanAccess &other) override - { - if (this->GetSerializableType() != other.GetSerializableType()) - return ChanAccess::operator<(other); - else - return this->GetLevel() < anope_dynamic_static_cast(&other)->GetLevel(); - } -#endif -}; - -class AccessChanAccessType : public Serialize::Type -{ - public: - Serialize::Field level; - - AccessChanAccessType(Module *me) : Serialize::Type(me, "AccessChanAccess") - , level(this, "level") - { - SetParent(ChanServ::chanaccess); - } -}; - -int AccessChanAccess::GetLevel() -{ - return Get(&AccessChanAccessType::level); -} - -void AccessChanAccess::SetLevel(const int &i) -{ - Object::Set(&AccessChanAccessType::level, i); -} - -class CommandCSAccess : public Command -{ - void DoAdd(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - Anope::string mask = params[2]; - ChanServ::Privilege *p = NULL; - int level = ChanServ::ACCESS_INVALID; - - try - { - level = convertTo(params[3]); - } - catch (const ConvertException &) - { - p = ChanServ::service ? ChanServ::service->FindPrivilege(params[3]) : nullptr; - if (p != NULL && p->level) - level = p->level; - } - - if (!level) - { - source.Reply(_("Access level must be non-zero.")); - return; - } - - if (level <= ChanServ::ACCESS_INVALID || level >= ChanServ::ACCESS_FOUNDER) - { - source.Reply(_("Access level must be between \002{0}\002 and \002{1}\002 inclusive."), ChanServ::ACCESS_INVALID + 1, ChanServ::ACCESS_FOUNDER - 1); - return; - } - - ChanServ::AccessGroup u_access = source.AccessFor(ci); - ChanServ::ChanAccess *highest = u_access.Highest(); - - AccessChanAccess tmp_access(nullptr); - tmp_access.SetChannel(ci); - tmp_access.SetLevel(level); - - bool override = false; - - if ((!highest || *highest <= tmp_access) && !u_access.founder) - { - if (source.HasPriv("chanserv/access/modify")) - override = true; - else - { - source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to add someone at level \002{1}\002."), ci->GetName(), level); - return; - } - } - - NickServ::Nick *na = nullptr; - ChanServ::Channel *targ_ci = nullptr; - - if (IRCD->IsChannelValid(mask)) - { - if (Config->GetModule("chanserv")->Get("disallow_channel_access")) - { - source.Reply(_("Channels may not be on access lists.")); - return; - } - - targ_ci = ChanServ::Find(mask); - if (targ_ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), mask); - return; - } - - if (ci == targ_ci) - { - source.Reply(_("You can't add a channel to its own access list.")); - return; - } - - mask = targ_ci->GetName(); - } - else - { - na = NickServ::FindNick(mask); - - if (!na && Config->GetModule("chanserv")->Get("disallow_hostmask_access")) - { - source.Reply(_("Masks and unregistered users may not be on access lists.")); - return; - } - - if (mask.find_first_of("!*@") == Anope::string::npos && !na) - { - User *targ = User::Find(mask, true); - if (targ != NULL) - mask = "*!*@" + targ->GetDisplayedHost(); - else - { - source.Reply(_("\002{0}\002 isn't registered."), mask); - return; - } - } - - if (na) - mask = na->GetNick(); - } - - for (unsigned i = ci->GetAccessCount(); i > 0; --i) - { - ChanServ::ChanAccess *access = ci->GetAccess(i - 1); - if (mask.equals_ci(access->Mask())) - { - /* Don't allow lowering from a level >= u_level */ - if ((!highest || *access >= *highest) && !u_access.founder && !source.HasPriv("chanserv/access/modify")) - { - source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to lower the access of \002{1}\002."), ci->GetName(), access->Mask()); - return; - } - delete access; - break; - } - } - - unsigned access_max = Config->GetModule("chanserv")->Get("accessmax", "1024"); - if (access_max && ci->GetDeepAccessCount() >= access_max) - { - source.Reply(_("Sorry, you can only have %d access entries on a channel, including access entries from other channels."), access_max); - return; - } - - AccessChanAccess *access = anope_dynamic_static_cast(accesschanaccess.Create()); - if (na) - access->SetObj(na->GetAccount()); - else if (targ_ci) - access->SetObj(targ_ci); - access->SetChannel(ci); - access->SetMask(mask); - access->SetCreator(source.GetNick()); - access->SetLevel(level); - access->SetLastSeen(0); - access->SetCreated(Anope::CurTime); - - Event::OnAccessAdd(&Event::AccessAdd::OnAccessAdd, ci, source, access); - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to add " << mask << " with level " << level; - if (p != NULL) - source.Reply(_("\002{0}\002 added to the access list of \002{1}\002 with privilege \002{2}\002 (level \002{3}\002)."), access->Mask(), ci->GetName(), p->name, level); - else - source.Reply(_("\002{0}\002 added to the access list of \002{1}\002 at level \002{2}\002."), access->Mask(), ci->GetName(), level); - } - - void DoDel(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - Anope::string mask = params[2]; - - if (!ci->GetAccessCount()) - { - source.Reply(_("The access list for \002{0}\002 is empty."), ci->GetName()); - return; - } - - if (!isdigit(mask[0]) && mask.find_first_of("#!*@") == Anope::string::npos && !NickServ::FindNick(mask)) - { - User *targ = User::Find(mask, true); - if (targ != NULL) - mask = "*!*@" + targ->GetDisplayedHost(); - else - { - source.Reply(_("\002{0}\002 isn't registered."), mask); - return; - } - } - - if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) - { - bool override = !source.AccessFor(ci).HasPriv("ACCESS_CHANGE") && source.HasPriv("chanserv/access/modify"); - Anope::string nicks; - bool denied = false; - unsigned int deleted = 0; - - NumberList(mask, true, - [&](unsigned int num) - { - if (!num || num > ci->GetAccessCount()) - return; - - ChanServ::ChanAccess *access = ci->GetAccess(num - 1); - - ChanServ::AccessGroup ag = source.AccessFor(ci); - ChanServ::ChanAccess *u_highest = ag.Highest(); - - if ((!u_highest || *u_highest <= *access) && !ag.founder && !override && access->GetObj() != source.nc) - { - denied = true; - return; - } - - ++deleted; - if (!nicks.empty()) - nicks += ", " + access->Mask(); - else - nicks = access->Mask(); - - Event::OnAccessDel(&Event::AccessDel::OnAccessDel, ci, source, access); - delete access; - }, - [&]() - { - if (denied && !deleted) - source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to remove any access entries matching \002{1}\002.")); - else if (!deleted) - source.Reply(_("There are no entries matching \002{0}\002 on the access list of \002{1}\002."), mask, ci->GetName()); - else - { - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << nicks; - - if (deleted == 1) - source.Reply(_("Deleted \0021\002 entry from the access list of \002{0}\002."), ci->GetName()); - else - source.Reply(_("Deleted \002{0}\002 entries from the access list of \002{1}\002."), deleted, ci->GetName()); - } - }); - } - else - { - ChanServ::AccessGroup u_access = source.AccessFor(ci); - ChanServ::ChanAccess *highest = u_access.Highest(); - - for (unsigned i = ci->GetAccessCount(); i > 0; --i) - { - ChanServ::ChanAccess *access = ci->GetAccess(i - 1); - if (mask.equals_ci(access->Mask())) - { - if (access->GetObj() != source.nc && !u_access.founder && (!highest || *highest <= *access) && !source.HasPriv("chanserv/access/modify")) - source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to remove the access of \002{1}\002."), ci->GetName(), access->Mask()); - else - { - source.Reply(_("\002{0}\002 deleted from the access list of \002{1}\002."), access->Mask(), ci->GetName()); - bool override = !u_access.founder && !u_access.HasPriv("ACCESS_CHANGE") && !access->Mask().equals_ci(source.nc->GetDisplay()); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << access->Mask(); - - Event::OnAccessDel(&Event::AccessDel::OnAccessDel, ci, source, access); - delete access; - } - return; - } - } - - source.Reply(_("\002{0}\002 was not found on the access list of \002{1}\002."), mask, ci->GetName()); - } - } - - void ProcessList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms, ListFormatter &list) - { - const Anope::string &nick = params.size() > 2 ? params[2] : ""; - - if (!ci->GetAccessCount()) - { - source.Reply(_("The access list for \002{0}\002 is empty."), ci->GetName()); - return; - } - - if (!nick.empty() && nick.find_first_not_of("1234567890,-") == Anope::string::npos) - { - NumberList(nick, false, - [&](unsigned int number) - { - if (!number || number > ci->GetAccessCount()) - return; - - ChanServ::ChanAccess *access = ci->GetAccess(number - 1); - - Anope::string timebuf; - if (ci->c) - for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end; ++cit) - { - ChanServ::ChanAccess::Path p; - if (access->Matches(cit->second->user, cit->second->user->Account(), p)) - timebuf = "Now"; - } - if (timebuf.empty()) - { - if (access->GetLastSeen() == 0) - timebuf = "Never"; - else - timebuf = Anope::strftime(access->GetLastSeen(), NULL, true); - } - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(number); - entry["Level"] = access->AccessSerialize(); - entry["Mask"] = access->Mask(); - entry["By"] = access->GetCreator(); - entry["Last seen"] = timebuf; - list.AddEntry(entry); - }, - [&](){}); - } - else - { - for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i) - { - ChanServ::ChanAccess *access = ci->GetAccess(i); - - if (!nick.empty() && !Anope::Match(access->Mask(), nick)) - continue; - - Anope::string timebuf; - if (ci->c) - for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end; ++cit) - { - ChanServ::ChanAccess::Path p; - if (access->Matches(cit->second->user, cit->second->user->Account(), p)) - timebuf = "Now"; - } - if (timebuf.empty()) - { - if (access->GetLastSeen() == 0) - timebuf = "Never"; - else - timebuf = Anope::strftime(access->GetLastSeen(), NULL, true); - } - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(i + 1); - entry["Level"] = access->AccessSerialize(); - entry["Mask"] = access->Mask(); - entry["By"] = access->GetCreator(); - entry["Last seen"] = timebuf; - list.AddEntry(entry); - } - } - - if (list.IsEmpty()) - { - source.Reply(_("No matching entries on the access list of \002{0}\002."), ci->GetName()); - return; - } - - std::vector replies; - list.Process(replies); - - source.Reply(_("Access list for \002{0}\002:"), ci->GetName()); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("End of access list.")); - } - - void DoList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - if (!ci->GetAccessCount()) - { - source.Reply(_("The access list for \002{0}\002 is empty."), ci->GetName()); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Level")).AddColumn(_("Mask")); - this->ProcessList(source, ci, params, list); - } - - void DoView(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - if (!ci->GetAccessCount()) - { - source.Reply(_("The access list for \002{0}\002 is empty."), ci->GetName()); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Level")).AddColumn(_("Mask")).AddColumn(_("By")).AddColumn(_("Last seen")); - this->ProcessList(source, ci, params, list); - } - - void DoClear(CommandSource &source, ChanServ::Channel *ci) - { - if (!source.IsFounder(ci) && !source.HasPriv("chanserv/access/modify")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); - return; - } - - Event::OnAccessClear(&Event::AccessClear::OnAccessClear, ci, source); - - ci->ClearAccess(); - - source.Reply(_("The access list of \002{0}\002 has been cleared."), ci->GetName()); - - bool override = !source.IsFounder(ci); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clear the access list"; - } - - public: - CommandCSAccess(Module *creator) : Command(creator, "chanserv/access", 2, 4) - { - this->SetDesc(_("Modify the list of privileged users")); - this->SetSyntax(_("\037channel\037 ADD \037mask\037 \037level\037")); - this->SetSyntax(_("\037channel\037 DEL {\037mask\037 | \037entry-num\037 | \037list\037}")); - this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]")); - this->SetSyntax(_("\037channel\037 VIEW [\037mask\037 | \037list\037]")); - this->SetSyntax(_("\037channel\037 CLEAR")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &cmd = params[1]; - const Anope::string &nick = params.size() > 2 ? params[2] : ""; - const Anope::string &s = params.size() > 3 ? params[3] : ""; - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - bool is_list = cmd.equals_ci("LIST") || cmd.equals_ci("VIEW"); - bool is_clear = cmd.equals_ci("CLEAR"); - bool is_del = cmd.equals_ci("DEL"); - - ChanServ::AccessGroup access = source.AccessFor(ci); - - bool has_access = false; - if (source.HasPriv("chanserv/access/modify")) - has_access = true; - else if (is_list && source.HasPriv("chanserv/access/list")) - has_access = true; - else if (is_list && source.AccessFor(ci).HasPriv("ACCESS_LIST")) - has_access = true; - else if (source.AccessFor(ci).HasPriv("ACCESS_CHANGE")) - has_access = true; - else if (is_del) - { - NickServ::Nick *na = NickServ::FindNick(nick); - if (na && na->GetAccount() == source.GetAccount()) - has_access = true; - } - - /* If LIST, we don't *require* any parameters, but we can take any. - * If DEL, we require a nick and no level. - * Else (ADD), we require a level (which implies a nick). */ - if (is_list || is_clear ? 0 : (cmd.equals_ci("DEL") ? (nick.empty() || !s.empty()) : s.empty())) - { - this->OnSyntaxError(source, cmd); - return; - } - - if (!has_access) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), is_list ? "ACCESS_LIST" : "ACCESS_CHANGE", ci->GetName()); - return; - } - - if (Anope::ReadOnly && !is_list) - { - source.Reply(_("Sorry, channel access list modification is temporarily disabled.")); - return; - } - - if (cmd.equals_ci("ADD")) - this->DoAdd(source, ci, params); - else if (cmd.equals_ci("DEL")) - this->DoDel(source, ci, params); - else if (cmd.equals_ci("LIST")) - this->DoList(source, ci, params); - else if (cmd.equals_ci("VIEW")) - this->DoView(source, ci, params); - else if (cmd.equals_ci("CLEAR")) - this->DoClear(source, ci); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (subcommand.equals_ci("ADD")) - { - source.Reply(_("The \002{0} ADD\002 adds \037mask\037 to the access list of \037channel\037 at level \037level\037." - " If \037mask\037 is already present on the access list, the access level for it is changed to \037level\037." - " The \037level\037 may be a numerical level between \002{1}\002 and \002{2}\002 or the name of a privilege (eg. \002{3}\002)." - " The privilege set granted to a given user is the union of the privileges of access entries that match the user." - " Use of this command requires the \002{4}\002 privilege on \037channel\037."), - source.command, ChanServ::ACCESS_INVALID + 1, ChanServ::ACCESS_FOUNDER - 1, "AUTOOP", "ACCESS_CHANGE"); - - if (!Config->GetModule("chanserv")->Get("disallow_channel_access")) - source.Reply(_("The given \037mask\037 may also be a channel, which will use the access list from the other channel up to the given \037level\037.")); - - //XXX show def levels - - source.Reply(_("\n" - "Examples:\n" - " {command} #anope ADD Adam 9001\n" - " Adds \"Adam\" to the access list of \"#anope\" at level \"9001\".\n" - "\n" - " {command} #anope ADD *!*@anope.org AUTOOP\n" - " Adds the host mask \"*!*@anope.org\" to the access list of \"#anope\" with the privilege \"AUTOOP\".")); - } - else if (subcommand.equals_ci("DEL")) - source.Reply(_("The \002{0} DEL\002 command removes \037mask\037 from the access list of \037channel\037." - " If a list of entry numbers is given, those entries are deleted." - " You may remove yourself from an access list, even if you do not have access to modify that list otherwise." - " Use of this command requires the \002{1}\002 privilege on \037channel\037.\n" - "\n" - "Example:\n" - " {command} #anope del DukePyrolator\n" - " Removes the access of \"DukePyrolator\" from \"#anope\"."), - source.command, "ACCESS_CHANGE"); - else if (subcommand.equals_ci("LIST") || subcommand.equals_ci("VIEW")) - source.Reply(_("The \002{0} LIST\002 and \002{0} VIEW\002 command displays the access list of \037channel\037." - " If a wildcard mask is given, only those entries matching the mask are displayed." - " If a list of entry numbers is given, only those entries are shown." - " \002VIEW\002 is similar to \002LIST\002 but also shows who created the access entry, and when the access entry was last used." - " Use of these commands requires the \002{1}\002 privilege on \037channel\037.\n" - "\n" - "Example:\n" - " {0} #anope LIST 2-5,7-9\n" - " Lists access entries numbered 2 through 5 and 7 through 9 on #anope."), - source.command, "ACCESS_LIST"); - else if (subcommand.equals_ci("CLEAR")) - source.Reply(_("The \002{0} CLEAR\002 command clears the access list of \037channel\037." - " Use of this command requires the \002{1}\002 privilege on \037channel\037."), - source.command, "FOUNDER"); - - else - { - source.Reply(_("Maintains the access list for \037channel\037. The access list specifies which users are granted which privileges to the channel." - " The access system uses numerical levels to represent different sets of privileges. Users who are identified but do not match any entries on" - " the access list has a level of 0. Unregistered or unidentified users who do not match any entries have a user level of 0.")); - ServiceBot *bi; - Anope::string name; - CommandInfo *help = source.service->FindCommand("generic/help"); - if (Command::FindCommandFromService("chanserv/levels", bi, name) && help) - source.Reply(_("\n" - "Access levels can be configured via the \002{levels}\002 command. See \002{msg}{service} {help} {levels}\002 for more information."), - "msg"_kw = Config->StrictPrivmsg, "service"_kw = bi->nick, "help"_kw = help->cname, "levels"_kw = name); - - if (help) - source.Reply(_("\n" - "The \002ADD\002 command adds \037mask\037 to the access list at level \037level\037.\n" - "Use of this command requires the \002{change}\002 privilege on \037channel\037.\n" - "\002{msg}{service} {help} {command} ADD\002 for more information.\n" - "\n" - "The \002DEL\002 command removes \037mask\037 from the access list.\n" - "Use of this command requires the \002{change}\002 privilege on \037channel\037.\n" - "\002{msg}{service} {help} {command} DEL\002 for more information.\n" - "\n" - "The \002LIST\002 and \002VIEW\002 commands both show the access list for \037channel\037, but \002VIEW\002 also shows who created the access entry, and when the user was last seen.\n" - "Use of these commands requires the \002{list}\002 privilege on \037channel\037.\n" - "\002{msg}{service} {help} {command} [LIST | VIEW]\002 for more information.\n" - "\n" - "The \002CLEAR\002 command clears the access list." - "Use of this command requires the \002{founder}\002 privilege on \037channel\037.\n" - "\002{msg}{service} {help} {command} CLEAR\002 for more information."), - "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "command"_kw = source.command, - "help"_kw = help->cname, "change"_kw = "ACCESS_CHANGE", "list"_kw = "ACCESS_LIST", "founder"_kw = "FOUNDER"); - } - - return true; - } -}; - -class CommandCSLevels : public Command -{ - void DoSet(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - const Anope::string &what = params[2]; - const Anope::string &lev = params[3]; - - int level; - - if (lev.equals_ci("FOUNDER")) - level = ChanServ::ACCESS_FOUNDER; - else - { - try - { - level = convertTo(lev); - } - catch (const ConvertException &) - { - this->OnSyntaxError(source, "SET"); - return; - } - } - - if (level <= ChanServ::ACCESS_INVALID || level > ChanServ::ACCESS_FOUNDER) - source.Reply(_("Level must be between \002{0}\002 and \002{1}\002 inclusive."), ChanServ::ACCESS_INVALID + 1, ChanServ::ACCESS_FOUNDER - 1); - else - { - ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(what) : nullptr; - if (p == NULL) - { - CommandInfo *help = source.service->FindCommand("generic/help"); - if (help) - source.Reply(_("There is no such privilege \002{0}\002. See \002{0}{1} {2} {3}\002 for a list of valid settings."), - what, Config->StrictPrivmsg, source.service->nick, help->cname, source.command); - } - else - { - bool override = !source.AccessFor(ci).HasPriv("FOUNDER"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to set " << p->name << " to level " << level; - - ci->SetLevel(p->name, level); - this->onlevelchange(&Event::LevelChange::OnLevelChange, source, ci, p->name, level); - - if (level == ChanServ::ACCESS_FOUNDER) - source.Reply(_("Level for privilege \002{0}\002 on channel \002{1}\002 changed to \002founder only\002."), p->name, ci->GetName()); - else - source.Reply(_("Level for privilege \002{0}\002 on channel \002{1}\002 changed to \002{3}\002."), p->name, ci->GetName(), level); - } - } - } - - void DoDisable(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - const Anope::string &what = params[2]; - - /* Don't allow disabling of the founder level. It would be hard to change it back if you don't have access to use this command */ - if (what.equals_ci("FOUNDER")) - { - source.Reply(_("You can not disable the founder privilege because it would be impossible to reenable it at a later time.")); - return; - } - - ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(what) : nullptr; - if (p != NULL) - { - bool override = !source.AccessFor(ci).HasPriv("FOUNDER"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable " << p->name; - - ci->SetLevel(p->name, ChanServ::ACCESS_INVALID); - this->onlevelchange(&Event::LevelChange::OnLevelChange, source, ci, p->name, ChanServ::ACCESS_INVALID); - - source.Reply(_("Privileged \002{0}\002 disabled on channel \002{1}\002."), p->name, ci->GetName()); - return; - } - - CommandInfo *help = source.service->FindCommand("generic/help"); - if (help) - source.Reply(_("There is no such privilege \002{0}\002. See \002{0}{1} {2} {3}\002 for a list of valid settings."), - what, Config->StrictPrivmsg, source.service->nick, help->cname, source.command); - } - - void DoList(CommandSource &source, ChanServ::Channel *ci) - { - if (!ChanServ::service) - return; - - source.Reply(_("Access level settings for channel \002{0}\002"), ci->GetName()); - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Name")).AddColumn(_("Level")); - - const std::vector &privs = ChanServ::service->GetPrivileges(); - - for (unsigned i = 0; i < privs.size(); ++i) - { - const ChanServ::Privilege &p = privs[i]; - int16_t j = ci->GetLevel(p.name); - - ListFormatter::ListEntry entry; - entry["Name"] = p.name; - - if (j == ChanServ::ACCESS_INVALID) - entry["Level"] = Language::Translate(source.GetAccount(), _("(disabled)")); - else if (j == ChanServ::ACCESS_FOUNDER) - entry["Level"] = Language::Translate(source.GetAccount(), _("(founder only)")); - else - entry["Level"] = stringify(j); - - list.AddEntry(entry); - } - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - - void DoReset(CommandSource &source, ChanServ::Channel *ci) - { - bool override = !source.AccessFor(ci).HasPriv("FOUNDER"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to reset all levels"; - - ci->ClearLevels(); - this->onlevelchange(&Event::LevelChange::OnLevelChange, source, ci, "ALL", 0); - - source.Reply(_("Levels for \002{0}\002 reset to defaults."), ci->GetName()); - } - - EventHandlers &onlevelchange; - - public: - CommandCSLevels(Module *creator, EventHandlers &event) : Command(creator, "chanserv/levels", 2, 4), onlevelchange(event) - { - this->SetDesc(_("Redefine the meanings of access levels")); - this->SetSyntax(_("\037channel\037 SET \037privilege\037 \037level\037")); - this->SetSyntax(_("\037channel\037 {DIS | DISABLE} \037privilege\037")); - this->SetSyntax(_("\037channel\037 LIST")); - this->SetSyntax(_("\037channel\037 RESET")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &cmd = params[1]; - const Anope::string &what = params.size() > 2 ? params[2] : ""; - const Anope::string &s = params.size() > 3 ? params[3] : ""; - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - bool has_access = false; - if (source.HasPriv("chanserv/access/modify")) - has_access = true; - else if (cmd.equals_ci("LIST") && source.HasPriv("chanserv/access/list")) - has_access = true; - else if (source.AccessFor(ci).HasPriv("FOUNDER")) - has_access = true; - - /* If SET, we want two extra parameters; if DIS[ABLE] or FOUNDER, we want only - * one; else, we want none. - */ - if (cmd.equals_ci("SET") ? s.empty() : (cmd.substr(0, 3).equals_ci("DIS") ? (what.empty() || !s.empty()) : !what.empty())) - { - this->OnSyntaxError(source, cmd); - return; - } - - if (!has_access) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); - return; - } - - if (Anope::ReadOnly && !cmd.equals_ci("LIST")) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - if (cmd.equals_ci("SET")) - this->DoSet(source, ci, params); - else if (cmd.equals_ci("DIS") || cmd.equals_ci("DISABLE")) - this->DoDisable(source, ci, params); - else if (cmd.equals_ci("LIST")) - this->DoList(source, ci); - else if (cmd.equals_ci("RESET")) - this->DoReset(source, ci); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (subcommand.equals_ci("DESC")) - { - source.Reply(_("The following privileges are available:")); - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Name")).AddColumn(_("Description")); - - if (ChanServ::service) - { - const std::vector &privs = ChanServ::service->GetPrivileges(); - for (unsigned i = 0; i < privs.size(); ++i) - { - const ChanServ::Privilege &p = privs[i]; - ListFormatter::ListEntry entry; - entry["Name"] = p.name; - entry["Description"] = Language::Translate(source.nc, p.desc.c_str()); - list.AddEntry(entry); - } - } - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - else - { - ServiceBot *bi; - Anope::string name; - if (!Command::FindCommandFromService("chanserv/access", bi, name) || bi != source.service) - return false; - CommandInfo *help = source.service->FindCommand("generic/help"); - if (!help) - return false; - - source.Reply(_("The \002{0}\002 command allows fine control over the meaning of numeric access levels used in the \002{1}\001 command.\n" - "\n" - "\002{0} SET\002 allows changing which \037privilege\037 is included in a given \037level\37.\n" - "\n" - "\002{0} DISABLE\002 disables a privilege and prevents anyone from be granted it, even channel founders." - " The \002{2}\002 privilege can not be disabled.\n" - "\n" - "\002{0} LIST\002 shows the current level for each privilege.\n" - "\n" - "\002{0} RESET\002 resets the levels to the default levels for newly registered channels.\n" - "\n" - "For the list of privileges and their descriptions, see \002{3} {4} DESC\002."), - source.command, name, "FOUNDER", help->cname, source.command); - } - return true; - } -}; - -class CSAccess : public Module - , public EventHook -{ - CommandCSAccess commandcsaccess; - CommandCSLevels commandcslevels; - EventHandlers onlevelchange; - AccessChanAccessType accesschanaccesstype; - - public: - CSAccess(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsaccess(this) - , commandcslevels(this, onlevelchange) - , onlevelchange(this) - , accesschanaccesstype(this) - { - this->SetPermanent(true); - - } - - EventReturn OnGroupCheckPriv(const ChanServ::AccessGroup *group, const Anope::string &priv) override - { - if (group->ci == NULL) - return EVENT_CONTINUE; - /* Special case. Allows a level of < 0 to match anyone, and a level of 0 to match anyone identified. */ - int16_t level = group->ci->GetLevel(priv); - if (level < 0) - return EVENT_ALLOW; - else if (level == 0 && group->nc) - return EVENT_ALLOW; - return EVENT_CONTINUE; - } -}; - -template<> void ModuleInfo(ModuleDef *def) -{ - def->Depends("chanserv"); -} - -MODULE_INIT(CSAccess) diff --git a/modules/commands/cs_akick.cpp b/modules/commands/cs_akick.cpp deleted file mode 100644 index 8cd1f778a..000000000 --- a/modules/commands/cs_akick.cpp +++ /dev/null @@ -1,718 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_akick.h" - -class AutoKickImpl : public AutoKick -{ - public: - AutoKickImpl(Serialize::TypeBase *type) : AutoKick(type) { } - AutoKickImpl(Serialize::TypeBase *type, Serialize::ID id) : AutoKick(type, id) { } - - ChanServ::Channel *GetChannel() override; - void SetChannel(ChanServ::Channel *ci) override; - - Anope::string GetMask() override; - void SetMask(const Anope::string &mask) override; - - NickServ::Account *GetAccount() override; - void SetAccount(NickServ::Account *nc) override; - - Anope::string GetReason() override; - void SetReason(const Anope::string &r) override; - - Anope::string GetCreator() override; - void SetCreator(const Anope::string &c) override; - - time_t GetAddTime() override; - void SetAddTime(const time_t &t) override; - - time_t GetLastUsed() override; - void SetLastUsed(const time_t &t) override; -}; - -class AutoKickType : public Serialize::Type -{ - public: - Serialize::ObjectField ci; - - Serialize::Field mask; - Serialize::ObjectField nc; - - Serialize::Field reason; - Serialize::Field creator; - Serialize::Field addtime; - Serialize::Field last_time; - - AutoKickType(Module *me) - : Serialize::Type(me, "AutoKick") - , ci(this, "ci", true) - , mask(this, "mask") - , nc(this, "nc", true) - , reason(this, "reason") - , creator(this, "creator") - , addtime(this, "addtime") - , last_time(this, "last_time") - { - } -}; - -ChanServ::Channel *AutoKickImpl::GetChannel() -{ - return Get(&AutoKickType::ci); -} - -void AutoKickImpl::SetChannel(ChanServ::Channel *ci) -{ - Set(&AutoKickType::ci, ci); -} - -Anope::string AutoKickImpl::GetMask() -{ - return Get(&AutoKickType::mask); -} - -void AutoKickImpl::SetMask(const Anope::string &mask) -{ - Set(&AutoKickType::mask, mask); -} - -NickServ::Account *AutoKickImpl::GetAccount() -{ - return Get(&AutoKickType::nc); -} - -void AutoKickImpl::SetAccount(NickServ::Account *nc) -{ - Set(&AutoKickType::nc, nc); -} - -Anope::string AutoKickImpl::GetReason() -{ - return Get(&AutoKickType::reason); -} - -void AutoKickImpl::SetReason(const Anope::string &r) -{ - Set(&AutoKickType::reason, r); -} - -Anope::string AutoKickImpl::GetCreator() -{ - return Get(&AutoKickType::creator); -} - -void AutoKickImpl::SetCreator(const Anope::string &c) -{ - Set(&AutoKickType::creator, c); -} - -time_t AutoKickImpl::GetAddTime() -{ - return Get(&AutoKickType::addtime); -} - -void AutoKickImpl::SetAddTime(const time_t &t) -{ - Set(&AutoKickType::addtime, t); -} - -time_t AutoKickImpl::GetLastUsed() -{ - return Get(&AutoKickType::last_time); -} - -void AutoKickImpl::SetLastUsed(const time_t &t) -{ - Set(&AutoKickType::last_time, t); -} - -class CommandCSAKick : public Command -{ - void DoAdd(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - Anope::string mask = params[2]; - Anope::string reason = params.size() > 3 ? params[3] : ""; - NickServ::Nick *na = NickServ::FindNick(mask); - NickServ::Account *nc = NULL; - unsigned reasonmax = Config->GetModule("chanserv")->Get("reasonmax", "200"); - - if (reason.length() > reasonmax) - reason = reason.substr(0, reasonmax); - - if (IRCD->IsExtbanValid(mask)) - ; /* If this is an extban don't try to complete the mask */ - else if (IRCD->IsChannelValid(mask)) - { - /* Also don't try to complete the mask if this is a channel */ - - if (mask.equals_ci(ci->GetName()) && ci->HasFieldS("PEACE")) - { - source.Reply(_("Access denied.")); - return; - } - } - else if (!na) - { - /* If the mask contains a realname the reason must be prepended with a : */ - if (mask.find('#') != Anope::string::npos) - { - size_t r = reason.find(':'); - if (r != Anope::string::npos) - { - mask += " " + reason.substr(0, r); - mask.trim(); - reason = reason.substr(r + 1); - reason.trim(); - } - else - { - mask = mask + " " + reason; - reason.clear(); - } - } - - Entry e("", mask); - - mask = (e.nick.empty() ? "*" : e.nick) + "!" - + (e.user.empty() ? "*" : e.user) + "@" - + (e.host.empty() ? "*" : e.host); - if (!e.real.empty()) - mask += "#" + e.real; - } - else - nc = na->GetAccount(); - - /* Check excepts BEFORE we get this far */ - if (ci->c) - { - std::vector modes = ci->c->GetModeList("EXCEPT"); - for (unsigned int i = 0; i < modes.size(); ++i) - { - if (Anope::Match(modes[i], mask)) - { - source.Reply(_("\002{0}\002 matches an except on \002{1}\002 and cannot be banned until the except has been removed."), mask, ci->GetName()); - return; - } - } - } - - bool override = !source.AccessFor(ci).HasPriv("AKICK"); - /* Opers overriding get to bypass PEACE */ - if (override) - ; - /* These peace checks are only for masks */ - else if (IRCD->IsChannelValid(mask)) - ; - /* Check whether target nick has equal/higher access - * or whether the mask matches a user with higher/equal access - Viper */ - else if (ci->HasFieldS("PEACE") && nc) - { - ChanServ::AccessGroup nc_access = ci->AccessFor(nc), u_access = source.AccessFor(ci); - if (nc == ci->GetFounder() || nc_access >= u_access) - { - source.Reply(_("Access denied. You can not auto kick \002{0}\002 because they have more privileges than you on \002{1}\002 and \002{2}\002 is enabled."), nc->GetDisplay(), ci->GetName(), "PEACE"); - return; - } - } - else if (ci->HasFieldS("PEACE")) - { -#if 0 - /* Match against all currently online users with equal or - * higher access. - Viper */ - for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) - { - User *u2 = it->second; - - ChanServ::AccessGroup nc_access = ci->AccessFor(nc), u_access = source.AccessFor(ci); - Entry entry_mask("", mask); - - if ((ci->AccessFor(u2).HasPriv("FOUNDER") || nc_access >= u_access) && entry_mask.Matches(u2)) - { - source.Reply(ACCESS_DENIED); - return; - } - } - - /* Match against the lastusermask of all nickalias's with equal - * or higher access. - Viper */ - for (nickalias_map::const_iterator it = NickServ::NickList->begin(), it_end = NickServ::NickList->end(); it != it_end; ++it) - { - na = it->second; - - ChanServ::AccessGroup nc_access = ci->AccessFor(na->GetAccount()), u_access = source.AccessFor(ci); - if (na->GetAccount() && (na->GetAccount() == ci->GetFounder() || nc_access >= u_access)) - { - Anope::string buf = na->GetNick() + "!" + na->GetLastUsermask(); - if (Anope::Match(buf, mask)) - { - source.Reply(ACCESS_DENIED); - return; - } - } - } -#endif - } - - for (unsigned j = 0, end = ci->GetAkickCount(); j < end; ++j) - { - AutoKick *ak = ci->GetAkick(j); - if (ak->GetAccount() ? ak->GetAccount() == nc : mask.equals_ci(ak->GetMask())) - { - source.Reply(_("\002{0}\002 already exists on \002{1}\002 autokick list."), ak->GetAccount() ? ak->GetAccount()->GetDisplay() : ak->GetMask(), ci->GetName()); - return; - } - } - - if (ci->GetAkickCount() >= Config->GetModule(this->owner)->Get("autokickmax")) - { - source.Reply(_("Sorry, you can only have \002{0}\002 autokick masks on a channel."), Config->GetModule(this->owner)->Get("autokickmax")); - return; - } - - AutoKick *ak; - if (nc) - ak = ci->AddAkick(source.GetNick(), nc, reason); - else - ak = ci->AddAkick(source.GetNick(), mask, reason); - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to add " << mask << (reason == "" ? "" : ": ") << reason; - - this->akickevents(&Event::Akick::OnAkickAdd, source, ci, ak); - - source.Reply(_("\002{0}\002 added to \002{1}\002 autokick list."), mask, ci->GetName()); - - this->DoEnforce(source, ci); - } - - void DoDel(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - const Anope::string &mask = params[2]; - bool override = !source.AccessFor(ci).HasPriv("AKICK"); - - if (!ci->GetAkickCount()) - { - source.Reply(_("The autokick list of \002{0}\002 is empty."), ci->GetName()); - return; - } - - /* Special case: is it a number/list? Only do search if it isn't. */ - if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) - { - unsigned int deleted = 0; - - NumberList(mask, true, - [&](unsigned int number) - { - if (!number || number > ci->GetAkickCount()) - return; - - AutoKick *ak = ci->GetAkick(number - 1); - - this->akickevents(&Event::Akick::OnAkickDel, source, ci, ak); - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << (ak->GetAccount() ? ak->GetAccount()->GetDisplay() : ak->GetMask()); - - ++deleted; - delete ak; - }, - [&]() - { - if (!deleted) - source.Reply(_("There are no entries matching \002{0}\002 on the auto kick list of \002{1}\002."), - mask, ci->GetName()); - else if (deleted == 1) - source.Reply(_("Deleted \0021\002 entry from the autokick list of \002{0}\002."), ci->GetName()); - else - source.Reply(_("Deleted \002{0}\002 entries from \002{1}\002 autokick list."), deleted, ci->GetName()); - }); - } - else - { - NickServ::Nick *na = NickServ::FindNick(mask); - NickServ::Account *nc = na ? na->GetAccount() : nullptr; - - unsigned int i, end; - for (i = 0, end = ci->GetAkickCount(); i < end; ++i) - { - AutoKick *ak = ci->GetAkick(i); - - if (ak->GetAccount() ? ak->GetAccount() == nc : mask.equals_ci(ak->GetMask())) - break; - } - - if (i == ci->GetAkickCount()) - { - source.Reply(_("\002{0}\002 was not found on the auto kick list of \002{1}\002."), mask, ci->GetName()); - return; - } - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << mask; - - this->akickevents(&Event::Akick::OnAkickDel, source, ci, ci->GetAkick(i)); - - delete ci->GetAkick(i); - - source.Reply(_("\002{0}\002 deleted from the auto kick list of \002{1}\002."), mask, ci->GetName()); - } - } - - void ProcessList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms, ListFormatter &list) - { - const Anope::string &mask = params.size() > 2 ? params[2] : ""; - - if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) - { - NumberList(mask, false, - [&](unsigned int number) - { - if (!number || number > ci->GetAkickCount()) - return; - - AutoKick *ak = ci->GetAkick(number - 1); - - Anope::string timebuf, lastused; - if (ak->GetAddTime()) - timebuf = Anope::strftime(ak->GetAddTime(), NULL, true); - else - timebuf = _(""); - if (ak->GetLastUsed()) - lastused = Anope::strftime(ak->GetLastUsed(), NULL, true); - else - lastused = _(""); - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(number); - if (ak->GetAccount()) - entry["Mask"] = ak->GetAccount()->GetDisplay(); - else - entry["Mask"] = ak->GetMask(); - entry["Creator"] = ak->GetCreator(); - entry["Created"] = timebuf; - entry["Last used"] = ak->GetLastUsed(); - entry["Reason"] = ak->GetReason(); - list.AddEntry(entry); - }, - []{}); - } - else - { - for (unsigned i = 0, end = ci->GetAkickCount(); i < end; ++i) - { - AutoKick *ak = ci->GetAkick(i); - - if (!mask.empty()) - { - if (!ak->GetAccount() && !Anope::Match(ak->GetMask(), mask)) - continue; - if (ak->GetAccount() && !Anope::Match(ak->GetAccount()->GetDisplay(), mask)) - continue; - } - - Anope::string timebuf, lastused; - if (ak->GetAddTime()) - timebuf = Anope::strftime(ak->GetAddTime(), NULL, true); - else - timebuf = _(""); - if (ak->GetLastUsed()) - lastused = Anope::strftime(ak->GetLastUsed(), NULL, true); - else - lastused = _(""); - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(i + 1); - if (ak->GetAccount()) - entry["Mask"] = ak->GetAccount()->GetDisplay(); - else - entry["Mask"] = ak->GetMask(); - entry["Creator"] = ak->GetCreator(); - entry["Created"] = timebuf; - entry["Last used"] = lastused; - entry["Reason"] = ak->GetReason(); - list.AddEntry(entry); - } - } - - if (list.IsEmpty()) - { - source.Reply(_("There are no entries matching \002{0}\002 on the autokick list of \002{1}\002."), mask, ci->GetName()); - return; - } - - std::vector replies; - list.Process(replies); - - source.Reply(_("Autokick list for \002{0}\002:"), ci->GetName()); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("End of autokick list.")); - } - - void DoList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - if (!ci->GetAkickCount()) - { - source.Reply(_("The autokick list of \002{0}\002 is empty."), ci->GetName()); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Reason")); - this->ProcessList(source, ci, params, list); - } - - void DoView(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - if (!ci->GetAkickCount()) - { - source.Reply(_("The autokick list of \002{0}\002 is empty."), ci->GetName()); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Last used")).AddColumn(_("Reason")); - this->ProcessList(source, ci, params, list); - } - - void DoEnforce(CommandSource &source, ChanServ::Channel *ci) - { - Channel *c = ci->c; - int count = 0; - - if (!c) - { - source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); - return; - } - - for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ) - { - ChanUserContainer *uc = it->second; - ++it; - - if (c->CheckKick(uc->user)) - ++count; - } - - bool override = !source.AccessFor(ci).HasPriv("AKICK"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "ENFORCE, affects " << count << " users"; - - source.Reply(_("Autokick enforce for \002{0}\002 complete; \002{1}\002 users were affected."), ci->GetName(), count); - } - - void DoClear(CommandSource &source, ChanServ::Channel *ci) - { - bool override = !source.AccessFor(ci).HasPriv("AKICK"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clear the akick list"; - - ci->ClearAkick(); - source.Reply(_("The autokick list of \002{0}\002 has been cleared."), ci->GetName()); - } - - EventHandlers &akickevents; - - public: - CommandCSAKick(Module *creator, EventHandlers &events) : Command(creator, "chanserv/akick", 2, 4), akickevents(events) - { - this->SetDesc(_("Maintain the AutoKick list")); - this->SetSyntax(_("\037channel\037 ADD {\037nick\037 | \037mask\037} [\037reason\037]")); - this->SetSyntax(_("\037channel\037 DEL {\037nick\037 | \037mask\037 | \037entry-num\037 | \037list\037}")); - this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037entry-num\037 | \037list\037]")); - this->SetSyntax(_("\037channel\037 VIEW [\037mask\037 | \037entry-num\037 | \037list\037]")); - this->SetSyntax(_("\037channel\037 ENFORCE")); - this->SetSyntax(_("\037channel\037 CLEAR")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &cmd = params[1]; - const Anope::string &mask = params.size() > 2 ? params[2] : ""; - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - bool is_list = cmd.equals_ci("LIST") || cmd.equals_ci("VIEW"); - - if (mask.empty() && (cmd.equals_ci("ADD") || cmd.equals_ci("DEL"))) - { - this->OnSyntaxError(source, cmd); - return; - } - - if (!source.AccessFor(ci).HasPriv("AKICK") && !source.HasPriv("chanserv/access/modify") && (!is_list || source.HasPriv("chanserv/access/list"))) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "AKICK", ci->GetName()); - return; - } - - if (Anope::ReadOnly && (cmd.equals_ci("ADD") || cmd.equals_ci("DEL") || cmd.equals_ci("CLEAR"))) - { - source.Reply(_("Sorry, channel autokick list modification is temporarily disabled.")); - return; - } - - if (cmd.equals_ci("ADD")) - this->DoAdd(source, ci, params); - else if (cmd.equals_ci("DEL")) - this->DoDel(source, ci, params); - else if (cmd.equals_ci("LIST")) - this->DoList(source, ci, params); - else if (cmd.equals_ci("VIEW")) - this->DoView(source, ci, params); - else if (cmd.equals_ci("ENFORCE")) - this->DoEnforce(source, ci); - else if (cmd.equals_ci("CLEAR")) - this->DoClear(source, ci); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (subcommand.equals_ci("ADD")) - source.Reply(_("The \002{0} ADD\002 command adds \037mask\037 to the auto kick list list of \037channel\037." - " If \037reason\037 is given, it is used when the user is kicked." - " If \037mask\037 is an account, then all users using the account will be affected.\n" - "\n" - "Examples:\n" - " {command} #anope ADD Cronus noob!\n" - " Adds the account \"Cronus\" to the auto kick list of \"#anope\" with the reason \"noob!\".\n" - "\n" - " {command} #anope ADD Guest*!*@*\n" - " Adds the mask \"Guest*!*@*\" to the auto kick list of \"#anope\"."), - source.command); - else if (subcommand.equals_ci("DEL")) - source.Reply(_("The \002{0} DEL\002 command removes \037mask\037 from the auto kick list." - " It does not, however, remove any bans placed by an auto kick; those must be removed manually.\n" - "\n" - "Example:\n" - " {command} #anope DEL Cronus!\n" - " Removes the auto kick for \"Cronus\" from the auto kick list of \"#anope\".\n"), - source.command); - else if (subcommand.equals_ci("LIST") || subcommand.equals_ci("VIEW")) - source.Reply(_("The \002{0} LIST\002 and \002{0} VIEW\002 command displays the auto kick list of \037channel\037." - " If a wildcard mask is given, only those entries matching the mask are displayed." - " If a list of entry numbers is given, only those entries are shown." - " \002VIEW\002 is similar to \002LIST\002 but also shows who created the auto kick entry, when it was created, and when it was last used." - "\n" - "Example:\n" - " \002{0} #anope LIST 2-5,7-9\002\n" - " Lists auto kick entries numbered 2 through 5 and 7 through 9 on #anope."), - source.command); - else if (subcommand.equals_ci("ENFORCE")) - source.Reply(_("The \002{0} ENFORCE\002 command enforces the auto kick list by forcibly removing users who match an entry on the auto kick list." - "This can be useful if someone does not authenticate or change their host mask until after joining.\n" - "\n" - "Example:\n" - " \002{0} #anope ENFORCE\002\n" - " Enforces the auto kick list of #anope."), - source.command); - else if (subcommand.equals_ci("CLEAR")) - source.Reply(_("The \002{0} CLEAR\002 command clears the auto kick list of \037channel\37. As with the \002DEL\002 command, existing bans placed by auto kick will not be removed.\n" - "\n" - "Example:\n" - " \002{0} #anope CLEAR\002\n" - " Clears the auto kick list of #anope."), - source.command); - else - { - source.Reply(_("Maintains the auto kick list for \037channel\037." - " If a user matching an entry on the auto kick list joins the channel, {0} will place a ban on the user and kick them.\n" - "\n" - "Use of this command requires the \002{1}\002 privilege on \037channel\037."), - source.service->nick, "AKICK"); - CommandInfo *help = source.service->FindCommand("generic/help"); - if (help) - source.Reply(_("\n" - "The \002ADD\002 command adds \037nick\037 or \037mask\037 to the auto kick list of \037channel\037.\n" - "\002{msg}{service} {help} {command} ADD\002 for more information.\n" - "\n" - "The \002DEL\002 command removes \037mask\037 from the auto kick list of \037channel\037.\n" - "\002{msg}{service} {help} {command} DEL\002 for more information.\n" - "\n" - "The \002LIST\002 and \002VIEW\002 commands both display the auto kick list of \037channel\037, but \002VIEW\002 also displays who created the auto kick entry, when it was created, and when it was last used.\n" - "\002{msg}{service} {help} {command} [LIST | VIEW]\002 for more information.\n" - "\n" - "The \002ENFORCE\002 command enforces the auto kick list by forcibly removing users who match an entry on the auto kick list.\n" - "\002{msg}{service} {help} {command} ENFORCE\002 for more information.\n" - "" - "The \002CLEAR\002 clears the auto kick list of \037channel\037." - "\002{msg}{service} {help} {command} CLEAR\002 for more information.\n"), - "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "help"_kw = help->cname, "command"_kw = source.command); - } - - return true; - } -}; - -class CSAKick : public Module - , public EventHook -{ - CommandCSAKick commandcsakick; - EventHandlers akickevents; - AutoKickType akick_type; - - public: - CSAKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsakick(this, akickevents) - , akickevents(this) - , akick_type(this) - { - } - - EventReturn OnCheckKick(User *u, Channel *c, Anope::string &mask, Anope::string &reason) override - { - if (!c->ci || c->MatchesList(u, "EXCEPT")) - return EVENT_CONTINUE; - - for (unsigned j = 0, end = c->ci->GetAkickCount(); j < end; ++j) - { - AutoKick *ak = c->ci->GetAkick(j); - bool kick = false; - - if (ak->GetAccount()) - kick = ak->GetAccount() == u->Account(); - else if (IRCD->IsChannelValid(ak->GetMask())) - { - Channel *chan = Channel::Find(ak->GetMask()); - kick = chan != NULL && chan->FindUser(u); - } - else - kick = Entry("BAN", ak->GetMask()).Matches(u); - - if (kick) - { - Log(LOG_DEBUG_2) << u->nick << " matched akick " << (ak->GetAccount() ? ak->GetAccount()->GetDisplay() : ak->GetMask()); - ak->SetLastUsed(Anope::CurTime); - if (!ak->GetAccount() && ak->GetMask().find('#') == Anope::string::npos) - mask = ak->GetMask(); - reason = ak->GetReason(); - if (reason.empty()) - reason = Language::Translate(u, Config->GetModule(this)->Get("autokickreason").c_str()); - if (reason.empty()) - reason = Language::Translate(u, _("User has been banned from the channel")); - return EVENT_STOP; - } - } - - return EVENT_CONTINUE; - } -}; - -MODULE_INIT(CSAKick) diff --git a/modules/commands/cs_ban.cpp b/modules/commands/cs_ban.cpp deleted file mode 100644 index f797eda0f..000000000 --- a/modules/commands/cs_ban.cpp +++ /dev/null @@ -1,268 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - - -#include "module.h" - -static Module *me; - -class TempBan : public Timer -{ - private: - Anope::string channel; - Anope::string mask; - Anope::string mode; - - public: - TempBan(time_t seconds, Channel *c, const Anope::string &banmask, const Anope::string &mod) : Timer(me, seconds), channel(c->name), mask(banmask), mode(mod) { } - - void Tick(time_t ctime) override - { - Channel *c = Channel::Find(this->channel); - if (c) - c->RemoveMode(NULL, mode, this->mask); - } -}; - -class CommandCSBan : public Command -{ - public: - CommandCSBan(Module *creator) : Command(creator, "chanserv/ban", 2, 4) - { - this->SetDesc(_("Bans a given nick or mask on a channel")); - this->SetSyntax(_("\037channel\037 [+\037expiry\037] {\037nick\037 | \037mask\037} [\037reason\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - Configuration::Block *block = Config->GetCommand(source); - const Anope::string &mode = block->Get("mode", "BAN"); - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - Channel *c = ci->c; - if (c == NULL) - { - source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); - return; - } - - if (IRCD->GetMaxListFor(c) && c->HasMode(mode) >= IRCD->GetMaxListFor(c)) - { - source.Reply(_("The %s list for %s is full."), mode.lower().c_str(), c->name.c_str()); - return; - } - - Anope::string expiry, target, reason; - time_t ban_time; - if (params[1][0] == '+') - { - ban_time = Anope::DoTime(params[1]); - if (ban_time == -1) - { - source.Reply(_("Invalid expiry time \002{0}\002."), params[1]); - return; - } - if (params.size() < 3) - { - this->SendSyntax(source); - return; - } - target = params[2]; - reason = "Requested"; - if (params.size() > 3) - reason = params[3]; - } - else - { - ban_time = 0; - target = params[1]; - reason = "Requested"; - if (params.size() > 2) - reason = params[2]; - if (params.size() > 3) - reason += " " + params[3]; - } - - unsigned reasonmax = Config->GetModule("chanserv")->Get("reasonmax", "200"); - if (reason.length() > reasonmax) - reason = reason.substr(0, reasonmax); - - Anope::string signkickformat = Config->GetModule("chanserv")->Get("signkickformat", "%m (%n)"); - signkickformat = signkickformat.replace_all_cs("%n", source.GetNick()); - - User *u = source.GetUser(); - User *u2 = User::Find(target, true); - - ChanServ::AccessGroup u_access = source.AccessFor(ci); - - if (!u_access.HasPriv("BAN") && !source.HasPriv("chanserv/kick")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "BAN", ci->GetName()); - return; - } - - if (u2) - { - ChanServ::AccessGroup u2_access = ci->AccessFor(u2); - - if (u != u2 && ci->HasFieldS("PEACE") && u2_access >= u_access && !source.HasPriv("chanserv/kick")) - { - source.Reply(_("Access denied. \002{0}\002 has the same or more privileges than you on \002{1}\002."), u2->nick, ci->GetName()); - return; - } - - /* - * Don't ban/kick the user on channels where he is excepted - * to prevent services <-> server wars. - */ - if (c->MatchesList(u2, "EXCEPT")) - { - source.Reply(_("\002{0}\002 matches an except on \002{1}\002 and can not be banned until the except has been removed."), u2->nick, ci->GetName()); - return; - } - - if (u2->IsProtected()) - { - source.Reply(_("Access denied. \002{0}\002 is protected and can not be kicked."), u2->nick); - return; - } - - Anope::string mask = ci->GetIdealBan(u2); - - bool override = !u_access.HasPriv("BAN") || (u != u2 && ci->HasFieldS("PEACE") && u2_access >= u_access); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << mask; - - if (!c->HasMode(mode, mask)) - { - c->SetMode(NULL, mode, mask); - if (ban_time) - { - new TempBan(ban_time, c, mask, mode); - source.Reply(_("Ban on \002{0}\002 expires in \002{1}\002."), mask, Anope::Duration(ban_time, source.GetAccount())); - } - } - - /* We still allow host banning while not allowing to kick */ - if (!c->FindUser(u2)) - return; - - if (block->Get("kick", "yes")) - { - if (ci->HasFieldS("SIGNKICK") || (ci->HasFieldS("SIGNKICK_LEVEL") && !source.AccessFor(ci).HasPriv("SIGNKICK"))) - { - signkickformat = signkickformat.replace_all_cs("%m", reason); - c->Kick(ci->WhoSends(), u2, "%s", signkickformat.c_str()); - } - else - c->Kick(ci->WhoSends(), u2, "%s", reason.c_str()); - } - } - else - { - bool founder = u_access.HasPriv("FOUNDER"); - bool override = !founder && !u_access.HasPriv("BAN"); - - Anope::string mask = IRCD->NormalizeMask(target); - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << mask; - - if (!c->HasMode(mode, mask)) - { - c->SetMode(NULL, mode, mask); - if (ban_time) - { - new TempBan(ban_time, c, mask, mode); - source.Reply(_("Ban on \002{0}\002 expires in \002{1}\002."), mask, Anope::Duration(ban_time, source.GetAccount())); - } - } - - int matched = 0, kicked = 0; - for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end;) - { - ChanUserContainer *uc = it->second; - ++it; - - Entry e(mode, mask); - if (e.Matches(uc->user)) - { - ++matched; - - ChanServ::AccessGroup u2_access = ci->AccessFor(uc->user); - - if (matched > 1 && !founder) - continue; - if (u != uc->user && ci->HasFieldS("PEACE") && u2_access >= u_access) - continue; - else if (ci->c->MatchesList(uc->user, "EXCEPT")) - continue; - else if (uc->user->IsProtected()) - continue; - - if (block->Get("kick", "yes")) - { - ++kicked; - - if (ci->HasFieldS("SIGNKICK") || (ci->HasFieldS("SIGNKICK_LEVEL") && !u_access.HasPriv("SIGNKICK"))) - { - reason += " (Matches " + mask + ")"; - signkickformat = signkickformat.replace_all_cs("%m", reason); - c->Kick(ci->WhoSends(), uc->user, "%s", signkickformat.c_str()); - } - else - c->Kick(ci->WhoSends(), uc->user, "%s (Matches %s)", reason.c_str(), mask.c_str()); - } - } - } - - if (matched) - source.Reply(_("Kicked \002{0}/{1}\002 users matching \002{2}\002 from \002{3}\002."), kicked, matched, mask, c->name); - else - source.Reply(_("No users on \002{0}\002 match \002{1}\002."), c->name, mask); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Bans a given \037nick\037 or \037mask\037 on \002channel\002. An optional expiry may be given to cause services to remove the ban after a set amount of time.\n" - "\n" - "Use of this command requires the \002{0}\002 privilege on \037channel\037.\n" - "\n" - "Examples:\n" - " {command} #anope Jobe\n" - " Bans the user \"Jobe\" from \"#anope\".\n" - "\n" - " {command} #anope Guest!*@*\n" - " Bans the mask \"Guest!*@*\" on \"#anope\"."), - "BAN"); - return true; - } -}; - -class CSBan : public Module -{ - CommandCSBan commandcsban; - - public: - CSBan(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsban(this) - { - me = this; - } -}; - -MODULE_INIT(CSBan) diff --git a/modules/commands/cs_clone.cpp b/modules/commands/cs_clone.cpp deleted file mode 100644 index 256446bd1..000000000 --- a/modules/commands/cs_clone.cpp +++ /dev/null @@ -1,221 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/bs_badwords.h" -#include "modules/cs_akick.h" - -class CommandCSClone : public Command -{ -#if 0 - void CopyLevels(CommandSource &source, ChannelInfo *ci, ChannelInfo *target_ci) - { - const Anope::map &cilevels = ci->GetLevelEntries(); - - for (Anope::map::const_iterator it = cilevels.begin(); it != cilevels.end(); ++it) - { - target_ci->SetLevel(it->first, it->second); - } - - source.Reply(_("All level entries from \002%s\002 have been cloned into \002%s\002."), ci->name.c_str(), target_ci->name.c_str()); - } -#endif - -public: - CommandCSClone(Module *creator) : Command(creator, "chanserv/clone", 2, 3) - { - this->SetDesc(_("Copy all settings from one channel to another")); - this->SetSyntax(_("\037channel\037 \037target\037 [\037what\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &channel = params[0]; - const Anope::string &target = params[1]; - Anope::string what = params.size() > 2 ? params[2] : ""; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - User *u = source.GetUser(); - ChanServ::Channel *ci = ChanServ::Find(channel); - bool override = false; - - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), channel); - return; - } - - ChanServ::Channel *target_ci = ChanServ::Find(target); - if (!target_ci) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), target); - return; - } - - if (ci == target_ci) - { - source.Reply(_("Cannot clone channel \002{0}\002 to itself!"), ci->GetName()); - return; - } - - if (!source.IsFounder(ci) || !source.IsFounder(target_ci)) - { - if (!source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have the privilege \002{0}\002 on \002{1}\002 and \002{2}\002."), "FOUNDER", ci->GetName(), target_ci->GetName()); - return; - } - else - override = true; - } - - if (what.equals_ci("ALL")) - what.clear(); - - if (what.empty()) - { - target_ci->Delete(); - target_ci = ChanServ::channel.Create(); - target_ci->SetName(target); - ChanServ::registered_channel_map& map = ChanServ::service->GetChannels(); - map[target_ci->GetName()] = target_ci; - target_ci->c = Channel::Find(target_ci->GetName()); - - if (ci->GetBot()) - ci->GetBot()->Assign(u, target_ci); - else - target_ci->SetBot(nullptr); - - if (target_ci->c) - { - target_ci->c->ci = target_ci; - - target_ci->c->CheckModes(); - - target_ci->c->SetCorrectModes(u, true); - } - - if (target_ci->c && !target_ci->c->topic.empty()) - { - target_ci->SetLastTopic(target_ci->GetLastTopic()); - target_ci->SetLastTopicSetter(target_ci->c->topic_setter); - target_ci->SetLastTopicTime(target_ci->c->topic_time); - } - else - target_ci->SetLastTopicSetter(source.service->nick); - - Event::OnChanRegistered(&Event::ChanRegistered::OnChanRegistered, target_ci); - - source.Reply(_("All settings from \002{0}\002 have been cloned to \002{0}\002."), channel, target); - } - else if (what.equals_ci("ACCESS")) - { - std::set masks; - unsigned access_max = Config->GetModule("chanserv")->Get("accessmax", "1024"); - unsigned count = 0; - - for (unsigned i = 0; i < target_ci->GetAccessCount(); ++i) - masks.insert(target_ci->GetAccess(i)->Mask()); - - for (unsigned i = 0; i < ci->GetAccessCount(); ++i) - { - ChanServ::ChanAccess *taccess = ci->GetAccess(i); - - if (access_max && target_ci->GetDeepAccessCount() >= access_max) - break; - - if (masks.count(taccess->Mask())) - continue; - masks.insert(taccess->Mask()); - - ChanServ::ChanAccess *newaccess = anope_dynamic_static_cast(taccess->GetSerializableType()->Create()); - newaccess->SetChannel(taccess->GetChannel()); - newaccess->SetMask(taccess->GetMask()); - newaccess->SetMask(taccess->GetMask()); - newaccess->SetCreator(taccess->GetCreator()); - newaccess->SetLastSeen(taccess->GetLastSeen()); - newaccess->SetCreated(taccess->GetCreated()); - newaccess->AccessUnserialize(taccess->AccessSerialize()); - - ++count; - } - - source.Reply(_("\002{0}\002 access entries from \002{1}\002 have been cloned to \002{2}\002."), count, channel, target); - } - else if (what.equals_ci("AKICK")) - { - target_ci->ClearAkick(); - for (unsigned i = 0; i < ci->GetAkickCount(); ++i) - { - AutoKick *ak = ci->GetAkick(i); - if (ak->GetAccount()) - target_ci->AddAkick(ak->GetCreator(), ak->GetAccount(), ak->GetReason(), ak->GetAddTime(), ak->GetLastUsed()); - else - target_ci->AddAkick(ak->GetCreator(), ak->GetMask(), ak->GetReason(), ak->GetAddTime(), ak->GetLastUsed()); - } - - source.Reply(_("All auto kick entries from \002{0}\002 have been cloned to \002{1}\002."), channel, target); - } - else if (what.equals_ci("BADWORDS")) - { - if (!badwords) - { - source.Reply(_("All badword entries from \002{0}\002 have been cloned to \002{1}\002."), channel, target); // BotServ doesn't exist/badwords isn't loaded - return; - } - - badwords->ClearBadWords(target_ci); - - for (unsigned i = 0; i < badwords->GetBadWordCount(ci); ++i) - { - BadWord *bw = badwords->GetBadWord(ci, i); - badwords->AddBadWord(target_ci, bw->GetWord(), bw->GetType()); - } - - source.Reply(_("All badword entries from \002{0}\002 have been cloned to \002{1}\002."), channel, target); - } - else - { - this->OnSyntaxError(source, ""); - return; - } - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clone " << (what.empty() ? "everything from it" : what) << " to " << target_ci->GetName(); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Copies all settings, access, akicks, etc. from \037channel\037 to the \037target\037 channel." - " If \037what\037 is \002ACCESS\002, \002AKICK\002, or \002BADWORDS\002 then only the respective settings are cloned.\n" - "\n" - "You must be the founder of \037channel\037 and \037target\037.")); - return true; - } -}; - -class CSClone : public Module -{ - CommandCSClone commandcsclone; - - public: - CSClone(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsclone(this) - { - - } -}; - -MODULE_INIT(CSClone) diff --git a/modules/commands/cs_drop.cpp b/modules/commands/cs_drop.cpp deleted file mode 100644 index a1020bcab..000000000 --- a/modules/commands/cs_drop.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_drop.h" - -class CommandCSDrop : public Command -{ - EventHandlers &onchandrop; - - public: - CommandCSDrop(Module *creator, EventHandlers &event) : Command(creator, "chanserv/drop", 1, 2), onchandrop(event) - { - this->SetDesc(_("Cancel the registration of a channel")); - this->SetSyntax(_("\037channel\037 \037channel\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - - if (Anope::ReadOnly && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Sorry, channel de-registration is temporarily disabled.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (params.size() < 2 || !chan.equals_ci(params[1])) - { - source.Reply(_("You must enter the channel name twice as a confirmation that you wish to drop \002{0}\002."), ci->GetName()); - return; - } - - if ((ci->HasFieldS("SECUREFOUNDER") ? !source.IsFounder(ci) : !source.AccessFor(ci).HasPriv("FOUNDER")) && !source.HasCommand("chanserv/drop")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); - return; - } - - EventReturn MOD_RESULT = this->onchandrop(&Event::ChanDrop::OnChanDrop, source, ci); - if (MOD_RESULT == EVENT_STOP) - return; - - bool override = (ci->HasFieldS("SECUREFOUNDER") ? !source.IsFounder(ci) : !source.AccessFor(ci).HasPriv("FOUNDER")); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "(founder was: " << (ci->GetFounder() ? ci->GetFounder()->GetDisplay() : "none") << ")"; - - Reference c = ci->c; - ci->Delete(); - - source.Reply(_("Channel \002{0}\002 has been dropped."), chan); - - if (c) - c->CheckModes(); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Unregisters \037channel\037. All channel settings will be deleted. As a precaution, you must give the \037channel\037 name twice.\n" - "\n" - "Use of this command requires the \002{0}\002 privilege on \037channel\037."), - "FOUNDER"); - - if (source.HasCommand("chanserv/drop")) - source.Reply(_("\n" - "As a Services Operator with the command \002{0}\002, you may drop any channel."), - "chanserv/drop"); - - return true; - } -}; - -class CSDrop : public Module -{ - CommandCSDrop commandcsdrop; - EventHandlers onchandrop; - - public: - CSDrop(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsdrop(this, onchandrop) - , onchandrop(this) - { - - } -}; - -MODULE_INIT(CSDrop) diff --git a/modules/commands/cs_enforce.cpp b/modules/commands/cs_enforce.cpp deleted file mode 100644 index 10069d3f8..000000000 --- a/modules/commands/cs_enforce.cpp +++ /dev/null @@ -1,307 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Original Coder: GeniusDex - * - * Please read COPYING and README for further details. - * - * Send any bug reports to the Anope Coder, as he will be able - * to deal with it best. - */ - -#include "module.h" - -class CommandCSEnforce : public Command -{ - private: - void DoSecureOps(CommandSource &source, ChanServ::Channel *ci) - { - bool override = !source.AccessFor(ci).HasPriv("AKICK") && source.HasPriv("chanserv/access/modify"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enforce secureops"; - - /* Dirty hack to allow Channel::SetCorrectModes to work ok. - * We pretend like SECUREOPS is on so it doesn't ignore that - * part of the code. This way we can enforce SECUREOPS even - * if it's off. - */ - bool hadsecureops = ci->HasFieldS("SECUREOPS"); - ci->SetS("SECUREOPS", true); - - for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) - { - ChanUserContainer *uc = it->second; - - ci->c->SetCorrectModes(uc->user, false); - } - - if (!hadsecureops) - ci->UnsetS("SECUREOPS"); - - source.Reply(_("\002Secureops\002 enforced on \002{0}\002."), ci->GetName()); - } - - void DoRestricted(CommandSource &source, ChanServ::Channel *ci) - { - bool override = !source.AccessFor(ci).HasPriv("AKICK") && source.HasPriv("chanserv/access/modify"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enforce restricted"; - - std::vector users; - for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) - { - ChanUserContainer *uc = it->second; - User *user = uc->user; - - if (user->IsProtected()) - continue; - - if (ci->AccessFor(user).empty()) - users.push_back(user); - } - - for (unsigned i = 0; i < users.size(); ++i) - { - User *user = users[i]; - - Anope::string mask = ci->GetIdealBan(user); - Anope::string reason = Language::Translate(user, _("RESTRICTED enforced by ")) + source.GetNick(); - ci->c->SetMode(NULL, "BAN", mask); - ci->c->Kick(NULL, user, "%s", reason.c_str()); - } - - source.Reply(_("\002Restricted\002 enforced on \002{0}\002."), ci->GetName()); - } - - void DoRegOnly(CommandSource &source, ChanServ::Channel *ci) - { - bool override = !source.AccessFor(ci).HasPriv("AKICK") && source.HasPriv("chanserv/access/modify"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enforce registered only"; - - std::vector users; - for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) - { - ChanUserContainer *uc = it->second; - User *user = uc->user; - - if (user->IsProtected()) - continue; - - if (!user->IsIdentified()) - users.push_back(user); - } - - for (unsigned i = 0; i < users.size(); ++i) - { - User *user = users[i]; - - Anope::string mask = ci->GetIdealBan(user); - Anope::string reason = Language::Translate(user, _("REGONLY enforced by ")) + source.GetNick(); - if (!ci->c->HasMode("REGISTEREDONLY")) - ci->c->SetMode(NULL, "BAN", mask); - ci->c->Kick(NULL, user, "%s", reason.c_str()); - } - - source.Reply(_("\002Registered only\002 enforced on \002{0}\002."), ci->GetName()); - } - - void DoSSLOnly(CommandSource &source, ChanServ::Channel *ci) - { - bool override = !source.AccessFor(ci).HasPriv("AKICK") && source.HasPriv("chanserv/access/modify"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enforce SSL only"; - - std::vector users; - for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) - { - ChanUserContainer *uc = it->second; - User *user = uc->user; - - if (user->IsProtected()) - continue; - - if (!user->HasMode("SSL") && !user->HasExtOK("ssl")) - users.push_back(user); - } - - for (unsigned i = 0; i < users.size(); ++i) - { - User *user = users[i]; - - Anope::string mask = ci->GetIdealBan(user); - Anope::string reason = Language::Translate(user, _("SSLONLY enforced by ")) + source.GetNick(); - if (!ci->c->HasMode("SSL")) - ci->c->SetMode(NULL, "BAN", mask); - ci->c->Kick(NULL, user, "%s", reason.c_str()); - } - - source.Reply(_("\002SSL only\002 enforced on %s."), ci->GetName().c_str()); - } - - void DoBans(CommandSource &source, ChanServ::Channel *ci) - { - bool override = !source.AccessFor(ci).HasPriv("AKICK") && source.HasPriv("chanserv/access/modify"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enforce bans"; - - std::vector users; - for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) - { - ChanUserContainer *uc = it->second; - User *user = uc->user; - - if (user->IsProtected()) - continue; - - if (ci->c->MatchesList(user, "BAN") && !ci->c->MatchesList(user, "EXCEPT")) - users.push_back(user); - } - - for (unsigned i = 0; i < users.size(); ++i) - { - User *user = users[i]; - - Anope::string reason = Language::Translate(user, _("BANS enforced by ")) + source.GetNick(); - ci->c->Kick(NULL, user, "%s", reason.c_str()); - } - - source.Reply(_("\002Bans\002 enforced on %s."), ci->GetName().c_str()); - } - - void DoLimit(CommandSource &source, ChanServ::Channel *ci) - { - bool override = !source.AccessFor(ci).HasPriv("AKICK") && source.HasPriv("chanserv/access/modify"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enforce limit"; - - Anope::string l_str; - if (!ci->c->GetParam("LIMIT", l_str)) - { - source.Reply(_("There is no limit is set on \002{0}\002."), ci->GetName()); - return; - } - - int l; - try - { - l = convertTo(l_str); - if (l < 0) - throw ConvertException(); - } - catch (const ConvertException &) - { - source.Reply(_("The limit on \002{0}\002 is not valid."), ci->GetName()); - return; - } - - std::vector users; - /* The newer users are at the end of the list, so kick users starting from the end */ - for (Channel::ChanUserList::reverse_iterator it = ci->c->users.rbegin(), it_end = ci->c->users.rend(); it != it_end; ++it) - { - ChanUserContainer *uc = it->second; - User *user = uc->user; - - if (user->IsProtected()) - continue; - - if (!ci->AccessFor(user).empty()) - continue; - - if (ci->c->users.size() - users.size() <= static_cast(l)) - continue; - - users.push_back(user); - } - - for (unsigned i = 0; i < users.size(); ++i) - { - User *user = users[i]; - - Anope::string reason = Language::Translate(user, _("LIMIT enforced by ")) + source.GetNick(); - ci->c->Kick(NULL, user, "%s", reason.c_str()); - } - - source.Reply(_("LIMIT enforced on \002{0}\002, \002{1]\002 users removed."), ci->GetName(), users.size()); - } - - public: - CommandCSEnforce(Module *creator) : Command(creator, "chanserv/enforce", 2, 2) - { - this->SetDesc(_("Enforce various channel modes and set options")); - this->SetSyntax(_("\037channel\037 \037what\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &what = params.size() > 1 ? params[1] : ""; - - ChanServ::Channel *ci = ChanServ::Find(params[0]); - - if (!ci) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), params[0]); - return; - } - - if (!ci->c) - { - source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); - return; - } - - if (!source.AccessFor(ci).HasPriv("AKICK") && !source.HasPriv("chanserv/access/modify")) - { - source.Reply("Access denied. You do not have the \002{0}\002 privilege on \002{1}\002.", "AKICK", ci->GetName()); - return; - } - - if (what.equals_ci("SECUREOPS")) - this->DoSecureOps(source, ci); - else if (what.equals_ci("RESTRICTED")) - this->DoRestricted(source, ci); - else if (what.equals_ci("REGONLY")) - this->DoRegOnly(source, ci); - else if (what.equals_ci("SSLONLY")) - this->DoSSLOnly(source, ci); - else if (what.equals_ci("BANS")) - this->DoBans(source, ci); - else if (what.equals_ci("LIMIT")) - this->DoLimit(source, ci); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Enforce various channel modes and set options on \037channel\037." - "\n" - "The \002SECUREOPS\002 option enforces SECUREOPS, even if it is not enabled.\n" - "See \002/msg ChanServ HELP SET SECUREOPS\002 for information about SECUREOPS.\n" //XXX - "\n" - "The \002RESTRICTED\002 option enforces RESTRICTED, even if it is not enabled.\n" - "See \002/msg ChanServ HELP SET RESTRICTED\002 for information about RESTRICTED.\n" //XXX - "\n" - "The \002REGONLY\002 option removes all unregistered users.\n" - "\n" - "The \002SSLONLY\002 option removes all users not using a secure connection.\n" - "\n" - "The \002BANS\002 option enforces bans on the channel by removing users affected by then.\n" - "\n" - "The \002LIMIT\002 option will remove users until the user count drops below the channel limit, if one is set.\n" - "\n" - "Use of this command requires the \002{0}\002 privilege."), - "AKICK"); - return true; - } -}; - -class CSEnforce : public Module -{ - CommandCSEnforce commandcsenforce; - - public: - CSEnforce(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsenforce(this) - { - - } -}; - -MODULE_INIT(CSEnforce) diff --git a/modules/commands/cs_entrymsg.cpp b/modules/commands/cs_entrymsg.cpp deleted file mode 100644 index 31253599e..000000000 --- a/modules/commands/cs_entrymsg.cpp +++ /dev/null @@ -1,270 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_entrymsg.h" - -class EntryMsgImpl : public EntryMsg -{ - public: - EntryMsgImpl(Serialize::TypeBase *type) : EntryMsg(type) { } - EntryMsgImpl(Serialize::TypeBase *type, Serialize::ID id) : EntryMsg(type, id) { } - - ChanServ::Channel *GetChannel() override; - void SetChannel(ChanServ::Channel *ci) override; - - Anope::string GetCreator() override; - void SetCreator(const Anope::string &c) override; - - Anope::string GetMessage() override; - void SetMessage(const Anope::string &m) override; - - time_t GetWhen() override; - void SetWhen(const time_t &t) override; -}; - -class EntryMsgType : public Serialize::Type -{ - public: - Serialize::ObjectField chan; - Serialize::Field creator, message; - Serialize::Field when; - - EntryMsgType(Module *me) : Serialize::Type(me, "EntryMsg") - , chan(this, "chan", true) - , creator(this, "creator") - , message(this, "message") - , when(this, "when") - { - } -}; - -ChanServ::Channel *EntryMsgImpl::GetChannel() -{ - return Get(&EntryMsgType::chan); -} - -void EntryMsgImpl::SetChannel(ChanServ::Channel *ci) -{ - Set(&EntryMsgType::chan, ci); -} - -Anope::string EntryMsgImpl::GetCreator() -{ - return Get(&EntryMsgType::creator); -} - -void EntryMsgImpl::SetCreator(const Anope::string &c) -{ - Set(&EntryMsgType::creator, c); -} - -Anope::string EntryMsgImpl::GetMessage() -{ - return Get(&EntryMsgType::message); -} - -void EntryMsgImpl::SetMessage(const Anope::string &m) -{ - Set(&EntryMsgType::message, m); -} - -time_t EntryMsgImpl::GetWhen() -{ - return Get(&EntryMsgType::when); -} - -void EntryMsgImpl::SetWhen(const time_t &t) -{ - Set(&EntryMsgType::when, t); -} - -class CommandEntryMessage : public Command -{ - private: - void DoList(CommandSource &source, ChanServ::Channel *ci) - { - std::vector messages = ci->GetRefs(entrymsg); - - if (messages.empty()) - { - source.Reply(_("Entry message list for \002{0}\002 is empty."), ci->GetName()); - return; - } - - source.Reply(_("Entry message list for \002{0}\002:"), ci->GetName()); - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Message")); - for (unsigned i = 0; i < messages.size(); ++i) - { - EntryMsg *msg = messages[i]; - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(i + 1); - entry["Creator"] = msg->GetCreator(); - entry["Created"] = Anope::strftime(msg->GetWhen(), NULL, true); - entry["Message"] = msg->GetMessage(); - list.AddEntry(entry); - } - - std::vector replies; - list.Process(replies); - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("End of entry message list.")); - } - - void DoAdd(CommandSource &source, ChanServ::Channel *ci, const Anope::string &message) - { - std::vector messages = ci->GetRefs(entrymsg); - - if (messages.size() >= Config->GetModule(this->owner)->Get("maxentries")) - { - source.Reply(_("The entry message list for \002{0}\002 is full."), ci->GetName()); - return; - } - - EntryMsg *msg = entrymsg.Create(); - msg->SetChannel(ci); - msg->SetCreator(source.GetNick()); - msg->SetMessage(message); - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to add a message"; - source.Reply(_("Entry message added to \002{0}\002"), ci->GetName()); - } - - void DoDel(CommandSource &source, ChanServ::Channel *ci, const Anope::string &message) - { - std::vector messages = ci->GetRefs(entrymsg); - - if (!message.is_pos_number_only()) - source.Reply(("Entry message \002{0}\002 not found on channel \002{1}\002."), message, ci->GetName()); - else if (messages.empty()) - source.Reply(_("Entry message list for \002{0}\002 is empty."), ci->GetName()); - else - { - try - { - unsigned i = convertTo(message); - if (i > 0 && i <= messages.size()) - { - messages[i - 1]->Delete(); - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to remove a message"; - source.Reply(_("Entry message \002{0}\002 for \002{1]\002 deleted."), i, ci->GetName()); - } - else - throw ConvertException(); - } - catch (const ConvertException &) - { - source.Reply(_("Entry message \002{0}\002 not found on channel \002{1}\002."), message, ci->GetName()); - } - } - } - - void DoClear(CommandSource &source, ChanServ::Channel *ci) - { - for (EntryMsg *e : ci->GetRefs(entrymsg)) - delete e; - - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to remove all messages"; - source.Reply(_("Entry messages for \002{0}\002 have been cleared."), ci->GetName()); - } - - public: - CommandEntryMessage(Module *creator) : Command(creator, "chanserv/entrymsg", 2, 3) - { - this->SetDesc(_("Manage the channel's entry messages")); - this->SetSyntax(_("\037channel\037 ADD \037message\037")); - this->SetSyntax(_("\037channel\037 DEL \037num\037")); - this->SetSyntax(_("\037channel\037 LIST")); - this->SetSyntax(_("\037channel\037 CLEAR")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (Anope::ReadOnly && !params[1].equals_ci("LIST")) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - if (!source.AccessFor(ci).HasPriv("SET") && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); - return; - } - - if (params[1].equals_ci("LIST")) - this->DoList(source, ci); - else if (params[1].equals_ci("CLEAR")) - this->DoClear(source, ci); - else if (params.size() < 3) - this->OnSyntaxError(source, ""); - else if (params[1].equals_ci("ADD")) - this->DoAdd(source, ci, params[2]); - else if (params[1].equals_ci("DEL")) - this->DoDel(source, ci, params[2]); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Controls what messages will be sent to users when they join \037channel\037.\n" - "\n" - "The \002{0} ADD\002 command adds the given message to the list of messages to be shown to users when they join the channel.\n" - "\n" - "The \002{0} DEL\002 command removes the given message from the entry message list.\n" - " You can remove the message by specifying its number which you can get by listing the messages as explained below.\n" - "\n" - "The \002{0} LIST\002 command displays the list of messages on the entry message list.\n" - "\n" - "The \002{0} CLEAR\002 command clears the entry message list.\n" - "\n" - "Use of this command requires the \002SET\002 privilege on \037channel\037."), - source.command); - return true; - } -}; - -class CSEntryMessage : public Module - , public EventHook -{ - CommandEntryMessage commandentrymsg; - EntryMsgType entrymsg_type; - - public: - CSEntryMessage(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandentrymsg(this) - , entrymsg_type(this) - { - } - - void OnJoinChannel(User *u, Channel *c) override - { - if (u && c && c->ci && u->server->IsSynced()) - for (EntryMsg *msg : c->ci->GetRefs(entrymsg)) - u->SendMessage(c->ci->WhoSends(), "[{0}] {1}", c->ci->GetName(), msg->GetMessage()); - } -}; - -MODULE_INIT(CSEntryMessage) diff --git a/modules/commands/cs_flags.cpp b/modules/commands/cs_flags.cpp deleted file mode 100644 index 88cc501ac..000000000 --- a/modules/commands/cs_flags.cpp +++ /dev/null @@ -1,530 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -/* Dependencies: chanserv */ - -#include "module.h" -#include "modules/cs_access.h" -#include "../pseudoclients/chanserv/chanaccess.h" -#include "../pseudoclients/chanserv/chanaccesstype.h" - -static std::map defaultFlags; - -class FlagsChanAccess : public ChanAccessImpl -{ - public: - FlagsChanAccess(Serialize::TypeBase *type) : ChanAccessImpl(type) { } - FlagsChanAccess(Serialize::TypeBase *type, Serialize::ID id) : ChanAccessImpl(type, id) { } - - Anope::string GetFlags(); - void SetFlags(const Anope::string &); - - bool HasPriv(const Anope::string &priv) override - { - std::map::iterator it = defaultFlags.find(priv); - return it != defaultFlags.end() && GetFlags().find(it->second) != Anope::string::npos; - } - - Anope::string AccessSerialize() - { - return GetFlags(); - } - - void AccessUnserialize(const Anope::string &data) override - { - SetFlags(data); - } - - static Anope::string DetermineFlags(ChanServ::ChanAccess *access) - { - if (access->GetSerializableType()->GetName() != "FlagsChanAccess") - return access->AccessSerialize(); - - std::set buffer; - - for (std::map::iterator it = defaultFlags.begin(), it_end = defaultFlags.end(); it != it_end; ++it) - if (access->HasPriv(it->first)) - buffer.insert(it->second); - - if (buffer.empty()) - return "(none)"; - else - return Anope::string(buffer.begin(), buffer.end()); - } -}; - -class FlagsChanAccessType : public Serialize::Type -{ - public: - Serialize::Field flags; - //Serialize::Field>> flags; XXX? - - FlagsChanAccessType(Module *me) : Serialize::Type(me, "FlagsChanAccess") - , flags(this, "flags") - { - SetParent(ChanServ::chanaccess); - } -}; - -Anope::string FlagsChanAccess::GetFlags() -{ - return Get(&FlagsChanAccessType::flags); -} - -void FlagsChanAccess::SetFlags(const Anope::string &i) -{ - Object::Set(&FlagsChanAccessType::flags, i); -} - -class CommandCSFlags : public Command -{ - void DoModify(CommandSource &source, ChanServ::Channel *ci, Anope::string mask, const Anope::string &flags) - { - if (flags.empty()) - { - this->OnSyntaxError(source, ""); - return; - } - - ChanServ::AccessGroup u_access = source.AccessFor(ci); - ChanServ::ChanAccess *highest = u_access.Highest(); - - NickServ::Nick *na = nullptr; - ChanServ::Channel *targ_ci = nullptr; - - if (IRCD->IsChannelValid(mask)) - { - if (Config->GetModule("chanserv")->Get("disallow_channel_access")) - { - source.Reply(_("Channels may not be on access lists.")); - return; - } - - targ_ci = ChanServ::Find(mask); - if (targ_ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), mask); - return; - } - else if (ci == targ_ci) - { - source.Reply(_("You can't add a channel to its own access list.")); - return; - } - - mask = targ_ci->GetName(); - } - else - { - na = NickServ::FindNick(mask); - if (!na && Config->GetModule("chanserv")->Get("disallow_hostmask_access")) - { - source.Reply(_("Masks and unregistered users may not be on access lists.")); - return; - } - else if (mask.find_first_of("!*@") == Anope::string::npos && !na) - { - User *targ = User::Find(mask, true); - if (targ != NULL) - mask = "*!*@" + targ->GetDisplayedHost(); - else - { - source.Reply(_("\002{0}\002 isn't registered."), mask); - return; - } - } - - if (na) - mask = na->GetNick(); - } - - ChanServ::ChanAccess *current = NULL; - unsigned current_idx; - std::set current_flags; - bool override = false; - for (current_idx = ci->GetAccessCount(); current_idx > 0; --current_idx) - { - ChanServ::ChanAccess *access = ci->GetAccess(current_idx - 1); - if ((na && na->GetAccount() == access->GetAccount()) || mask.equals_ci(access->Mask())) - { - // Flags allows removing others that have the same access as you, - // but no other access system does. - if (highest && highest->GetSerializableType()->GetName() != "FlagsChanAccess" && !u_access.founder) - // operator<= on the non-me entry! - if (*highest <= *access) - { - if (source.HasPriv("chanserv/access/modify")) - override = true; - else - { - source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to modify the access of \002{1}\002."), ci->GetName(), access->Mask()); - return; - } - } - - current = access; - Anope::string cur_flags = FlagsChanAccess::DetermineFlags(access); - for (unsigned j = cur_flags.length(); j > 0; --j) - current_flags.insert(cur_flags[j - 1]); - break; - } - } - - unsigned access_max = Config->GetModule("chanserv")->Get("accessmax", "1024"); - if (access_max && ci->GetDeepAccessCount() >= access_max) - { - source.Reply(_("Sorry, you can only have \002{0}\002 access entries on a channel, including access entries from other channels."), access_max); - return; - } - - ChanServ::Privilege *p = NULL; - bool add = true; - for (size_t i = 0; i < flags.length(); ++i) - { - char f = flags[i]; - switch (f) - { - case '+': - add = true; - break; - case '-': - add = false; - break; - case '*': - for (std::map::iterator it = defaultFlags.begin(), it_end = defaultFlags.end(); it != it_end; ++it) - { - bool has = current_flags.count(it->second); - // If we are adding a flag they already have or removing one they don't have, don't bother - if (add == has) - continue; - - if (!u_access.HasPriv(it->first) && !u_access.founder) - { - if (source.HasPriv("chanserv/access/modify")) - override = true; - else - continue; - } - - if (add) - current_flags.insert(it->second); - else - current_flags.erase(it->second); - } - break; - default: - p = ChanServ::service ? ChanServ::service->FindPrivilege(flags.substr(i)) : nullptr; - if (p != NULL && defaultFlags[p->name]) - { - f = defaultFlags[p->name]; - i = flags.length(); - } - - for (std::map::iterator it = defaultFlags.begin(), it_end = defaultFlags.end(); it != it_end; ++it) - { - if (f != it->second) - continue; - else if (!u_access.HasPriv(it->first) && !u_access.founder) - { - if (source.HasPriv("chanserv/access/modify")) - override = true; - else - { - source.Reply(_("You can not set the \002{0}\002 flag."), f); - break; - } - } - if (add) - current_flags.insert(f); - else - current_flags.erase(f); - break; - } - } - } - if (current_flags.empty()) - { - if (current != NULL) - { - Event::OnAccessDel(&Event::AccessDel::OnAccessDel, ci, source, current); - delete current; - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << mask; - source.Reply(_("\002{0}\002 removed from the access list of \002{1}\002."), mask, ci->GetName()); - } - else - { - source.Reply(_("\002{0}\002 not found on the access list of \002{1}\002."), mask, ci->GetName()); - } - return; - } - - FlagsChanAccess *access = anope_dynamic_static_cast(flagschanaccess.Create()); - if (na) - access->SetObj(na->GetAccount()); - else if (targ_ci) - access->SetObj(targ_ci); - access->SetChannel(ci); - access->SetMask(mask); - access->SetCreator(source.GetNick()); - access->SetLastSeen(current ? current->GetLastSeen() : 0); - access->SetCreated(Anope::CurTime); - access->SetFlags(Anope::string(current_flags.begin(), current_flags.end())); - - if (current != NULL) - delete current; - - Event::OnAccessAdd(&Event::AccessAdd::OnAccessAdd, ci, source, access); - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to modify " << mask << "'s flags to " << access->AccessSerialize(); - if (p != NULL) - { - if (add) - source.Reply(_("Privilege \002{0}\002 added to \002{1}\002 on \002{2}\002, new flags are +\002{3}\002"), p->name, access->Mask(), ci->GetName(), access->AccessSerialize()); - else - source.Reply(_("Privilege \002{0}\002 removed from \002{1}\002 on \002{2}\002, new flags are +\002{3}\002"), p->name, access->Mask(), ci->GetName(), access->AccessSerialize()); - } - else - source.Reply(_("Flags for \002{0}\002 on \002{1}\002 set to +\002{2}\002"), access->Mask(), ci->GetName(), access->AccessSerialize()); - } - - void DoList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - const Anope::string &arg = params.size() > 2 ? params[2] : ""; - - if (!ci->GetAccessCount()) - { - source.Reply(_("The access list of \002{0}\002 is empty."), ci->GetName()); - return; - } - - ListFormatter list(source.GetAccount()); - - list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Flags")).AddColumn(_("Creator")).AddColumn(_("Created")); - - unsigned count = 0; - for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i) - { - ChanServ::ChanAccess *access = ci->GetAccess(i); - const Anope::string &flags = FlagsChanAccess::DetermineFlags(access); - - if (!arg.empty()) - { - if (arg[0] == '+') - { - bool pass = true; - for (size_t j = 1; j < arg.length(); ++j) - if (flags.find(arg[j]) == Anope::string::npos) - pass = false; - if (pass == false) - continue; - } - else if (!Anope::Match(access->Mask(), arg)) - continue; - } - - ListFormatter::ListEntry entry; - ++count; - entry["Number"] = stringify(i + 1); - entry["Mask"] = access->Mask(); - entry["Flags"] = flags; - entry["Creator"] = access->GetCreator(); - entry["Created"] = Anope::strftime(access->GetCreated(), source.nc, true); - list.AddEntry(entry); - } - - if (list.IsEmpty()) - source.Reply(_("No matching entries on the access list of \002{0}\002."), ci->GetName()); - else - { - std::vector replies; - list.Process(replies); - - source.Reply(_("Flags list for \002{0}\002:"), ci->GetName()); - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - if (count == ci->GetAccessCount()) - source.Reply(_("End of access list.")); - else - source.Reply(_("End of access list - \002{0}/{1}\002 entries shown."), count, ci->GetAccessCount()); - } - } - - void DoClear(CommandSource &source, ChanServ::Channel *ci) - { - if (!source.IsFounder(ci) && !source.HasPriv("chanserv/access/modify")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); - return; - } - - ci->ClearAccess(); - - Event::OnAccessClear(&Event::AccessClear::OnAccessClear, ci, source); - - source.Reply(_("The access list of \002{0}\002 has been cleared."), ci->GetName()); - - bool override = !source.IsFounder(ci); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clear the access list"; - } - - public: - CommandCSFlags(Module *creator) : Command(creator, "chanserv/flags", 1, 4) - { - this->SetDesc(_("Modify the list of privileged users")); - this->SetSyntax(_("\037channel\037 [MODIFY] \037mask\037 \037changes\037")); - this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | +\037flags\037]")); - this->SetSyntax(_("\037channel\037 CLEAR")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &cmd = params.size() > 1 ? params[1] : ""; - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - bool is_list = cmd.empty() || cmd.equals_ci("LIST"); - bool has_access = false; - if (source.HasPriv("chanserv/access/modify")) - has_access = true; - else if (is_list && source.HasPriv("chanserv/access/list")) - has_access = true; - else if (is_list && source.AccessFor(ci).HasPriv("ACCESS_LIST")) - has_access = true; - else if (source.AccessFor(ci).HasPriv("ACCESS_CHANGE")) - has_access = true; - - if (!has_access) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), is_list ? "ACCESS_LIST" : "ACCESS_CHANGE", ci->GetName()); - return; - } - - if (Anope::ReadOnly && !is_list) - { - source.Reply(_("Sorry, channel access list modification is temporarily disabled.")); - return; - } - - if (is_list) - this->DoList(source, ci, params); - else if (cmd.equals_ci("CLEAR")) - this->DoClear(source, ci); - else - { - Anope::string mask, flags; - if (cmd.equals_ci("MODIFY")) - { - mask = params.size() > 2 ? params[2] : ""; - flags = params.size() > 3 ? params[3] : ""; - } - else - { - mask = cmd; - flags = params.size() > 2 ? params[2] : ""; - } - - this->DoModify(source, ci, mask, flags); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("{0} allow granularly granting privileges on channels to users by assigning them flags, which map to one or more privileges.\n" - "\n" - "The \002MODIFY\002 command allows you to modify the access list." - " If \037mask\037 is not already on the access list is it added, then the \037changes\037 are applied." - " If the mask has no more flags, then the mask is removed from the access list." - " Additionally, you may use +* or -* to add or remove all flags, respectively." - " You are only able to modify the access list if you have the \002{1}\002 privilege on the channel, and can only give privileges to up what you have."), - source.command, "ACCESS_CHANGE"); - source.Reply(" "); - source.Reply(_("The \002LIST\002 command allows you to list existing entries on the channel access list." - " If \037mask\037 is given, the \037mask\037 is wildcard matched against all existing entries on the access list, and only those entries are returned." - " If a set of flags is given, only those on the access list with the specified flags are returned.")); - source.Reply(" "); - source.Reply(_("The \002CLEAR\002 command clears the channel access list, which requires being the founder of \037channel\037.")); - source.Reply(" "); - source.Reply(_("\n" - "Examples:\n" - " {command} #anope MODIFY Attila +fHhu-i\n" - " Modifies the flags of \"Attila\" on the access list of \"#anope\" to \"+fHhu-i\".\n" - "\n" - " {command} #anope MODIFY *!*@anope.org +*\n" - " Modifies the flags of the host mask \"*!*@anope.org\" on the access list of \"#anope\" to have all flags.")); - source.Reply(" "); - source.Reply(_("The available flags are:")); - - typedef std::multimap reverse_map; - reverse_map reverse; - for (std::map::iterator it = defaultFlags.begin(), it_end = defaultFlags.end(); it != it_end; ++it) - reverse.insert(std::make_pair(it->second, it->first)); - - for (reverse_map::iterator it = reverse.begin(), it_end = reverse.end(); it != it_end; ++it) - { - ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(it->second) : nullptr; - if (p == NULL) - continue; - source.Reply(" %c - %s", it->first, Language::Translate(source.nc, p->desc.c_str())); - } - - return true; - } -}; - -class CSFlags : public Module -{ - CommandCSFlags commandcsflags; - FlagsChanAccessType flagsaccesstype; - - public: - CSFlags(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsflags(this) - , flagsaccesstype(this) - { - this->SetPermanent(true); - - } - - void OnReload(Configuration::Conf *conf) override - { - defaultFlags.clear(); - - for (int i = 0; i < conf->CountBlock("privilege"); ++i) - { - Configuration::Block *priv = conf->GetBlock("privilege", i); - - const Anope::string &pname = priv->Get("name"); - - ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(pname) : nullptr; - if (p == NULL) - continue; - - const Anope::string &value = priv->Get("flag"); - if (value.empty()) - continue; - - defaultFlags[p->name] = value[0]; - } - } -}; - -template<> void ModuleInfo(ModuleDef *def) -{ - def->Depends("chanserv"); -} - -MODULE_INIT(CSFlags) diff --git a/modules/commands/cs_getkey.cpp b/modules/commands/cs_getkey.cpp deleted file mode 100644 index 31fe56ec5..000000000 --- a/modules/commands/cs_getkey.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandCSGetKey : public Command -{ - public: - CommandCSGetKey(Module *creator) : Command(creator, "chanserv/getkey", 1, 1) - { - this->SetDesc(_("Returns the key of the given channel")); - this->SetSyntax(_("\037channel\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (!source.AccessFor(ci).HasPriv("GETKEY") && !source.HasCommand("chanserv/getkey")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "GETKEY", ci->GetName()); - return; - } - - Anope::string key; - if (!ci->c || !ci->c->GetParam("KEY", key)) - { - source.Reply(_("Channel \002{0}\002 does not have a key."), ci->GetName()); - return; - } - - bool override = !source.AccessFor(ci).HasPriv("GETKEY"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci); - - source.Reply(_("Key for channel \002{0}\002 is \002{1}\002."), ci->GetName(), key); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Returns the key of \037channel\037.\n" - "\n" - "Use of this command requires the \002{0}\002 privilege on \037channel\037."), - "GETKEY"); - return true; - } -}; - -class CSGetKey : public Module -{ - CommandCSGetKey commandcsgetkey; - - public: - CSGetKey(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsgetkey(this) - { - - } -}; - -MODULE_INIT(CSGetKey) diff --git a/modules/commands/cs_info.cpp b/modules/commands/cs_info.cpp deleted file mode 100644 index 267aee66a..000000000 --- a/modules/commands/cs_info.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_info.h" - -class CommandCSInfo : public Command -{ - EventHandlers &eventonchaninfo; - - public: - CommandCSInfo(Module *creator, EventHandlers &event) : Command(creator, "chanserv/info", 1, 2), eventonchaninfo(event) - { - this->SetDesc(_("Lists information about the specified registered channel")); - this->SetSyntax(_("\037channel\037")); - this->AllowUnregistered(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - - NickServ::Account *nc = source.nc; - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - bool has_auspex = source.HasPriv("chanserv/auspex"); - bool show_all = false; - - /* Should we show all fields? Only for sadmins and identified users */ - if (source.AccessFor(ci).HasPriv("INFO") || has_auspex) - show_all = true; - - InfoFormatter info(nc); - - source.Reply(_("Information for channel \002{0}\002:"), ci->GetName()); - if (ci->GetFounder()) - info[_("Founder")] = ci->GetFounder()->GetDisplay(); - - if (show_all && ci->GetSuccessor()) - info[_("Successor")] = ci->GetSuccessor()->GetDisplay(); - - if (!ci->GetDesc().empty()) - info[_("Description")] = ci->GetDesc(); - - info[_("Registered")] = Anope::strftime(ci->GetTimeRegistered(), source.GetAccount()); - info[_("Last used")] = Anope::strftime(ci->GetLastUsed(), source.GetAccount()); - - if (show_all) - { - info[_("Ban type")] = stringify(ci->GetBanType()); - } - - this->eventonchaninfo(&Event::ChanInfo::OnChanInfo, source, ci, info, show_all); - - std::vector replies; - info.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Shows information about \037channel\037, including its founder, description, time of registration, and last time used." - " If the user issuing the command has \002{0}\002 privilege on \037channel\037, then the successor, last topic set, settings and expiration time will also be displayed when applicable.")); - return true; - } -}; - -class CSInfo : public Module -{ - CommandCSInfo commandcsinfo; - EventHandlers eventonchaninfo; - - public: - CSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsinfo(this, eventonchaninfo) - , eventonchaninfo(this) - { - - } -}; - -MODULE_INIT(CSInfo) diff --git a/modules/commands/cs_invite.cpp b/modules/commands/cs_invite.cpp deleted file mode 100644 index 8e6f94d6c..000000000 --- a/modules/commands/cs_invite.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandCSInvite : public Command -{ - public: - CommandCSInvite(Module *creator) : Command(creator, "chanserv/invite", 1, 3) - { - this->SetDesc(_("Invites you or an optionally specified nick into a channel")); - this->SetSyntax(_("\037channel\037 [\037nick\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - - User *u = source.GetUser(); - Channel *c = Channel::Find(chan); - - if (!c) - { - source.Reply(_("Channel \002{0}\002 doesn't exist."), chan); - return; - } - - ChanServ::Channel *ci = c->ci; - if (!ci) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), c->name); - return; - } - - if (!source.AccessFor(ci).HasPriv("INVITE") && !source.HasCommand("chanserv/invite")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "INVITE", ci->GetName()); - return; - } - - User *u2; - if (params.size() == 1) - u2 = u; - else - u2 = User::Find(params[1], true); - - if (!u2) - { - source.Reply(_("\002{0}\002 isn't currently online."), params.size() > 1 ? params[1] : source.GetNick()); - return; - } - - if (c->FindUser(u2)) - { - if (u2 == u) - source.Reply(_("You are already in \002{0}\002!"), c->name); - else - source.Reply(_("\002{0}\002 is already in \002{1}\002!"), u2->nick, c->name); - - return; - } - - bool override = !source.AccessFor(ci).HasPriv("INVITE"); - - IRCD->SendInvite(ci->WhoSends(), c, u2); - if (u2 != u) - { - source.Reply(_("\002{0}\002 has been invited to \002{1}\002."), u2->nick, c->name); - u2->SendMessage(ci->WhoSends(), _("You have been invited to \002{0}\002 by \002{1}\002."), c->name, source.GetNick()); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << u2->nick; - } - else - { - u2->SendMessage(ci->WhoSends(), _("You have been invited to \002{0}\002."), c->name); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Causes \002{0}\002 to invite you or an optionally specified \037nick\037 to \037channel\037.\n" - "\n" - "Use of this command requires the \002{0}\002 privilege on \037channel\037."), - source.service->nick, "INVITE"); - return true; - } -}; - -class CSInvite : public Module -{ - CommandCSInvite commandcsinvite; - - public: - CSInvite(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsinvite(this) - { - - } -}; - -MODULE_INIT(CSInvite) diff --git a/modules/commands/cs_kick.cpp b/modules/commands/cs_kick.cpp deleted file mode 100644 index 89536df7f..000000000 --- a/modules/commands/cs_kick.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandCSKick : public Command -{ - public: - CommandCSKick(Module *creator) : Command(creator, "chanserv/kick", 2, 3) - { - this->SetDesc(_("Kicks a specified nick from a channel")); - this->SetSyntax(_("\037channel\037 \037user\037 [\037reason\037]")); - this->SetSyntax(_("\037channel\037 \037mask\037 [\037reason\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &target = params[1]; - Anope::string reason = params.size() > 2 ? params[2] : "Requested"; - - User *u = source.GetUser(); - ChanServ::Channel *ci = ChanServ::Find(params[0]); - Channel *c = Channel::Find(params[0]); - User *u2 = User::Find(target, true); - - if (!c) - { - source.Reply(_("Channel \002{0}\002 doesn't exist."), chan); - return; - } - - if (!ci) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), c->name); - return; - } - - unsigned reasonmax = Config->GetModule("chanserv")->Get("reasonmax", "200"); - if (reason.length() > reasonmax) - reason = reason.substr(0, reasonmax); - - ChanServ::AccessGroup u_access = source.AccessFor(ci); - - Anope::string signkickformat = Config->GetModule("chanserv")->Get("signkickformat", "%m (%n)"); - signkickformat = signkickformat.replace_all_cs("%n", source.GetNick()); - - if (!u_access.HasPriv("KICK") && !source.HasPriv("chanserv/kick")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "KICK", ci->GetName()); - return; - } - - if (u2) - { - ChanServ::AccessGroup u2_access = ci->AccessFor(u2); - if (u != u2 && ci->HasFieldS("PEACE") && u2_access >= u_access && !source.HasPriv("chanserv/kick")) - source.Reply(_("Access denied. \002{0}\002 has the same or more privileges than you on \002{1}\002."), u2->nick, ci->GetName()); - else if (u2->IsProtected()) - source.Reply(_("Access denied. \002{0}\002 is protected and can not be kicked."), u2->nick); - else if (!c->FindUser(u2)) - source.Reply(_("User \002{0}\002 is not on channel \002{1}\002."), u2->nick, c->name); - else - { - bool override = !u_access.HasPriv("KICK") || (u != u2 && ci->HasFieldS("PEACE") && u2_access >= u_access); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "for " << u2->nick; - - if (ci->HasFieldS("SIGNKICK") || (ci->HasFieldS("SIGNKICK_LEVEL") && !u_access.HasPriv("SIGNKICK"))) - { - signkickformat = signkickformat.replace_all_cs("%m", reason); - c->Kick(ci->WhoSends(), u2, "%s", signkickformat.c_str()); - } - else - c->Kick(ci->WhoSends(), u2, "%s", reason.c_str()); - } - } - else if (u_access.HasPriv("FOUNDER")) - { - Anope::string mask = IRCD->NormalizeMask(target); - - Log(LOG_COMMAND, source, this, ci) << "for " << mask; - - int matched = 0, kicked = 0; - for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end;) - { - ChanUserContainer *uc = it->second; - ++it; - - Entry e("", mask); - if (e.Matches(uc->user)) - { - ++matched; - - ChanServ::AccessGroup u2_access = ci->AccessFor(uc->user); - if (u != uc->user && ci->HasFieldS("PEACE") && u2_access >= u_access) - continue; - else if (uc->user->IsProtected()) - continue; - - ++kicked; - - if (ci->HasFieldS("SIGNKICK") || (ci->HasFieldS("SIGNKICK_LEVEL") && !u_access.HasPriv("SIGNKICK"))) - { - reason += " (Matches " + mask + ")"; - signkickformat = signkickformat.replace_all_cs("%m", reason); - c->Kick(ci->WhoSends(), uc->user, "%s", signkickformat.c_str()); - } - else - c->Kick(ci->WhoSends(), uc->user, "%s (Matches %s)", reason.c_str(), mask.c_str()); - } - } - - if (matched) - source.Reply(_("Kicked \002{0}/{1}\002 users matching \002{2}\002 from \002{3}\002."), kicked, matched, mask, c->name); - else - source.Reply(_("No users on\002{0}\002 match \002{1}\002."), c->name, mask); - } - else - source.Reply(_("\002{0}\002 isn't currently in use."), target); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Kicks \037user\037 from a \037channel\037. Users with the \002{0}\002 privilege on \037channel\037 may give a \037mask\037, which kicks all users who match the wildcard mask.\n" - " \n" - "Use of this command requires the \002{1}\002 privilege on \037channel\037."), - "FOUNDER", "KICK"); - return true; - } -}; - -class CSKick : public Module -{ - CommandCSKick commandcskick; - - public: - CSKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcskick(this) - { - - } -}; - -MODULE_INIT(CSKick) diff --git a/modules/commands/cs_list.cpp b/modules/commands/cs_list.cpp deleted file mode 100644 index 5a4c58bff..000000000 --- a/modules/commands/cs_list.cpp +++ /dev/null @@ -1,263 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_info.h" -#include "modules/cs_set.h" -#include "modules/cs_mode.h" - -class CommandCSList : public Command -{ - public: - CommandCSList(Module *creator) : Command(creator, "chanserv/list", 1, 2) - { - this->SetDesc(_("Lists all registered channels matching the given pattern")); - this->SetSyntax(_("\037pattern\037 [SUSPENDED] [NOEXPIRE]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - Anope::string pattern = params[0]; - unsigned nchans; - bool is_servadmin = source.HasCommand("chanserv/list"); - int count = 0, from = 0, to = 0; - bool suspended = false, channoexpire = false; - - if (pattern[0] == '#') - { - Anope::string n1, n2; - sepstream(pattern.substr(1), '-').GetToken(n1, 0); - sepstream(pattern, '-').GetToken(n2, 1); - - try - { - from = convertTo(n1); - to = convertTo(n2); - } - catch (const ConvertException &) - { - source.Reply(_("Incorrect range specified. The correct syntax is \002#\037from\037-\037to\037\002.")); - source.Reply(_("To search for channels starting with #, search for the channel name without the #-sign prepended (\002anope\002 instead of \002#anope\002).")); - return; - } - - pattern = "*"; - } - - nchans = 0; - - if (is_servadmin && params.size() > 1) - { - Anope::string keyword; - spacesepstream keywords(params[1]); - while (keywords.GetToken(keyword)) - { - if (keyword.equals_ci("SUSPENDED")) - suspended = true; - if (keyword.equals_ci("NOEXPIRE")) - channoexpire = true; - } - } - - Anope::string spattern = "#" + pattern; - unsigned listmax = Config->GetModule(this->owner)->Get("listmax", "50"); - - source.Reply(_("List of entries matching \002{0}\002:"), pattern); - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Name")).AddColumn(_("Description")); - - // XXX wtf - Anope::map ordered_map; - if (ChanServ::service) - for (auto& it : ChanServ::service->GetChannels()) - ordered_map[it.first] = it.second; - - for (Anope::map::const_iterator it = ordered_map.begin(), it_end = ordered_map.end(); it != it_end; ++it) - { - ChanServ::Channel *ci = it->second; - - if (!is_servadmin) - { - if (ci->HasFieldS("CS_PRIVATE") || ci->HasFieldS("CS_SUSPENDED")) - continue; - if (ci->c && ci->c->HasMode("SECRET")) - continue; - - if (mlocks) - { - ModeLock *secret = mlocks->GetMLock(ci, "SECRET"); - if (secret && secret->GetSet()) - continue; - } - } - - if (suspended && !ci->HasFieldS("CS_SUSPENDED")) - continue; - - if (channoexpire && !ci->HasFieldS("CS_NO_EXPIRE")) - continue; - - if (pattern.equals_ci(ci->GetName()) || ci->GetName().equals_ci(spattern) || Anope::Match(ci->GetName(), pattern, false, true) || Anope::Match(ci->GetName(), spattern, false, true) || Anope::Match(ci->GetDesc(), pattern, false, true) || Anope::Match(ci->GetLastTopic(), pattern, false, true)) - { - if (((count + 1 >= from && count + 1 <= to) || (!from && !to)) && ++nchans <= listmax) - { - bool isnoexpire = false; - if (is_servadmin && (ci->HasFieldS("CS_NO_EXPIRE"))) - isnoexpire = true; - - ListFormatter::ListEntry entry; - entry["Name"] = (isnoexpire ? "!" : "") + ci->GetName(); - if (ci->HasFieldS("CS_SUSPENDED")) - entry["Description"] = Language::Translate(source.GetAccount(), _("[Suspended]")); - else - entry["Description"] = ci->GetDesc(); - list.AddEntry(entry); - } - ++count; - } - } - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("End of list - \002{0}/{1}\002 matches shown."), nchans > listmax ? listmax : nchans, nchans); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Lists all registered channels matching the given pattern." - " Channels with the \002PRIVATE\002 option set will only be displayed to Services Operators with the proper access." - " Channels with the \002NOEXPIRE\002 option set will have a \002!\002 prefixed to the channel for Services Operators to see." - "\n" - "Note that a preceding '#' specifies a range, channel names are to be written without '#'.\n" - "\n" - "If the SUSPENDED or NOEXPIRE options are given, only channels which, respectively, are SUSPENDED or have the NOEXPIRE flag set will be displayed." - " If multiple options are given, all channels matching at least one option will be displayed." - " Note that these options are limited to \037Services Operators\037.\n" - "\n" - "Examples:\n" - "\n" - " {0} *anope*\n" - " Lists all registered channels with \002anope\002 in their names (case insensitive).\n" - "\n" - " {0} * NOEXPIRE\n" - " Lists all registered channels which have been set to not expire.\n" - "\n" - " {0} #51-100\n" - " Lists all registered channels within the given range (51-100).")); - - if (!Config->GetBlock("options")->Get("regexengine").empty()) - { - source.Reply(" "); - source.Reply(_("Regex matches are also supported using the {0} engine. Enclose your pattern in // if this is desired."), Config->GetBlock("options")->Get("regexengine")); - } - - return true; - } -}; - -class CommandCSSetPrivate : public Command -{ - public: - CommandCSSetPrivate(Module *creator, const Anope::string &cname = "chanserv/set/private") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Hide channel from the LIST command")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, params[1]); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (params[1].equals_ci("ON")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable private"; - ci->SetS("CS_PRIVATE", true); - source.Reply(_("Private option for \002{0}\002 is now \002on\002."), ci->GetName()); - } - else if (params[1].equals_ci("OFF")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable private"; - ci->UnsetS("CS_PRIVATE"); - source.Reply(_("Private option for \002{0}\002 is now \002off\002."), ci->GetName()); - } - else - this->OnSyntaxError(source, "PRIVATE"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Enables or disables the \002private\002 option for a channel.")); - - ServiceBot *bi; - Anope::string cmd; - if (Command::FindCommandFromService("chanserv/list", bi, cmd)) - source.Reply(_("When \002private\002 is set, the channel will not appear in the \002{0}\002 command of \002{1}\002."), cmd, bi->nick); - return true; - } -}; - -class CSList : public Module - , public EventHook -{ - CommandCSList commandcslist; - CommandCSSetPrivate commandcssetprivate; - - ExtensibleItem priv; // XXX - - public: - CSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcslist(this) - , commandcssetprivate(this) - , priv(this, "CS_PRIVATE") - { - } - - void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override - { - if (!show_all) - return; - - if (priv.HasExt(ci)) - info.AddOption(_("Private")); - } -}; - -MODULE_INIT(CSList) diff --git a/modules/commands/cs_log.cpp b/modules/commands/cs_log.cpp deleted file mode 100644 index 6a90115f5..000000000 --- a/modules/commands/cs_log.cpp +++ /dev/null @@ -1,448 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_log.h" -#include "modules/memoserv.h" - -class LogSettingImpl : public LogSetting -{ - public: - LogSettingImpl(Serialize::TypeBase *type) : LogSetting(type) { } - LogSettingImpl(Serialize::TypeBase *type, Serialize::ID id) : LogSetting(type, id) { } - - ChanServ::Channel *GetChannel() override; - void SetChannel(ChanServ::Channel *) override; - - Anope::string GetServiceName() override; - void SetServiceName(const Anope::string &) override; - - Anope::string GetCommandService() override; - void SetCommandService(const Anope::string &) override; - - Anope::string GetCommandName() override; - void SetCommandName(const Anope::string &) override; - - Anope::string GetMethod() override; - virtual void SetMethod(const Anope::string &) override; - - Anope::string GetExtra() override; - void SetExtra(const Anope::string &) override; - - Anope::string GetCreator() override; - void SetCreator(const Anope::string &) override; - - time_t GetCreated() override; - void SetCreated(const time_t &) override; -}; - -class LogSettingType : public Serialize::Type -{ - public: - Serialize::ObjectField ci; - Serialize::Field service_name, command_service, command_name, method, extra, creator; - Serialize::Field created; - - LogSettingType(Module *me) : Serialize::Type(me, "LogSetting") - , ci(this, "ci", true) - , service_name(this, "service_name") - , command_service(this, "command_service") - , command_name(this, "command_name") - , method(this, "method") - , extra(this, "extra") - , creator(this, "creator") - , created(this, "created") - { - } -}; - -ChanServ::Channel *LogSettingImpl::GetChannel() -{ - return Get(&LogSettingType::ci); -} - -void LogSettingImpl::SetChannel(ChanServ::Channel *ci) -{ - Set(&LogSettingType::ci, ci); -} - -Anope::string LogSettingImpl::GetServiceName() -{ - return Get(&LogSettingType::service_name); -} - -void LogSettingImpl::SetServiceName(const Anope::string &s) -{ - Set(&LogSettingType::service_name, s); -} - -Anope::string LogSettingImpl::GetCommandService() -{ - return Get(&LogSettingType::command_service); -} - -void LogSettingImpl::SetCommandService(const Anope::string &s) -{ - Set(&LogSettingType::command_service, s); -} - -Anope::string LogSettingImpl::GetCommandName() -{ - return Get(&LogSettingType::command_name); -} - -void LogSettingImpl::SetCommandName(const Anope::string &s) -{ - Set(&LogSettingType::command_name, s); -} - -Anope::string LogSettingImpl::GetMethod() -{ - return Get(&LogSettingType::method); -} - -void LogSettingImpl::SetMethod(const Anope::string &m) -{ - Set(&LogSettingType::method, m); -} - -Anope::string LogSettingImpl::GetExtra() -{ - return Get(&LogSettingType::extra); -} - -void LogSettingImpl::SetExtra(const Anope::string &e) -{ - Set(&LogSettingType::extra, e); -} - -Anope::string LogSettingImpl::GetCreator() -{ - return Get(&LogSettingType::creator); -} - -void LogSettingImpl::SetCreator(const Anope::string &creator) -{ - Set(&LogSettingType::extra, creator); -} - -time_t LogSettingImpl::GetCreated() -{ - return Get(&LogSettingType::created); -} - -void LogSettingImpl::SetCreated(const time_t &t) -{ - Set(&LogSettingType::created, t); -} - -class CommandCSLog : public Command -{ -public: - CommandCSLog(Module *creator) : Command(creator, "chanserv/log", 1, 4) - { - this->SetDesc(_("Configures channel logging settings")); - this->SetSyntax(_("\037channel\037")); - this->SetSyntax(_("\037channel\037 \037command\037 \037method\037 [\037status\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &channel = params[0]; - - ChanServ::Channel *ci = ChanServ::Find(channel); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), channel); - return; - } - - if (!source.AccessFor(ci).HasPriv("SET") && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (params.size() == 1) - { - std::vector ls = ci->GetRefs(logsetting); - if (ls.empty()) - { - source.Reply(_("There currently are no logging configurations for \002{0}\002."), ci->GetName()); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Service")).AddColumn(_("Command")).AddColumn(_("Method")).AddColumn(""); - - for (unsigned i = 0; i < ls.size(); ++i) - { - LogSetting *log = ls[i]; - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(i + 1); - entry["Service"] = log->GetCommandService(); - entry["Command"] = !log->GetCommandName().empty() ? log->GetCommandName() : log->GetServiceName(); - entry["Method"] = log->GetMethod(); - entry[""] = log->GetExtra(); - list.AddEntry(entry); - } - - source.Reply(_("Log list for \002{0}\002:"), ci->GetName()); - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - else if (params.size() > 2) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - const Anope::string &command = params[1]; - const Anope::string &method = params[2]; - const Anope::string &extra = params.size() > 3 ? params[3] : ""; - - size_t sl = command.find('/'); - if (sl == Anope::string::npos) - { - source.Reply(_("\002{0}\002 is not a valid command."), command); - return; - } - - Anope::string service = command.substr(0, sl), - command_name = command.substr(sl + 1); - ServiceBot *bi = ServiceBot::Find(service, true); - - Anope::string service_name; - - /* Allow either a command name or a service name. */ - if (bi && bi->commands.count(command_name)) - { - /* Get service name from command */ - service_name = bi->commands[command_name].name; - } - else if (ServiceReference("Command", command.lower())) - { - /* This is the service name, don't use any specific command */ - service_name = command; - bi = NULL; - command_name.clear(); - } - else - { - source.Reply(_("\002{0}\002 is not a valid command."), command); - return; - } - - if (!method.equals_ci("MESSAGE") && !method.equals_ci("NOTICE") && !method.equals_ci("MEMO")) - { - source.Reply(_("\002%s\002 is not a valid logging method.")); - return; - } - - for (unsigned i = 0; i < extra.length(); ++i) - if (ModeManager::GetStatusChar(extra[i]) == 0) - { - source.Reply(_("\002%c\002 is an unknown status mode."), extra[i]); - return; - } - - bool override = !source.AccessFor(ci).HasPriv("SET"); - - std::vector ls = ci->GetRefs(logsetting); - for (unsigned i = ls.size(); i > 0; --i) - { - LogSetting *log = ls[i - 1]; - - if (log->GetServiceName() == service_name && log->GetMethod().equals_ci(method) && command_name.equals_ci(log->GetCommandName())) - { - if (log->GetExtra() == extra) - { - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to remove logging for " << command << " with method " << method << (extra == "" ? "" : " ") << extra; - source.Reply(_("Logging for command \002{0}\002 on \002{1}\002 with log method \002{2}{3}{4}\002 has been removed."), !log->GetCommandName().empty() ? log->GetCommandName() : log->GetServiceName(), !log->GetCommandService().empty() ? log->GetCommandService() : "any service", method, extra.empty() ? "" : " ", extra); - delete log; - } - else - { - log->SetExtra(extra); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to change logging for " << command << " to method " << method << (extra == "" ? "" : " ") << extra; - source.Reply(_("Logging changed for command \002{0}\002 on \002{1}\002, now using log method \002{2}{3}{4]\002."), !log->GetCommandName().empty() ? log->GetCommandName() : log->GetServiceName(), !log->GetCommandService().empty() ? log->GetCommandService() : "any service", method, extra.empty() ? "" : " ", extra); - } - return; - } - } - - LogSetting *log = logsetting.Create(); - log->SetChannel(ci); - log->SetServiceName(service_name); - if (bi) - log->SetCommandService(bi->nick); - log->SetCommandName(command_name); - log->SetMethod(method); - log->SetExtra(extra); - log->SetCreated(Anope::CurTime); - log->SetCreator(source.GetNick()); - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to log " << command << " with method " << method << (extra == "" ? "" : " ") << extra; - - source.Reply(_("Logging is now active for command \002{0}\002 on \002{1}\002, using log method \002{2}{3}{4}\002."), !command_name.empty() ? command_name : service_name, bi ? bi->nick : "any service", method, extra.empty() ? "" : " ", extra); - } - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("The {0} command allows users to configure logging settings for \037channel\037." - " If no parameters are given this command lists the current logging methods in place on \037channel\037." - " Otherwise, \037command\037 must be a command name, and \037method\037 must be one of the following logging methods:\n" - "\n" - " MESSAGE [status], NOTICE [status], MEMO\n" - "\n" - "Which are used to message, notice, and memo the channel respectively." - " With MESSAGE or NOTICE you must have a service bot assigned to and joined to your channel." - " Status may be a channel status such as @ or +.\n" - "\n" - "To remove a logging method use the same syntax as you would to add it.\n" - "\n" - "Use of this command requires the \002{1}\002 privilege on \037channel\037." - "\n" - "Example:\n" - " {command} #anope chanserv/access MESSAGE @\n" - " Would message any channel operators of \"#anope\" whenever someone used the \"ACCESS\" command on ChanServ for \"#anope\"."), - source.command, "SET"); - return true; - } -}; - -class CSLog : public Module - , public EventHook - , public EventHook -{ - CommandCSLog commandcslog; - LogSettingType logtype; - - struct LogDefault - { - Anope::string service, command, method; - }; - - std::vector defaults; - - public: - CSLog(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcslog(this) - , logtype(this) - { - - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - defaults.clear(); - - for (int i = 0; i < block->CountBlock("default"); ++i) - { - Configuration::Block *def = block->GetBlock("default", i); - - LogDefault ld; - - ld.service = def->Get("service"); - ld.command = def->Get("command"); - ld.method = def->Get("method"); - - defaults.push_back(ld); - } - } - - void OnChanRegistered(ChanServ::Channel *ci) override - { - if (defaults.empty()) - return; - - for (unsigned i = 0; i < defaults.size(); ++i) - { - LogDefault &d = defaults[i]; - - LogSetting *log = logsetting.Create(); - log->SetChannel(ci); - - if (!d.service.empty()) - { - log->SetServiceName(d.service.lower() + "/" + d.command.lower()); - log->SetCommandService(d.service); - log->SetCommandName(d.command); - } - else - log->SetServiceName(d.command); - - spacesepstream sep(d.method); - Anope::string method, extra; - sep.GetToken(method); - extra = sep.GetRemaining(); - - log->SetMethod(method); - log->SetExtra(extra); - log->SetCreated(Anope::CurTime); - log->SetCreator(ci->GetFounder() ? ci->GetFounder()->GetDisplay() : "(default)"); - } - } - - void OnLog(::Log *l) override - { - if (l->type != LOG_COMMAND || l->u == NULL || l->c == NULL || l->ci == NULL || !Me || !Me->IsSynced()) - return; - - std::vector ls = l->ci->GetRefs(logsetting); - for (unsigned i = 0; i < ls.size(); ++i) - { - LogSetting *log = ls[i]; - - /* wrong command */ - if (log->GetServiceName() != l->c->name) - continue; - - /* if a command name is given check the service and the command */ - if (!log->GetCommandName().empty()) - { - /* wrong service (only check if not a fantasy command, though) */ - if (!l->source->c && log->GetCommandService() != l->source->service->nick) - continue; - - if (!log->GetCommandName().equals_ci(l->source->command)) - continue; - } - - Anope::string buffer = l->u->nick + " used " + l->source->command.upper() + " " + l->buf.str(); - - if (log->GetMethod().equals_ci("MEMO") && MemoServ::service && l->ci->WhoSends() != NULL) - MemoServ::service->Send(l->ci->WhoSends()->nick, l->ci->GetName(), buffer, true); - else if (l->source->c) - /* Sending a channel message or notice in response to a fantasy command */; - else if (log->GetMethod().equals_ci("MESSAGE") && l->ci->c) - { - IRCD->SendPrivmsg(l->ci->WhoSends(), log->GetExtra() + l->ci->c->name, "%s", buffer.c_str()); - //l->ci->WhoSends()->lastmsg = Anope::CurTime; XXX - } - else if (log->GetMethod().equals_ci("NOTICE") && l->ci->c) - IRCD->SendNotice(l->ci->WhoSends(), log->GetExtra() + l->ci->c->name, "%s", buffer.c_str()); - } - } -}; - -MODULE_INIT(CSLog) diff --git a/modules/commands/cs_mode.cpp b/modules/commands/cs_mode.cpp deleted file mode 100644 index 121179a58..000000000 --- a/modules/commands/cs_mode.cpp +++ /dev/null @@ -1,999 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_mode.h" -#include "modules/cs_info.h" - -static EventHandlers *events; - -class ModeLockImpl : public ModeLock -{ - public: - ModeLockImpl(Serialize::TypeBase *type) : ModeLock(type) { } - ModeLockImpl(Serialize::TypeBase *type, Serialize::ID id) : ModeLock(type, id) { } - - ChanServ::Channel *GetChannel() override; - void SetChannel(ChanServ::Channel *ci) override; - - bool GetSet() override; - void SetSet(const bool &) override; - - Anope::string GetName() override; - void SetName(const Anope::string &name) override; - - Anope::string GetParam() override; - void SetParam(const Anope::string &p) override; - - Anope::string GetSetter() override; - void SetSetter(const Anope::string &s) override; - - time_t GetCreated() override; - void SetCreated(const time_t &) override; -}; - -class ModeLockType : public Serialize::Type -{ - public: - Serialize::ObjectField ci; - Serialize::Field set; - Serialize::Field name, param, setter; - Serialize::Field created; - - ModeLockType(Module *me) : Serialize::Type(me, "ModeLock") - , ci(this, "ci", true) - , set(this, "set") - , name(this, "name") - , param(this, "param") - , setter(this, "setter") - , created(this, "created") - { - } -}; - -ChanServ::Channel *ModeLockImpl::GetChannel() -{ - return Get(&ModeLockType::ci); -} - -void ModeLockImpl::SetChannel(ChanServ::Channel *ci) -{ - Set(&ModeLockType::ci, ci); -} - -bool ModeLockImpl::GetSet() -{ - return Get(&ModeLockType::set); -} - -void ModeLockImpl::SetSet(const bool &b) -{ - Set(&ModeLockType::set, b); -} - -Anope::string ModeLockImpl::GetName() -{ - return Get(&ModeLockType::name); -} - -void ModeLockImpl::SetName(const Anope::string &name) -{ - Set(&ModeLockType::name, name); -} - -Anope::string ModeLockImpl::GetParam() -{ - return Get(&ModeLockType::param); -} - -void ModeLockImpl::SetParam(const Anope::string &p) -{ - Set(&ModeLockType::name, p); -} - -Anope::string ModeLockImpl::GetSetter() -{ - return Get(&ModeLockType::setter); -} - -void ModeLockImpl::SetSetter(const Anope::string &s) -{ - Set(&ModeLockType::name, s); -} - -time_t ModeLockImpl::GetCreated() -{ - return Get(&ModeLockType::created); -} - -void ModeLockImpl::SetCreated(const time_t &c) -{ - Set(&ModeLockType::created, c); -} - -class ModeLocksImpl : public ModeLocks -{ - public: - using ModeLocks::ModeLocks; - - bool HasMLock(ChanServ::Channel *ci, ChannelMode *mode, const Anope::string ¶m, bool status) const override - { - if (!mode) - return false; - - for (ModeLock *ml : ci->GetRefs(modelock)) - if (ml->GetName() == mode->name && ml->GetSet() == status && ml->GetParam() == param) - return true; - - return false; - } - - bool SetMLock(ChanServ::Channel *ci, ChannelMode *mode, bool status, const Anope::string ¶m, Anope::string setter, time_t created = Anope::CurTime) override - { - if (!mode) - return false; - - RemoveMLock(ci, mode, status, param); - - if (setter.empty()) - setter = ci->GetFounder() ? ci->GetFounder()->GetDisplay() : "Unknown"; - - ModeLock *ml = modelock.Create(); - ml->SetChannel(ci); - ml->SetSet(status); - ml->SetName(name); - ml->SetParam(param); - ml->SetSetter(setter); - ml->SetCreated(created); - - EventReturn MOD_RESULT; - MOD_RESULT = (*events)(&Event::MLockEvents::OnMLock, ci, ml); - if (MOD_RESULT == EVENT_STOP) - { - delete ml; - return false; - } - - return true; - } - - bool RemoveMLock(ChanServ::Channel *ci, ChannelMode *mode, bool status, const Anope::string ¶m = "") override - { - if (!mode) - return false; - - for (ModeLock *m : ci->GetRefs(modelock)) - if (m->GetName() == mode->name) - { - // For list or status modes, we must check the parameter - if (mode->type == MODE_LIST || mode->type == MODE_STATUS) - if (m->GetParam() != param) - continue; - - EventReturn MOD_RESULT; - MOD_RESULT = (*events)(&Event::MLockEvents::OnUnMLock, ci, m); - if (MOD_RESULT == EVENT_STOP) - break; - - delete m; - return true; - } - - return false; - } - - void ClearMLock(ChanServ::Channel *ci) override - { - for (ModeLock *m : ci->GetRefs(modelock)) - delete m; - } - - ModeList GetMLock(ChanServ::Channel *ci) const override - { - return ci->GetRefs(modelock); - } - - std::list GetModeLockList(ChanServ::Channel *ci, const Anope::string &name) override - { - std::list mlist; - for (ModeLock *m : ci->GetRefs(modelock)) - if (m->GetName() == name) - mlist.push_back(m); - return mlist; - } - - ModeLock *GetMLock(ChanServ::Channel *ci, const Anope::string &mname, const Anope::string ¶m = "") override - { - for (ModeLock *m : ci->GetRefs(modelock)) - if (m->GetName() == mname && m->GetParam() == param) - return m; - - return NULL; - } - - Anope::string GetMLockAsString(ChanServ::Channel *ci, bool complete) const override - { - Anope::string pos = "+", neg = "-", params; - - for (ModeLock *ml : ci->GetRefs(modelock)) - { - ChannelMode *cm = ModeManager::FindChannelModeByName(ml->GetName()); - - if (!cm || cm->type == MODE_LIST || cm->type == MODE_STATUS) - continue; - - if (ml->GetSet()) - pos += cm->mchar; - else - neg += cm->mchar; - - if (complete && ml->GetSet() && !ml->GetParam().empty() && cm->type == MODE_PARAM) - params += " " + ml->GetParam(); - } - - if (pos.length() == 1) - pos.clear(); - if (neg.length() == 1) - neg.clear(); - - return pos + neg + params; - } -}; - -class CommandCSMode : public Command -{ - bool CanSet(CommandSource &source, ChanServ::Channel *ci, ChannelMode *cm, bool self) - { - if (!ci || !cm || cm->type != MODE_STATUS) - return false; - - return source.AccessFor(ci).HasPriv(cm->name + (self ? "ME" : "")); - } - - void DoLock(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - User *u = source.GetUser(); - const Anope::string &subcommand = params[2]; - const Anope::string ¶m = params.size() > 3 ? params[3] : ""; - - bool override = !source.AccessFor(ci).HasPriv("MODE"); - - if (Anope::ReadOnly && !subcommand.equals_ci("LIST")) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - if ((subcommand.equals_ci("ADD") || subcommand.equals_ci("SET")) && !param.empty()) - { - /* If setting, remove the existing locks */ - if (subcommand.equals_ci("SET")) - for (ModeLock *ml : mlocks->GetMLock(ci)) - { - ChannelMode *cm = ModeManager::FindChannelModeByName(ml->GetName()); - if (cm && cm->CanSet(source.GetUser())) - mlocks->RemoveMLock(ci, cm, ml->GetSet(), ml->GetParam()); - } - - spacesepstream sep(param); - Anope::string modes; - - sep.GetToken(modes); - - Anope::string pos = "+", neg = "-", pos_params, neg_params; - - int adding = 1; - bool needreply = true; - for (size_t i = 0; i < modes.length(); ++i) - { - switch (modes[i]) - { - case '+': - adding = 1; - break; - case '-': - adding = 0; - break; - default: - needreply = false; - ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); - if (!cm) - { - source.Reply(_("Unknown mode character \002{0}\002 ignored."), modes[i]); - break; - } - else if (u && !cm->CanSet(u)) - { - source.Reply(_("You may not (un)lock mode \002{0}\002."), modes[i]); - break; - } - - Anope::string mode_param; - if (((cm->type == MODE_STATUS || cm->type == MODE_LIST) && !sep.GetToken(mode_param)) || (cm->type == MODE_PARAM && adding && !sep.GetToken(mode_param))) - source.Reply(_("Missing parameter for mode \002{0}\002."), cm->mchar); - else if (cm->type == MODE_LIST && ci->c && IRCD->GetMaxListFor(ci->c) && ci->c->HasMode(cm->name) >= IRCD->GetMaxListFor(ci->c)) - source.Reply(_("List for mode \002{0}\002 is full."), cm->mchar); - else if (ci->GetRefs(modelock).size() >= Config->GetModule(this->owner)->Get("max", "32")) - source.Reply(_("The mode lock list of \002{0}\002 is full."), ci->GetName()); - else - { - mlocks->SetMLock(ci, cm, adding, mode_param, source.GetNick()); - - if (adding) - { - pos += cm->mchar; - if (!mode_param.empty()) - pos_params += " " + mode_param; - } - else - { - neg += cm->mchar; - if (!mode_param.empty()) - neg_params += " " + mode_param; - } - } - } - } - - if (pos == "+") - pos.clear(); - if (neg == "-") - neg.clear(); - Anope::string reply = pos + neg + pos_params + neg_params; - - if (!reply.empty()) - { - source.Reply(_("\002{0}\002 locked on \002{1}\002."), reply, ci->GetName()); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to lock " << reply; - } - else if (needreply) - source.Reply(_("Nothing to do.")); - - if (ci->c) - ci->c->CheckModes(); - } - else if (subcommand.equals_ci("DEL") && !param.empty()) - { - spacesepstream sep(param); - Anope::string modes; - - sep.GetToken(modes); - - int adding = 1; - bool needreply = true; - for (size_t i = 0; i < modes.length(); ++i) - { - switch (modes[i]) - { - case '+': - adding = 1; - break; - case '-': - adding = 0; - break; - default: - needreply = false; - ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); - if (!cm) - { - source.Reply(_("Unknown mode character \002{0}\002 ignored."), modes[i]); - break; - } - else if (u && !cm->CanSet(u)) - { - source.Reply(_("You may not (un)lock mode \002{0}\002."), modes[i]); - break; - } - - Anope::string mode_param; - if (cm->type != MODE_REGULAR && !sep.GetToken(mode_param)) - source.Reply(_("Missing parameter for mode \002{0}\002."), cm->mchar); - else - { - if (mlocks->RemoveMLock(ci, cm, adding, mode_param)) - { - if (!mode_param.empty()) - mode_param = " " + mode_param; - source.Reply(_("\002{0}{1}{2}\002 has been unlocked from \002{3}\002."), adding == 1 ? '+' : '-', cm->mchar, mode_param, ci->GetName()); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to unlock " << (adding ? '+' : '-') << cm->mchar << mode_param; - } - else - source.Reply(_("\002{0}{1}\002 is not locked on \002{2}\002."), adding == 1 ? '+' : '-', cm->mchar, ci->GetName()); - } - } - } - - if (needreply) - source.Reply(_("Nothing to do.")); - } - else if (subcommand.equals_ci("LIST")) - { - ModeLocks::ModeList locks = mlocks->GetMLock(ci); - if (locks.empty()) - { - source.Reply(_("Channel \002{0}\002 has no mode locks."), ci->GetName()); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Mode")).AddColumn(_("Param")).AddColumn(_("Creator")).AddColumn(_("Created")); - - for (ModeLock *ml : locks) - { - ChannelMode *cm = ModeManager::FindChannelModeByName(ml->GetName()); - if (!cm) - continue; - - ListFormatter::ListEntry entry; - entry["Mode"] = Anope::printf("%c%c", ml->GetSet() ? '+' : '-', cm->mchar); - entry["Param"] = ml->GetParam(); - entry["Creator"] = ml->GetSetter(); - entry["Created"] = Anope::strftime(ml->GetCreated(), NULL, true); - list.AddEntry(entry); - } - - source.Reply(_("Mode locks for \002{0}\002:"), ci->GetName()); - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - else - this->OnSyntaxError(source, subcommand); - } - - void DoSet(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - User *u = source.GetUser(); - - bool has_access = source.AccessFor(ci).HasPriv("MODE") || source.HasPriv("chanserv/administration"); - bool can_override = source.HasPriv("chanserv/administration"); - - spacesepstream sep(params.size() > 3 ? params[3] : ""); - Anope::string modes = params[2], param; - - bool override = !source.AccessFor(ci).HasPriv("MODE") && source.HasPriv("chanserv/administration"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to set " << params[2] << (params.size() > 3 ? " " + params[3] : ""); - - int adding = -1; - for (size_t i = 0; i < modes.length(); ++i) - { - switch (modes[i]) - { - case '+': - adding = 1; - break; - case '-': - adding = 0; - break; - case '*': - if (adding == -1 || !has_access) - break; - for (unsigned j = 0; j < ModeManager::GetChannelModes().size() && ci->c; ++j) - { - ChannelMode *cm = ModeManager::GetChannelModes()[j]; - - if (!u || cm->CanSet(u) || can_override) - { - if (cm->type == MODE_REGULAR || (!adding && cm->type == MODE_PARAM)) - { - if (adding) - ci->c->SetMode(NULL, cm); - else - ci->c->RemoveMode(NULL, cm); - } - } - } - break; - default: - if (adding == -1) - break; - ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); - if (!cm || (u && !cm->CanSet(u) && !can_override)) - continue; - switch (cm->type) - { - case MODE_REGULAR: - if (!has_access) - break; - if (adding) - ci->c->SetMode(NULL, cm); - else - ci->c->RemoveMode(NULL, cm); - break; - case MODE_PARAM: - if (!has_access) - break; - if (adding && !sep.GetToken(param)) - break; - if (adding) - ci->c->SetMode(NULL, cm, param); - else - ci->c->RemoveMode(NULL, cm); - break; - case MODE_STATUS: - { - if (!sep.GetToken(param)) - param = source.GetNick(); - - ChanServ::AccessGroup u_access = source.AccessFor(ci); - - if (param.find_first_of("*?") != Anope::string::npos) - { - if (!this->CanSet(source, ci, cm, false) && !can_override) - { - source.Reply(_("You do not have access to set mode \002{0}\002."), cm->mchar); - break; - } - - for (Channel::ChanUserList::const_iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end;) - { - ChanUserContainer *uc = it->second; - ++it; - - ChanServ::AccessGroup targ_access = ci->AccessFor(uc->user); - - if (uc->user->IsProtected() || (ci->HasFieldS("PEACE") && targ_access >= u_access && !can_override)) - { - source.Reply(_("You do not have the access to change the modes of \002{0}\002."), uc->user->nick.c_str()); - continue; - } - - if (Anope::Match(uc->user->GetMask(), param)) - { - if (adding) - ci->c->SetMode(NULL, cm, uc->user->GetUID()); - else - ci->c->RemoveMode(NULL, cm, uc->user->GetUID()); - } - } - } - else - { - User *target = User::Find(param, true); - if (target == NULL) - { - source.Reply(_("User \002{0}\002 isn't currently online."), param); - break; - } - - if (!this->CanSet(source, ci, cm, source.GetUser() == target) && !can_override) - { - source.Reply(_("You do not have access to set mode \002{0}\002."), cm->mchar); - break; - } - - if (source.GetUser() != target) - { - ChanServ::AccessGroup targ_access = ci->AccessFor(target); - if (ci->HasFieldS("PEACE") && targ_access >= u_access && !can_override) - { - source.Reply(_("You do not have the access to change the modes of \002{0}\002"), target->nick); - break; - } - else if (target->IsProtected()) - { - source.Reply(_("Access denied. \002{0}\002 is protected and can not have their modes changed."), target->nick); - break; - } - } - - if (adding) - ci->c->SetMode(NULL, cm, target->GetUID()); - else - ci->c->RemoveMode(NULL, cm, target->GetUID()); - } - break; - } - case MODE_LIST: - if (!has_access) - break; - if (!sep.GetToken(param)) - break; - if (adding) - { - if (IRCD->GetMaxListFor(ci->c) && ci->c->HasMode(cm->name) < IRCD->GetMaxListFor(ci->c)) - ci->c->SetMode(NULL, cm, param); - } - else - { - std::vector v = ci->c->GetModeList(cm->name); - for (unsigned j = 0; j < v.size(); ++j) - if (Anope::Match(v[j], param)) - ci->c->RemoveMode(NULL, cm, v[j]); - } - } - } - } - } - - void DoClear(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - const Anope::string ¶m = params.size() > 2 ? params[2] : ""; - - if (param.empty()) - { - std::vector new_params; - new_params.push_back(params[0]); - new_params.push_back("SET"); - new_params.push_back("-*"); - this->DoSet(source, ci, new_params); - return; - } - - ChannelMode *cm; - if (param.length() == 1) - cm = ModeManager::FindChannelModeByChar(param[0]); - else - { - cm = ModeManager::FindChannelModeByName(param.upper()); - if (!cm) - cm = ModeManager::FindChannelModeByName(param.substr(0, param.length() - 1).upper()); - } - - if (!cm) - { - source.Reply(_("There is no such mode \002{0}\002."), param); - return; - } - - if (cm->type != MODE_STATUS && cm->type != MODE_LIST) - { - source.Reply(_("Mode \002{0}\002 is not a status or list mode."), param); - return; - } - - std::vector new_params; - new_params.push_back(params[0]); - new_params.push_back("SET"); - new_params.push_back("-" + stringify(cm->mchar)); - new_params.push_back("*"); - this->DoSet(source, ci, new_params); - } - - public: - CommandCSMode(Module *creator) : Command(creator, "chanserv/mode", 2, 4) - { - this->SetDesc(_("Control modes and mode locks on a channel")); - this->SetSyntax(_("\037channel\037 LOCK {ADD|DEL|SET|LIST} [\037what\037]")); - this->SetSyntax(_("\037channel\037 SET \037modes\037")); - this->SetSyntax(_("\037channel\037 CLEAR [\037what\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &subcommand = params[1]; - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (!ci) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (subcommand.equals_ci("LOCK") && params.size() > 2) - { - if (!source.AccessFor(ci).HasPriv("MODE") && !source.HasPriv("chanserv/administration")) - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MODE", ci->GetName()); - else - this->DoLock(source, ci, params); - } - else if (!ci->c) - source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); - else if (subcommand.equals_ci("SET") && params.size() > 2) - this->DoSet(source, ci, params); - else if (subcommand.equals_ci("CLEAR")) - { - if (!source.AccessFor(ci).HasPriv("MODE") && !source.HasPriv("chanserv/administration")) - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MODE", ci->GetName()); - else - this->DoClear(source, ci, params); - } - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Controls mode locks and allows changing modes on a channel." - "\n" - "The \002{0} LOCK\002 command allows you to add, delete, and view mode locks on a channel." - " If a mode is locked on or off, services will not allow that mode to be changed." - " The \002SET\002 command will clear all existing mode locks and set the new one given, while \002ADD\002 and \002DEL\002 modify the existing mode lock.\n" - "\n" - "Example:\n" - " {0} #channel LOCK ADD +bmnt *!*@*aol*\n" - "\n" - "The \002{0} SET\002 command allows you to set modes through services. Wildcards * and ? may be given as parameters for list and status modes.\n" - "Example:\n" - " {0} #channel SET +v *\n" - " Sets voice status to all users in the channel.\n" - "\n" - " {0} #channel SET -b ~c:*\n" - " Clears all extended bans that start with ~c:\n" - "\n" - "The \002{0} CLEAR\002 command is an easy way to clear modes on a channel. \037what\037 may be any mode name. Examples include bans, excepts, inviteoverrides, ops, halfops, and voices." - " If \037what\037 is not given then all basic modes are removed."), - source.command.upper()); - return true; - } -}; - -static Anope::map > modes; - -class CommandCSModes : public Command -{ - public: - CommandCSModes(Module *creator) : Command(creator, "chanserv/modes", 1, 2) - { - this->SetSyntax(_("\037channel\037 [\037user\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - User *u = source.GetUser(), - *targ = params.size() > 1 ? User::Find(params[1], true) : u; - - if (!targ) - { - source.Reply(_("User \002{0}\002 isn't currently online."), params.size() > 1 ? params[1] : source.GetNick()); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (!ci) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (!ci->c) - { - source.Reply(_("Channel \002%s\002 doesn't exist."), ci->GetName()); - return; - } - - ChanServ::AccessGroup u_access = source.AccessFor(ci), targ_access = ci->AccessFor(targ); - const std::pair &m = modes[source.command]; - - bool can_override = source.HasPriv("chanserv/administration"); - bool override = false; - - if (m.second.empty()) - return; // Configuration issue - - const Anope::string &want = u == targ ? m.second + "ME" : m.second; - if (!u_access.HasPriv(want)) - { - if (!can_override) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), want, ci->GetName()); - return; - } - else - override = true; - } - - if (!override && !m.first && u != targ && (targ->IsProtected() || (ci->HasFieldS("PEACE") && targ_access >= u_access))) - { - if (!can_override) - { - source.Reply(_("Access denied. \002{0}\002 has the same or more privileges than you on \002{1}\002."), targ->nick, ci->GetName()); - return; - } - else - override = true; - } - - if (!ci->c->FindUser(targ)) - { - source.Reply(_("User \002{0}\002 is not on channel \002{1}\002."), targ->nick, ci->GetName()); - return; - } - - if (m.first) - ci->c->SetMode(NULL, m.second, targ->GetUID()); - else - ci->c->RemoveMode(NULL, m.second, targ->GetUID()); - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "on " << targ->nick; - } - - const Anope::string GetDesc(CommandSource &source) const override - { - const std::pair &m = modes[source.command]; - if (!m.second.empty()) - { - if (m.first) - return Anope::printf(Language::Translate(source.GetAccount(), _("Gives you or the specified nick %s status on a channel")), m.second.c_str()); - else - return Anope::printf(Language::Translate(source.GetAccount(), _("Removes %s status from you or the specified nick on a channel")), m.second.c_str()); - } - else - return ""; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - const std::pair &m = modes[source.command]; - if (m.second.empty()) - return false; - - if (m.first) - source.Reply(_("Gives {0} status to the selected \037user\037 on \037channel\037. If \037user\037 is not given, it will give the status to you."), - m.second.upper()); - else - source.Reply(_("Removes {0} status from the selected \037user\037 on \037channel\037. If \037user\037 is not given, it will remove the status from you."), - m.second.upper()); - source.Reply(" "); - source.Reply(_("Use of this command requires the \002{1}(ME)\002 privilege on \037channel\037."), m.second.upper()); - - return true; - } -}; - -class CSMode : public Module - , public EventHook - , public EventHook - , public EventHook -{ - CommandCSMode commandcsmode; - CommandCSModes commandcsmodes; - ModeLocksImpl modelock; - ModeLockType modelock_type; - - EventHandlers modelockevents; - - public: - CSMode(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsmode(this) - , commandcsmodes(this) - , modelock(this) - , modelock_type(this) - , modelockevents(this) - { - events = &modelockevents; - } - - void OnReload(Configuration::Conf *conf) override - { - modes.clear(); - - for (int i = 0; i < conf->CountBlock("command"); ++i) - { - Configuration::Block *block = conf->GetBlock("command", i); - - const Anope::string &cname = block->Get("name"), - &cmd = block->Get("command"); - - if (cname.empty() || cmd != "chanserv/modes") - continue; - - const Anope::string &set = block->Get("set"), - &unset = block->Get("unset"); - - if (set.empty() && unset.empty()) - continue; - - modes[cname] = std::make_pair(!set.empty(), !set.empty() ? set : unset); - } - } - - void OnCheckModes(Reference &c) override - { - if (!c || !c->ci) - return; - - ModeLocks::ModeList locks = mlocks->GetMLock(c->ci); - for (ModeLock *ml : locks) - { - ChannelMode *cm = ModeManager::FindChannelModeByName(ml->GetName()); - if (!cm) - continue; - - if (cm->type == MODE_REGULAR) - { - if (!c->HasMode(cm->name) && ml->GetSet()) - c->SetMode(NULL, cm, "", false); - else if (c->HasMode(cm->name) && !ml->GetSet()) - c->RemoveMode(NULL, cm, "", false); - } - else if (cm->type == MODE_PARAM) - { - /* If the channel doesn't have the mode, or it does and it isn't set correctly */ - if (ml->GetSet()) - { - Anope::string param; - c->GetParam(cm->name, param); - - if (!c->HasMode(cm->name) || (!param.empty() && !ml->GetParam().empty() && !param.equals_cs(ml->GetParam()))) - c->SetMode(NULL, cm, ml->GetParam(), false); - } - else - { - if (c->HasMode(cm->name)) - c->RemoveMode(NULL, cm, "", false); - } - } - else if (cm->type == MODE_LIST || cm->type == MODE_STATUS) - { - if (ml->GetSet()) - c->SetMode(NULL, cm, ml->GetParam(), false); - else - c->RemoveMode(NULL, cm, ml->GetParam(), false); - } - } - } - - void OnChanRegistered(ChanServ::Channel *ci) override - { - Anope::string mlock; - spacesepstream sep(Config->GetModule(this)->Get("mlock", "+nt")); - if (sep.GetToken(mlock)) - { - bool add = true; - for (unsigned i = 0; i < mlock.length(); ++i) - { - if (mlock[i] == '+') - { - add = true; - continue; - } - - if (mlock[i] == '-') - { - add = false; - continue; - } - - ChannelMode *cm = ModeManager::FindChannelModeByChar(mlock[i]); - if (!cm) - continue; - - Anope::string param; - if (cm->type == MODE_PARAM) - { - ChannelModeParam *cmp = anope_dynamic_static_cast(cm); - if (add || !cmp->minus_no_arg) - { - sep.GetToken(param); - if (param.empty() || !cmp->IsValid(param)) - continue; - } - } - else if (cm->type != MODE_REGULAR) - { - sep.GetToken(param); - if (param.empty()) - continue; - } - - mlocks->SetMLock(ci, cm, add, param); - } - } - } - - void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_hidden) override - { - if (!show_hidden) - return; - - const Anope::string &m = mlocks->GetMLockAsString(ci, true); - if (!m.empty()) - info[_("Mode lock")] = m; - } -}; - -MODULE_INIT(CSMode) diff --git a/modules/commands/cs_register.cpp b/modules/commands/cs_register.cpp deleted file mode 100644 index eb2832074..000000000 --- a/modules/commands/cs_register.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandCSRegister : public Command -{ - public: - CommandCSRegister(Module *creator) : Command(creator, "chanserv/register", 1, 2) - { - this->SetDesc(_("Register a channel")); - this->SetSyntax(_("\037channel\037 [\037description\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &chdesc = params.size() > 1 ? params[1] : ""; - - User *u = source.GetUser(); - NickServ::Account *nc = source.nc; - - if (Anope::ReadOnly) - { - source.Reply(_("Sorry, channel registration is temporarily disabled.")); - return; - } - - if (nc->HasFieldS("UNCONFIRMED")) - { - source.Reply(_("You must confirm your account before you can register a channel.")); - return; - } - - if (chan[0] == '&') - { - source.Reply(_("Local channels can not be registered.")); - return; - } - - if (chan[0] != '#') - { - source.Reply(_("Please use the symbol of \002#\002 when attempting to register.")); - return; - } - - if (!IRCD->IsChannelValid(chan)) - { - source.Reply(_("Channel \002{0}\002 is not a valid channel."), chan); - return; - } - - Channel *c = Channel::Find(params[0]); - if (!c && u) - { - source.Reply(_("Channel \002{0}\002 doesn't exist."), chan); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci) - { - source.Reply(_("Channel \002{0}\002 is already registered!"), chan); - return; - } - - if (c && u && !c->HasUserStatus(u, "OP")) - { - source.Reply(_("You must be a channel operator to register the channel.")); - return; - } - - unsigned maxregistered = Config->GetModule("chanserv")->Get("maxregistered"); - if (maxregistered && nc->GetChannelCount() >= maxregistered && !source.HasPriv("chanserv/no-register-limit")) - { - if (nc->GetChannelCount() > maxregistered) - source.Reply(_("Sorry, you have already exceeded your limit of \002{0}\002 channels."), maxregistered); - else - source.Reply(_("Sorry, you have already reached your limit of \002{0}\002 channels."), maxregistered); - return; - } - - if (!ChanServ::service) - return; - ci = ChanServ::channel.Create(); - ci->SetName(chan); - ci->SetFounder(nc); - ci->SetDesc(chdesc); - - if (c && !c->topic.empty()) - { - ci->SetLastTopic(c->topic); - ci->SetLastTopicSetter(c->topic_setter); - ci->SetLastTopicTime(c->topic_time); - } - else - ci->SetLastTopicSetter(source.service->nick); - - Log(LOG_COMMAND, source, this, ci); - source.Reply(_("Channel \002{0}\002 registered under your account: \002{1}\002"), chan, nc->GetDisplay()); - - /* Implement new mode lock */ - if (c) - { - c->CheckModes(); - if (u) - c->SetCorrectModes(u, true); - - Event::OnChanRegistered(&Event::ChanRegistered::OnChanRegistered, ci); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Registers a channel, which sets you as the founder and prevents other users from gaining unauthorized access in the channel." - " To use this command, you must first be a channel operator on \037channel\037." - " The description, which is optional, is a general description of the channel's purpose.\n" - "\n" - "When you register a channel, you are recorded as the founder of the channel." - " The channel founder is allowed to change all of the channel settings for the channel," - " and will automatically be given channel operator status when entering the channel.")); - - ServiceBot *bi; - Anope::string cmd; - CommandInfo *help = source.service->FindCommand("generic/help"); - if (Command::FindCommandFromService("chanserv/access", bi, cmd) && help) - source.Reply(_("\n" - "See the \002{0}\002 command (\002{1}{2} {3} {0}\002) for information on giving a subset of these privileges to other users."), - cmd, Config->StrictPrivmsg, bi->nick, help->cname); - - return true; - } -}; - - -class CSRegister : public Module -{ - CommandCSRegister commandcsregister; - - public: - CSRegister(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsregister(this) - { - } -}; - -MODULE_INIT(CSRegister) diff --git a/modules/commands/cs_seen.cpp b/modules/commands/cs_seen.cpp deleted file mode 100644 index b97ffe238..000000000 --- a/modules/commands/cs_seen.cpp +++ /dev/null @@ -1,477 +0,0 @@ -/* cs_seen: provides a seen command by tracking all users - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - - -#if 0 -#include "module.h" - -enum TypeInfo -{ - NEW, NICK_TO, NICK_FROM, JOIN, PART, QUIT, KICK -}; - -static bool simple; -struct SeenInfo; -static SeenInfo *FindInfo(const Anope::string &nick); -typedef Anope::hash_map database_map; -database_map database; - -struct SeenInfo : Serialize::Object -{ - Anope::string nick; - Anope::string vhost; - TypeInfo type; - Anope::string nick2; // for nickchanges and kicks - Anope::string channel; // for join/part/kick - Anope::string message; // for part/kick/quit - time_t last; // the time when the user was last seen - - SeenInfo() : Serialize::Object("SeenInfo") - { - } - - ~SeenInfo() - { - database_map::iterator iter = database.find(nick); - if (iter != database.end() && iter->second == this) - database.erase(iter); - } - -#if 0 - void Serialize(Serialize::Data &data) const override - { - data["nick"] << nick; - data["vhost"] << vhost; - data["type"] << type; - data["nick2"] << nick2; - data["channel"] << channel; - data["message"] << message; - data.SetType("last", Serialize::Data::DT_INT); data["last"] << last; - } - - static Serializable* Unserialize(Serializable *obj, Serialize::Data &data) - { - Anope::string snick; - - data["nick"] >> snick; - - SeenInfo *s; - if (obj) - s = anope_dynamic_static_cast(obj); - else - { - SeenInfo* &info = database[snick]; - if (!info) - info = new SeenInfo(); - s = info; - } - - s->nick = snick; - data["vhost"] >> s->vhost; - unsigned int n; - data["type"] >> n; - s->type = static_cast(n); - data["nick2"] >> s->nick2; - data["channel"] >> s->channel; - data["message"] >> s->message; - data["last"] >> s->last; - - if (!obj) - database[s->nick] = s; - return s; - } -#endif -}; - -static SeenInfo *FindInfo(const Anope::string &nick) -{ - database_map::iterator iter = database.find(nick); - if (iter != database.end()) - return iter->second; - return NULL; -} - -static bool ShouldHide(const Anope::string &channel, User *u) -{ - Channel *targetchan = Channel::Find(channel); - const ChanServ::Channel *targetchan_ci = targetchan ? *targetchan->ci : ChanServ::Find(channel); - - if (targetchan && targetchan->HasMode("SECRET")) - return true; - else if (targetchan_ci && targetchan_ci->HasExt("CS_PRIVATE")) - return true; - else if (u && u->HasMode("PRIV")) - return true; - return false; -} - -class CommandOSSeen : public Command -{ - public: - CommandOSSeen(Module *creator) : Command(creator, "operserv/seen", 1, 2) - { - this->SetDesc(_("Statistics and maintenance for seen data")); - this->SetSyntax("STATS"); - this->SetSyntax(_("CLEAR \037time\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (params[0].equals_ci("STATS")) - { - size_t mem_counter; - mem_counter = sizeof(database_map); - for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end; ++it) - { - mem_counter += (5 * sizeof(Anope::string)) + sizeof(TypeInfo) + sizeof(time_t); - mem_counter += it->first.capacity(); - mem_counter += it->second->vhost.capacity(); - mem_counter += it->second->nick2.capacity(); - mem_counter += it->second->channel.capacity(); - mem_counter += it->second->message.capacity(); - } - source.Reply(_("%lu nicks are stored in the database, using %.2Lf kB of memory."), database.size(), static_cast(mem_counter) / 1024); - } - else if (params[0].equals_ci("CLEAR")) - { - time_t time = 0; - if ((params.size() < 2) || (0 >= (time = Anope::DoTime(params[1])))) - { - this->OnSyntaxError(source, params[0]); - return; - } - time = Anope::CurTime - time; - database_map::iterator buf; - size_t counter = 0; - for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end;) - { - buf = it; - ++it; - if (time < buf->second->last) - { - Log(LOG_DEBUG) << buf->first << " was last seen " << Anope::strftime(buf->second->last) << ", deleting entry"; - delete buf->second; - counter++; - } - } - Log(LOG_ADMIN, source, this) << "CLEAR and removed " << counter << " nicks that were added after " << Anope::strftime(time, NULL, true); - source.Reply(_("Database cleared, removed %lu nicks that were added after %s."), counter, Anope::strftime(time, source.nc, true).c_str()); - } - else - this->SendSyntax(source); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { -<<<<<<< HEAD - source.Reply(_("The \002STATS\002 command prints out statistics about stored nicks and memory usage.\n" - "The \002CLEAR\002 command lets you clean the database by removing all entries from the entries from the database that were added within \037time\037.\n" - "\n" - "Example:\n" - " {0} CLEAR 30m\n" - " Will remove all entries that were added within the last 30 minutes."), - source.command); -======= - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("The \002STATS\002 command prints out statistics about stored nicks and memory usage.")); - source.Reply(_("The \002CLEAR\002 command lets you clean the database by removing all entries from the\n" - "database that were added within \037time\037.\n" - " \n" - "Example:\n" - " %s CLEAR 30m\n" - " Will remove all entries that were added within the last 30 minutes."), source.command.c_str()); ->>>>>>> 2.0 - return true; - } -}; - -class CommandSeen : public Command -{ - void SimpleSeen(CommandSource &source, const std::vector ¶ms) - { - if (!source.c || !source.c->ci) - { - if (source.IsOper()) - source.Reply("Seen in simple mode is designed as a fantasy command only!"); - return; - } - - ServiceBot *bi = ServiceBot::Find(params[0], true); - if (bi) - { - if (bi == source.c->ci->GetBot()) - source.Reply(_("You found me, %s!"), source.GetNick().c_str()); - else - source.Reply(_("%s is a network service."), bi->nick.c_str()); - return; - } - - NickServ::Nick *na = NickServ::FindNick(params[0]); - if (!na) - { - source.Reply(_("I don't know who %s is."), params[0].c_str()); - return; - } - - if (source.GetAccount() == na->GetAccount()) - { - source.Reply(_("Looking for yourself, eh %s?"), source.GetNick().c_str()); - return; - } - - User *target = User::Find(params[0], true); - - if (target && source.c->FindUser(target)) - { - source.Reply(_("%s is on the channel right now!"), target->nick.c_str()); - return; - } - - for (Channel::ChanUserList::const_iterator it = source.c->users.begin(), it_end = source.c->users.end(); it != it_end; ++it) - { - ChanUserContainer *uc = it->second; - User *u = uc->user; - - if (u->Account() == na->GetAccount()) - { - source.Reply(_("%s is on the channel right now (as %s)!"), params[0].c_str(), u->nick.c_str()); - return; - } - } - - ChanServ::AccessGroup ag = source.c->ci->AccessFor(na->GetAccount()); - time_t last = 0; - for (unsigned i = 0; i < ag.size(); ++i) - { - ChanServ::ChanAccess *a = ag[i]; - - if (a->GetAccount() == na->GetAccount() && a->GetLastSeen() > last) - last = a->GetLastSeen(); - } - - if (last > Anope::CurTime || !last) - source.Reply(_("I've never seen %s on this channel."), na->GetNick().c_str()); - else - source.Reply(_("%s was last seen here %s ago."), na->GetNick().c_str(), Anope::Duration(Anope::CurTime - last, source.GetAccount()).c_str()); - } - - public: - CommandSeen(Module *creator) : Command(creator, "chanserv/seen", 1, 2) - { - this->SetDesc(_("Tells you about the last time a user was seen")); - this->SetSyntax(_("\037nick\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &target = params[0]; - - if (simple) - return this->SimpleSeen(source, params); - - if (target.length() > Config->GetBlock("networkinfo")->Get("nicklen")) - { - source.Reply(_("Nick too long, max length is \002{0}\002 characters."), Config->GetBlock("networkinfo")->Get("nicklen")); - return; - } - - if (ServiceBot::Find(target, true) != NULL) - { - source.Reply(_("\002{0}\002 is a service bot."), target); - return; - } - - if (target.equals_ci(source.GetNick())) - { - source.Reply(_("You might see yourself in the mirror, \002{0}\002."), source.GetNick()); - return; - } - - SeenInfo *info = FindInfo(target); - if (!info) - { - source.Reply(_("Sorry, I have not seen \002{0}\002."), target); - return; - } - - User *u2 = User::Find(target, true); - Anope::string onlinestatus; - if (u2) - onlinestatus = "."; - else - onlinestatus = Anope::printf(_(" but %s mysteriously dematerialized."), target.c_str()); - - Anope::string timebuf = Anope::Duration(Anope::CurTime - info->last, source.nc); - Anope::string timebuf2 = Anope::strftime(info->last, source.nc, true); - - if (info->type == NEW) - { - source.Reply(_("\002{0}\002 (\002{1}\002) was last seen connecting \002{2}\002 ago (\002{3}\002){4}"), - target, info->vhost, timebuf, timebuf2, onlinestatus); - } - else if (info->type == NICK_TO) - { - u2 = User::Find(info->nick2, true); - if (u2) - onlinestatus = Anope::printf( _(". %s is still online."), u2->nick.c_str()); - else - onlinestatus = Anope::printf(_(", but %s mysteriously dematerialized."), info->nick2.c_str()); - - source.Reply(_("\002{0}\002 (\002{1}\002) was last seen changing nick to \002{2}\002 \002{3}\002 ago{4}"), - target, info->vhost, info->nick2, timebuf, onlinestatus); - } - else if (info->type == NICK_FROM) - { - source.Reply(_("\002{0}\002 (\002{1}\002) was last seen changing nick from \002{2}\002 to \002{3} {4}\002 ago{5}"), - target, info->vhost, info->nick2, target, timebuf, onlinestatus); - } - else if (info->type == JOIN) - { - if (ShouldHide(info->channel, u2)) - source.Reply(_("\002{0}\002 (\002{1}\002) was last seen joining a secret channel \002{2}\002 ago{3}"), - target, info->vhost, timebuf, onlinestatus); - else - source.Reply(_("\002{0}\002 (\002{1}\002) was last seen joining \002{2} {3}\002 ago{4}"), - target, info->vhost, info->channel, timebuf, onlinestatus); - } - else if (info->type == PART) - { - if (ShouldHide(info->channel, u2)) - source.Reply(_("\002{0}\002 (\002{1}\002) was last seen parting a secret channel \002{2}\002 ago{3}"), - target, info->vhost, timebuf, onlinestatus); - else - source.Reply(_("\002{0}\002 (\002{1}\002) was last seen parting \002{2} {3}\002 ago{4}"), - target, info->vhost, info->channel, timebuf, onlinestatus); - } - else if (info->type == QUIT) - { - source.Reply(_("\002{0}\002 (\002{1}\002) was last seen quitting (\002{2}\002) \002{3}\002 ago (\002{4}\\2)."), - target, info->vhost, info->message, timebuf, timebuf2); - } - else if (info->type == KICK) - { - if (ShouldHide(info->channel, u2)) - source.Reply(_("\002{0}\002 (\002{1}\002) was kicked from a secret channel \002{2}\002 ago{3}"), - target, info->vhost, timebuf, onlinestatus); - else - source.Reply(_("\002{0}\002 (\002{1}\002) was kicked from \002{2}\002 (\"{3}\") {4} ago{5}"), - target, info->vhost, info->channel, info->message, timebuf, onlinestatus); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Checks for the last time \037nick\037 was seen joining, leaving, or changing nick on the network and tells you when and, depending on channel or user settings, where it was.")); - return true; - } -}; - -class CSSeen : public Module - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - //Serialize::TypeBase seeninfo_type; - CommandSeen commandseen; - CommandOSSeen commandosseen; - - public: - CSSeen(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - // , seeninfo_type("SeenInfo", SeenInfo::Unserialize) - , commandseen(this) - , commandosseen(this) - { - } - - void OnReload(Configuration::Conf *conf) override - { - simple = conf->GetModule(this)->Get("simple"); - } - - void OnExpireTick() override - { - size_t previous_size = database.size(); - time_t purgetime = Config->GetModule(this)->Get("purgetime"); - if (!purgetime) - purgetime = Anope::DoTime("30d"); - for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end;) - { - database_map::iterator cur = it; - ++it; - - if ((Anope::CurTime - cur->second->last) > purgetime) - { - Log(LOG_DEBUG) << cur->first << " was last seen " << Anope::strftime(cur->second->last) << ", purging entries"; - delete cur->second; - } - } - Log(LOG_DEBUG) << "cs_seen: Purged database, checked " << previous_size << " nicks and removed " << (previous_size - database.size()) << " old entries."; - } - - void OnUserConnect(User *u, bool &exempt) override - { - if (!u->Quitting()) - UpdateUser(u, NEW, u->nick, "", "", ""); - } - - void OnUserNickChange(User *u, const Anope::string &oldnick) override - { - UpdateUser(u, NICK_TO, oldnick, u->nick, "", ""); - UpdateUser(u, NICK_FROM, u->nick, oldnick, "", ""); - } - - void OnUserQuit(User *u, const Anope::string &msg) override - { - UpdateUser(u, QUIT, u->nick, "", "", msg); - } - - void OnJoinChannel(User *u, Channel *c) override - { - UpdateUser(u, JOIN, u->nick, "", c->name, ""); - } - - void OnPartChannel(User *u, Channel *c, const Anope::string &channel, const Anope::string &msg) override - { - UpdateUser(u, PART, u->nick, "", channel, msg); - } - - EventReturn OnPreUserKicked(const MessageSource &source, ChanUserContainer *cu, const Anope::string &msg) override - { - UpdateUser(cu->user, KICK, cu->user->nick, source.GetSource(), cu->chan->name, msg); - return EVENT_CONTINUE; - } - - private: - void UpdateUser(const User *u, const TypeInfo Type, const Anope::string &nick, const Anope::string &nick2, const Anope::string &channel, const Anope::string &message) - { - if (simple || !u->server->IsSynced()) - return; - - SeenInfo* &info = database[nick]; - if (!info) - info = new SeenInfo(); - info->nick = nick; - info->vhost = u->GetVIdent() + "@" + u->GetDisplayedHost(); - info->type = Type; - info->last = Anope::CurTime; - info->nick2 = nick2; - info->channel = channel; - info->message = message; - } -}; - -MODULE_INIT(CSSeen) -#endif diff --git a/modules/commands/cs_set.cpp b/modules/commands/cs_set.cpp deleted file mode 100644 index 06c996f52..000000000 --- a/modules/commands/cs_set.cpp +++ /dev/null @@ -1,1265 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_mode.h" -#include "modules/chanserv.h" -#include "modules/cs_info.h" -#include "modules/cs_set.h" - -class CommandCSSet : public Command -{ - public: - CommandCSSet(Module *creator) : Command(creator, "chanserv/set", 2, 3) - { - this->SetDesc(_("Set channel options and information")); - this->SetSyntax(_("\037option\037 \037channel\037 \037parameters\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Allows the channel founder to set various channel options and other information.\n" - "\n" - "Available options:")); - Anope::string this_name = source.command; - bool hide_privileged_commands = Config->GetBlock("options")->Get("hideprivilegedcommands"), - hide_registered_commands = Config->GetBlock("options")->Get("hideregisteredcommands"); - for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) - { - const Anope::string &c_name = it->first; - const CommandInfo &info = it->second; - if (c_name.find_ci(this_name + " ") == 0) - { - ServiceReference c("Command", info.name); - - // XXX dup - if (!c) - continue; - else if (hide_registered_commands && !c->AllowUnregistered() && !source.GetAccount()) - continue; - else if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission)) - continue; - - source.command = it->first; - c->OnServHelp(source); - } - } - - CommandInfo *help = source.service->FindCommand("generic/help"); - if (help) - source.Reply(_("Type \002{0}{1} {2} {3} \037option\037\002 for more information on a particular option."), - Config->StrictPrivmsg, source.service->nick, help->cname, this_name); - - return true; - } -}; - -class CommandCSSetAutoOp : public Command -{ - public: - CommandCSSetAutoOp(Module *creator, const Anope::string &cname = "chanserv/set/autoop") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Should services automatically give status to users")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, params[1]); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (params[1].equals_ci("ON")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable autoop"; - ci->UnsetS("NOAUTOOP"); - source.Reply(_("Services will now automatically give modes to users in \002{0}\002."), ci->GetName()); - } - else if (params[1].equals_ci("OFF")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable autoop"; - ci->SetS("NOAUTOOP", true); - source.Reply(_("Services will no longer automatically give modes to users in \002{0}\002."), ci->GetName()); - } - else - this->OnSyntaxError(source, "AUTOOP"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables AUTOOP for a \037channel\037. When disabled, users who join \037channel\037 will not automatically gain any status from {0}."), source.service->nick); - return true; - } -}; - -class CommandCSSetBanType : public Command -{ - public: - CommandCSSetBanType(Module *creator, const Anope::string &cname = "chanserv/set/bantype") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Set how Services make bans on the channel")); - this->SetSyntax(_("\037channel\037 \037bantype\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, params[1]); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - try - { - int16_t new_type = convertTo(params[1]); - if (new_type < 0 || new_type > 3) - throw ConvertException("Invalid range"); - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change the ban type to " << new_type; - ci->SetBanType(new_type); - source.Reply(_("Ban type for channel \002{0}\002 is now \002#{1}\002."), ci->GetName(), new_type); - } - catch (const ConvertException &) - { - source.Reply(_("\002{0}\002 is not a valid ban type."), params[1]); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Sets the ban type that will be used by services whenever they need to ban someone from your channel.\n" - "\n" - "Bantype is a number between 0 and 3 that means:\n" - "\n" - "0: ban in the form *!user@host\n" - "1: ban in the form *!*user@host\n" - "2: ban in the form *!*@host\n" - "3: ban in the form *!*user@*.domain")); - return true; - } -}; - -class CommandCSSetDescription : public Command -{ - public: - CommandCSSetDescription(Module *creator, const Anope::string &cname = "chanserv/set/description") : Command(creator, cname, 1, 2) - { - this->SetDesc(_("Set the channel description")); - this->SetSyntax(_("\037channel\037 [\037description\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params.size() > 1 ? params[1] : ""; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - ci->SetDesc(param); - if (!param.empty()) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change the description to " << ci->GetDesc(); - source.Reply(_("Description of \002{0}\002 changed to \002{1}\002."), ci->GetName(), ci->GetDesc()); - } - else - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to unset the description"; - source.Reply(_("Description of \002{0}\002 unset."), ci->GetName()); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Sets the description for the channel, which shows up with the \002LIST\002 and \002INFO\002 commands.")); - return true; - } -}; - -class CommandCSSetFounder : public Command -{ - public: - CommandCSSetFounder(Module *creator, const Anope::string &cname = "chanserv/set/founder") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Set the founder of a channel")); - this->SetSyntax(_("\037channel\037 \037user\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && (ci->HasFieldS("SECUREFOUNDER") ? !source.IsFounder(ci) : !source.AccessFor(ci).HasPriv("FOUNDER")) && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); - return; - } - - NickServ::Nick *na = NickServ::FindNick(param); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), param); - return; - } - - NickServ::Account *nc = na->GetAccount(); - unsigned max_reg = Config->GetModule("chanserv")->Get("maxregistered"); - if (max_reg && nc->GetChannelCount() >= max_reg && !source.HasPriv("chanserv/no-register-limit")) - { - source.Reply(_("\002{0}\002 has too many channels registered."), na->GetNick()); - return; - } - - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change the founder from " << (ci->GetFounder() ? ci->GetFounder()->GetDisplay() : "(none)") << " to " << nc->GetDisplay(); - - ci->SetFounder(nc); - - source.Reply(_("Founder of \002{0}\002 changed to \002{1}\002."), ci->GetName(), na->GetNick()); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Changes the founder of \037channel\037 to \037user\037. Using this command will cause you to lose your founder access to \037channel\037, and cannot be undone." - "\n" - "Use of this command requires being the founder or \037channel\037 or having the \002{0}\002 privilege, if secure founder is enabled or not, respectively.")); - return true; - } -}; - -class CommandCSSetKeepModes : public Command -{ - public: - CommandCSSetKeepModes(Module *creator, const Anope::string &cname = "chanserv/set/keepmodes") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Retain modes when channel is not in use")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (param.equals_ci("ON")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable keep modes"; - ci->SetS("CS_KEEP_MODES", true); - source.Reply(_("Keep modes for \002{0}\002 is now \002on\002."), ci->GetName()); - if (ci->c) - for (const std::pair &p : ci->c->GetModes()) - { - ChanServ::Mode *mode = ChanServ::mode.Create(); - mode->SetChannel(ci); - mode->SetMode(p.first); - mode->SetParam(p.second); - } - } - else if (param.equals_ci("OFF")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable keep modes"; - ci->UnsetS("CS_KEEP_MODES"); - source.Reply(_("Keep modes for \002{0}\002 is now \002off\002."), ci->GetName()); - for (ChanServ::Mode *m : ci->GetRefs(ChanServ::mode)) - m->Delete(); - } - else - this->OnSyntaxError(source, "KEEPMODES"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables keepmodes for \037channel\037. If keepmodes is enabled, services will remember modes set on the channel and re-set them the next time the channel is created.")); - return true; - } -}; - -class CommandCSSetPeace : public Command -{ - public: - CommandCSSetPeace(Module *creator, const Anope::string &cname = "chanserv/set/peace") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Regulate the use of critical commands")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (param.equals_ci("ON")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable peace"; - ci->SetS("PEACE", true); - source.Reply(_("Peace option for \002{0}\002 is now \002on\002."), ci->GetName()); - } - else if (param.equals_ci("OFF")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable peace"; - ci->UnsetS("PEACE"); - source.Reply(_("Peace option for \002{0}\002 is now \002off\002."), ci->GetName()); - } - else - this->OnSyntaxError(source, "PEACE"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables the \002peace\002 option for \037channel\037." - " When \002peace\002 is set, a user won't be able to kick, ban or remove a channel status of a user that has a level superior or equal to his via services."), - source.service->nick); - return true; - } -}; - -inline static Anope::string BotModes() -{ - return Config->GetModule("botserv")->Get("botmodes", - Config->GetModule("chanserv")->Get("botmodes", "o") - ); -} - -class CommandCSSetPersist : public Command -{ - public: - CommandCSSetPersist(Module *creator, const Anope::string &cname = "chanserv/set/persist") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Set the channel as permanent")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - ChannelMode *cm = ModeManager::FindChannelModeByName("PERM"); - - if (params[1].equals_ci("ON")) - { - if (!ci->HasFieldS("PERSIST")) - { - ci->SetS("PERSIST", true); - - /* Channel doesn't exist, create it */ - if (!ci->c) - { - bool created; - Channel *c = Channel::FindOrCreate(ci->GetName(), created); - if (ci->GetBot()) - { - ChannelStatus status(BotModes()); - ci->GetBot()->Join(c, &status); - } - if (created) - c->Sync(); - } - - /* Set the perm mode */ - if (cm) - { - if (ci->c && !ci->c->HasMode("PERM")) - ci->c->SetMode(NULL, cm); - /* Add it to the channels mlock */ - if (mlocks) - mlocks->SetMLock(ci, cm, true, "", source.GetNick()); - } - /* No botserv bot, no channel mode, give them ChanServ. - * Yes, this works fine with no BotServ. - */ - else if (!ci->GetBot()) - { - ServiceBot *ChanServ = Config->GetClient("ChanServ"); - if (!ChanServ) - { - source.Reply(_("ChanServ is required to enable persist on this network.")); - return; - } - - ChanServ->Assign(NULL, ci); - if (!ci->c->FindUser(ChanServ)) - { - ChannelStatus status(BotModes()); - ChanServ->Join(ci->c, &status); - } - } - } - - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable persist"; - source.Reply(_("Channel \002{0}\002 is now persistent."), ci->GetName()); - } - else if (params[1].equals_ci("OFF")) - { - if (ci->HasFieldS("PERSIST")) - { - ci->UnsetS("PERSIST"); - - ServiceBot *ChanServ = Config->GetClient("ChanServ"), - *BotServ = Config->GetClient("BotServ"); - - /* Unset perm mode */ - if (cm) - { - if (ci->c && ci->c->HasMode("PERM")) - ci->c->RemoveMode(NULL, cm); - /* Remove from mlock */ - if (mlocks) - mlocks->RemoveMLock(ci, cm, true); - } - /* No channel mode, no BotServ, but using ChanServ as the botserv bot - * which was assigned when persist was set on - */ - else if (!cm && !BotServ && ci->GetBot()) - { - if (!ChanServ) - { - source.Reply(_("ChanServ is required to enable persist on this network.")); - return; - } - - /* Unassign bot */ - ChanServ->UnAssign(NULL, ci); - } - } - - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable persist"; - source.Reply(_("Channel \002{0}\002 is no longer persistent."), ci->GetName()); - } - else - this->OnSyntaxError(source, "PERSIST"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables the persistent channel setting." - " When persistent is set, the service bot will remain in the channel when it has emptied of users.")); - return true; - } -}; - -class CommandCSSetRestricted : public Command -{ - public: - CommandCSSetRestricted(Module *creator, const Anope::string &cname = "chanserv/set/restricted") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Restrict access to the channel")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (param.equals_ci("ON")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable restricted"; - ci->SetS("RESTRICTED", true); - source.Reply(_("Restricted access option for \002{0}\002 is now \002on\002."), ci->GetName()); - } - else if (param.equals_ci("OFF")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable restricted"; - ci->UnsetS("RESTRICTED"); - source.Reply(_("Restricted access option for \002{0}\002 is now \002off\002."), ci->GetName()); - } - else - this->OnSyntaxError(source, "RESTRICTED"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables the \002restricted access\002 option for \037channel\037. When \002restricted access\002 is set, users not on the access list will not be permitted to join the channel.")); - return true; - } -}; - -class CommandCSSetSecure : public Command -{ - public: - CommandCSSetSecure(Module *creator, const Anope::string &cname = "chanserv/set/secure") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Activate security features")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (param.equals_ci("ON")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable secure"; - ci->SetS("CS_SECURE", true); - source.Reply(_("Secure option for \002{0}\002 is now \002on\002."), ci->GetName()); - } - else if (param.equals_ci("OFF")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable secure"; - ci->UnsetS("CS_SECURE"); - source.Reply(_("Secure option for \002{0}\002 is now \002off\002."), ci->GetName()); - } - else - this->OnSyntaxError(source, "SECURE"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables security features for a channel." - " When \002secure\002 is set, only users who have logged in (eg. not recognized based on their hostmask) will be given access to the channel as controlled by the access list.")); - return true; - } -}; - -class CommandCSSetSecureFounder : public Command -{ - public: - CommandCSSetSecureFounder(Module *creator, const Anope::string &cname = "chanserv/set/securefounder") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Stricter control of channel founder status")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && (ci->HasFieldS("SECUREFOUNDER") ? !source.IsFounder(ci) : !source.AccessFor(ci).HasPriv("FOUNDER")) && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); - return; - } - - if (param.equals_ci("ON")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable secure founder"; - ci->SetS("SECUREFOUNDER", true); - source.Reply(_("Secure founder option for \002{0}\002 is now \002on\002."), ci->GetName()); - } - else if (param.equals_ci("OFF")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable secure founder"; - ci->UnsetS("SECUREFOUNDER"); - source.Reply(_("Secure founder option for \002{0}\002 is now \002off\002."), ci->GetName()); - } - else - this->OnSyntaxError(source, "SECUREFOUNDER"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Enables or disables the \002secure founder\002 option for a channel." - " When \002secure founder\002 is set, only the real founder will be able to drop the channel, change its founder, and change its successor." - " Otherwise, anyone with the \002{0}\002 privilege will be able to use these commands."), - "FOUNDER"); - return true; - } -}; - -class CommandCSSetSecureOps : public Command -{ - public: - CommandCSSetSecureOps(Module *creator, const Anope::string &cname = "chanserv/set/secureops") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Stricter control of chanop status")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (param.equals_ci("ON")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable secure ops"; - ci->SetS("SECUREOPS", true); - source.Reply(_("Secure ops option for \002{0}\002 is now \002on\002."), ci->GetName()); - } - else if (param.equals_ci("OFF")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable secure ops"; - ci->UnsetS("SECUREOPS"); - source.Reply(_("Secure ops option for \002{0}\002 is now \002off\002."), ci->GetName()); - } - else - this->OnSyntaxError(source, "SECUREOPS"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables the \002secure ops\002 option for \037channel\037." - " When \002secure ops\002 is set, users will not be allowed to have channel operator status if they do not have the privileges for it.")); - return true; - } -}; - -class CommandCSSetSignKick : public Command -{ - public: - CommandCSSetSignKick(Module *creator, const Anope::string &cname = "chanserv/set/signkick") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Sign kicks that are done with the KICK command")); - this->SetSyntax(_("\037channel\037 {ON | LEVEL | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (param.equals_ci("ON")) - { - ci->SetS("SIGNKICK", true); - ci->UnsetS("SIGNKICK_LEVEL"); - source.Reply(_("Signed kick option for \002{0}\002 is now \002on\002."), ci->GetName()); - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable sign kick"; - } - else if (param.equals_ci("LEVEL")) - { - ci->SetS("SIGNKICK_LEVEL", true); - ci->UnsetS("SIGNKICK"); - source.Reply(_("Signed kick option for \002{0}\002 is now \002on\002, but depends of the privileges of the user that is using the command."), ci->GetName()); - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable sign kick level"; - } - else if (param.equals_ci("OFF")) - { - ci->UnsetS("SIGNKICK"); - ci->UnsetS("SIGNKICK_LEVEL"); - source.Reply(_("Signed kick option for \002{0}\002 is now \002off\002."), ci->GetName()); - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable sign kick"; - } - else - this->OnSyntaxError(source, "SIGNKICK"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables \002signed kicks\002 for \037channel\037." - " When \002signed kicks\002 is enabled, kicks issued through services will have the nickname of the user who performed the kick included in the kick reason." - " If you use \002LEVEL\002 setting, then only users who do not have the \002{0}\002 privilege will have their kicks signed."), - "SIGNKICK"); - return true; - } -}; - -class CommandCSSetSuccessor : public Command -{ - public: - CommandCSSetSuccessor(Module *creator, const Anope::string &cname = "chanserv/set/successor") : Command(creator, cname, 1, 2) - { - this->SetDesc(_("Set the successor for a channel")); - this->SetSyntax(_("\037channel\037 \037nick\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && (ci->HasFieldS("SECUREFOUNDER") ? !source.IsFounder(ci) : !source.AccessFor(ci).HasPriv("FOUNDER")) && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); - return; - } - - NickServ::Account *nc; - - if (!param.empty()) - { - NickServ::Nick *na = NickServ::FindNick(param); - - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), param); - return; - } - - if (na->GetAccount() == ci->GetFounder()) - { - source.Reply(_("\002{0}\002 cannot be the successor of channel \002{1}\002 as they are the founder."), na->GetNick(), ci->GetName()); - return; - } - - nc = na->GetAccount(); - } - else - nc = NULL; - - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change the successor from " << (ci->GetSuccessor() ? ci->GetSuccessor()->GetDisplay() : "(none)") << " to " << (nc ? nc->GetDisplay() : "(none)"); - - ci->SetSuccessor(nc); - - if (nc) - source.Reply(_("Successor for \002{0}\002 changed to \002{1}\002."), ci->GetName(), nc->GetDisplay()); - else - source.Reply(_("Successor for \002{0}\002 unset."), ci->GetName()); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Changes the successor of \037channel\037." - " The successor of a channel is automatically given ownership of the channel if the founder's account drops of expires." - " If the success has too many registered channels or there is no successor, the channel may instead be given to one of the users on the channel access list with the most privileges.")); - return true; - } -}; - -class CommandCSSetNoexpire : public Command -{ - public: - CommandCSSetNoexpire(Module *creator) : Command(creator, "chanserv/saset/noexpire", 2, 2) - { - this->SetDesc(_("Prevent the channel from expiring")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (source.permission.empty() && !source.AccessFor(ci).HasPriv("SET")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (param.equals_ci("ON")) - { - Log(LOG_ADMIN, source, this, ci) << "to enable noexpire"; - ci->SetS("CS_NO_EXPIRE", true); - source.Reply(_("Channel \002{0} will not\002 expire."), ci->GetName()); - } - else if (param.equals_ci("OFF")) - { - Log(LOG_ADMIN, source, this, ci) << "to disable noexpire"; - ci->UnsetS("CS_NO_EXPIRE"); - source.Reply(_("Channel \002{0} will\002 expire."), ci->GetName()); - } - else - this->OnSyntaxError(source, "NOEXPIRE"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Sets whether the given \037channel\037 will expire. Setting \002 noexpire\002 to \002on\002 prevents the channel from expiring.")); - return true; - } -}; - -class CSSet : public Module - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - Serialize::Field noautoop, peace, securefounder, - restricted, secure, secureops, signkick, signkick_level, noexpire, keep_modes, persist; - - CommandCSSet commandcsset; - CommandCSSetAutoOp commandcssetautoop; - CommandCSSetBanType commandcssetbantype; - CommandCSSetDescription commandcssetdescription; - CommandCSSetFounder commandcssetfounder; - CommandCSSetKeepModes commandcssetkeepmodes; - CommandCSSetPeace commandcssetpeace; - CommandCSSetPersist commandcssetpersist; - CommandCSSetRestricted commandcssetrestricted; - CommandCSSetSecure commandcssetsecure; - CommandCSSetSecureFounder commandcssetsecurefounder; - CommandCSSetSecureOps commandcssetsecureops; - CommandCSSetSignKick commandcssetsignkick; - CommandCSSetSuccessor commandcssetsuccessor; - CommandCSSetNoexpire commandcssetnoexpire; - - ExtensibleRef inhabit; - - bool persist_lower_ts; - - public: - CSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - - , noautoop(this, ChanServ::channel, "NOAUTOOP") - , peace(this, ChanServ::channel, "PEACE") - , securefounder(this, ChanServ::channel, "SECUREFOUNDER") - , restricted(this, ChanServ::channel, "RESTRICTED") - , secure(this, ChanServ::channel, "CS_SECURE") - , secureops(this, ChanServ::channel, "SECUREOPS") - , signkick(this, ChanServ::channel, "SIGNKICK") - , signkick_level(this, ChanServ::channel, "SIGNKICK_LEVEL") - , noexpire(this, ChanServ::channel, "CS_NO_EXPIRE") - , keep_modes(this, ChanServ::channel, "CS_KEEP_MODES") - , persist(this, ChanServ::channel, "PERSIST") - - , commandcsset(this) - , commandcssetautoop(this) - , commandcssetbantype(this) - , commandcssetdescription(this) - , commandcssetfounder(this) - , commandcssetkeepmodes(this) - , commandcssetpeace(this) - , commandcssetpersist(this) - , commandcssetrestricted(this) - , commandcssetsecure(this) - , commandcssetsecurefounder(this) - , commandcssetsecureops(this) - , commandcssetsignkick(this) - , commandcssetsuccessor(this) - , commandcssetnoexpire(this) - - , inhabit("inhabit") - { - } - - void OnReload(Configuration::Conf *conf) override - { - persist_lower_ts = conf->GetModule(this)->Get("persist_lower_ts"); - } - - void OnCreateChan(ChanServ::Channel *ci) override - { - ci->SetBanType(Config->GetModule(this)->Get("defbantype", "2")); - } - - void OnChannelSync(Channel *c) override - { - if (c->ci && keep_modes.HasExt(c->ci)) - for (ChanServ::Mode *m : c->ci->GetRefs(ChanServ::mode)) - c->SetMode(c->ci->WhoSends(), m->GetMode(), m->GetParam()); - } - - EventReturn OnCheckKick(User *u, Channel *c, Anope::string &mask, Anope::string &reason) override - { - if (!c->ci || !restricted.HasExt(c->ci) || c->MatchesList(u, "EXCEPT")) - return EVENT_CONTINUE; - - if (c->ci->AccessFor(u).empty() && (!c->ci->GetFounder() || u->Account() != c->ci->GetFounder())) - return EVENT_STOP; - - return EVENT_CONTINUE; - } - - void OnDelChan(ChanServ::Channel *ci) override - { - if (ci->c && persist.HasExt(ci)) - ci->c->RemoveMode(ci->WhoSends(), "PERM", "", false); - persist.Unset(ci); - } - - EventReturn OnChannelModeSet(Channel *c, const MessageSource &setter, ChannelMode *mode, const Anope::string ¶m) override - { - if (c->ci) - { - /* Channel mode +P or so was set, mark this channel as persistent */ - if (mode->name == "PERM") - persist.Set(c->ci, true); - - if (mode->type != MODE_STATUS && !c->syncing && Me->IsSynced() && ChanServ::mode && (!inhabit || !inhabit->HasExt(c))) - { - ChanServ::Mode *m = ChanServ::mode.Create(); - m->SetChannel(c->ci); - m->SetMode(mode->name); - m->SetParam(param); - } - } - - return EVENT_CONTINUE; - } - - EventReturn OnChannelModeUnset(Channel *c, const MessageSource &setter, ChannelMode *mode, const Anope::string ¶m) override - { - if (mode->name == "PERM") - { - if (c->ci) - persist.Unset(c->ci); - } - - if (c->ci && mode->type != MODE_STATUS && !c->syncing && Me->IsSynced() && (!inhabit || !inhabit->HasExt(c))) - for (ChanServ::Mode *m : c->ci->GetRefs(ChanServ::mode)) - if (m->GetMode() == mode->name && m->GetParam().equals_ci(param)) - m->Delete(); - - return EVENT_CONTINUE; - } - - void OnJoinChannel(User *u, Channel *c) override - { - if (persist_lower_ts && c->ci && persist.HasExt(c->ci) && c->creation_time > c->ci->GetTimeRegistered()) - { - Log(LOG_DEBUG) << "Changing TS of " << c->name << " from " << c->creation_time << " to " << c->ci->GetTimeRegistered(); - c->creation_time = c->ci->GetTimeRegistered(); - IRCD->SendChannel(c); - c->Reset(); - } - } - - void OnSetCorrectModes(User *user, Channel *chan, ChanServ::AccessGroup &access, bool &give_modes, bool &take_modes) override - { - if (chan->ci) - { - if (noautoop.HasExt(chan->ci)) - give_modes = false; - if (secureops.HasExt(chan->ci)) - // This overrides what chanserv does because it is loaded after chanserv - take_modes = true; - } - } - - void OnPreChanExpire(ChanServ::Channel *ci, bool &expire) override - { - if (noexpire.HasExt(ci)) - expire = false; - } - - void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override - { - if (!show_all) - return; - - if (peace.HasExt(ci)) - info.AddOption(_("Peace")); - if (restricted.HasExt(ci)) - info.AddOption(_("Restricted access")); - if (secure.HasExt(ci)) - info.AddOption(_("Security")); - if (securefounder.HasExt(ci)) - info.AddOption(_("Secure founder")); - if (secureops.HasExt(ci)) - info.AddOption(_("Secure ops")); - if (signkick.HasExt(ci) || signkick_level.HasExt(ci)) - info.AddOption(_("Signed kicks")); - if (persist.HasExt(ci)) - info.AddOption(_("Persistent")); - if (noexpire.HasExt(ci)) - info.AddOption(_("No expire")); - if (keep_modes.HasExt(ci)) - info.AddOption(_("Keep modes")); - if (noautoop.HasExt(ci)) - info.AddOption(_("No auto-op")); - } -}; - -MODULE_INIT(CSSet) diff --git a/modules/commands/cs_set_misc.cpp b/modules/commands/cs_set_misc.cpp deleted file mode 100644 index 7af347087..000000000 --- a/modules/commands/cs_set_misc.cpp +++ /dev/null @@ -1,211 +0,0 @@ -/* - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_set_misc.h" -#include "modules/cs_info.h" -#include "modules/cs_set.h" - -static Anope::map descriptions; - -class CSMiscDataImpl : public CSMiscData -{ - public: - CSMiscDataImpl(Serialize::TypeBase *type) : CSMiscData(type) { } - CSMiscDataImpl(Serialize::TypeBase *type, Serialize::ID id) : CSMiscData(type, id) { } - - ChanServ::Channel *GetChannel() override; - void SetChannel(ChanServ::Channel *s) override; - - Anope::string GetName() override; - void SetName(const Anope::string &n) override; - - Anope::string GetData() override; - void SetData(const Anope::string &d) override; -}; - -class CSMiscDataType : public Serialize::Type -{ - public: - Serialize::ObjectField owner; - Serialize::Field name, data; - - CSMiscDataType(Module *me) : Serialize::Type(me, "CSMiscData") - , owner(this, "owner", true) - , name(this, "name") - , data(this, "data") - { - } -}; - -ChanServ::Channel *CSMiscDataImpl::GetChannel() -{ - return Get(&CSMiscDataType::owner); -} - -void CSMiscDataImpl::SetChannel(ChanServ::Channel *s) -{ - Set(&CSMiscDataType::owner, s); -} - -Anope::string CSMiscDataImpl::GetName() -{ - return Get(&CSMiscDataType::name); -} - -void CSMiscDataImpl::SetName(const Anope::string &n) -{ - Set(&CSMiscDataType::name, n); -} - -Anope::string CSMiscDataImpl::GetData() -{ - return Get(&CSMiscDataType::data); -} - -void CSMiscDataImpl::SetData(const Anope::string &d) -{ - Set(&CSMiscDataType::data, d); -} - -class CommandCSSetMisc : public Command -{ - Anope::string GetAttribute(const Anope::string &command) - { - size_t sp = command.rfind(' '); - if (sp != Anope::string::npos) - return command.substr(sp + 1); - return command; - } - - public: - CommandCSSetMisc(Module *creator, const Anope::string &cname = "chanserv/set/misc") : Command(creator, cname, 1, 2) - { - this->SetSyntax(_("\037channel\037 [\037parameters\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - Anope::string scommand = GetAttribute(source.command); - - /* remove existing */ - for (CSMiscData *data : ci->GetRefs(csmiscdata)) - if (data->GetName() == scommand) - { - data->Delete(); - break; - } - - if (!param.empty()) - { - CSMiscData *data = csmiscdata.Create(); - data->SetChannel(ci); - data->SetName(scommand); - data->SetData(param); - - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change it to " << param; - source.Reply(_("\002{0}\002 for \002{1}\002 set to \002{2}\002."), scommand, ci->GetName(), param); - } - else - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to unset it"; - source.Reply(_("\002{0}\002 for \002{1}\002 unset."), scommand, ci->GetName()); - } - } - - void OnServHelp(CommandSource &source) override - { - if (descriptions.count(source.command)) - { - this->SetDesc(descriptions[source.command]); - Command::OnServHelp(source); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (descriptions.count(source.command)) - { - source.Reply("%s", Language::Translate(source.nc, descriptions[source.command].c_str())); - return true; - } - return false; - } -}; - -class CSSetMisc : public Module - , public EventHook -{ - CommandCSSetMisc commandcssetmisc; - CSMiscDataType type; - - public: - CSSetMisc(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcssetmisc(this) - , type(this) - { - } - - void OnReload(Configuration::Conf *conf) override - { - descriptions.clear(); - - for (int i = 0; i < conf->CountBlock("command"); ++i) - { - Configuration::Block *block = conf->GetBlock("command", i); - - if (block->Get("command") != "chanserv/set/misc") - continue; - - Anope::string cname = block->Get("name"); - Anope::string desc = block->Get("misc_description"); - - if (cname.empty() || desc.empty()) - continue; - - descriptions[cname] = desc; - } - } - - void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool) override - { - for (CSMiscData *data : ci->GetRefs(csmiscdata)) - info[data->GetName()] = data->GetData(); - } -}; - -MODULE_INIT(CSSetMisc) diff --git a/modules/commands/cs_status.cpp b/modules/commands/cs_status.cpp deleted file mode 100644 index 35676fa53..000000000 --- a/modules/commands/cs_status.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_akick.h" - -class CommandCSStatus : public Command -{ -public: - CommandCSStatus(Module *creator) : Command(creator, "chanserv/status", 1, 2) - { - this->SetDesc(_("Find a user's status on a channel")); - this->SetSyntax(_("\037channel\037 [\037user\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &channel = params[0]; - - ChanServ::Channel *ci = ChanServ::Find(channel); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), channel); - return; - } - - if (!source.AccessFor(ci).HasPriv("ACCESS_CHANGE") && !source.HasPriv("chanserv/auspex")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "ACCESS_CHANGE", ci->GetName()); - return; - } - - Anope::string nick = source.GetNick(); - if (params.size() > 1) - nick = params[1]; - - ChanServ::AccessGroup ag; - User *u = User::Find(nick, true); - NickServ::Nick *na = NULL; - if (u != NULL) - ag = ci->AccessFor(u); - else - { - na = NickServ::FindNick(nick); - if (na != NULL) - ag = ci->AccessFor(na->GetAccount()); - } - - if (ag.super_admin) - source.Reply(_("\002{0}\002 is a super administrator."), nick); - else if (ag.founder) - source.Reply(_("\002{0}\002 is the founder of \002{1}\002."), nick, ci->GetName()); - else if (ag.empty()) - source.Reply(_("\002{0}\002 has no access on \002{1}\002."), nick, ci->GetName()); - else - { - source.Reply(_("Access for \002{0}\002 on \002{1}\002:"), nick, ci->GetName()); - - for (unsigned i = 0; i < ag.size(); ++i) - { - ChanServ::ChanAccess *acc = ag[i]; - - source.Reply(_("\002{0}\002 matches access entry \002{1}\002, which has privilege \002{2}\002."), nick, acc->Mask(), acc->AccessSerialize()); - } - } - - for (unsigned j = 0, end = ci->GetAkickCount(); j < end; ++j) - { - AutoKick *ak = ci->GetAkick(j); - - if (ak->GetAccount()) - { - if (na && ak->GetAccount() == na->GetAccount()) - source.Reply(_("\002{0}\002 is on the auto kick list of \002{1}\002 ({2})."), na->GetAccount()->GetDisplay(), ci->GetName(), ak->GetReason()); - } - else if (u != NULL) - { - Entry akick_mask("", ak->GetMask()); - if (akick_mask.Matches(u)) - source.Reply(_("\002{0}\002 matches auto kick entry \002{1}\002 on \002{2}\002 ({3})."), u->nick, ak->GetMask(), ci->GetName(), ak->GetReason()); - } - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("This command tells you what access \037user\037 has on \037channel\037." - "It will also tell you which access and auto kick entries match \037user\037.\n" - "\n" - "Use of this command requires the \002{0}\002 privilege on \037channel\037."), - "ACCESS_CHANGE"); - return true; - } -}; - -class CSStatus : public Module -{ - CommandCSStatus commandcsstatus; - - public: - CSStatus(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsstatus(this) - { - } -}; - -MODULE_INIT(CSStatus) diff --git a/modules/commands/cs_suspend.cpp b/modules/commands/cs_suspend.cpp deleted file mode 100644 index 73a034192..000000000 --- a/modules/commands/cs_suspend.cpp +++ /dev/null @@ -1,339 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_suspend.h" -#include "modules/cs_drop.h" -#include "modules/chanserv.h" -#include "modules/cs_info.h" - -class CSSuspendInfoImpl : public CSSuspendInfo -{ - public: - CSSuspendInfoImpl(Serialize::TypeBase *type) : CSSuspendInfo(type) { } - CSSuspendInfoImpl(Serialize::TypeBase *type, Serialize::ID id) : CSSuspendInfo(type, id) { } - - ChanServ::Channel *GetChannel() override; - void SetChannel(ChanServ::Channel *s) override; - - Anope::string GetBy() override; - void SetBy(const Anope::string &by) override; - - Anope::string GetReason() override; - void SetReason(const Anope::string &reason) override; - - time_t GetWhen() override; - void SetWhen(const time_t &w) override; - - time_t GetExpires() override; - void SetExpires(const time_t &e) override; -}; - -class CSSuspendType : public Serialize::Type -{ - public: - Serialize::ObjectField channel; - Serialize::Field by, reason; - Serialize::Field when, expires; - - CSSuspendType(Module *me) : Serialize::Type(me, "CSSuspendInfo") - , channel(this, "chan", true) - , by(this, "by") - , reason(this, "reason") - , when(this, "time") - , expires(this, "expires") - { - } -}; - -ChanServ::Channel *CSSuspendInfoImpl::GetChannel() -{ - return Get(&CSSuspendType::channel); -} - -void CSSuspendInfoImpl::SetChannel(ChanServ::Channel *s) -{ - Set(&CSSuspendType::channel, s); -} - -Anope::string CSSuspendInfoImpl::GetBy() -{ - return Get(&CSSuspendType::by); -} - -void CSSuspendInfoImpl::SetBy(const Anope::string &by) -{ - Set(&CSSuspendType::by, by); -} - -Anope::string CSSuspendInfoImpl::GetReason() -{ - return Get(&CSSuspendType::reason); -} - -void CSSuspendInfoImpl::SetReason(const Anope::string &reason) -{ - Set(&CSSuspendType::reason, reason); -} - -time_t CSSuspendInfoImpl::GetWhen() -{ - return Get(&CSSuspendType::when); -} - -void CSSuspendInfoImpl::SetWhen(const time_t &w) -{ - Set(&CSSuspendType::when, w); -} - -time_t CSSuspendInfoImpl::GetExpires() -{ - return Get(&CSSuspendType::expires); -} - -void CSSuspendInfoImpl::SetExpires(const time_t &e) -{ - Set(&CSSuspendType::expires, e); -} - -class CommandCSSuspend : public Command -{ - EventHandlers &onchansuspend; - public: - CommandCSSuspend(Module *creator, EventHandlers &event) : Command(creator, "chanserv/suspend", 2, 3), onchansuspend(event) - { - this->SetDesc(_("Prevent a channel from being used preserving channel data and settings")); - this->SetSyntax(_("\037channel\037 [+\037expiry\037] [\037reason\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - Anope::string expiry = params[1]; - Anope::string reason = params.size() > 2 ? params[2] : ""; - time_t expiry_secs = Config->GetModule(this->owner)->Get("expire"); - - if (!expiry.empty() && expiry[0] != '+') - { - reason = expiry + " " + reason; - reason.trim(); - expiry.clear(); - } - else - { - expiry_secs = Anope::DoTime(expiry); - if (expiry_secs == -1) - { - source.Reply(_("Invalid expiry time \002{0}\002."), expiry); - return; - } - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - CSSuspendInfo *si = ci->GetRef(cssuspendinfo); - if (si) - { - source.Reply(_("\002{0}\002 is already suspended."), ci->GetName()); - return; - } - - si = cssuspendinfo.Create(); - si->SetChannel(ci); - si->SetBy(source.GetNick()); - si->SetReason(reason); - si->SetWhen(Anope::CurTime); - si->SetExpires(expiry_secs ? expiry_secs + Anope::CurTime : 0); - - if (ci->c) - { - std::vector users; - - for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) - { - ChanUserContainer *uc = it->second; - User *user = uc->user; - if (!user->HasMode("OPER") && user->server != Me) - users.push_back(user); - } - - for (unsigned i = 0; i < users.size(); ++i) - ci->c->Kick(NULL, users[i], "%s", !reason.empty() ? reason.c_str() : Language::Translate(users[i], _("This channel has been suspended."))); - } - - Log(LOG_ADMIN, source, this, ci) << "(" << (!reason.empty() ? reason : "No reason") << "), expires on " << (expiry_secs ? Anope::strftime(Anope::CurTime + expiry_secs) : "never"); - source.Reply(_("Channel \002{0}\002 is now suspended."), ci->GetName()); - - this->onchansuspend(&Event::ChanSuspend::OnChanSuspend, ci); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Disallows anyone from using the given channel." - " All channel settings are preserved while the channel is suspended." - "If \037expiry\037 is given the channel will be unsuspended after that period of time.")); - return true; - } -}; - -class CommandCSUnSuspend : public Command -{ - EventHandlers &onchanunsuspend; - - public: - CommandCSUnSuspend(Module *creator, EventHandlers &event) : Command(creator, "chanserv/unsuspend", 1, 1), onchanunsuspend(event) - { - this->SetDesc(_("Releases a suspended channel")); - this->SetSyntax(_("\037channel\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - CSSuspendInfo *si = ci->GetRef(cssuspendinfo); - if (!si) - { - source.Reply(_("Channel \002{0}\002 isn't suspended."), ci->GetName()); - return; - } - - Log(LOG_ADMIN, source, this, ci) << "which was suspended by " << si->GetBy() << " for: " << (!si->GetReason().empty() ? si->GetReason() : "No reason"); - - si->Delete(); - - source.Reply(_("Channel \002%s\002 is now released."), ci->GetName().c_str()); - - this->onchanunsuspend(&Event::ChanUnsuspend::OnChanUnsuspend, ci); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Releases a suspended channel. All data and settings are preserved from before the suspension.")); - return true; - } -}; - -class CSSuspend : public Module - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - CommandCSSuspend commandcssuspend; - CommandCSUnSuspend commandcsunsuspend; - EventHandlers onchansuspend; - EventHandlers onchanunsuspend; - std::vector show; - CSSuspendType cst; - - struct trim - { - Anope::string operator()(Anope::string s) const - { - return s.trim(); - } - }; - - bool Show(CommandSource &source, const Anope::string &what) const - { - return source.IsOper() || std::find(show.begin(), show.end(), what) != show.end(); - } - - public: - CSSuspend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcssuspend(this, onchansuspend) - , commandcsunsuspend(this, onchanunsuspend) - , onchansuspend(this) - , onchanunsuspend(this) - , cst(this) - { - } - - void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_hidden) override - { - CSSuspendInfo *si = ci->GetRef(cssuspendinfo); - if (!si) - return; - - if (show_hidden || Show(source, "suspended")) - info[_("Suspended")] = _("This channel is \002suspended\002."); - if (!si->GetBy().empty() && (show_hidden || Show(source, "by"))) - info[_("Suspended by")] = si->GetBy(); - if (!si->GetReason().empty() && (show_hidden || Show(source, "reason"))) - info[_("Suspend reason")] = si->GetReason(); - if (si->GetWhen() && (show_hidden || Show(source, "on"))) - info[_("Suspended on")] = Anope::strftime(si->GetWhen(), source.GetAccount()); - if (si->GetExpires() && (show_hidden || Show(source, "expires"))) - info[_("Suspension expires")] = Anope::strftime(si->GetExpires(), source.GetAccount()); - } - - void OnPreChanExpire(ChanServ::Channel *ci, bool &expire) override - { - CSSuspendInfo *si = ci->GetRef(cssuspendinfo); - if (!si) - return; - - expire = false; - - if (!si->GetExpires()) - return; - - if (si->GetExpires() < Anope::CurTime) - { - ci->SetLastUsed(Anope::CurTime); - si->Delete(); - - Log(this) << "Expiring suspend for " << ci->GetName(); - } - } - - EventReturn OnCheckKick(User *u, Channel *c, Anope::string &mask, Anope::string &reason) override - { - if (u->HasMode("OPER") || !c->ci || !c->ci->GetRef(cssuspendinfo)) - return EVENT_CONTINUE; - - reason = Language::Translate(u, _("This channel may not be used.")); - return EVENT_STOP; - } - - EventReturn OnChanDrop(CommandSource &source, ChanServ::Channel *ci) override - { - CSSuspendInfo *si = ci->GetRef(cssuspendinfo); - if (si && !source.HasCommand("chanserv/drop")) - { - source.Reply(_("Channel \002{0}\002 is currently suspended."), ci->GetName()); - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } -}; - -MODULE_INIT(CSSuspend) diff --git a/modules/commands/cs_sync.cpp b/modules/commands/cs_sync.cpp deleted file mode 100644 index 37b56798c..000000000 --- a/modules/commands/cs_sync.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandCSSync : public Command -{ - public: - CommandCSSync(Module *creator) : Command(creator, "chanserv/sync", 1, 1) - { - this->SetDesc(_("Sync users channel modes")); - this->SetSyntax(_("\037channel\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (ci->c == NULL) - { - source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); - return; - } - - if (!source.AccessFor(ci).HasPriv("ACCESS_CHANGE") && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "ACCESS_CHANGE", ci->GetName()); - return; - } - - bool override = !source.AccessFor(ci).HasPriv("ACCESS_CHANGE") && source.HasPriv("chanserv/administration"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci); - - for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) - ci->c->SetCorrectModes(it->second->user, true); - - source.Reply(_("All user modes on \002{0}\002 have been synced.")); - } - - bool OnHelp(CommandSource &source, const Anope::string ¶ms) override - { - source.Reply(_("Syncs all channel status modes on all users on \037channel\037 with the modes they should have based on the channel access list.\n" - "\n" - "Use of this command requires the \002{0}\002 privilege on \037channel\037."), - "ACCESS_CHNAGE"); - return true; - } -}; - -class CSSync : public Module -{ - CommandCSSync commandcssync; - - public: - CSSync(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcssync(this) - { - - } -}; - -MODULE_INIT(CSSync) diff --git a/modules/commands/cs_topic.cpp b/modules/commands/cs_topic.cpp deleted file mode 100644 index a132900a6..000000000 --- a/modules/commands/cs_topic.cpp +++ /dev/null @@ -1,276 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_mode.h" -#include "modules/cs_info.h" -#include "modules/cs_set.h" - -class CommandCSSetKeepTopic : public Command -{ - public: - CommandCSSetKeepTopic(Module *creator, const Anope::string &cname = "chanserv/set/keeptopic") : Command(creator, cname, 2, 2) - { - this->SetDesc(_("Retain topic when channel is not in use")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string ¶m = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (param.equals_ci("ON")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable keeptopic"; - ci->SetS("KEEPTOPIC", true); - source.Reply(_("Topic retention option for \002{0}\002 is now \002on\002."), ci->GetName()); - } - else if (param.equals_ci("OFF")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable keeptopic"; - ci->UnsetS("KEEPTOPIC"); - source.Reply(_("Topic retention option for \002{0}\002 is now \002off\002."), ci->GetName()); - } - else - this->OnSyntaxError(source, "KEEPTOPIC"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables the \002topic retention\002 option for a \037channel\037." - " When \002topic retention\002 is set, the topic for the channel will be remembered by {0} even after the last user leaves the channel, and will be restored the next time the channel is created."), - source.service->nick); - return true; - } -}; - -class CommandCSTopic : public Command -{ - ExtensibleRef topiclock; - - void Lock(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, "topiclock on"); - if (MOD_RESULT == EVENT_STOP) - return; - - topiclock->Set(ci, true); - source.Reply(_("Topic lock option for \002{0}\002 is now \002on\002."), ci->GetName()); - } - - void Unlock(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, "topiclock off"); - if (MOD_RESULT == EVENT_STOP) - return; - - topiclock->Unset(ci); - source.Reply(_("Topic lock option for \002{0}\002 is now \002off\002."), ci->GetName()); - } - - void Set(CommandSource &source, ChanServ::Channel *ci, const Anope::string &topic) - { - bool has_topiclock = topiclock->HasExt(ci); - topiclock->Unset(ci); - ci->c->ChangeTopic(source.GetNick(), topic, Anope::CurTime); - if (has_topiclock) - topiclock->Set(ci, true); - - bool override = !source.AccessFor(ci).HasPriv("TOPIC"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << (!topic.empty() ? "to change the topic to: " : "to unset the topic") << (!topic.empty() ? topic : ""); - } - - void Append(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - const Anope::string &topic = params[2]; - - Anope::string new_topic; - if (!ci->c->topic.empty()) - { - new_topic = ci->c->topic + " " + topic; - ci->SetLastTopic(""); - } - else - new_topic = topic; - - this->Set(source, ci, new_topic); - } - - public: - CommandCSTopic(Module *creator) : Command(creator, "chanserv/topic", 2, 3), - topiclock("TOPICLOCK") - { - this->SetDesc(_("Manipulate the topic of the specified channel")); - this->SetSyntax(_("\037channel\037 [SET] [\037topic\037]")); - this->SetSyntax(_("\037channel\037 APPEND \037topic\037")); - this->SetSyntax(_("\037channel\037 [UNLOCK|LOCK]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &channel = params[0]; - const Anope::string &subcmd = params[1]; - - ChanServ::Channel *ci = ChanServ::Find(channel); - if (ci == NULL) - source.Reply(_("Channel \002{0}\002 isn't registered."), channel); - else if (!source.AccessFor(ci).HasPriv("TOPIC") && !source.HasCommand("chanserv/topic")) - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "TOPIC", ci->GetName()); - else if (subcmd.equals_ci("LOCK")) - this->Lock(source, ci, params); - else if (subcmd.equals_ci("UNLOCK")) - this->Unlock(source, ci, params); - else if (!ci->c) - source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); - else if (subcmd.equals_ci("APPEND") && params.size() > 2) - this->Append(source, ci, params); - else - { - Anope::string topic; - if (subcmd.equals_ci("SET")) - { - topic = params.size() > 2 ? params[2] : ""; - } - else - { - topic = subcmd; - if (params.size() > 2) - topic += " " + params[2]; - } - this->Set(source, ci, topic); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Allows manipulating the topic of the specified channel." - " The \002SET\002 command changes the topic of the channel to the given topic or unsets the topic if no topic is given." - " The \002APPEND\002 command appends the given topic to the existing topic.\n" - "\n" - "\002LOCK\002 and \002UNLOCK\002 may be used to enable and disable topic lock." - " When topic lock is set, the channel topic will be unchangeable by users who do not have the \002TOPIC\002 privilege.\n" - "\n" - "Use of this command requires the \002{0}\002 privilege on \037channel\037."), - "TOPIC"); - return true; - } -}; - -class CSTopic : public Module - , public EventHook - , public EventHook - , public EventHook -{ - CommandCSTopic commandcstopic; - CommandCSSetKeepTopic commandcssetkeeptopic; - - /*Serializable*/ExtensibleItem topiclock, keeptopic; - - public: - CSTopic(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcstopic(this) - , commandcssetkeeptopic(this) - , topiclock(this, "TOPICLOCK") - , keeptopic(this, "KEEPTOPIC") - { - - } - - void OnChannelSync(Channel *c) override - { - if (c->ci) - { - /* Update channel topic */ - if ((topiclock.HasExt(c->ci) || keeptopic.HasExt(c->ci)) && c->ci->GetLastTopic() != c->topic) - { - c->ChangeTopic(!c->ci->GetLastTopicSetter().empty() ? c->ci->GetLastTopicSetter() : c->ci->WhoSends()->nick, c->ci->GetLastTopic(), c->ci->GetLastTopicTime() ? c->ci->GetLastTopicTime() : Anope::CurTime); - } - } - } - - void OnTopicUpdated(User *source, Channel *c, const Anope::string &user, const Anope::string &topic) override - { - if (!c->ci) - return; - - /* We only compare the topics here, not the time or setter. This is because some (old) IRCds do not - * allow us to set the topic as someone else, meaning we have to bump the TS and change the setter to us. - * This desyncs what is really set with what we have stored, and we end up resetting the topic often when - * it is not required - */ - if (topiclock.HasExt(c->ci) && c->ci->GetLastTopic() != c->topic && (!source || !c->ci->AccessFor(source).HasPriv("TOPIC"))) - { - c->ChangeTopic(c->ci->GetLastTopicSetter(), c->ci->GetLastTopic(), c->ci->GetLastTopicTime()); - } - else - { - c->ci->SetLastTopic(c->topic); - c->ci->SetLastTopicSetter(c->topic_setter); - c->ci->SetLastTopicTime(c->topic_ts); - } - } - - void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override - { - if (keeptopic.HasExt(ci)) - info.AddOption(_("Topic retention")); - if (topiclock.HasExt(ci)) - info.AddOption(_("Topic lock")); - - ModeLock *secret = mlocks ? mlocks->GetMLock(ci, "SECRET") : nullptr; - if (!ci->GetLastTopic().empty() && (show_all || ((!secret || secret->GetSet() == false) && (!ci->c || !ci->c->HasMode("SECRET"))))) - { - info[_("Last topic")] = ci->GetLastTopic(); - info[_("Topic set by")] = ci->GetLastTopicSetter(); - } - } -}; - -MODULE_INIT(CSTopic) diff --git a/modules/commands/cs_unban.cpp b/modules/commands/cs_unban.cpp deleted file mode 100644 index 73391ac30..000000000 --- a/modules/commands/cs_unban.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandCSUnban : public Command -{ - public: - CommandCSUnban(Module *creator) : Command(creator, "chanserv/unban", 0, 2) - { - this->SetDesc(_("Remove all bans preventing a user from entering a channel")); - this->SetSyntax(_("\037channel\037 [\037nick\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChannelMode *cm = ModeManager::FindChannelModeByName("BAN"); - if (!cm) - return; - - std::vector modes = cm->listeners; - modes.push_back(cm); - - if (params.empty()) - { - if (!source.GetUser()) - return; - - unsigned count = 0; - - for (ChanServ::Channel *ci : source.GetAccount()->GetRefs(ChanServ::channel)) - { - if (!ci->c || !source.AccessFor(ci).HasPriv("UNBAN")) - continue; - - for (unsigned j = 0; j < modes.size(); ++j) - if (ci->c->Unban(source.GetUser(), modes[j]->name, true)) - ++count; - } - - Log(LOG_COMMAND, source, this, NULL) << "on all channels"; - source.Reply(_("You have been unbanned from %d channels."), count); - - return; - } - - const Anope::string &chan = params[0]; - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (ci->c == NULL) - { - source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); - return; - } - - if (!source.AccessFor(ci).HasPriv("UNBAN") && !source.HasPriv("chanserv/kick")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "UNBAN", ci->GetName()); - return; - } - - User *u2 = source.GetUser(); - if (params.size() > 1) - u2 = User::Find(params[1], true); - - if (!u2) - { - if (params.size() > 1) - source.Reply(_("User \002{0}\002 isn't currently online."), params[1]); - return; - } - - bool override = !source.AccessFor(ci).HasPriv("UNBAN") && source.HasPriv("chanserv/kick"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to unban " << u2->nick; - - for (unsigned i = 0; i < modes.size(); ++i) - ci->c->Unban(u2, modes[i]->name, source.GetUser() == u2); - if (u2 == source.GetUser()) - source.Reply(_("You have been unbanned from \002{0}\002."), ci->c->name); - else - source.Reply(_("\002{0}\002 has been unbanned from \002{1}\002."), u2->nick, ci->c->name); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Tells {0} to remove all bans preventing you or the given \037user\037 from entering \037channel\037." - " If no channel is given, all bans affecting you in channels you have access in are removed.\n" - "\n" - "Use of this command requires the \002{1}\002 privilege on \037channel\037."), - source.service->nick, "UNBAN"); - return true; - } -}; - -class CSUnban : public Module -{ - CommandCSUnban commandcsunban; - - public: - CSUnban(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsunban(this) - { - - } -}; - -MODULE_INIT(CSUnban) diff --git a/modules/commands/cs_updown.cpp b/modules/commands/cs_updown.cpp deleted file mode 100644 index 383cc6e17..000000000 --- a/modules/commands/cs_updown.cpp +++ /dev/null @@ -1,261 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandCSUp : public Command -{ - void SetModes(User *u, Channel *c) - { - if (!c->ci) - return; - - /* whether or not we are giving modes */ - bool giving = true; - /* whether or not we have given a mode */ - bool given = false; - ChanServ::AccessGroup u_access = c->ci->AccessFor(u); - - for (unsigned i = 0; i < ModeManager::GetStatusChannelModesByRank().size(); ++i) - { - ChannelModeStatus *cm = ModeManager::GetStatusChannelModesByRank()[i]; - bool has_priv = u_access.HasPriv("AUTO" + cm->name) || u_access.HasPriv(cm->name); - - if (has_priv) - { - /* Always give op. If we have already given one mode, don't give more until it has a symbol */ - if (cm->name == "OP" || !given || (giving && cm->symbol)) - { - c->SetMode(NULL, cm, u->GetUID(), false); - /* Now if this contains a symbol don't give any more modes, to prevent setting +qaohv etc on users */ - giving = !cm->symbol; - given = true; - } - } - } - } - public: - CommandCSUp(Module *creator) : Command(creator, "chanserv/up", 0, 2) - { - this->SetDesc(_("Updates a selected nicks status on a channel")); - this->SetSyntax(_("[\037channel\037 [\037user\037]]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (params.empty()) - { - if (!source.GetUser()) - return; - for (User::ChanUserList::iterator it = source.GetUser()->chans.begin(); it != source.GetUser()->chans.end(); ++it) - { - Channel *c = it->second->chan; - SetModes(source.GetUser(), c); - } - Log(LOG_COMMAND, source, this, NULL) << "on all channels to update their status modes"; - } - else - { - const Anope::string &chan = params[0]; - const Anope::string &nick = params.size() > 1 ? params[1] : source.GetNick(); - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (ci->c == NULL) - { - source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); - return; - } - - User *u = User::Find(nick, true); - User *srcu = source.GetUser(); - Channel *c = ci->c; - bool override = false; - - if (u == NULL) - { - source.Reply(_("User \002{0}\002 isn't currently online."), nick); - return; - } - - if (srcu && !srcu->FindChannel(c)) - { - source.Reply(_("You must be in \002%s\002 to use this command."), c->name.c_str()); - return; - } - - if (!u->FindChannel(c)) - { - source.Reply(_("You must be on channel \002{0}\002 to use this command."), c->name); - return; - } - - if (!u->FindChannel(c)) - { - source.Reply(_("\002{0}\002 is not on channel \002{1}\002."), u->nick, c->name); - return; - } - - if (source.GetUser() && u != source.GetUser() && c->ci->HasFieldS("PEACE")) - { - if (c->ci->AccessFor(u) > c->ci->AccessFor(source.GetUser())) - { - if (source.HasPriv("chanserv/administration")) - override = true; - else - { - source.Reply(_("Access denied. \002{0}\002 has more privileges than you on \002{1}\002."), u->nick, ci->GetName()); - return; - } - } - } - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, c->ci) << "to update the status modes of " << u->nick; - SetModes(u, c); - } - - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Updates the status modes of \037user\037 on \037channel\037." - " If \037user\037 is omitted then your status is updated." - " If \037channel\037 is omitted then your status is updated on every channel you are in.")); - return true; - } -}; - -class CommandCSDown : public Command -{ - void RemoveAll(User *u, Channel *c) - { - ChanUserContainer *cu = c->FindUser(u); - if (cu != NULL) - for (size_t i = cu->status.Modes().length(); i > 0;) - c->RemoveMode(NULL, ModeManager::FindChannelModeByChar(cu->status.Modes()[--i]), u->GetUID()); - } - - public: - CommandCSDown(Module *creator) : Command(creator, "chanserv/down", 0, 2) - { - this->SetDesc(_("Removes a selected nicks status from a channel")); - this->SetSyntax(_("[\037channel\037 [\037nick\037]]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (params.empty()) - { - if (!source.GetUser()) - return; - for (User::ChanUserList::iterator it = source.GetUser()->chans.begin(); it != source.GetUser()->chans.end(); ++it) - { - Channel *c = it->second->chan; - RemoveAll(source.GetUser(), c); - } - Log(LOG_COMMAND, source, this, NULL) << "on all channels to remove their status modes"; - } - else - { - const Anope::string &channel = params[0]; - const Anope::string &nick = params.size() > 1 ? params[1] : source.GetNick(); - - ChanServ::Channel *ci = ChanServ::Find(channel); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), channel); - return; - } - - if (ci->c == NULL) - { - source.Reply(_("Channel \002{0}\002 doesn't exist."), ci->GetName()); - return; - } - - User *u = User::Find(nick, true); - Channel *c = ci->c; - - User *srcu = source.GetUser(); - bool override = false; - - if (u == NULL) - { - source.Reply(_("User \002{0}\002 isn't currently online."), nick); - return; - } - - if (srcu && !srcu->FindChannel(c)) - { - source.Reply(_("You must be on channel \002{0}\002 to use this command."), c->name); - return; - } - - if (srcu && !srcu->FindChannel(c)) - { - source.Reply(_("You must be in \002%s\002 to use this command."), c->name.c_str()); - return; - } - - if (!u->FindChannel(c)) - { - source.Reply(_("\002%s\002 is not on channel %s."), u->nick, c->name); - return; - } - - if (source.GetUser() && u != source.GetUser() && c->ci->HasFieldS("PEACE")) - { - if (c->ci->AccessFor(u) > c->ci->AccessFor(source.GetUser())) - { - if (source.HasPriv("chanserv/administration")) - override = true; - else - { - source.Reply(_("Access denied. \002{0}\002 has more privileges than you on \002{1}\002."), u->nick, ci->GetName()); - return; - } - } - } - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, c->ci) << "to remove the status modes from " << u->nick; - RemoveAll(u, c); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Removes a selected nicks status modes on a channel." - " If \037nick\037 is ommited then your status is removed." - " If \037channel\037 is ommited then channel status is removed on every channel you are in.")); - return true; - } -}; - -class CSUpDown : public Module -{ - CommandCSUp commandcsup; - CommandCSDown commandcsdown; - - public: - CSUpDown(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsup(this) - , commandcsdown(this) - { - - } -}; - -MODULE_INIT(CSUpDown) diff --git a/modules/commands/cs_xop.cpp b/modules/commands/cs_xop.cpp deleted file mode 100644 index 891ac77e7..000000000 --- a/modules/commands/cs_xop.cpp +++ /dev/null @@ -1,660 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -/* Dependencies: chanserv */ - -#include "module.h" -#include "modules/cs_access.h" -#include "../pseudoclients/chanserv/chanaccess.h" -#include "../pseudoclients/chanserv/chanaccesstype.h" - -namespace -{ - std::vector order; - std::map > permissions; -} - -class XOPChanAccess : public ChanAccessImpl -{ - public: - XOPChanAccess(Serialize::TypeBase *type) : ChanAccessImpl(type) { } - XOPChanAccess(Serialize::TypeBase *type, Serialize::ID id) : ChanAccessImpl(type, id) { } - - Anope::string GetType(); - void SetType(const Anope::string &); - - bool HasPriv(const Anope::string &priv) override - { - for (std::vector::iterator it = std::find(order.begin(), order.end(), this->GetType()); it != order.end(); ++it) - { - const std::vector &privs = permissions[*it]; - if (std::find(privs.begin(), privs.end(), priv) != privs.end()) - return true; - } - return false; - } - - Anope::string AccessSerialize() override - { - return this->GetType(); - } - - void AccessUnserialize(const Anope::string &data) override - { - this->SetType(data); - } - - static Anope::string DetermineLevel(ChanServ::ChanAccess *access) - { - if (access->GetSerializableType()->GetName() == "XOPChanAccess") - { - XOPChanAccess *xaccess = anope_dynamic_static_cast(access); - return xaccess->GetType(); - } - - std::map count; - - for (std::map >::const_iterator it = permissions.begin(), it_end = permissions.end(); it != it_end; ++it) - { - int &c = count[it->first]; - const std::vector &perms = it->second; - for (unsigned i = 0; i < perms.size(); ++i) - if (access->HasPriv(perms[i])) - ++c; - } - - Anope::string max; - int maxn = 0; - for (std::map::iterator it = count.begin(), it_end = count.end(); it != it_end; ++it) - if (it->second > maxn) - { - maxn = it->second; - max = it->first; - } - - return max; - } - -}; - -class XOPChanAccessType : public Serialize::Type -{ - public: - Serialize::Field type; - - XOPChanAccessType(Module *me) : Serialize::Type(me, "XOPChanAccess") - , type(this, "type") - { - SetParent(ChanServ::chanaccess); - } -}; - -Anope::string XOPChanAccess::GetType() -{ - return Get(&XOPChanAccessType::type); -} - -void XOPChanAccess::SetType(const Anope::string &i) -{ - Object::Set(&XOPChanAccessType::type, i); -} - -class CommandCSXOP : public Command -{ - private: - void DoAdd(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - Anope::string mask = params.size() > 2 ? params[2] : ""; - - if (mask.empty()) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - if (Anope::ReadOnly) - { - source.Reply(_("Sorry, channel %s list modification is temporarily disabled."), source.command.c_str()); - return; - } - - XOPChanAccess tmp_access(NULL); - tmp_access.SetChannel(ci); - tmp_access.SetType(source.command.upper()); - - ChanServ::AccessGroup access = source.AccessFor(ci); - ChanServ::ChanAccess *highest = access.Highest(); - bool override = false; - - std::vector::iterator cmd_it = std::find(order.begin(), order.end(), source.command.upper()), - access_it = highest ? std::find(order.begin(), order.end(), XOPChanAccess::DetermineLevel(highest)) : order.end(); - - if (!access.founder && (!access.HasPriv("ACCESS_CHANGE") || cmd_it <= access_it)) - { - if (source.HasPriv("chanserv/access/modify")) - override = true; - else - { - source.Reply(_("Access denied. You do not have the \002{0}\002 privilege on \002{1}\002."), "ACCESS_CHANGE", ci->GetName()); - return; - } - } - - NickServ::Nick *na = nullptr; - ChanServ::Channel *targ_ci = nullptr; - - if (IRCD->IsChannelValid(mask)) - { - if (Config->GetModule("chanserv")->Get("disallow_channel_access")) - { - source.Reply(_("Channels may not be on access lists.")); - return; - } - - targ_ci = ChanServ::Find(mask); - if (targ_ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), mask); - return; - } - - if (ci == targ_ci) - { - source.Reply(_("You can't add a channel to its own access list.")); - return; - } - - mask = targ_ci->GetName(); - } - else - { - na = NickServ::FindNick(mask); - - if (!na && Config->GetModule("chanserv")->Get("disallow_hostmask_access")) - { - source.Reply(_("Masks and unregistered users may not be on access lists.")); - return; - } - - if (mask.find_first_of("!*@") == Anope::string::npos && !na) - { - User *targ = User::Find(mask, true); - if (targ != NULL) - mask = "*!*@" + targ->GetDisplayedHost(); - else - { - source.Reply(_("\002{0}\002 isn't registered."), mask); - return; - } - } - - if (na) - mask = na->GetNick(); - } - - for (unsigned i = 0; i < ci->GetAccessCount(); ++i) - { - ChanServ::ChanAccess *a = ci->GetAccess(i); - - if ((na && na->GetAccount() == a->GetAccount()) || mask.equals_ci(a->Mask())) - { - if ((!highest || *a >= *highest) && !access.founder && !source.HasPriv("chanserv/access/modify")) - { - source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to lower the access of \002{1}\002."), ci->GetName(), a->Mask()); - return; - } - - a->Delete(); - break; - } - } - - unsigned access_max = Config->GetModule("chanserv")->Get("accessmax", "1024"); - if (access_max && ci->GetDeepAccessCount() >= access_max) - { - source.Reply(_("Sorry, you can only have %d access entries on a channel, including access entries from other channels."), access_max); - return; - } - - XOPChanAccess *acc = anope_dynamic_static_cast(xopchanaccess.Create()); - if (na) - acc->SetObj(na->GetAccount()); - else if (targ_ci) - acc->SetObj(targ_ci); - acc->SetChannel(ci); - acc->SetMask(mask); - acc->SetCreator(source.GetNick()); - acc->SetType(source.command.upper()); - acc->SetLastSeen(0); - acc->SetCreated(Anope::CurTime); - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to add " << mask; - - Event::OnAccessAdd(&Event::AccessAdd::OnAccessAdd, ci, source, acc); - source.Reply(_("\002%s\002 added to %s %s list."), acc->Mask(), ci->GetName().c_str(), source.command.c_str()); - } - - void DoDel(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - NickServ::Account *nc = source.nc; - Anope::string mask = params.size() > 2 ? params[2] : ""; - - if (mask.empty()) - { - this->OnSyntaxError(source, "DEL"); - return; - } - - if (Anope::ReadOnly) - { - source.Reply(_("Sorry, channel %s list modification is temporarily disabled."), source.command.c_str()); - return; - } - - if (!ci->GetAccessCount()) - { - source.Reply(_("%s %s list is empty."), ci->GetName().c_str(), source.command.c_str()); - return; - } - - ChanServ::AccessGroup access = source.AccessFor(ci); - ChanServ::ChanAccess *highest = access.Highest(); - bool override = false; - - if (!isdigit(mask[0]) && mask.find_first_of("#!*@") == Anope::string::npos && !NickServ::FindNick(mask)) - { - User *targ = User::Find(mask, true); - if (targ != NULL) - mask = "*!*@" + targ->GetDisplayedHost(); - else - { - source.Reply(_("\002{0}\002 isn't registered."), mask); - return; - } - } - - std::vector::iterator cmd_it = std::find(order.begin(), order.end(), source.command.upper()), - access_it = highest ? std::find(order.begin(), order.end(), XOPChanAccess::DetermineLevel(highest)) : order.end(); - - if (!mask.equals_ci(nc->GetDisplay()) && !access.founder && (!access.HasPriv("ACCESS_CHANGE") || cmd_it <= access_it)) - { - if (source.HasPriv("chanserv/access/modify")) - override = true; - else - { - source.Reply(_("Access denied. You do not have the \002{0}\002 privilege on \002{1}\002."), "ACCESS_CHANGE", ci->GetName()); - return; - } - } - - /* Special case: is it a number/list? Only do search if it isn't. */ - if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) - { - unsigned int deleted = 0; - Anope::string nicks; - - NumberList(mask, true, - [&](unsigned int number) - { - if (!number || number > ci->GetAccessCount()) - return; - - ChanServ::ChanAccess *caccess = ci->GetAccess(number - 1); - - if (caccess->GetSerializableType()->GetName() != "XOPChanAccess" || source.command.upper() != caccess->AccessSerialize()) - return; - - ++deleted; - if (!nicks.empty()) - nicks += ", " + caccess->Mask(); - else - nicks = caccess->Mask(); - - Event::OnAccessDel(&Event::AccessDel::OnAccessDel, ci, source, caccess); - delete caccess; - }, - [&]() - { - if (!deleted) - source.Reply(_("No matching entries on %s %s list."), ci->GetName().c_str(), source.command.c_str()); - else - { - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << nicks; - - if (deleted == 1) - source.Reply(_("Deleted one entry from %s %s list."), ci->GetName().c_str(), source.command.c_str()); - else - source.Reply(_("Deleted %d entries from %s %s list."), deleted, ci->GetName().c_str(), source.command.c_str()); - } - }); - } - else - { - for (unsigned i = 0; i < ci->GetAccessCount(); ++i) - { - ChanServ::ChanAccess *a = ci->GetAccess(i); - - if (a->GetSerializableType()->GetName() != "XOPChanAccess" || source.command.upper() != a->AccessSerialize()) - continue; - - if (a->Mask().equals_ci(mask)) - { - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << a->Mask(); - - source.Reply(_("\002%s\002 deleted from %s %s list."), a->Mask().c_str(), ci->GetName().c_str(), source.command.c_str()); - - Event::OnAccessDel(&Event::AccessDel::OnAccessDel, ci, source, a); - delete a; - - return; - } - } - - source.Reply(_("\002%s\002 not found on %s %s list."), mask.c_str(), ci->GetName().c_str(), source.command.c_str()); - } - } - - void DoList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) - { - - const Anope::string &nick = params.size() > 2 ? params[2] : ""; - - ChanServ::AccessGroup access = source.AccessFor(ci); - - if (!access.HasPriv("ACCESS_LIST") && !source.HasPriv("chanserv/access/list")) - { - source.Reply(_("Access denied. You do not have the \002{0}\002 privilege on \002{1}\002."), "ACCESS_LIST", ci->GetName()); - return; - } - - if (!ci->GetAccessCount()) - { - source.Reply(_("%s %s list is empty."), ci->GetName().c_str(), source.command.c_str()); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Mask")); - - if (!nick.empty() && nick.find_first_not_of("1234567890,-") == Anope::string::npos) - { - NumberList(nick, false, - [&](unsigned int number) - { - if (!number || number > ci->GetAccessCount()) - return; - - ChanServ::ChanAccess *a = ci->GetAccess(number - 1); - - if (a->GetSerializableType()->GetName() != "XOPChanAccess" || source.command.upper() != a->AccessSerialize()) - return; - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(number); - entry["Mask"] = a->Mask(); - list.AddEntry(entry); - }, - []{}); - } - else - { - for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i) - { - ChanServ::ChanAccess *a = ci->GetAccess(i); - - if (a->GetSerializableType()->GetName() != "XOPChanAccess" || source.command.upper() != a->AccessSerialize()) - continue; - if (!nick.empty() && !Anope::Match(a->Mask(), nick)) - continue; - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(i + 1); - entry["Mask"] = a->Mask(); - list.AddEntry(entry); - } - } - - if (list.IsEmpty()) - source.Reply(_("No matching entries on %s access list."), ci->GetName().c_str()); - else - { - std::vector replies; - list.Process(replies); - - source.Reply(_("%s list for %s"), source.command.c_str(), ci->GetName().c_str()); - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - } - - void DoClear(CommandSource &source, ChanServ::Channel *ci) - { - if (Anope::ReadOnly) - { - source.Reply(_("Sorry, channel %s list modification is temporarily disabled."), source.command.c_str()); - return; - } - - if (!ci->GetAccessCount()) - { - source.Reply(_("%s %s list is empty."), ci->GetName().c_str(), source.command.c_str()); - return; - } - - if (!source.AccessFor(ci).HasPriv("FOUNDER") && !source.HasPriv("chanserv/access/modify")) - { - source.Reply(_("Access denied. You do not have the \002{0}\002 privilege on \002{1}\002."), "FOUNDER", ci->GetName()); - return; - } - - bool override = !source.AccessFor(ci).HasPriv("FOUNDER"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clear the access list"; - - for (unsigned i = ci->GetAccessCount(); i > 0; --i) - { - ChanServ::ChanAccess *access = ci->GetAccess(i - 1); - - if (access->GetSerializableType()->GetName() != "XOPChanAccess" || source.command.upper() != access->AccessSerialize()) - continue; - - delete access; - } - - Event::OnAccessClear(&Event::AccessClear::OnAccessClear, ci, source); - - source.Reply(_("Channel %s %s list has been cleared."), ci->GetName().c_str(), source.command.c_str()); - } - - public: - CommandCSXOP(Module *modname) : Command(modname, "chanserv/xop", 2, 4) - { - this->SetSyntax(_("\037channel\037 ADD \037mask\037")); - this->SetSyntax(_("\037channel\037 DEL {\037mask\037 | \037entry-num\037 | \037list\037}")); - this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]")); - this->SetSyntax(_("\037channel\037 CLEAR")); - } - - const Anope::string GetDesc(CommandSource &source) const override - { - return Anope::printf(Language::Translate(source.GetAccount(), _("Modify the list of %s users")), source.command.upper().c_str()); - } - - void Execute(CommandSource &source, const std::vector ¶ms) - { - ChanServ::Channel *ci = ChanServ::Find(params[0]); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), params[0]); - return; - } - - const Anope::string &cmd = params[1]; - - if (cmd.equals_ci("ADD")) - return this->DoAdd(source, ci, params); - else if (cmd.equals_ci("DEL")) - return this->DoDel(source, ci, params); - else if (cmd.equals_ci("LIST")) - return this->DoList(source, ci, params); - else if (cmd.equals_ci("CLEAR")) - return this->DoClear(source, ci); - else - this->OnSyntaxError(source, ""); - } - - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (subcommand.equals_ci("ADD")) - { - source.Reply(_("The \002{0} ADD\002 command adds \037mask\037 to the {0} list of \037channel\037." - " If you have the privilege to use this command, you may use it if you have more privileges than it grants."), - source.command); - - source.Reply(_("Use of this command requires the \002{0}\002 privilege on \037channel\037.\n" - "Example:\n" - " {1} #anope ADD Adam" - " Adds \"Adam\" to the access list of \"#anope\" with the privilege set {1}"), - "ACCESS_CHANGE", source.command); - } - else if (subcommand.equals_ci("DEL")) - source.Reply(_("The \002{0} DEL\002 command removes the given \037mask\037 from the {0} list of \037channel\037." - " If a list of entry numbers is given, those entries are deleted.\n" - "\n" - "Use of this command requires the \002{1}\002 privilege on \037channel\037.\n" - "Example:\n" - " {0} #anope DEL Cronus" - " Removes \"Cronus\" from the {0} list of \"#anope\""), - source.command, "ACCESS_CHANGE"); - else if (subcommand.equals_ci("LIST")) - source.Reply(_("The \002{0} LIST\002 command displays the {0} list of \037channel\037." - " If a wildcard mask is given, only those entries matching the mask are displayed." - " If a list of entry numbers is given, only those entries are shown.\n" - "\n" - "Use of this command requires the \002{1}\002 privilege on \037channel\037.\n" - "\n" - "Example:" - " {0} #anope LIST 2-5,7-9\n" - " List entries numbered 2 through 5 and 7 through 9 on the {0} of \"#anope\""), - source.command, "ACCESS_LIST"); - else if (subcommand.equals_ci("CLEAR")) - source.Reply(_("The \002{0} CLEAR\002 command clears the {0} list of \037channel\037." - "\n" - "Use of this command requires the \002{1}\002 privilege on \037channel\037."), - source.command, "FOUNDER"); - - else - { - source.Reply(_("Maintains the \002{0} list\002 for a channel." - " Users who match an access entry on the {0} list receive the following privileges:\n" - "\n"), - source.command); - - Anope::string buf; - for (unsigned i = 0; i < permissions[source.command].size(); ++i) - buf += "," + permissions[source.command][i]; - - source.Reply(buf); - - CommandInfo *help = source.service->FindCommand("generic/help"); - if (help) - { - source.Reply(_("\n" - "The \002ADD\002 command adds \037mask\037 to the {0} list.\n" - "Use of this command requires the \002{change}\002 privilege on \037channel\037.\n" - "\002{msg}{service} {help} {command} ADD\002 for more information.\n" - "\n" - "The \002DEL\002 command removes \037mask\037 from the {0} list.\n" - "Use of this command requires the \002{change}\002 privilege on \037channel\037.\n" - "\002{msg}{service} {help} {command} DEL\002 for more information.\n" - "\n" - "The \002LIST\002 command shows the {0} list for \037channel\037.\n" - "Use of this commands requires the \002{list}\002 privilege on \037channel\037.\n" - "\002{msg}{service} {help} {command} LIST\002 for more information.\n" - "\n" - "The \002CLEAR\002 command clears the {0} list." - "Use of this command requires the \002{founder}\002 privilege on \037channel\037.\n" - "\002{msg}{service} {help} {command} CLEAR\002 for more information.)"), - source.command, "change"_kw = "ACCESS_CHANGE", "list"_kw = "ACCESS_LIST", "help"_kw = help->cname); - - Anope::string access_cmd, flags_cmd; - ServiceBot *access_bi, *flags_bi; - Command::FindCommandFromService("chanserv/access", access_bi, access_cmd); - Command::FindCommandFromService("chanserv/flags", flags_bi, access_cmd); - if (!access_cmd.empty() || !flags_cmd.empty()) - { - source.Reply(_("\n" - "Alternative methods of modifying channel access lists are available:\n")); - if (!access_cmd.empty()) - source.Reply(_("See \002{0}{1} {2} {3}\002 for more information about the access list."), Config->StrictPrivmsg, access_bi->nick, help->cname, access_cmd); - if (!flags_cmd.empty()) - source.Reply(_("See \002{0}{1} {2} {3}\002 for more information about the flags system."), Config->StrictPrivmsg, flags_bi->nick, help->cname, flags_cmd); - } - } - } - return true; - } -}; - -class CSXOP : public Module -{ - CommandCSXOP commandcsxop; - XOPChanAccessType xopaccesstype; - - public: - CSXOP(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandcsxop(this) - , xopaccesstype(this) - { - this->SetPermanent(true); - - } - - void OnReload(Configuration::Conf *conf) override - { - order.clear(); - permissions.clear(); - - for (int i = 0; i < conf->CountBlock("privilege"); ++i) - { - Configuration::Block *block = conf->GetBlock("privilege", i); - const Anope::string &pname = block->Get("name"); - - ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(pname) : nullptr; - if (p == NULL) - continue; - - const Anope::string &xop = block->Get("xop"); - if (pname.empty() || xop.empty()) - continue; - - permissions[xop].push_back(pname); - } - - for (int i = 0; i < conf->CountBlock("command"); ++i) - { - Configuration::Block *block = conf->GetBlock("command", i); - const Anope::string &cname = block->Get("name"), - &cserv = block->Get("command"); - if (cname.empty() || cserv != "chanserv/xop") - continue; - - order.push_back(cname); - } - } -}; - -template<> void ModuleInfo(ModuleDef *def) -{ - def->Depends("chanserv"); -} - -MODULE_INIT(CSXOP) diff --git a/modules/commands/gl_global.cpp b/modules/commands/gl_global.cpp deleted file mode 100644 index d6106b2be..000000000 --- a/modules/commands/gl_global.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* Global core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/global.h" - -class CommandGLGlobal : public Command -{ - public: - CommandGLGlobal(Module *creator) : Command(creator, "global/global", 1, 1) - { - this->SetDesc(_("Send a message to all users")); - this->SetSyntax(_("\037message\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &msg = params[0]; - - if (!Global::service) - { - source.Reply("No global reference, is global loaded?"); - return; - } - - Log(LOG_ADMIN, source, this); - Global::service->SendGlobal(NULL, source.GetNick(), msg); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Allows Services Operators to send a message to all users on the network." - " The message will be sent from \002{0}\002."), source.service->nick); - return true; - } -}; - -class GLGlobal : public Module -{ - CommandGLGlobal commandglglobal; - - public: - GLGlobal(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandglglobal(this) - { - - } -}; - -MODULE_INIT(GLGlobal) diff --git a/modules/commands/greet.cpp b/modules/commands/greet.cpp deleted file mode 100644 index ebc8c1612..000000000 --- a/modules/commands/greet.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_info.h" -#include "modules/bs_info.h" -#include "modules/ns_set.h" - -class CommandBSSetGreet : public Command -{ - public: - CommandBSSetGreet(Module *creator, const Anope::string &sname = "botserv/set/greet") : Command(creator, sname, 2, 2) - { - this->SetDesc(_("Enable greet messages")); - this->SetSyntax(_("\037channel\037 {\037ON|OFF\037}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &value = params[1]; - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - ChanServ::Channel *ci = ChanServ::Find(chan); - if (ci == NULL) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (!source.HasPriv("botserv/administration") && !source.AccessFor(ci).HasPriv("SET")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); - return; - } - - if (value.equals_ci("ON")) - { - bool override = !source.AccessFor(ci).HasPriv("SET"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enable greets"; - - ci->SetS("BS_GREET", true); - source.Reply(_("Greet mode for \002{0}\002 is now \002on\002."), ci->GetName()); - } - else if (value.equals_ci("OFF")) - { - bool override = !source.AccessFor(ci).HasPriv("SET"); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable greets"; - - ci->UnsetS("BS_GREET"); - source.Reply(_("Greet mode for \002{0}\002 is now \002off\002."), ci->GetName()); - } - else - this->OnSyntaxError(source, source.command); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables \002greet\002 mode on \037channel\037." - " When \002greet\002 mode is enabled, the assigned service bot will display the greet messages of users joining the channel, if they have the \002{0}\002 privilege."), - "GREET"); - return true; - } -}; - -class CommandNSSetGreet : public Command -{ - public: - CommandNSSetGreet(Module *creator, const Anope::string &sname = "nickserv/set/greet", size_t min = 0) : Command(creator, sname, min, min + 1) - { - this->SetDesc(_("Associate a greet message with your nickname")); - this->SetSyntax(_("\037message\037")); - } - - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - NickServ::Account *nc = na->GetAccount(); - - EventReturn MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (!param.empty()) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the greet of " << nc->GetDisplay(); - nc->SetS("greet", param); - source.Reply(_("Greet message for \002{0}\002 changed to \002{1}\002."), nc->GetDisplay(), param); - } - else - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to unset the greet of " << nc->GetDisplay(); - nc->UnsetS("greet"); - source.Reply(_("Greet message for \002{0}\002 unset."), nc->GetDisplay()); - } - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), params.size() > 0 ? params[0] : ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Sets your greet to \037message\037. Your greet is displayed when you enter channels that have the greet option enabled if you have the \002{0}\002 privilege on the channel."), - "GREET"); - return true; - } -}; - -class CommandNSSASetGreet : public CommandNSSetGreet -{ - public: - CommandNSSASetGreet(Module *creator) : CommandNSSetGreet(creator, "nickserv/saset/greet", 1) - { - this->ClearSyntax(); - this->SetSyntax(_("\037user\037 \037message\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params.size() > 1 ? params[1] : ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Sets the greet for \037user\037 to \037message\037. Greet messages are displayed when users enter channels that have the greet option enabiled if they have the \002{0}\002 privilege on the channel."), - "GREET"); - return true; - } -}; - -class Greet : public Module - , public EventHook - , public EventHook - , public EventHook -{ - /* channel setting for whether or not greet should be shown */ - Serialize::Field bs_greet; - /* user greets */ - Serialize::Field ns_greet; - - CommandBSSetGreet commandbssetgreet; - CommandNSSetGreet commandnssetgreet; - CommandNSSASetGreet commandnssasetgreet; - - public: - Greet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , bs_greet(this, ChanServ::channel, "BS_GREET") - , ns_greet(this, NickServ::account, "greet") - , commandbssetgreet(this) - , commandnssetgreet(this) - , commandnssasetgreet(this) - { - } - - void OnJoinChannel(User *user, Channel *c) override - { - /* Only display the greet if the main uplink we're connected - * to has synced, or we'll get greet-floods when the net - * recovers from a netsplit. -GD - */ - if (!c->ci || !c->ci->GetBot() || !user->server->IsSynced() || !user->Account()) - return; - - Anope::string greet = ns_greet.Get(user->Account()); - if (bs_greet.HasExt(c->ci) && !greet.empty() && c->FindUser(c->ci->GetBot()) && c->ci->AccessFor(user).HasPriv("GREET")) - { - IRCD->SendPrivmsg(c->ci->GetBot(), c->name, "[%s] %s", user->Account()->GetDisplay().c_str(), greet.c_str()); - c->ci->GetBot()->lastmsg = Anope::CurTime; - } - } - - void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override - { - Anope::string greet = ns_greet.Get(na->GetAccount()); - if (!greet.empty()) - info[_("Greet")] = greet; - } - - void OnServiceBot(CommandSource &source, ServiceBot *bi, ChanServ::Channel *ci, InfoFormatter &info) override - { - if (bs_greet.HasExt(ci)) - info.AddOption(_("Greet")); - } -}; - -MODULE_INIT(Greet) diff --git a/modules/commands/help.cpp b/modules/commands/help.cpp deleted file mode 100644 index debb25837..000000000 --- a/modules/commands/help.cpp +++ /dev/null @@ -1,197 +0,0 @@ -/* Core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/help.h" - -class CommandHelp : public Command -{ - static CommandGroup *FindGroup(const Anope::string &name) - { - for (unsigned i = 0; i < Config->CommandGroups.size(); ++i) - { - CommandGroup &gr = Config->CommandGroups[i]; - if (gr.name == name) - return &gr; - } - - return NULL; - } - - EventHandlers &onhelp; - - public: - CommandHelp(Module *creator, EventHandlers &event) : Command(creator, "generic/help", 0), onhelp(event) - { - this->SetDesc(_("Displays this list and give information about commands")); - this->AllowUnregistered(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - EventReturn MOD_RESULT = this->onhelp(&Event::Help::OnPreHelp, source, params); - if (MOD_RESULT == EVENT_STOP) - return; - - Anope::string source_command = source.command; - const ServiceBot *bi = source.service; - const CommandInfo::map &map = source.c ? Config->Fantasy : bi->commands; - bool hide_privileged_commands = Config->GetBlock("options")->Get("hideprivilegedcommands"), - hide_registered_commands = Config->GetBlock("options")->Get("hideregisteredcommands"); - - if (params.empty() || params[0].equals_ci("ALL")) - { - bool all = !params.empty() && params[0].equals_ci("ALL"); - typedef std::map > GroupInfo; - GroupInfo groups; - - if (all) - source.Reply(_("All available commands for \002{0}\002:"), source.service->nick); - - for (CommandInfo::map::const_iterator it = map.begin(), it_end = map.end(); it != it_end; ++it) - { - const Anope::string &c_name = it->first; - const CommandInfo &info = it->second; - - if (info.hide) - continue; - - // Smaller command exists - Anope::string cmd; - spacesepstream(c_name).GetToken(cmd, 0); - if (cmd != it->first && map.count(cmd)) - continue; - - ServiceReference c("Command", info.name); - if (!c) - continue; - - if (hide_registered_commands && !c->AllowUnregistered() && !source.GetAccount()) - continue; - - if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission)) - continue; - - if (!info.group.empty() && !all) - { - CommandGroup *gr = FindGroup(info.group); - if (gr != NULL) - { - groups[gr].push_back(c_name); - continue; - } - } - - source.command = c_name; - c->OnServHelp(source); - - } - - for (GroupInfo::iterator it = groups.begin(), it_end = groups.end(); it != it_end; ++it) - { - CommandGroup *gr = it->first; - - source.Reply(" "); - source.Reply("{0}", gr->description); - - Anope::string buf; - for (std::list::iterator it2 = it->second.begin(), it2_end = it->second.end(); it2 != it2_end; ++it2) - { - const Anope::string &c_name = *it2; - buf += ", " + c_name; - } - if (buf.length() > 2) - source.Reply(" {0}", buf.substr(2)); - } - if (!groups.empty()) - { - source.Reply(" "); - source.Reply(_("Use the \002{0} ALL\002 command to list all commands and their descriptions."), source_command); - } - } - else - { - bool helped = false; - for (unsigned max = params.size(); max > 0; --max) - { - Anope::string full_command; - for (unsigned i = 0; i < max; ++i) - full_command += " " + params[i]; - full_command.erase(full_command.begin()); - - CommandInfo::map::const_iterator it = map.find(full_command); - if (it == map.end()) - continue; - - const CommandInfo &info = it->second; - - ServiceReference c("Command", info.name); - if (!c) - continue; - - if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission)) - continue; - - // Allow unregistered users to see help for commands that they explicitly request help for - - const Anope::string &subcommand = params.size() > max ? params[max] : ""; - source.command = it->first; - - c->SendSyntax(source); - if (!c->OnHelp(source, subcommand)) - continue; - - helped = true; - - /* Inform the user what permission is required to use the command */ - if (!info.permission.empty()) - { - source.Reply(" "); - source.Reply(_("Access to this command requires the permission \002{0}\002 to be present in your opertype."), info.permission); - } - if (!c->AllowUnregistered() && !source.nc) - { - if (info.permission.empty()) - source.Reply(" "); - source.Reply( _("You need to be identified to use this command.")); - } - /* User doesn't have the proper permission to use this command */ - else if (!info.permission.empty() && !source.HasCommand(info.permission)) - { - source.Reply(_("You cannot use this command.")); - } - - break; - } - - if (helped == false) - source.Reply(_("No help available for \002{0}\002."), params[0]); - } - - this->onhelp(&Event::Help::OnPostHelp, source, params); - } -}; - -class Help : public Module -{ - CommandHelp commandhelp; - EventHandlers onhelp; - - public: - Help(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandhelp(this, onhelp) - , onhelp(this) - { - - } -}; - -MODULE_INIT(Help) diff --git a/modules/commands/hs_del.cpp b/modules/commands/hs_del.cpp deleted file mode 100644 index b45c10a61..000000000 --- a/modules/commands/hs_del.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* HostServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/hs_del.h" - -class CommandHSDel : public Command -{ - EventHandlers &OnDeleteVhost; - - public: - CommandHSDel(Module *creator, EventHandlers &onDeleteVhost) : Command(creator, "hostserv/del", 1, 1), OnDeleteVhost(onDeleteVhost) - { - this->SetDesc(_("Delete the vhost of another user")); - this->SetSyntax(_("\037user\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - const Anope::string &nick = params[0]; - NickServ::Nick *na = NickServ::FindNick(nick); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - Log(LOG_ADMIN, source, this) << "for user " << na->GetNick(); - this->OnDeleteVhost(&Event::DeleteVhost::OnDeleteVhost, na); - na->RemoveVhost(); - source.Reply(_("Vhost for \002{0}\002 has been removed."), na->GetNick()); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Removes the vhost of \037user\037.")); - return true; - } -}; - -class CommandHSDelAll : public Command -{ - EventHandlers &ondeletevhost; - - public: - CommandHSDelAll(Module *creator, EventHandlers &event) : Command(creator, "hostserv/delall", 1, 1), ondeletevhost(event) - { - this->SetDesc(_("Delete the vhost for all nicks in a group")); - this->SetSyntax(_("\037group\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - const Anope::string &nick = params[0]; - NickServ::Nick *na = NickServ::FindNick(nick); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - this->ondeletevhost(&Event::DeleteVhost::OnDeleteVhost, na); - - NickServ::Account *nc = na->GetAccount(); - for (NickServ::Nick *na2 : nc->GetRefs(NickServ::nick)) - na2->RemoveVhost(); - Log(LOG_ADMIN, source, this) << "for all nicks in group " << nc->GetDisplay(); - source.Reply(_("Vhosts for group \002{0}\002 have been removed."), nc->GetDisplay()); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Removes the vhost of all nicks in the group \037group\037.")); - return true; - } -}; - -class HSDel : public Module -{ - CommandHSDel commandhsdel; - CommandHSDelAll commandhsdelall; - EventHandlers ondeletevhost; - - public: - HSDel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandhsdel(this, ondeletevhost) - , commandhsdelall(this, ondeletevhost) - , ondeletevhost(this) - { - if (!IRCD || !IRCD->CanSetVHost) - throw ModuleException("Your IRCd does not support vhosts"); - } -}; - -MODULE_INIT(HSDel) diff --git a/modules/commands/hs_group.cpp b/modules/commands/hs_group.cpp deleted file mode 100644 index c3971fdea..000000000 --- a/modules/commands/hs_group.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* HostServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_group.h" - -class CommandHSGroup : public Command -{ - bool setting; - - public: - void Sync(NickServ::Nick *na) - { - if (setting) - return; - - if (!na || !na->HasVhost()) - return; - - setting = true; - for (NickServ::Nick *nick : na->GetAccount()->GetRefs(NickServ::nick)) - { - nick->SetVhost(na->GetVhostIdent(), na->GetVhostHost(), na->GetVhostCreator()); - Event::OnSetVhost(&Event::SetVhost::OnSetVhost, nick); - } - setting = false; - } - - CommandHSGroup(Module *creator) : Command(creator, "hostserv/group", 0, 0), setting(false) - { - this->SetDesc(_("Syncs the vhost for all nicks in a group")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(source.GetNick()); - if (!na || na->GetAccount() != source.GetAccount()) - { - source.Reply(_("Access denied.")); - return; - } - - if (!na->HasVhost()) - { - source.Reply(_("There is no vhost assigned to this nickname.")); - return; - } - - this->Sync(na); - if (!na->GetVhostIdent().empty()) - source.Reply(_("All vhosts in the group \002{0}\002 have been set to \002{1}\002@\002{2}\002."), source.nc->GetDisplay(), na->GetVhostIdent(), na->GetVhostHost()); - else - source.Reply(_("All vhosts in the group \002{0}\002 have been set to \002{1}\002."), source.nc->GetDisplay(), na->GetVhostHost()); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets the vhost of all nicks in your group to the vhost of your current nick.")); - return true; - } -}; - -class HSGroup : public Module - , public EventHook - , public EventHook -{ - CommandHSGroup commandhsgroup; - bool syncongroup; - bool synconset; - - public: - HSGroup(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandhsgroup(this) - { - if (!IRCD || !IRCD->CanSetVHost) - throw ModuleException("Your IRCd does not support vhosts"); - } - - void OnSetVhost(NickServ::Nick *na) override - { - if (!synconset) - return; - - commandhsgroup.Sync(na); - } - - void OnNickGroup(User *u, NickServ::Nick *na) override - { - if (!syncongroup) - return; - - commandhsgroup.Sync(na); - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - syncongroup = block->Get("syncongroup"); - synconset = block->Get("synconset"); - } -}; - -MODULE_INIT(HSGroup) diff --git a/modules/commands/hs_list.cpp b/modules/commands/hs_list.cpp deleted file mode 100644 index 738f9f1bd..000000000 --- a/modules/commands/hs_list.cpp +++ /dev/null @@ -1,159 +0,0 @@ -/* HostServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandHSList : public Command -{ - public: - CommandHSList(Module *creator) : Command(creator, "hostserv/list", 0, 1) - { - this->SetDesc(_("Displays one or more vhost entries")); - this->SetSyntax(_("[\037key\037|\037#X-Y\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &key = !params.empty() ? params[0] : ""; - int from = 0, to = 0, counter = 1; - - /** - * Do a check for a range here, then in the next loop - * we'll only display what has been requested.. - **/ - if (!key.empty() && key[0] == '#') - { - size_t tmp = key.find('-'); - if (tmp == Anope::string::npos || tmp == key.length() || tmp == 1) - { - source.Reply(_("Incorrect range specified. The correct syntax is \002#\037from\037-\037to\037\002.")); - return; - } - for (unsigned i = 1, end = key.length(); i < end; ++i) - { - if (!isdigit(key[i]) && i != tmp) - { - source.Reply(_("Incorrect range specified. The correct syntax is \002#\037from\037-\037to\037\002.")); - return; - } - try - { - from = convertTo(key.substr(1, tmp - 1)); - to = convertTo(key.substr(tmp + 1)); - } - catch (const ConvertException &) { } - } - } - - unsigned display_counter = 0, listmax = Config->GetModule(this->owner)->Get("listmax", "50"); - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Nick")).AddColumn(_("Vhost")).AddColumn(_("Creator")).AddColumn(_("Created")); - - for (NickServ::Nick *na : NickServ::service->GetNickList()) - { - if (!na->HasVhost()) - continue; - - if (!key.empty() && key[0] != '#') - { - if ((Anope::Match(na->GetNick(), key) || Anope::Match(na->GetVhostHost(), key)) && display_counter < listmax) - { - ++display_counter; - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(display_counter); - entry["Nick"] = na->GetNick(); - if (!na->GetVhostIdent().empty()) - entry["Vhost"] = na->GetVhostIdent() + "@" + na->GetVhostHost(); - else - entry["Vhost"] = na->GetVhostHost(); - entry["Creator"] = na->GetVhostCreator(); - entry["Created"] = Anope::strftime(na->GetVhostCreated(), NULL, true); - list.AddEntry(entry); - } - } - else - { - /** - * List the host if its in the display range, and not more - * than NSListMax records have been displayed... - **/ - if (((counter >= from && counter <= to) || (!from && !to)) && display_counter < listmax) - { - ++display_counter; - ListFormatter::ListEntry entry; - entry["Number"] = stringify(display_counter); - entry["Nick"] = na->GetNick(); - if (!na->GetVhostIdent().empty()) - entry["Vhost"] = na->GetVhostIdent() + "@" + na->GetVhostHost(); - else - entry["Vhost"] = na->GetVhostHost(); - entry["Creator"] = na->GetVhostCreator(); - entry["Created"] = Anope::strftime(na->GetVhostCreated(), NULL, true); - list.AddEntry(entry); - } - } - ++counter; - } - - if (!display_counter) - { - source.Reply(_("No records to display.")); - return; - } - - if (!key.empty()) - source.Reply(_("Displayed records matching key \002{0}\002 (count: \002{1}\002)."), key, display_counter); - else - { - if (from) - source.Reply(_("Displayed records from \002{0}\002 to \002{1}\002."), from, to); - else - source.Reply(_("Displayed all records (count: \002{0}\002)."), display_counter); - } - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Lists all vhosts. If \037key\037 is specified, only entries whose nick or vhost match the pattern given in \037key\037 are displayed." - "If a \037#X-Y\037 style is used, only entries between the range of \002X\002 and \002Y\002 will be displayed.\n" - "\n" - "Examples:\n" - " {0} Rob*\n" - " Lists all entries with the nick or vhost beginning with \"Rob\"\n" - "\n" - " {0} #1-3\n" - " Lists the first three entries."), - source.command); - return true; - } -}; - -class HSList : public Module -{ - CommandHSList commandhslist; - - public: - HSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandhslist(this) - { - if (!IRCD || !IRCD->CanSetVHost) - throw ModuleException("Your IRCd does not support vhosts"); - } -}; - -MODULE_INIT(HSList) diff --git a/modules/commands/hs_off.cpp b/modules/commands/hs_off.cpp deleted file mode 100644 index 8f3c7e3da..000000000 --- a/modules/commands/hs_off.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* HostServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandHSOff : public Command -{ - public: - CommandHSOff(Module *creator) : Command(creator, "hostserv/off", 0, 0) - { - this->SetDesc(_("Deactivates your assigned vhost")); - this->RequireUser(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - User *u = source.GetUser(); - NickServ::Nick *na = NickServ::FindNick(u->nick); - - if (!na || !na->HasVhost() || na->GetAccount() != source.GetAccount()) - { - source.Reply(_("There is no vhost assigned to this nickname.")); - return; - } - - u->vhost.clear(); - IRCD->SendVhostDel(u); - Log(LOG_COMMAND, source, this) << "to disable their vhost"; - source.Reply(_("Your vhost was removed and the normal cloaking restored.")); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Deactivates your vhost.")); - return true; - } -}; - -class HSOff : public Module -{ - CommandHSOff commandhsoff; - - public: - HSOff(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandhsoff(this) - { - if (!IRCD || !IRCD->CanSetVHost) - throw ModuleException("Your IRCd does not support vhosts"); - } -}; - -MODULE_INIT(HSOff) diff --git a/modules/commands/hs_on.cpp b/modules/commands/hs_on.cpp deleted file mode 100644 index 409d56ac5..000000000 --- a/modules/commands/hs_on.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* HostServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandHSOn : public Command -{ - public: - CommandHSOn(Module *creator) : Command(creator, "hostserv/on", 0, 0) - { - this->SetDesc(_("Activates your assigned vhost")); - this->RequireUser(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (!IRCD->CanSetVHost) - return; // HostServ wouldn't even be loaded at this point - - User *u = source.GetUser(); - NickServ::Nick *na = NickServ::FindNick(u->nick); - - if (!na || !na->HasVhost() || na->GetAccount() != u->Account()) - { - source.Reply(_("There is no vhost assigned to this nickname.")); - return; - } - - if (!na->GetVhostIdent().empty()) - source.Reply(_("Your vhost of \002{0}\002@\002{1}\002 is now activated."), na->GetVhostIdent(), na->GetVhostHost()); - else - source.Reply(_("Your vhost of \002{0}\002 is now activated."), na->GetVhostHost()); - - Log(LOG_COMMAND, source, this) << "to enable their vhost of " << (!na->GetVhostIdent().empty() ? na->GetVhostIdent() + "@" : "") << na->GetVhostHost(); - IRCD->SendVhost(u, na->GetVhostIdent(), na->GetVhostHost()); - u->vhost = na->GetVhostHost(); - if (IRCD->CanSetVIdent && !na->GetVhostIdent().empty()) - u->SetVIdent(na->GetVhostIdent()); - u->UpdateHost(); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Activates your vhost.")); - return true; - } -}; - -class HSOn : public Module -{ - CommandHSOn commandhson; - - public: - HSOn(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandhson(this) - { - if (!IRCD || !IRCD->CanSetVHost) - throw ModuleException("Your IRCd does not support vhosts"); - } -}; - -MODULE_INIT(HSOn) diff --git a/modules/commands/hs_request.cpp b/modules/commands/hs_request.cpp deleted file mode 100644 index 8a064a84c..000000000 --- a/modules/commands/hs_request.cpp +++ /dev/null @@ -1,414 +0,0 @@ -/* - * - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - */ - -#include "module.h" -#include "modules/memoserv.h" - -static void req_send_memos(Module *me, CommandSource &source, const Anope::string &vIdent, const Anope::string &vHost); - -class HostRequest : public Serialize::Object -{ - public: - HostRequest(Serialize::TypeBase *type) : Serialize::Object(type) { } - HostRequest(Serialize::TypeBase *type, Serialize::ID id) : Serialize::Object(type, id) { } - - NickServ::Nick *GetNick(); - void SetNick(NickServ::Nick *na); - - Anope::string GetIdent(); - void SetIdent(const Anope::string &i); - - Anope::string GetHost(); - void SetHost(const Anope::string &h); - - time_t GetTime(); - void SetTime(const time_t &t); -}; - -class HostRequestType : public Serialize::Type -{ - public: - Serialize::ObjectField na; - Serialize::Field ident, host; - Serialize::Field time; - - HostRequestType(Module *me) : Serialize::Type(me, "HostRequest") - , na(this, "na", true) - , ident(this, "ident") - , host(this, "host") - , time(this, "time") - { - } -}; - -NickServ::Nick *HostRequest::GetNick() -{ - return Get(&HostRequestType::na); -} - -void HostRequest::SetNick(NickServ::Nick *na) -{ - Set(&HostRequestType::na, na); -} - -Anope::string HostRequest::GetIdent() -{ - return Get(&HostRequestType::ident); -} - -void HostRequest::SetIdent(const Anope::string &i) -{ - Set(&HostRequestType::ident, i); -} - -Anope::string HostRequest::GetHost() -{ - return Get(&HostRequestType::host); -} - -void HostRequest::SetHost(const Anope::string &h) -{ - Set(&HostRequestType::host, h); -} - -time_t HostRequest::GetTime() -{ - return Get(&HostRequestType::time); -} - -void HostRequest::SetTime(const time_t &t) -{ - Set(&HostRequestType::time, t); -} - -static Serialize::TypeReference hostrequest("HostRequest"); - -class CommandHSRequest : public Command -{ - public: - CommandHSRequest(Module *creator) : Command(creator, "hostserv/request", 1, 1) - { - this->SetDesc(_("Request a vHost for your nick")); - this->SetSyntax(_("vhost")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - User *u = source.GetUser(); - NickServ::Nick *na = NickServ::FindNick(source.GetNick()); - if (!na || na->GetAccount() != source.GetAccount()) - { - source.Reply(_("Access denied.")); //XXX with nonickownership this should be allowed. - return; - } - - if (source.GetAccount()->HasFieldS("UNCONFIRMED")) - { - source.Reply(_("You must confirm your account before you may request a vhost.")); - return; - } - - Anope::string rawhostmask = params[0]; - - Anope::string user, host; - size_t a = rawhostmask.find('@'); - - if (a == Anope::string::npos) - host = rawhostmask; - else - { - user = rawhostmask.substr(0, a); - host = rawhostmask.substr(a + 1); - } - - if (host.empty()) - { - this->OnSyntaxError(source, ""); - return; - } - - if (!user.empty()) - { - if (user.length() > Config->GetBlock("networkinfo")->Get("userlen")) - { - source.Reply(_("The username \002{0}\002 is too long, please use a username shorter than %d characters."), Config->GetBlock("networkinfo")->Get("userlen")); - return; - } - - if (!IRCD->CanSetVIdent) - { - source.Reply(_("Vhosts may not contain a username.")); - return; - } - - if (!IRCD->IsIdentValid(user)) - { - source.Reply(_("The requested username is not valid.")); - return; - } - } - - if (host.length() > Config->GetBlock("networkinfo")->Get("hostlen")) - { - source.Reply(_("The requested vhost is too long, please use a hostname no longer than {0} characters."), Config->GetBlock("networkinfo")->Get("hostlen")); - return; - } - - if (!IRCD->IsHostValid(host)) - { - source.Reply(_("The requested hostname is not valid.")); - return; - } - - time_t send_delay = Config->GetModule("memoserv")->Get("senddelay"); - if (Config->GetModule(this->owner)->Get("memooper") && send_delay > 0 && u && u->lastmemosend + send_delay > Anope::CurTime) - { - source.Reply(_("Please wait %d seconds before requesting a new vHost."), send_delay); - u->lastmemosend = Anope::CurTime; - return; - } - - HostRequest *req = hostrequest.Create(); - req->SetNick(na); - req->SetIdent(user); - req->SetHost(host); - req->SetTime(Anope::CurTime); - - source.Reply(_("Your vhost has been requested.")); - req_send_memos(owner, source, user, host); - Log(LOG_COMMAND, source, this) << "to request new vhost " << (!user.empty() ? user + "@" : "") << host; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Request the given \036vhost\037 to be actived for your nick by the network administrators. Please be patient while your request is being considered.")); - return true; - } -}; - -class CommandHSActivate : public Command -{ - public: - CommandHSActivate(Module *creator) : Command(creator, "hostserv/activate", 1, 1) - { - this->SetDesc(_("Approve the requested vHost of a user")); - this->SetSyntax(_("\037user\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - const Anope::string &nick = params[0]; - NickServ::Nick *na = NickServ::FindNick(nick); - - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - HostRequest *req = na->GetExt("hostrequest"); - if (!req) - { - source.Reply(_("\002{0}\002 does not have a pending vhost request."), na->GetNick()); - return; - } - - na->SetVhost(req->GetIdent(), req->GetHost(), source.GetNick(), req->GetTime()); - Event::OnSetVhost(&Event::SetVhost::OnSetVhost, na); - - if (Config->GetModule(this->owner)->Get("memouser") && MemoServ::service) - MemoServ::service->Send(source.service->nick, na->GetNick(), _("[auto memo] Your requested vHost has been approved."), true); - - source.Reply(_("Vhost for \002{0}\002 has been activated."), na->GetNick()); - Log(LOG_COMMAND, source, this) << "for " << na->GetNick() << " for vhost " << (!req->GetIdent().empty() ? req->GetIdent() + "@" : "") << req->GetHost(); - req->Delete(); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Activate the requested vhost for the given user.")); - if (Config->GetModule(this->owner)->Get("memouser")) - source.Reply(_("A memo informing the user will also be sent.")); - - return true; - } -}; - -class CommandHSReject : public Command -{ - public: - CommandHSReject(Module *creator) : Command(creator, "hostserv/reject", 1, 2) - { - this->SetDesc(_("Reject the requested vHost of a user")); - this->SetSyntax(_("\037user\037 [\037reason\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - const Anope::string &nick = params[0]; - const Anope::string &reason = params.size() > 1 ? params[1] : ""; - - NickServ::Nick *na = NickServ::FindNick(nick); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - HostRequest *req = na->GetExt("hostrequest"); - if (!req) - { - source.Reply(_("\002{0}\002 does not have a pending vhost request."), na->GetNick()); - return; - } - - req->Delete(); - - if (Config->GetModule(this->owner)->Get("memouser") && MemoServ::service) - { - Anope::string message; - if (!reason.empty()) - message = Anope::printf(_("[auto memo] Your requested vHost has been rejected. Reason: %s"), reason.c_str()); - else - message = _("[auto memo] Your requested vHost has been rejected."); - - MemoServ::service->Send(source.service->nick, nick, Language::Translate(source.GetAccount(), message.c_str()), true); - } - - source.Reply(_("Vhost for \002{0}\002 has been rejected."), na->GetNick()); - Log(LOG_COMMAND, source, this) << "to reject vhost for " << nick << " (" << (!reason.empty() ? reason : "no reason") << ")"; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Reject the requested vhost for the given user.")); - if (Config->GetModule(this->owner)->Get("memouser")) - source.Reply(_("A memo informing the user will also be sent, which includes the reason for the rejection if supplied.")); - - return true; - } -}; - -class CommandHSWaiting : public Command -{ - public: - CommandHSWaiting(Module *creator) : Command(creator, "hostserv/waiting", 0, 0) - { - this->SetDesc(_("Retrieves the vhost requests")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - unsigned counter = 0; - unsigned display_counter = 0, listmax = Config->GetModule(this->owner)->Get("listmax"); - ListFormatter list(source.GetAccount()); - - list.AddColumn(_("Number")).AddColumn(_("Nick")).AddColumn(_("Vhost")).AddColumn(_("Created")); - - for (HostRequest *hr : Serialize::GetObjects(hostrequest)) - { - if (!listmax || display_counter < listmax) - { - ++display_counter; - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(display_counter); - entry["Nick"] = hr->GetNick()->GetNick(); - if (!hr->GetIdent().empty()) - entry["Vhost"] = hr->GetIdent() + "@" + hr->GetHost(); - else - entry["Vhost"] = hr->GetHost(); - entry["Created"] = Anope::strftime(hr->GetTime(), NULL, true); - list.AddEntry(entry); - } - ++counter; - } - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("Displayed \002{0}\002 records (\002{1}\002 total)."), display_counter, counter); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Lists the vhost requests.")); - - return true; - } -}; - -class HSRequest : public Module -{ - CommandHSRequest commandhsrequest; - CommandHSActivate commandhsactive; - CommandHSReject commandhsreject; - CommandHSWaiting commandhswaiting; - - HostRequestType hrtype; - - public: - HSRequest(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandhsrequest(this) - , commandhsactive(this) - , commandhsreject(this) - , commandhswaiting(this) - , hrtype(this) - { - if (!IRCD || !IRCD->CanSetVHost) - throw ModuleException("Your IRCd does not support vhosts"); - } -}; - -static void req_send_memos(Module *me, CommandSource &source, const Anope::string &vIdent, const Anope::string &vHost) -{ - Anope::string host; - std::list >::iterator it, it_end; - - if (!vIdent.empty()) - host = vIdent + "@" + vHost; - else - host = vHost; - - if (Config->GetModule(me)->Get("memooper") && MemoServ::service) - for (Oper *o : Serialize::GetObjects(operblock)) - { - NickServ::Nick *na = NickServ::FindNick(o->GetName()); - if (!na) - continue; - - Anope::string message = Anope::printf(_("[auto memo] vHost \002%s\002 has been requested by %s."), host.c_str(), source.GetNick().c_str()); - - MemoServ::service->Send(source.service->nick, na->GetNick(), message, true); - } -} - -MODULE_INIT(HSRequest) diff --git a/modules/commands/hs_set.cpp b/modules/commands/hs_set.cpp deleted file mode 100644 index a86afead4..000000000 --- a/modules/commands/hs_set.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/* HostServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandHSSet : public Command -{ - public: - CommandHSSet(Module *creator) : Command(creator, "hostserv/set", 2, 2) - { - this->SetDesc(_("Set the vhost of another user")); - this->SetSyntax(_("\037user\037 \037hostmask\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - const Anope::string &nick = params[0]; - - NickServ::Nick *na = NickServ::FindNick(nick); - if (na == NULL) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - Anope::string rawhostmask = params[1]; - - Anope::string user, host; - size_t a = rawhostmask.find('@'); - - if (a == Anope::string::npos) - host = rawhostmask; - else - { - user = rawhostmask.substr(0, a); - host = rawhostmask.substr(a + 1); - } - - if (host.empty()) - { - this->OnSyntaxError(source, ""); - return; - } - - if (!user.empty()) - { - if (!IRCD->CanSetVIdent) - { - source.Reply(_("Vhosts may not contain a username.")); - return; - } - - if (!IRCD->IsIdentValid(user)) - { - source.Reply(_("The requested username is not valid.")); - return; - } - } - - if (host.length() > Config->GetBlock("networkinfo")->Get("hostlen")) - { - source.Reply(_("The requested vhost is too long, please use a hostname no longer than {0} characters."), Config->GetBlock("networkinfo")->Get("hostlen")); - return; - } - - if (!IRCD->IsHostValid(host)) - { - source.Reply(_("The requested hostname is not valid.")); - return; - } - - Log(LOG_ADMIN, source, this) << "to set the vhost of " << na->GetNick() << " to " << (!user.empty() ? user + "@" : "") << host; - - na->SetVhost(user, host, source.GetNick()); - Event::OnSetVhost(&Event::SetVhost::OnSetVhost, na); - if (!user.empty()) - source.Reply(_("Vhost for \002{0}\002 set to \002{0}\002@\002{1}\002."), nick, user, host); - else - source.Reply(_("Vhost for \002{0}\002 set to \002{0}\002."), nick, host); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets the vhost of the given \037user\037 to the given \037hostmask\037.")); - return true; - } -}; - -class CommandHSSetAll : public Command -{ - void Sync(NickServ::Nick *na) - { - if (!na || !na->HasVhost()) - return; - - for (NickServ::Nick *nick : na->GetAccount()->GetRefs(NickServ::nick)) - nick->SetVhost(na->GetVhostIdent(), na->GetVhostHost(), na->GetVhostCreator()); - } - - public: - CommandHSSetAll(Module *creator) : Command(creator, "hostserv/setall", 2, 2) - { - this->SetDesc(_("Set the vhost for all nicks in a group")); - this->SetSyntax(_("\037user\037 \037hostmask\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - const Anope::string &nick = params[0]; - NickServ::Nick *na = NickServ::FindNick(nick); - if (na == NULL) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - Anope::string rawhostmask = params[1]; - - Anope::string user, host; - size_t a = rawhostmask.find('@'); - - if (a == Anope::string::npos) - host = rawhostmask; - else - { - user = rawhostmask.substr(0, a); - host = rawhostmask.substr(a + 1); - } - - if (host.empty()) - { - this->OnSyntaxError(source, ""); - return; - } - - if (!user.empty()) - { - if (!IRCD->CanSetVIdent) - { - source.Reply(_("Vhosts may not contain a username.")); - return; - } - - if (!IRCD->IsIdentValid(user)) - { - source.Reply(_("The requested username is not valid.")); - return; - } - } - - if (host.length() > Config->GetBlock("networkinfo")->Get("hostlen")) - { - source.Reply(_("The requested vhost is too long, please use a hostname no longer than {0} characters."), Config->GetBlock("networkinfo")->Get("hostlen")); - return; - } - - if (!IRCD->IsHostValid(host)) - { - source.Reply(_("The requested hostname is not valid.")); - return; - } - - Log(LOG_ADMIN, source, this) << "to set the vhost of " << na->GetNick() << " to " << (!user.empty() ? user + "@" : "") << host; - - na->SetVhost(user, host, source.GetNick()); - this->Sync(na); - Event::OnSetVhost(&Event::SetVhost::OnSetVhost, na); - if (!user.empty()) - source.Reply(_("Vhost for group \002{0}\002 set to \002{1}\002@\002{2}\002."), nick.c_str(), user.c_str(), host.c_str()); - else - source.Reply(_("host for group \002{0}\002 set to \002{1}\002."), nick.c_str(), host.c_str()); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets the vhost for all nicknames in the group of \037user\037.")); - return true; - } -}; - -class HSSet : public Module -{ - CommandHSSet commandhsset; - CommandHSSetAll commandhssetall; - - public: - HSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandhsset(this) - , commandhssetall(this) - { - if (!IRCD || !IRCD->CanSetVHost) - throw ModuleException("Your IRCd does not support vhosts"); - } -}; - -MODULE_INIT(HSSet) diff --git a/modules/commands/ms_cancel.cpp b/modules/commands/ms_cancel.cpp deleted file mode 100644 index e3da82581..000000000 --- a/modules/commands/ms_cancel.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/memoserv.h" - -class CommandMSCancel : public Command -{ - public: - CommandMSCancel(Module *creator) : Command(creator, "memoserv/cancel", 1, 1) - { - this->SetDesc(_("Cancel the last memo you sent")); - this->SetSyntax(_("{\037user\037 | \037channel\037}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - const Anope::string &nname = params[0]; - - bool ischan, isregistered; - MemoServ::MemoInfo *mi = MemoServ::service->GetMemoInfo(name, ischan, isregistered, false); - - if (!isregistered) - { - if (ischan) - source.Reply(_("Channel \002{0}\002 isn't registered."), nname); - else - source.Reply(_("\002{0}\002 isn't registered."), nname); - } - else - { - ChanServ::Channel *ci = NULL; - NickServ::Nick *na = NULL; - if (ischan) - ci = ChanServ::Find(nname); - else - na = NickServ::FindNick(nname); - if (mi) - { - auto memos = mi->GetMemos(); - for (int i = memos.size() - 1; i >= 0; --i) - { - MemoServ::Memo *m = memos[i]; - if (m->GetUnread() && source.nc->GetDisplay().equals_ci(m->GetSender())) - { - if (MemoServ::Event::OnMemoDel) - MemoServ::Event::OnMemoDel(&MemoServ::Event::MemoDel::OnMemoDel, ischan ? ci->GetName() : na->GetAccount()->GetDisplay(), mi, m); - mi->Del(i); - source.Reply(_("Your last memo to \002{0}\002 has been cancelled."), nname); - return; - } - } - } - - source.Reply(_("No memo to \002{0}\002 was cancelable."), nname); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Cancels the last memo you sent to \037user\037, provided it has not yet been read.")); - return true; - } -}; - -class MSCancel : public Module -{ - CommandMSCancel commandmscancel; - - public: - MSCancel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandmscancel(this) - { - } -}; - -MODULE_INIT(MSCancel) diff --git a/modules/commands/ms_check.cpp b/modules/commands/ms_check.cpp deleted file mode 100644 index 75a8ee3d0..000000000 --- a/modules/commands/ms_check.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandMSCheck : public Command -{ - public: - CommandMSCheck(Module *creator) : Command(creator, "memoserv/check", 1, 1) - { - this->SetDesc(_("Checks if last memo to a user was read")); - this->SetSyntax(_("\037user\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &recipient = params[0]; - - bool found = false; - - NickServ::Nick *na = NickServ::FindNick(recipient); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), recipient); - return; - } - - MemoServ::MemoInfo *mi = na->GetAccount()->GetMemos(); - - if (mi) - /* find the last memo */ - for (unsigned i = mi->GetMemos().size(); i > 0; --i) - { - MemoServ::Memo *m = mi->GetMemo(i - 1); - NickServ::Nick *na2 = NickServ::FindNick(m->GetSender()); - - if (na2 != NULL && na2->GetAccount() == source.GetAccount()) - { - found = true; /* Yes, we've found the memo */ - - if (m->GetUnread()) - source.Reply(_("The last memo you sent to \002{0}\002 (sent on \002{1}\002) has not yet been read."), na->GetNick(), Anope::strftime(m->GetTime(), source.GetAccount())); - else - source.Reply(_("The last memo you sent to \002{0}\002 (sent on \002{1}\002) has been read."), na->GetNick(), Anope::strftime(m->GetTime(), source.GetAccount())); - break; - } - } - - if (!found) - source.Reply(_("\002{0}\002 doesn't have a memo from you."), na->GetNick()); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Checks whether the last memo you sent to \037user\037 has been read or not.")); - return true; - } -}; - -class MSCheck : public Module -{ - CommandMSCheck commandmscheck; - - public: - MSCheck(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandmscheck(this) - { - - } -}; - -MODULE_INIT(MSCheck) diff --git a/modules/commands/ms_del.cpp b/modules/commands/ms_del.cpp deleted file mode 100644 index 79af11adc..000000000 --- a/modules/commands/ms_del.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/memoserv.h" - -class CommandMSDel : public Command -{ - public: - CommandMSDel(Module *creator) : Command(creator, "memoserv/del", 0, 2) - { - this->SetDesc(_("Delete a memo or memos")); - this->SetSyntax(_("[\037channel\037] {\037num\037 | \037list\037 | LAST | ALL}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - MemoServ::MemoInfo *mi; - ChanServ::Channel *ci = NULL; - Anope::string numstr = !params.empty() ? params[0] : "", chan; - - if (!numstr.empty() && numstr[0] == '#') - { - chan = numstr; - numstr = params.size() > 1 ? params[1] : ""; - - ci = ChanServ::Find(chan); - if (!ci) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (!source.AccessFor(ci).HasPriv("MEMO")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); - return; - } - - mi = ci->GetMemos(); - } - else - mi = source.nc->GetMemos(); - - if (numstr.empty() || (!isdigit(numstr[0]) && !numstr.equals_ci("ALL") && !numstr.equals_ci("LAST"))) - { - this->OnSyntaxError(source, numstr); - return; - } - - if (!mi || mi->GetMemos().empty()) - { - if (!chan.empty()) - source.Reply(_("\002{0}\002 has no memos."), chan); - else - source.Reply(_("You have no memos.")); - return; - } - - auto memos = mi->GetMemos(); - - if (isdigit(numstr[0])) - { - NumberList(numstr, true, - [&](unsigned int number) - { - if (!number || number > memos.size()) - return; - - if (MemoServ::Event::OnMemoDel) - MemoServ::Event::OnMemoDel(&MemoServ::Event::MemoDel::OnMemoDel, ci ? ci->GetName() : source.nc->GetDisplay(), mi, mi->GetMemo(number - 1)); - - mi->Del(number - 1); - source.Reply(_("Memo \002{0}\002 has been deleted."), number); - }, - [](){}); - } - else if (numstr.equals_ci("LAST")) - { - /* Delete last memo. */ - if (MemoServ::Event::OnMemoDel) - MemoServ::Event::OnMemoDel(&MemoServ::Event::MemoDel::OnMemoDel, ci ? ci->GetName() : source.nc->GetDisplay(), mi, mi->GetMemo(memos.size() - 1)); - mi->Del(memos.size() - 1); - source.Reply(_("Memo \002{0}\002 has been deleted."), memos.size() + 1); - } - else - { - /* Delete all memos. */ - std::for_each(memos.begin(), memos.end(), - [&](MemoServ::Memo *m) - { - if (MemoServ::Event::OnMemoDel) - MemoServ::Event::OnMemoDel(&MemoServ::Event::MemoDel::OnMemoDel, ci ? ci->GetName() : source.nc->GetDisplay(), mi, m); - delete m; - }); - if (!chan.empty()) - source.Reply(_("All memos for channel \002{0}\002 have been deleted."), chan); - else - source.Reply(_("All of your memos have been deleted.")); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Deletes the specified memo or memos." - " You can supply multiple memo numbers or ranges of numbers instead of a single number, as in the second example below." - " If \002LAST\002 is given, the last memo will be deleted." - " If \002ALL\002 is given, deletes all of your memos.\n" - "\n" - "Examples:\n" - "\n" - " {0} 1\n" - " Deletes your first memo.\n" - "\n" - " {0} 2-5,7-9\n" - " Deletes memos numbered 2 through 5 and 7 through 9."), - source.command); - return true; - } -}; - -class MSDel : public Module -{ - CommandMSDel commandmsdel; - - public: - MSDel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandmsdel(this) - { - - } -}; - -MODULE_INIT(MSDel) diff --git a/modules/commands/ms_ignore.cpp b/modules/commands/ms_ignore.cpp deleted file mode 100644 index c454444ea..000000000 --- a/modules/commands/ms_ignore.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandMSIgnore : public Command -{ - public: - CommandMSIgnore(Module *creator) : Command(creator, "memoserv/ignore", 1, 3) - { - this->SetDesc(_("Manage the memo ignore list")); - this->SetSyntax(_("[\037channel\037] ADD \037entry\037")); - this->SetSyntax(_("[\037channel\037] DEL \037entry\037")); - this->SetSyntax(_("[\037channel\037] LIST")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - if (!MemoServ::service) - return; - - Anope::string channel = params[0]; - Anope::string command = (params.size() > 1 ? params[1] : ""); - Anope::string param = (params.size() > 2 ? params[2] : ""); - - if (channel[0] != '#') - { - param = command; - command = channel; - channel = source.GetNick(); - } - - bool ischan, isregistered; - MemoServ::MemoInfo *mi = MemoServ::service->GetMemoInfo(channel, ischan, isregistered, true); - ChanServ::Channel *ci = ChanServ::Find(channel); - if (!isregistered) - { - if (ischan) - source.Reply(_("Channel \002{0}\002 isn't registered."), channel); - else - source.Reply(_("\002{0}\002 isn't registered."), channel); - return; - } - - if (ischan && !source.AccessFor(ci).HasPriv("MEMO")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); - return; - } - - auto ignores = mi->GetIgnores(); - - if (command.equals_ci("ADD") && !param.empty()) - { - if (ignores.size() >= Config->GetModule(this->owner)->Get("max", "32")) - { - source.Reply(_("Sorry, the memo ignore list for \002{0}\002 is full."), channel); - return; - } - - for (MemoServ::Ignore *ign : ignores) - if (ign->GetMask().equals_ci(param)) - { - source.Reply(_("\002{0}\002 is already on your memo ignore list."), param); - return; - } - - MemoServ::Ignore *ign = MemoServ::service->CreateIgnore(); - ign->SetMemoInfo(mi); - ign->SetMask(param); - - source.Reply(_("\002{0}\002 has been added to your memo ignore list."), param); - } - else if (command.equals_ci("DEL") && !param.empty()) - { - for (MemoServ::Ignore *ign : ignores) - if (ign->GetMask().equals_ci(param)) - { - delete ign; - source.Reply(_("\002{0}\002 has been removed from your memo ignore list."), param); - return; - } - - source.Reply(_("\002{0}\002 is not on your memo ignore list."), param); - } - else if (command.equals_ci("LIST")) - { - if (ignores.empty()) - { - source.Reply(_("Your memo ignore list is empty.")); - return; - } - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Mask")); - for (MemoServ::Ignore *ign : ignores) - { - ListFormatter::ListEntry entry; - entry["Mask"] = ign->GetMask(); - list.AddEntry(entry); - } - - source.Reply(_("Memo ignore list:")); - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Allows you to ignore users from memoing you or a channel." - " If someone on the memo ignore list tries to memo you or a channel, they will not be told that you have them ignored.")); - return true; - } -}; - -class MSIgnore : public Module -{ - CommandMSIgnore commandmsignore; - - public: - MSIgnore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandmsignore(this) - { - } -}; - -MODULE_INIT(MSIgnore) diff --git a/modules/commands/ms_info.cpp b/modules/commands/ms_info.cpp deleted file mode 100644 index d9bee1cb5..000000000 --- a/modules/commands/ms_info.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandMSInfo : public Command -{ - public: - CommandMSInfo(Module *creator) : Command(creator, "memoserv/info", 0, 1) - { - this->SetDesc(_("Displays information about your memos")); - this->SetSyntax(_("[\037user\037 | \037channel\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - NickServ::Account *nc = source.nc; - MemoServ::MemoInfo *mi; - NickServ::Nick *na = NULL; - ChanServ::Channel *ci = NULL; - const Anope::string &nname = !params.empty() ? params[0] : ""; - bool hardmax; - - if (!nname.empty() && nname[0] != '#' && source.HasPriv("memoserv/info")) - { - na = NickServ::FindNick(nname); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), nname); - return; - } - mi = na->GetAccount()->GetMemos(); - hardmax = na->GetAccount()->HasFieldS("MEMO_HARDMAX"); - } - else if (!nname.empty() && nname[0] == '#') - { - ci = ChanServ::Find(nname); - if (!ci) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), nname); - return; - } - - if (!source.AccessFor(ci).HasPriv("MEMO")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); - return; - } - - mi = ci->GetMemos(); - hardmax = ci->HasFieldS("MEMO_HARDMAX"); - } - else if (!nname.empty()) /* It's not a chan and we aren't an oper */ - { - source.Reply(_("Access denied. You do not have the correct operator privilege to see the memo info of \002{0}\002."), nname); - return; - } - else - { - mi = nc->GetMemos(); - hardmax = nc->HasFieldS("MEMO_HARDMAX"); - } - if (!mi) - return; - - auto memos = mi->GetMemos(); - - if (!nname.empty() && (ci || na->GetAccount() != nc)) - { - if (memos.empty()) - source.Reply(_("%s currently has no memos."), nname.c_str()); - else if (memos.size() == 1) - { - if (mi->GetMemo(0)->GetUnread()) - source.Reply(_("\002{0}\002 currently has \0021\002 memo, and it has not yet been read."), nname); - else - source.Reply(_("\002{0}\002 currently has \0021\002 memo."), nname); - } - else - { - unsigned count = 0, i, end; - for (i = 0, end = memos.size(); i < end; ++i) - if (mi->GetMemo(i)->GetUnread()) - ++count; - if (count == memos.size()) - source.Reply(_("\002{0}\002 currently has \002{1]\002 memos; all of them are unread."), nname, count); - else if (!count) - source.Reply(_("\002{0}\002 currently has \002{1}\002 memos."), nname, memos.size()); - else if (count == 1) - source.Reply(_("\002{0}\002 currently has \002{1}\002 memos, of which \0021\002 is unread."), nname, memos.size()); - else - source.Reply(_("\002{0}\002 currently has \002{1]\002 memos, of which \002{2}\002 are unread."), nname, memos.size(), count); - } - if (mi->GetMemoMax() >= 0) - { - if (hardmax) - source.Reply(_("The memo limit of \002{0}\002 is \002{1}\002, and may not be changed."), nname, mi->GetMemoMax()); - else - source.Reply(_("The memo limit of \002{0}\002 is \002{1}\002."), nname, mi->GetMemoMax()); - } - else - source.Reply(_("\002{0}\002 has no memo limit."), nname); - - if (na) - { - if (na->GetAccount()->HasFieldS("MEMO_RECEIVE") && na->GetAccount()->HasFieldS("MEMO_SIGNON")) - source.Reply(_("\002{0}\002 is notified of new memos at logon and when they arrive."), nname); - else if (na->GetAccount()->HasFieldS("MEMO_RECEIVE")) - source.Reply(_("\002{0}\002 is notified when new memos arrive."), nname); - else if (na->GetAccount()->HasFieldS("MEMO_SIGNON")) - source.Reply(_("\002{0}\002 is notified of news memos at logon."), nname); - else - source.Reply(_("\002{0}\002 is not notified of new memos."), nname); - } - } - else - { - if (memos.empty()) - source.Reply(_("You currently have no memos.")); - else if (memos.size() == 1) - { - if (mi->GetMemo(0)->GetUnread()) - source.Reply(_("You currently have \0021\002 memo, and it has not yet been read.")); - else - source.Reply(_("You currently have \0021\002 memo.")); - } - else - { - unsigned count = 0, i, end; - for (i = 0, end = memos.size(); i < end; ++i) - if (mi->GetMemo(i)->GetUnread()) - ++count; - if (count == memos.size()) - source.Reply(_("You currently have \002{0}\002 memos; all of them are unread."), count); - else if (!count) - source.Reply(_("You currently have \002{0}\002 memos."), memos.size()); - else if (count == 1) - source.Reply(_("You currently have \002{0}\002 memos, of which \0021\002 is unread."), memos.size()); - else - source.Reply(_("You currently have \002{0}\002 memos, of which \002{1}\002 are unread."), memos.size(), count); - } - - if (!mi->GetMemoMax()) - { - if (!source.IsServicesOper() && hardmax) - source.Reply(_("Your memo limit is \0020\002; you will not receive any new memos. You cannot change this limit.")); - else - source.Reply(_("Your memo limit is \0020\002; you will not receive any new memos.")); - } - else if (mi->GetMemoMax() > 0) - { - if (!source.IsServicesOper() && hardmax) - source.Reply(_("Your memo limit is \002{0}\002, and may not be changed."), mi->GetMemoMax()); - else - source.Reply(_("Your memo limit is \002{0}\002."), mi->GetMemoMax()); - } - else - source.Reply(_("You have no limit on the number of memos you may keep.")); - - if (nc->HasFieldS("MEMO_RECEIVE") && nc->HasFieldS("MEMO_SIGNON")) - source.Reply(_("You will be notified of new memos at logon and when they arrive.")); - else if (nc->HasFieldS("MEMO_RECEIVE")) - source.Reply(_("You will be notified when new memos arrive.")); - else if (nc->HasFieldS("MEMO_SIGNON")) - source.Reply(_("You will be notified of new memos at logon.")); - else - source.Reply(_("You will not be notified of new memos.")); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Without a parameter, displays information on the number of memos you have, how many of them are unread, and how many total memos you can receive." - " With a parameter, displays the same information for the given \037user\037 or \037channel\037, if you have the appropriate privilege.\n" - "\n" - "Use of this command on a channel requires the \002{0}\002 privilege on \037channel\037."), - source.command); - - return true; - } -}; - -class MSInfo : public Module -{ - CommandMSInfo commandmsinfo; - - public: - MSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandmsinfo(this) - { - - } -}; - -MODULE_INIT(MSInfo) diff --git a/modules/commands/ms_list.cpp b/modules/commands/ms_list.cpp deleted file mode 100644 index 8215315b0..000000000 --- a/modules/commands/ms_list.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandMSList : public Command -{ - public: - CommandMSList(Module *creator) : Command(creator, "memoserv/list", 0, 2) - { - this->SetDesc(_("List your memos")); - this->SetSyntax(_("[\037channel\037] [\037list\037 | NEW]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - - Anope::string param = !params.empty() ? params[0] : "", chan; - ChanServ::Channel *ci = NULL; - MemoServ::MemoInfo *mi; - - if (!param.empty() && param[0] == '#') - { - chan = param; - param = params.size() > 1 ? params[1] : ""; - - ci = ChanServ::Find(chan); - if (!ci) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (!source.AccessFor(ci).HasPriv("MEMO")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); - return; - } - - mi = ci->GetMemos(); - } - else - mi = source.nc->GetMemos(); - - if (!param.empty() && !isdigit(param[0]) && !param.equals_ci("NEW")) - { - this->OnSyntaxError(source, param); - return; - } - - if (!mi) - return; - - auto memos = mi->GetMemos(); - - if (!memos.size()) - { - if (!chan.empty()) - source.Reply(_("\002{0}\002 has no memos."), chan); - else - source.Reply(_("You have no memos.")); - return; - } - - ListFormatter list(source.GetAccount()); - - list.AddColumn(_("Number")).AddColumn(_("Sender")).AddColumn(_("Date/Time")); - - if (!param.empty() && isdigit(param[0])) - { - NumberList(param, false, - [&](unsigned int number) - { - if (!number || number > memos.size()) - return; - - MemoServ::Memo *m = mi->GetMemo(number - 1); - - ListFormatter::ListEntry entry; - entry["Number"] = (m->GetUnread() ? "* " : " ") + stringify(number); - entry["Sender"] = m->GetSender(); - entry["Date/Time"] = Anope::strftime(m->GetTime(), source.GetAccount()); - list.AddEntry(entry); - }, - []{}); - } - else - { - if (!param.empty()) - { - unsigned i, end; - for (i = 0, end = memos.size(); i < end; ++i) - if (mi->GetMemo(i)->GetUnread()) - break; - if (i == end) - { - if (!chan.empty()) - source.Reply(_("\002{0}\002 has no new memos."), chan); - else - source.Reply(_("You have no new memos.")); - return; - } - } - - for (unsigned i = 0, end = memos.size(); i < end; ++i) - { - if (!param.empty() && !mi->GetMemo(i)->GetUnread()) - continue; - - MemoServ::Memo *m = mi->GetMemo(i); - - ListFormatter::ListEntry entry; - entry["Number"] = (m->GetUnread() ? "* " : " ") + stringify(i + 1); - entry["Sender"] = m->GetSender(); - entry["Date/Time"] = Anope::strftime(m->GetTime(), source.GetAccount()); - list.AddEntry(entry); - } - } - - std::vector replies; - list.Process(replies); - - source.Reply(_("Memos for \002{0}\002:"), ci ? ci->GetName().c_str() : source.GetNick().c_str()); - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Lists the memos you currently have." - " With \002NEW\002, lists only new (unread) memos." - " Unread memos are marked with a \"*\" to the left of the memo number." - " You can also specify a list of numbers, as in the example below:\n" - "\n" - "Example:\n" - "\n" - " {0} 2-5,7-9\n" - " Lists memos numbered 2 through 5 and 7 through 9.")); - return true; - } -}; - -class MSList : public Module -{ - CommandMSList commandmslist; - - public: - MSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandmslist(this) - { - - } -}; - -MODULE_INIT(MSList) diff --git a/modules/commands/ms_read.cpp b/modules/commands/ms_read.cpp deleted file mode 100644 index 687843124..000000000 --- a/modules/commands/ms_read.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/memoserv.h" - -static void rsend_notify(CommandSource &source, MemoServ::MemoInfo *mi, MemoServ::Memo *m, const Anope::string &targ) -{ - /* Only send receipt if memos are allowed */ - if (MemoServ::service && !Anope::ReadOnly) - { - /* Get nick alias for sender */ - NickServ::Nick *na = NickServ::FindNick(m->GetSender()); - - if (!na) - return; - - /* Get nick core for sender */ - NickServ::Account *nc = na->GetAccount(); - - if (!nc) - return; - - /* Text of the memo varies if the recipient was a - nick or channel */ - Anope::string text = Anope::printf(Language::Translate(na->GetAccount(), _("\002[auto-memo]\002 The memo you sent to \002%s\002 has been viewed.")), targ.c_str()); - - /* Send notification */ - MemoServ::service->Send(source.GetNick(), m->GetSender(), text, true); - - /* Notify recipient of the memo that a notification has - been sent to the sender */ - source.Reply(_("A notification memo has been sent to \002{0}\002 informing him/her you have read his/her memo."), nc->GetDisplay()); - } - - /* Remove receipt flag from the original memo */ - m->SetReceipt(false); -} - -class CommandMSRead : public Command -{ - static void DoRead(CommandSource &source, MemoServ::MemoInfo *mi, ChanServ::Channel *ci, unsigned index) - { - MemoServ::Memo *m = mi->GetMemo(index); - if (!m) - return; - - if (ci) - source.Reply(_("Memo \002{0}\002 from \002{1}\002 (\002{2}\002)."), index + 1, m->GetSender(), Anope::strftime(m->GetTime(), source.GetAccount())); - else - source.Reply(_("Memo \002{0}\002 from \002{1}\002 (\002{2}\002)."), index + 1, m->GetSender(), Anope::strftime(m->GetTime(), source.GetAccount())); - - ServiceBot *bi; - Anope::string cmd; - if (Command::FindCommandFromService("memoserv/del", bi, cmd)) - { - if (ci) - source.Reply(_("To delete, use \002{0}{1} {2} {3} {4}\002"), Config->StrictPrivmsg, bi->nick, cmd, ci->GetName(), index + 1); - else - source.Reply(_("To delete, use \002{0}{1} {2} {3}\002"), Config->StrictPrivmsg, bi->nick, cmd, index + 1); - } - - source.Reply(m->GetText()); - m->SetUnread(false); - - /* Check if a receipt notification was requested */ - if (m->GetReceipt()) - rsend_notify(source, mi, m, ci ? ci->GetName() : source.GetNick()); - } - - public: - CommandMSRead(Module *creator) : Command(creator, "memoserv/read", 1, 2) - { - this->SetDesc(_("Read a memo or memos")); - this->SetSyntax(_("[\037channel\037] {\037num\037 | \037list\037 | LAST | NEW}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - - MemoServ::MemoInfo *mi; - ChanServ::Channel *ci = NULL; - Anope::string numstr = params[0], chan; - - if (!numstr.empty() && numstr[0] == '#') - { - chan = numstr; - numstr = params.size() > 1 ? params[1] : ""; - - ci = ChanServ::Find(chan); - if (!ci) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (!source.AccessFor(ci).HasPriv("MEMO")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); - return; - } - - mi = ci->GetMemos(); - } - else - mi = source.nc->GetMemos(); - - if (numstr.empty() || (!numstr.equals_ci("LAST") && !numstr.equals_ci("NEW") && !numstr.is_number_only())) - { - this->OnSyntaxError(source, numstr); - return; - } - - if (!mi) - return; - - auto memos = mi->GetMemos(); - - if (memos.empty()) - { - if (!chan.empty()) - source.Reply(_("\002{0}\002 has no memos."), chan); - else - source.Reply(_("You have no memos.")); - return; - } - - int i, end; - - if (numstr.equals_ci("NEW")) - { - int readcount = 0; - for (i = 0, end = memos.size(); i < end; ++i) - if (mi->GetMemo(i)->GetUnread()) - { - DoRead(source, mi, ci, i); - ++readcount; - } - if (!readcount) - { - if (!chan.empty()) - source.Reply(_("\002{0}\002 has no new memos."), chan); - else - source.Reply(_("You have no new memos.")); - } - } - else if (numstr.equals_ci("LAST")) - { - for (i = 0, end = memos.size() - 1; i < end; ++i); - DoRead(source, mi, ci, i); - } - else /* number[s] */ - { - NumberList(numstr, false, - [&](unsigned int number) - { - if (!number || number > memos.size()) - return; - - DoRead(source, mi, ci, number - 1); - }, - []{}); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sends you the text of the memos specified." - " If LAST is given, sends you the memo you most recently received." - " If NEW is given, sends you all of your new memos." - " Otherwise, sends you memo number \037num\037." - " You can also give a list of numbers, as in the example:\n" - "\n" - "Example:\n" - "\n" - " {0} 2-5,7-9\n" - " Displays memos numbered 2 through 5 and 7 through 9."), - source.command); - return true; - } -}; - -class MSRead : public Module -{ - CommandMSRead commandmsread; - - public: - MSRead(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandmsread(this) - { - - } -}; - -MODULE_INIT(MSRead) diff --git a/modules/commands/ms_rsend.cpp b/modules/commands/ms_rsend.cpp deleted file mode 100644 index 16f6105fa..000000000 --- a/modules/commands/ms_rsend.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/memoserv.h" - -class CommandMSRSend : public Command -{ - public: - CommandMSRSend(Module *creator) : Command(creator, "memoserv/rsend", 2, 2) - { - this->SetDesc(_("Sends a memo and requests a read receipt")); - this->SetSyntax(_("{\037nick\037 | \037channel\037} \037memo-text\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { -#if 0 - if (!MemoServ::service) - return; - - if (Anope::ReadOnly && !source.IsOper()) - { - source.Reply(_("Sorry, memo sending is temporarily disabled.")); - return; - } - - const Anope::string &nick = params[0]; - const Anope::string &text = params[1]; - const NickServ::Nick *na = NULL; - - /* prevent user from rsend to themselves */ - if ((na = NickServ::FindNick(nick)) && na->GetAccount() == source.GetAccount()) - { - source.Reply(_("You can not request a receipt when sending a memo to yourself.")); - return; - } - - if (Config->GetModule(this->owner)->Get("operonly") && !source.IsServicesOper()) - source.Reply(_("Access denied. This command is for operators only.")); - else - { - MemoServ::MemoServService::MemoResult result = MemoServ::service->Send(source.GetNick(), nick, text); - if (result == MemoServ::MemoServService::MEMO_INVALID_TARGET) - source.Reply(_("\002{0}\002 isn't registered."), nick); - else if (result == MemoServ::MemoServService::MEMO_TOO_FAST) - source.Reply(_("Please wait \002{0}\002 seconds before using the \002{1}\002 command again."), Config->GetModule("memoserv")->Get("senddelay"), source.command); - else if (result == MemoServ::MemoServService::MEMO_TARGET_FULL) - source.Reply(_("Sorry, \002{0}\002 currently has too many memos and cannot receive more."), nick); - else - { - source.Reply(_("Memo sent to \002{0}\002."), nick); - - bool ischan, isregistered; - MemoServ::MemoInfo *mi = MemoServ::service->GetMemoInfo(nick, ischan, isregistered, false); - if (mi == NULL) - throw CoreException("NULL mi in ms_rsend"); - MemoServ::Memo *m = (mi->memos->size() ? mi->GetMemo(mi->memos->size() - 1) : NULL); - if (m != NULL) - m->receipt = true; - } - } -#endif - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { -#if 0 - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Sends the named \037nick\037 or \037channel\037 a memo containing\n" - "\037memo-text\037. When sending to a nickname, the recipient will\n" - "receive a notice that he/she has a new memo. The target\n" - "nickname/channel must be registered.\n" - "Once the memo is read by its recipient, an automatic notification\n" - "memo will be sent to the sender informing him/her that the memo\n" - "has been read.")); - return true; -#endif - } -}; - -class MSRSend : public Module -{ - CommandMSRSend commandmsrsend; - - public: - MSRSend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandmsrsend(this) - { - if (!MemoServ::service) - throw ModuleException("No MemoServ!"); - throw ModuleException("XXX"); - } -}; - -MODULE_INIT(MSRSend) diff --git a/modules/commands/ms_send.cpp b/modules/commands/ms_send.cpp deleted file mode 100644 index 0d26b8684..000000000 --- a/modules/commands/ms_send.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/memoserv.h" - -class CommandMSSend : public Command -{ - public: - CommandMSSend(Module *creator) : Command(creator, "memoserv/send", 2, 2) - { - this->SetDesc(_("Send a memo to a nick or channel")); - this->SetSyntax(_("{\037user\037 | \037channel\037} \037memo-text\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (!MemoServ::service) - return; - - const Anope::string &nick = params[0]; - const Anope::string &text = params[1]; - - if (Anope::ReadOnly && !source.IsOper()) - { - source.Reply(_("Sorry, memo sending is temporarily disabled.")); - return; - } - - if (source.GetAccount()->HasFieldS("UNCONFIRMED")) - { - source.Reply(_("You must confirm your account before you may send a memo.")); - return; - } - - MemoServ::MemoServService::MemoResult result = MemoServ::service->Send(source.GetNick(), nick, text); - if (result == MemoServ::MemoServService::MEMO_SUCCESS) - { - source.Reply(_("Memo sent to \002%s\002."), nick.c_str()); - Log(LOG_COMMAND, source, this) << "to send a memo to " << nick; - } - else if (result == MemoServ::MemoServService::MEMO_INVALID_TARGET) - source.Reply(_("\002{0}\002 is not a registered unforbidden nick or channel."), nick); - else if (result == MemoServ::MemoServService::MEMO_TOO_FAST) - source.Reply(_("Please wait \002{0}\002 seconds before using the \002{1}\002 command again."), Config->GetModule("memoserv")->Get("senddelay"), source.command); - else if (result == MemoServ::MemoServService::MEMO_TARGET_FULL) - source.Reply(_("Sorry, \002{0}\002 currently has too many memos and cannot receive more."), nick); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sends the named \037user\037 or \037channel\037 a memo containing \037memo-text\037." - " The recipient will receive a notice that they have a new memo." - " The target user or channel must be registered.")); - return true; - } -}; - -class MSSend : public Module -{ - CommandMSSend commandmssend; - - public: - MSSend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandmssend(this) - { - - if (!MemoServ::service) - throw ModuleException("No MemoServ!"); - } -}; - -MODULE_INIT(MSSend) diff --git a/modules/commands/ms_sendall.cpp b/modules/commands/ms_sendall.cpp deleted file mode 100644 index 7c5365c4d..000000000 --- a/modules/commands/ms_sendall.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/memoserv.h" - -class CommandMSSendAll : public Command -{ - public: - CommandMSSendAll(Module *creator) : Command(creator, "memoserv/sendall", 1, 1) - { - this->SetDesc(_("Send a memo to all registered users")); - this->SetSyntax(_("\037memo-text\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (!MemoServ::service) - return; - - const Anope::string &text = params[0]; - - Log(LOG_ADMIN, source, this) << "to send " << text; - - for (NickServ::Account *nc : NickServ::service->GetAccountList()) - if (nc != source.nc) - MemoServ::service->Send(source.GetNick(), nc->GetDisplay(), text); - - source.Reply(_("A mass memo has been sent to all registered users.")); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sends all registered users a memo containing \037memo-text\037.")); - return true; - } -}; - -class MSSendAll : public Module -{ - CommandMSSendAll commandmssendall; - - public: - MSSendAll(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandmssendall(this) - { - if (!MemoServ::service) - throw ModuleException("No MemoServ!"); - } -}; - -MODULE_INIT(MSSendAll) diff --git a/modules/commands/ms_set.cpp b/modules/commands/ms_set.cpp deleted file mode 100644 index c924d0ea0..000000000 --- a/modules/commands/ms_set.cpp +++ /dev/null @@ -1,314 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandMSSet : public Command -{ - private: - void DoNotify(CommandSource &source, const std::vector ¶ms, MemoServ::MemoInfo *mi) - { - const Anope::string ¶m = params[1]; - NickServ::Account *nc = source.nc; - ServiceBot *MemoServ = Config->GetClient("MemoServ"); - - if (!MemoServ) - return; - - if (param.equals_ci("ON")) - { - nc->SetS("MEMO_SIGNON", true); - nc->SetS("MEMO_RECEIVE", true); - source.Reply(_("\002{0}\002 will now notify you of memos when you log on and when they are sent to you."), MemoServ->nick); - } - else if (param.equals_ci("LOGON")) - { - nc->SetS("MEMO_SIGNON", true); - nc->UnsetS("MEMO_RECEIVE"); - source.Reply(_("\002{0}\002 will now notify you of memos when you log on or unset /AWAY."), MemoServ->nick); - } - else if (param.equals_ci("NEW")) - { - nc->UnsetS("MEMO_SIGNON"); - nc->SetS("MEMO_RECEIVE", true); - source.Reply(_("\002{0}\002 will now notify you of memos when they are sent to you."), MemoServ->nick); - } - else if (param.equals_ci("MAIL")) - { - if (!nc->GetEmail().empty()) - { - nc->SetS("MEMO_MAIL", true); - source.Reply(_("You will now be informed about new memos via email.")); - } - else - source.Reply(_("There's no email address set for your nick.")); - } - else if (param.equals_ci("NOMAIL")) - { - nc->UnsetS("MEMO_MAIL"); - source.Reply(_("You will no longer be informed via email.")); - } - else if (param.equals_ci("OFF")) - { - nc->UnsetS("MEMO_SIGNON"); - nc->UnsetS("MEMO_RECEIVE"); - nc->UnsetS("MEMO_MAIL"); - source.Reply(_("\002{0}\002 will not send you any notification of memos."), MemoServ->nick); - } - else - this->OnSyntaxError(source, ""); - } - - void DoLimit(CommandSource &source, const std::vector ¶ms, MemoServ::MemoInfo *mi) - { - - Anope::string p1 = params[1]; - Anope::string p2 = params.size() > 2 ? params[2] : ""; - Anope::string p3 = params.size() > 3 ? params[3] : ""; - Anope::string user, chan; - int16_t limit; - NickServ::Account *nc = source.nc; - ChanServ::Channel *ci = NULL; - bool is_servadmin = source.HasPriv("memoserv/set-limit"); - - if (p1[0] == '#') - { - chan = p1; - p1 = p2; - p2 = p3; - p3 = params.size() > 4 ? params[4] : ""; - - ci = ChanServ::Find(chan); - if (!ci) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), chan); - return; - } - - if (!is_servadmin && !source.AccessFor(ci).HasPriv("MEMO")) - { - source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); - return; - } - mi = ci->GetMemos(); - } - if (is_servadmin) - { - if (!p2.empty() && !p2.equals_ci("HARD") && chan.empty()) - { - NickServ::Nick *na; - if (!(na = NickServ::FindNick(p1))) - { - source.Reply(_("\002{0}\002 isn't registered."), p1); - return; - } - user = p1; - mi = na->GetAccount()->GetMemos(); - nc = na->GetAccount(); - p1 = p2; - p2 = p3; - } - else if (p1.empty() || (!p1.is_pos_number_only() && !p1.equals_ci("NONE")) || (!p2.empty() && !p2.equals_ci("HARD"))) - { - this->OnSyntaxError(source, ""); - return; - } - if (!chan.empty()) - { - if (!p2.empty()) - ci->SetS("MEMO_HARDMAX", true); - else - ci->UnsetS("MEMO_HARDMAX"); - } - else - { - if (!p2.empty()) - nc->SetS("MEMO_HARDMAX", true); - else - nc->UnsetS("MEMO_HARDMAX"); - } - limit = -1; - try - { - limit = convertTo(p1); - } - catch (const ConvertException &) { } - } - else - { - if (p1.empty() || !p2.empty() || !isdigit(p1[0])) - { - this->OnSyntaxError(source, ""); - return; - } - if (!chan.empty() && ci->HasFieldS("MEMO_HARDMAX")) - { - source.Reply(_("The memo limit for \002{0}\002 may not be changed."), chan); - return; - } - if (chan.empty() && nc->HasFieldS("MEMO_HARDMAX")) - { - source.Reply(_("You are not permitted to change your memo limit.")); - return; - } - int max_memos = Config->GetModule("memoserv")->Get("maxmemos"); - limit = -1; - try - { - limit = convertTo(p1); - } - catch (const ConvertException &) { } - /* The first character is a digit, but we could still go negative - * from overflow... watch out! */ - if (limit < 0 || (max_memos > 0 && limit > max_memos)) - { - if (!chan.empty()) - source.Reply(_("You cannot set the memo limit for \002{0}\002 higher than \002{1}\002."), chan, max_memos); - else - source.Reply(_("You cannot set your memo limit higher than \002{0}\002."), max_memos); - return; - } - } - mi->SetMemoMax(limit); - if (limit > 0) - { - if (chan.empty() && nc == source.nc) - source.Reply(_("Your memo limit has been set to \002{0}\002."), limit); - else - source.Reply(_("Memo limit for \002{0}\002 set to \002{1}\002."), !chan.empty() ? chan : user, limit); - } - else if (!limit) - { - if (chan.empty() && nc == source.nc) - source.Reply(_("You will no longer be able to receive memos.")); - else - source.Reply(_("Memo limit for \002{0}\002 set to \0020\002."), !chan.empty() ? chan : user); - } - else - { - if (chan.empty() && nc == source.nc) - source.Reply(_("Your memo limit has been disabled.")); - else - source.Reply(_("Memo limit \002disabled\002 for \002{0}\002."), !chan.empty() ? chan : user); - } - } - public: - CommandMSSet(Module *creator) : Command(creator, "memoserv/set", 2, 5) - { - this->SetDesc(_("Set options related to memos")); - this->SetSyntax(_("\037option\037 \037parameters\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &cmd = params[0]; - MemoServ::MemoInfo *mi = source.nc->GetMemos(); - - if (Anope::ReadOnly) - source.Reply(_("Sorry, memo option setting is temporarily disabled.")); - else if (cmd.equals_ci("NOTIFY")) - return this->DoNotify(source, params, mi); - else if (cmd.equals_ci("LIMIT")) - return this->DoLimit(source, params, mi); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (subcommand.empty()) - { - CommandInfo *help = source.service->FindCommand("generic/help"); - if (!help) - return false; - source.Reply(_("Sets various memo options. \037option\037 can be one of:\n" - "\n" - " NOTIFY Changes when you will be notified about\n" - " new memos (only for nicknames)\n" - " LIMIT Sets the maximum number of memos you can\n" - " receive\n" - "\n" - "Type \002{0}{1} {2} {3} \037option\037\002 for more information on a specific option."), - Config->StrictPrivmsg, source.service->nick, help->cname, source.command); - } - else if (subcommand.equals_ci("NOTIFY")) - source.Reply(_("Syntax: \002NOTIFY {ON | LOGON | NEW | MAIL | NOMAIL | OFF}\002\n" - "\n" - "Changes when you will be notified about new memos:\n" - "\n" - " ON You will be notified of memos when you log on,\n" - " when you unset /AWAY, and when they are sent\n" - " to you.\n" - "\n" - " LOGON You will only be notified of memos when you log\n" - " on or when you unset /AWAY.\n" - "\n" - " NEW You will only be notified of memos when they\n" - " are sent to you.\n" - "\n" - " MAIL You will be notified of memos by email as well as\n" - " any other settings you have.\n" - "\n" - " NOMAIL You will not be notified of memos by email.\n" - "\n" - " OFF You will not receive any notification of memos.\n" - "\n" - "\002ON\002 is essentially \002LOGON\002 and \002NEW\002 combined.")); - else if (subcommand.equals_ci("LIMIT")) - { - int max_memos = Config->GetModule("memoserv")->Get("maxmemos"); - if (source.IsServicesOper()) - source.Reply(_("Syntax: \002LIMIT [\037user\037 | \037channel\037] {\037limit\037 | NONE} [HARD]\002\n" - "\n" - "Sets the maximum number of memos a user or channel is allowed to have." - " Setting the limit to 0 prevents the user from receiving any memos; setting it to \002NONE\002 allows the user to receive and keep as many memos as they want." - " If you do not give a nickname or channel, your own limit is set.\n" - "\n" - "Adding \002HARD\002 prevents the user from changing the limit." - " Not adding \002HARD\002 has the opposite effect, allowing the user to change the limit, even if a previous limit was set.\n" - " \n" - "This use of the \002{0} LIMIT\002 command is limited to \002Services Operators\002." - " Other users may only enter a limit for themselves or a channel on which they have the \002MEMO\002 privilege on, may not remove their limit, may not set a limit above {1}, and may not set a hard limit."), - source.command, max_memos); - else - source.Reply(_("Syntax: \002LIMIT [\037channel\037] \037limit\037\002\n" - "\n" - "Sets the maximum number of memos you, or the given channel, are allowed to have." - " If you set this to 0, no one will be able to send any memos to you." - "However, you cannot set this any higher than {0}."), max_memos); - } - else - return false; - - return true; - } -}; - -class MSSet : public Module -{ - CommandMSSet commandmsset; - Serialize::Field memo_signon, memo_receive, memo_mail, memo_hardmax_nick; - Serialize::Field memo_hardmax_channel; - - public: - MSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandmsset(this) - , memo_signon(this, NickServ::account, "MEMO_SIGNON") - , memo_receive(this, NickServ::account, "MEMO_RECEIVE") - , memo_mail(this, NickServ::account, "MEMO_MAIL") - , memo_hardmax_nick(this, NickServ::account, "MEMO_HARDMAX") - , memo_hardmax_channel(this, ChanServ::channel, "MEMO_HARDMAX") - { - - } -}; - -MODULE_INIT(MSSet) diff --git a/modules/commands/ms_staff.cpp b/modules/commands/ms_staff.cpp deleted file mode 100644 index f563fd9ea..000000000 --- a/modules/commands/ms_staff.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/memoserv.h" - -class CommandMSStaff : public Command -{ - public: - CommandMSStaff(Module *creator) : Command(creator, "memoserv/staff", 1, 1) - { - this->SetDesc(_("Send a memo to all opers/admins")); - this->SetSyntax(_("\037memo-text\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (!MemoServ::service) - return; - - const Anope::string &text = params[0]; - - for (NickServ::Account *nc : NickServ::service->GetAccountList()) - if (source.nc != nc && nc->IsServicesOper()) - MemoServ::service->Send(source.GetNick(), nc->GetDisplay(), text, true); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sends all services staff a memo containing \037memo-text\037.")); - - return true; - } -}; - -class MSStaff : public Module -{ - CommandMSStaff commandmsstaff; - - public: - MSStaff(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandmsstaff(this) - { - if (!MemoServ::service) - throw ModuleException("No MemoServ!"); - } -}; - -MODULE_INIT(MSStaff) diff --git a/modules/commands/ns_access.cpp b/modules/commands/ns_access.cpp deleted file mode 100644 index ba2116ac9..000000000 --- a/modules/commands/ns_access.cpp +++ /dev/null @@ -1,257 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/nickserv.h" -#include "modules/ns_access.h" - -class NickAccessImpl : public NickAccess -{ - public: - NickAccessImpl(Serialize::TypeBase *type) : NickAccess(type) { } - NickAccessImpl(Serialize::TypeBase *type, Serialize::ID id) : NickAccess(type, id) { } - - NickServ::Account *GetAccount() override; - void SetAccount(NickServ::Account *) override; - - Anope::string GetMask() override; - void SetMask(const Anope::string &) override; -}; - -class NickAccessType : public Serialize::Type -{ - public: - Serialize::ObjectField account; - Serialize::Field mask; - - NickAccessType(Module *creator) : Serialize::Type(creator, "NSAccess") - , account(this, "account", true) - , mask(this, "mask") - { - } -}; - -NickServ::Account *NickAccessImpl::GetAccount() -{ - return Get(&NickAccessType::account); -} - -void NickAccessImpl::SetAccount(NickServ::Account *acc) -{ - Set(&NickAccessType::account, acc); -} - -Anope::string NickAccessImpl::GetMask() -{ - return Get(&NickAccessType::mask); -} - -void NickAccessImpl::SetMask(const Anope::string &m) -{ - Set(&NickAccessType::mask, m); -} - -class CommandNSAccess : public Command -{ - private: - void DoAdd(CommandSource &source, NickServ::Account *nc, const Anope::string &mask) - { - if (mask.empty()) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - std::vector access = nc->GetRefs(nsaccess); - - if (access.size() >= Config->GetModule(this->owner)->Get("accessmax", "32")) - { - source.Reply(_("Sorry, the maximum of \002{0}\002 access entries has been reached."), Config->GetModule(this->owner)->Get("accessmax")); - return; - } - - for (NickAccess *a : access) - if (a->GetMask().equals_ci(mask)) - { - source.Reply(_("Mask \002{0}\002 already present on the access list of \002{1}\002."), mask, nc->GetDisplay()); - return; - } - - NickAccess *a = nsaccess.Create(); - a->SetAccount(nc); - a->SetMask(mask); - - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to ADD mask " << mask << " to " << nc->GetDisplay(); - source.Reply(_("\002{0}\002 added to the access list of \002{1}\002."), mask, nc->GetDisplay()); - } - - void DoDel(CommandSource &source, NickServ::Account *nc, const Anope::string &mask) - { - if (mask.empty()) - { - this->OnSyntaxError(source, "DEL"); - return; - } - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - for (NickAccess *a : nc->GetRefs(nsaccess)) - if (a->GetMask().equals_ci(mask)) - { - a->Delete(); - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to DELETE mask " << mask << " from " << nc->GetDisplay(); - source.Reply(_("\002{0}\002 deleted from the access list of \002{1}\002."), mask, nc->GetDisplay()); - return; - } - - - source.Reply(_("\002{0}\002 not found on the access list of \002{1}\002."), mask, nc->GetDisplay()); - } - - void DoList(CommandSource &source, NickServ::Account *nc, const Anope::string &mask) - { - std::vector access = nc->GetRefs(nsaccess); - if (access.empty()) - { - source.Reply(_("The access list of \002{0}\002 is empty."), nc->GetDisplay()); - return; - } - - source.Reply(_("Access list for \002{0}\002:"), nc->GetDisplay()); - for (NickAccess *a : access) - { - if (!mask.empty() && !Anope::Match(a->GetMask(), mask)) - continue; - - source.Reply(" {0}", a->GetMask()); - } - } - public: - CommandNSAccess(Module *creator) : Command(creator, "nickserv/access", 1, 3) - { - this->SetDesc(_("Modify the list of authorized addresses")); - this->SetSyntax(_("ADD [\037nickname\037] \037mask\037")); - this->SetSyntax(_("DEL [\037nickname\037] \037mask\037")); - this->SetSyntax(_("LIST [\037nickname\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &cmd = params[0]; - Anope::string nick, mask; - - if (cmd.equals_ci("LIST")) - nick = params.size() > 1 ? params[1] : ""; - else - { - nick = params.size() == 3 ? params[1] : ""; - mask = params.size() > 1 ? params[params.size() - 1] : ""; - } - - NickServ::Account *nc; - if (!nick.empty() && source.HasPriv("nickserv/access")) - { - NickServ::Nick *na = NickServ::FindNick(nick); - if (na == NULL) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && source.GetAccount() != na->GetAccount() && na->GetAccount()->IsServicesOper() && !cmd.equals_ci("LIST")) - { - source.Reply(_("You may view but not modify the access list of other Services Operators.")); - return; - } - - nc = na->GetAccount(); - } - else - nc = source.nc; - - if (!mask.empty() && (mask.find('@') == Anope::string::npos || mask.find('!') != Anope::string::npos)) - { - source.Reply(_("Mask must be in the form \037user\037@\037host\037.")); - source.Reply(_("\002%s%s HELP %s\002 for more information."), Config->StrictPrivmsg, source.service->nick, source.command); // XXX - } - else if (cmd.equals_ci("LIST")) - return this->DoList(source, nc, mask); - else if (nc->HasFieldS("NS_SUSPENDED")) - source.Reply(_("\002{0}\002 is suspended."), nc->GetDisplay()); - else if (cmd.equals_ci("ADD")) - return this->DoAdd(source, nc, mask); - else if (cmd.equals_ci("DEL")) - return this->DoDel(source, nc, mask); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Modifies or displays the access list for your account." - " The access list is a list of addresses that {1} uses to recognize you." - " If you match one of the hosts on the access list, services will not force you to change your nickname if the \002KILL\002 option is set." - " Furthermore, if the \002SECURE\002 option is disabled, services will recognize you just based on your hostmask, without having to supply a password." - " To gain access to channels when only recognized by your hostmask, the channel must too have the \002SECURE\002 option off." - " Services Operators may provide \037nickname\037 to modify other user's access lists.\n" - "\n" - "Examples:\n" - " \n" - " {command} ADD anyone@*.bepeg.com\n" - " Allows access to user \"anyone\" from any machine in the \"bepeg.com\" domain.\n" - "\n" - " {command} DEL anyone@*.bepeg.com\n" - " Reverses the previous command.\n" - "\n" - " {command} LIST\n" - " Displays the current access list."), - source.command, source.service->nick); - return true; - } -}; - -class NSAccess : public Module - , public EventHook -{ - CommandNSAccess commandnsaccess; - NickAccessType nick_type; - - public: - NSAccess(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsaccess(this) - , nick_type(this) - { - } - - void OnNickRegister(User *u, NickServ::Nick *na, const Anope::string &) override - { - if (u && Config->GetModule(this)->Get("addaccessonreg")) - { - NickAccess *a = nsaccess.Create(); - a->SetAccount(na->GetAccount()); - a->SetMask(u->Mask()); - //XXX? -// source.Reply(_("\002{0}\002 has been registered under your hostmask: \002{1}\002"), u_nick, na->GetAccount()->GetAccess(0)); - } - } -}; - -MODULE_INIT(NSAccess) diff --git a/modules/commands/ns_ajoin.cpp b/modules/commands/ns_ajoin.cpp deleted file mode 100644 index 135beb403..000000000 --- a/modules/commands/ns_ajoin.cpp +++ /dev/null @@ -1,381 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_ajoin.h" - -class AutoJoinImpl : public AutoJoin -{ - public: - AutoJoinImpl(Serialize::TypeBase *type) : AutoJoin(type) { } - AutoJoinImpl(Serialize::TypeBase *type, Serialize::ID id) : AutoJoin(type, id) { } - - NickServ::Account *GetOwner() override; - void SetOwner(NickServ::Account *acc) override; - - Anope::string GetChannel() override; - void SetChannel(const Anope::string &c) override; - - Anope::string GetKey() override; - void SetKey(const Anope::string &k) override; -}; - -class AutoJoinType : public Serialize::Type -{ - public: - Serialize::ObjectField owner; - Serialize::Field channel, key; - - AutoJoinType(Module *me) : Serialize::Type(me, "AutoJoin") - , owner(this, "owner", true) - , channel(this, "channel") - , key(this, "key") - { - } -}; - -NickServ::Account *AutoJoinImpl::GetOwner() -{ - return Get(&AutoJoinType::owner); -} - -void AutoJoinImpl::SetOwner(NickServ::Account *acc) -{ - Set(&AutoJoinType::owner, acc); -} - -Anope::string AutoJoinImpl::GetChannel() -{ - return Get(&AutoJoinType::channel); -} - -void AutoJoinImpl::SetChannel(const Anope::string &c) -{ - Set(&AutoJoinType::channel, c); -} - -Anope::string AutoJoinImpl::GetKey() -{ - return Get(&AutoJoinType::key); -} - -void AutoJoinImpl::SetKey(const Anope::string &k) -{ - Set(&AutoJoinType::key, k); -} - -class CommandNSAJoin : public Command -{ - void DoList(CommandSource &source, NickServ::Account *nc) - { - std::vector channels = nc->GetRefs(autojoin); - - if (channels.empty()) - { - source.Reply(_("The auto join list of \002{0}\002 is empty."), nc->GetDisplay()); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Channel")).AddColumn(_("Key")); - for (unsigned i = 0; i < channels.size(); ++i) - { - AutoJoin *aj = channels[i]; - ListFormatter::ListEntry entry; - entry["Number"] = stringify(i + 1); - entry["Channel"] = aj->GetChannel(); - entry["Key"] = aj->GetKey(); - list.AddEntry(entry); - } - - source.Reply(_("Auto join list of \002{0}\002:"), nc->GetDisplay()); - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - - void DoAdd(CommandSource &source, NickServ::Account *nc, const Anope::string &chans, const Anope::string &keys) - { - std::vector channels = nc->GetRefs(autojoin); - - Anope::string addedchans; - Anope::string alreadyadded; - Anope::string invalidkey; - commasepstream ksep(keys, true); - commasepstream csep(chans); - for (Anope::string chan, key; csep.GetToken(chan);) - { - ksep.GetToken(key); - - unsigned i = 0; - for (; i < channels.size(); ++i) - if (channels[i]->GetChannel().equals_ci(chan)) - break; - - if (channels.size() >= Config->GetModule(this->owner)->Get("ajoinmax")) - { - source.Reply(_("Sorry, the maximum of \002{0}\002 auto join entries has been reached."), Config->GetModule(this->owner)->Get("ajoinmax")); - return; - } - - if (i != channels.size()) - alreadyadded += chan + ", "; - else if (IRCD->IsChannelValid(chan) == false) - source.Reply(_("\002{0}\002 isn't a valid channel."), chan); - else - { - Channel *c = Channel::Find(chan); - Anope::string k; - if (c && c->GetParam("KEY", k) && key != k) - { - invalidkey += chan + ", "; - continue; - } - - AutoJoin *entry = autojoin.Create(); - entry->SetOwner(nc); - entry->SetChannel(chan); - entry->SetKey(key); - - addedchans += chan + ", "; - } - } - - if (!alreadyadded.empty()) - { - alreadyadded = alreadyadded.substr(0, alreadyadded.length() - 2); - source.Reply(_("\002{0}\002 is already on the auto join list of \002{1}\002."), alreadyadded, nc->GetDisplay()); - } - - if (!invalidkey.empty()) - { - invalidkey = invalidkey.substr(0, invalidkey.length() - 2); - source.Reply(_("\002{0}\002 had an invalid key specified, and was ignored."), invalidkey); - } - - if (addedchans.empty()) - return; - - addedchans = addedchans.substr(0, addedchans.length() - 2); - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to ADD channel " << addedchans << " to " << nc->GetDisplay(); - source.Reply(_("\002{0}\002 added to the auto join list of \002{1}\002."), addedchans, nc->GetDisplay()); - } - - void DoDel(CommandSource &source, NickServ::Account *nc, const Anope::string &chans) - { - std::vector channels = nc->GetRefs(autojoin); - Anope::string delchans; - Anope::string notfoundchans; - commasepstream sep(chans); - - for (Anope::string chan; sep.GetToken(chan);) - { - unsigned i = 0; - for (; i < channels.size(); ++i) - if (channels[i]->GetChannel().equals_ci(chan)) - break; - - if (i == channels.size()) - notfoundchans += chan + ", "; - else - { - delete channels[i]; - delchans += chan + ", "; - } - } - - if (!notfoundchans.empty()) - { - notfoundchans = notfoundchans.substr(0, notfoundchans.length() - 2); - source.Reply(_("\002{0}\002 was not found on the auto join list of \002{1}\002."), notfoundchans, nc->GetDisplay()); - } - - if (delchans.empty()) - return; - - delchans = delchans.substr(0, delchans.length() - 2); - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to DELETE channel " << delchans << " from " << nc->GetDisplay(); - source.Reply(_("\002{0}\002 was removed from the auto join list of \002{1}\002."), delchans, nc->GetDisplay()); - } - - public: - CommandNSAJoin(Module *creator) : Command(creator, "nickserv/ajoin", 1, 4) - { - this->SetDesc(_("Manage your auto join list")); - this->SetSyntax(_("ADD [\037nickname\037] \037channel\037 [\037key\037]")); - this->SetSyntax(_("DEL [\037nickname\037] \037channel\037")); - this->SetSyntax(_("LIST [\037nickname\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &cmd = params[0]; - Anope::string nick, param, param2; - - if (cmd.equals_ci("LIST")) - nick = params.size() > 1 ? params[1] : ""; - else - nick = (params.size() > 2 && IRCD->IsChannelValid(params[2])) ? params[1] : ""; - - NickServ::Account *nc; - if (!nick.empty() && !source.HasCommand("nickserv/ajoin")) - { - NickServ::Nick *na = NickServ::FindNick(nick); - if (na == NULL) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - nc = na->GetAccount(); - param = params.size() > 2 ? params[2] : ""; - param2 = params.size() > 3 ? params[3] : ""; - } - else - { - nc = source.nc; - param = params.size() > 1 ? params[1] : ""; - param2 = params.size() > 2 ? params[2] : ""; - } - - if (cmd.equals_ci("LIST")) - return this->DoList(source, nc); - else if (nc->HasFieldS("NS_SUSPENDED")) - source.Reply(_("\002{0}\002 isn't registered."), nc->GetDisplay()); - else if (param.empty()) - this->OnSyntaxError(source, ""); - else if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode.")); - else if (cmd.equals_ci("ADD")) - return this->DoAdd(source, nc, param, param2); - else if (cmd.equals_ci("DEL")) - return this->DoDel(source, nc, param); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("This command manages your auto join list." - " When you identify you will automatically join the channels on your auto join list." - " Services Operators may provide \037nickname\037 to modify other users' auto join lists.")); - return true; - } -}; - -class NSAJoin : public Module - , public EventHook -{ - CommandNSAJoin commandnsajoin; - AutoJoinType ajtype; - - public: - NSAJoin(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsajoin(this) - , ajtype(this) - { - - if (!IRCD || !IRCD->CanSVSJoin) - throw ModuleException("Your IRCd does not support SVSJOIN"); - - } - - void OnUserLogin(User *u) override - { - ServiceBot *NickServ = Config->GetClient("NickServ"); - if (!NickServ) - return; - - std::vector channels = u->Account()->GetRefs(autojoin); - if (channels.empty()) - return; - - /* Set +r now, so we can ajoin users into +R channels */ - ModeManager::ProcessModes(); - - for (AutoJoin *entry : channels) - { - Channel *c = Channel::Find(entry->GetChannel()); - ChanServ::Channel *ci; - - if (c) - ci = c->ci; - else - ci = ChanServ::Find(entry->GetChannel()); - - bool need_invite = false; - Anope::string key = entry->GetKey(); - ChanServ::AccessGroup u_access; - - if (ci != NULL) - { - if (ci->HasFieldS("CS_SUSPENDED")) - continue; - u_access = ci->AccessFor(u); - } - if (c != NULL) - { - if (c->FindUser(u) != NULL) - continue; - else if (c->HasMode("OPERONLY") && !u->HasMode("OPER")) - continue; - else if (c->HasMode("ADMINONLY") && !u->HasMode("ADMIN")) - continue; - else if (c->HasMode("SSL") && !(u->HasMode("SSL") || u->HasExtOK("ssl"))) - continue; - else if (c->MatchesList(u, "BAN") == true && c->MatchesList(u, "EXCEPT") == false) - need_invite = true; - else if (c->HasMode("INVITE") && c->MatchesList(u, "INVITEOVERRIDE") == false) - need_invite = true; - - if (c->HasMode("KEY")) - { - Anope::string k; - if (c->GetParam("KEY", k)) - { - if (u_access.HasPriv("GETKEY")) - key = k; - else if (key != k) - need_invite = true; - } - } - if (c->HasMode("LIMIT")) - { - Anope::string l; - if (c->GetParam("LIMIT", l)) - { - try - { - unsigned limit = convertTo(l); - if (c->users.size() >= limit) - need_invite = true; - } - catch (const ConvertException &) { } - } - } - } - - if (need_invite && c != NULL) - { - if (!u_access.HasPriv("INVITE")) - continue; - IRCD->SendInvite(NickServ, c, u); - } - - IRCD->SendSVSJoin(NickServ, u, entry->GetChannel(), key); - } - } -}; - -MODULE_INIT(NSAJoin) diff --git a/modules/commands/ns_alist.cpp b/modules/commands/ns_alist.cpp deleted file mode 100644 index e4aaff38b..000000000 --- a/modules/commands/ns_alist.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandNSAList : public Command -{ - static bool ChannelSort(ChanServ::Channel *ci1, ChanServ::Channel *ci2) - { - return ci::less()(ci1->GetName(), ci2->GetName()); - } - - public: - CommandNSAList(Module *creator) : Command(creator, "nickserv/alist", 0, 2) - { - this->SetDesc(_("List channels you have access on")); - this->SetSyntax(_("[\037nickname\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - Anope::string nick = source.GetNick(); - NickServ::Account *nc = source.nc; - - if (params.size() && source.HasPriv("nickserv/alist")) - { - nick = params[0]; - NickServ::Nick *na = NickServ::FindNick(nick); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - nc = na->GetAccount(); - } - - ListFormatter list(source.GetAccount()); - int chan_count = 0; - - list.AddColumn(_("Number")).AddColumn(_("Channel")).AddColumn(_("Access")).AddColumn(_("Description")); - - std::vector chans = nc->GetRefs(ChanServ::channel); - std::sort(chans.begin(), chans.end(), ChannelSort); - for (ChanServ::Channel *ci : chans) - { - ListFormatter::ListEntry entry; - - if (ci->GetFounder() == nc) - { - ++chan_count; - entry["Number"] = stringify(chan_count); - entry["Channel"] = (ci->HasFieldS("CS_NO_EXPIRE") ? "!" : "") + ci->GetName(); - entry["Access"] = Language::Translate(source.GetAccount(), _("Founder")); - entry["Description"] = ci->GetDesc(); - list.AddEntry(entry); - continue; - } - - if (ci->GetSuccessor() == nc) - { - ++chan_count; - entry["Number"] = stringify(chan_count); - entry["Channel"] = (ci->HasFieldS("CS_NO_EXPIRE") ? "!" : "") + ci->GetName(); - entry["Access"] = Language::Translate(source.GetAccount(), _("Successor")); - entry["Description"] = ci->GetDesc(); - list.AddEntry(entry); - continue; - } - - ChanServ::AccessGroup access = ci->AccessFor(nc, false); - if (access.empty()) - continue; - - ++chan_count; - - entry["Number"] = stringify(chan_count); - entry["Channel"] = (ci->HasFieldS("CS_NO_EXPIRE") ? "!" : "") + ci->GetName(); - for (unsigned j = 0; j < access.size(); ++j) - entry["Access"] = entry["Access"] + ", " + access[j]->AccessSerialize(); - entry["Access"] = entry["Access"].substr(2); - entry["Description"] = ci->GetDesc(); - list.AddEntry(entry); - } - - std::vector replies; - list.Process(replies); - - if (!chan_count) - { - source.Reply(_("\002{0}\002 has no access in any channels."), nc->GetDisplay()); - } - else - { - source.Reply(_("Channels that \002{0}\002 has access on:"), nc->GetDisplay()); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("End of list - \002{0}\002 channels shown."), chan_count); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Lists all channels you have access on.\n" - " \n" - "Channels that have the \037NOEXPIRE\037 option set will be prefixed by an exclamation mark. The nickname parameter is limited to Services Operators")); - - return true; - } -}; - -class NSAList : public Module -{ - CommandNSAList commandnsalist; - - public: - NSAList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsalist(this) - { - - } -}; - -MODULE_INIT(NSAList) diff --git a/modules/commands/ns_cert.cpp b/modules/commands/ns_cert.cpp deleted file mode 100644 index fc87f8cb0..000000000 --- a/modules/commands/ns_cert.cpp +++ /dev/null @@ -1,364 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_cert.h" -#include "modules/nickserv.h" - -static Anope::hash_map certmap; -static EventHandlers *events; - -class CertServiceImpl : public CertService -{ - public: - CertServiceImpl(Module *o) : CertService(o) { } - - NickServ::Account* FindAccountFromCert(const Anope::string &cert) override - { - Anope::hash_map::iterator it = certmap.find(cert); - if (it != certmap.end()) - return it->second; - return NULL; - } - - bool Matches(User *u, NickServ::Account *nc) override - { - std::vector cl = nc->GetRefs(certentry); - return !u->fingerprint.empty() && FindCert(cl, u->fingerprint); - } - - NSCertEntry *FindCert(const std::vector &cl, const Anope::string &certfp) override - { - for (NSCertEntry *e : cl) - if (e->GetCert() == certfp) - return e; - return nullptr; - } -}; - -class NSCertEntryImpl : public NSCertEntry -{ - public: - NSCertEntryImpl(Serialize::TypeBase *type) : NSCertEntry(type) { } - NSCertEntryImpl(Serialize::TypeBase *type, Serialize::ID id) : NSCertEntry(type, id) { } - ~NSCertEntryImpl(); - - NickServ::Account *GetAccount() override; - void SetAccount(NickServ::Account *) override; - - Anope::string GetCert() override; - void SetCert(const Anope::string &) override; -}; - -class NSCertEntryType : public Serialize::Type -{ - public: - struct Account : Serialize::ObjectField - { - using Serialize::ObjectField::ObjectField; - - void SetField(NSCertEntryImpl *s, NickServ::Account *acc) override - { - const Anope::string &cert = s->GetCert(); - if (!cert.empty()) - certmap.erase(cert); - - Serialize::ObjectField::SetField(s, acc); - - if (!cert.empty() && s->GetAccount()) - certmap[cert] = acc; - } - } nc; - - struct Mask : Serialize::Field - { - using Serialize::Field::Field; - - void SetField(NSCertEntryImpl *s, const Anope::string &m) override - { - const Anope::string &old = GetField(s); - if (!old.empty()) - certmap.erase(old); - - Serialize::Field::SetField(s, m); - - if (!m.empty() && s->GetAccount()) - certmap[m] = s->GetAccount(); - } - } mask; - - NSCertEntryType(Module *me) : Serialize::Type(me, "NSCertEntry") - , nc(this, "nc", true) - , mask(this, "mask") - { - } -}; - -NSCertEntryImpl::~NSCertEntryImpl() -{ - const Anope::string &old = GetCert(); - if (!old.empty()) - certmap.erase(old); -} - -NickServ::Account *NSCertEntryImpl::GetAccount() -{ - return Get(&NSCertEntryType::nc); -} - -void NSCertEntryImpl::SetAccount(NickServ::Account *nc) -{ - Set(&NSCertEntryType::nc, nc); -} - -Anope::string NSCertEntryImpl::GetCert() -{ - return Get(&NSCertEntryType::mask); -} - -void NSCertEntryImpl::SetCert(const Anope::string &mask) -{ - Set(&NSCertEntryType::mask, mask); -} - -class CommandNSCert : public Command -{ - NSCertEntry *FindCert(const std::vector &cl, const Anope::string &certfp) - { - for (NSCertEntry *e : cl) - if (e->GetCert() == certfp) - return e; - return nullptr; - } - - void DoAdd(CommandSource &source, NickServ::Account *nc, Anope::string certfp) - { - std::vector cl = nc->GetRefs(certentry); - unsigned max = Config->GetModule(this->owner)->Get("max", "5"); - - if (cl.size() >= max) - { - source.Reply(_("Sorry, the maximum of \002{0}\002 certificate entries has been reached."), max); - return; - } - - if (source.GetAccount() == nc) - { - User *u = source.GetUser(); - - if (!u || u->fingerprint.empty()) - { - source.Reply(_("You are not using a client certificate.")); - return; - } - - certfp = u->fingerprint; - } - - if (FindCert(cl, certfp)) - { - source.Reply(_("Fingerprint \002{0}\002 already present on the certificate list of \002{0}\002."), certfp, nc->GetDisplay()); - return; - } - - if (certmap.find(certfp) != certmap.end()) - { - source.Reply(_("Fingerprint \002{0}\002 is already in use."), certfp); - return; - } - - NSCertEntry *e = certentry.Create(); - e->SetAccount(nc); - e->SetCert(certfp); - - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to ADD certificate fingerprint " << certfp << " to " << nc->GetDisplay(); - source.Reply(_("\002{0}\002 added to the certificate list of \002{1}\002."), certfp, nc->GetDisplay()); - } - - void DoDel(CommandSource &source, NickServ::Account *nc, Anope::string certfp) - { - std::vector cl = nc->GetRefs(certentry); - - if (certfp.empty()) - { - User *u = source.GetUser(); - if (u) - certfp = u->fingerprint; - } - - if (certfp.empty()) - { - this->OnSyntaxError(source, "DEL"); - return; - } - - NSCertEntry *cert = FindCert(cl, certfp); - if (!cert) - { - source.Reply(_("\002{0}\002 not found on the certificate list of \002{1}\002."), certfp, nc->GetDisplay()); - return; - } - - cert->Delete(); - - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to DELETE certificate fingerprint " << certfp << " from " << nc->GetDisplay(); - source.Reply(_("\002{0}\002 deleted from the access list of \002{1}\002."), certfp, nc->GetDisplay()); - } - - void DoList(CommandSource &source, NickServ::Account *nc) - { - std::vector cl = nc->GetRefs(certentry); - - if (cl.empty()) - { - source.Reply(_("The certificate list of \002{0}\002 is empty."), nc->GetDisplay()); - return; - } - - source.Reply(_("Certificate list for \002{0}\002:"), nc->GetDisplay()); - for (NSCertEntry *e : cl) - source.Reply(" {0}", e->GetCert()); - } - - public: - CommandNSCert(Module *creator) : Command(creator, "nickserv/cert", 1, 3) - { - this->SetDesc(_("Modify the nickname client certificate list")); - this->SetSyntax(_("ADD [\037nickname\037] [\037fingerprint\037]")); - this->SetSyntax(_("DEL [\037nickname\037] \037fingerprint\037")); - this->SetSyntax(_("LIST [\037nickname\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &cmd = params[0]; - Anope::string nick, certfp; - - if (cmd.equals_ci("LIST")) - nick = params.size() > 1 ? params[1] : ""; - else - { - nick = params.size() == 3 ? params[1] : ""; - certfp = params.size() > 1 ? params[params.size() - 1] : ""; - } - - NickServ::Account *nc; - if (!nick.empty() && source.HasPriv("nickserv/access")) - { - NickServ::Nick *na = NickServ::FindNick(nick); - if (na == NULL) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && source.GetAccount() != na->GetAccount() && na->GetAccount()->IsServicesOper() && !cmd.equals_ci("LIST")) - { - source.Reply(_("You may view, but not modify, the certificate list of other Services Operators.")); - return; - } - - nc = na->GetAccount(); - } - else - nc = source.nc; - - if (cmd.equals_ci("LIST")) - return this->DoList(source, nc); - else if (nc->HasFieldS("NS_SUSPENDED")) - source.Reply(_("\002{0}\002 is suspended."), nc->GetDisplay()); - else if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode.")); - else if (cmd.equals_ci("ADD")) - return this->DoAdd(source, nc, certfp); - else if (cmd.equals_ci("DEL")) - return this->DoDel(source, nc, certfp); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Modifies or displays the certificate list for your account." - "If you connect to IRC and provide a client certificate with a matching fingerprint in the certificate list, you will be automatically identified to services." - " Services Operators may provide \037nickname\037 to modify other users' certificate lists.\n" - "\n" - "Examples:\n" - "\n" - " {0} ADD\n" - " Adds your current fingerprint to the certificate list and automatically identifies you when you connect to IRC using this certificate.\n" - "\n" - " {0} DEL \n" - " Removes \"\" from your certificate list.")); - return true; - } -}; - -class NSCert : public Module - , public EventHook - , public EventHook -{ - CommandNSCert commandnscert; - CertServiceImpl cs; - - EventHandlers onnickservevents; - - public: - NSCert(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnscert(this) - , cs(this) - , onnickservevents(this) - { - if (!IRCD || !IRCD->CanCertFP) - throw ModuleException("Your IRCd does not support ssl client certificates"); - - events = &onnickservevents; - } - - void OnFingerprint(User *u) override - { - ServiceBot *NickServ = Config->GetClient("NickServ"); - if (!NickServ || u->IsIdentified()) - return; - - NickServ::Account *nc = cs.FindAccountFromCert(u->fingerprint); - if (!nc || nc->HasFieldS("NS_SUSPENDED")) - return; - - NickServ::Nick *na = NickServ::FindNick(u->nick); - if (na && na->GetAccount() == nc) - u->Identify(na); - else - u->Login(nc); - - u->SendMessage(NickServ, _("SSL certificate fingerprint accepted, you are now identified to \002%s\002."), nc->GetDisplay().c_str()); - Log(NickServ) << u->GetMask() << " automatically identified for account " << nc->GetDisplay() << " via SSL certificate fingerprint"; - } - - EventReturn OnNickValidate(User *u, NickServ::Nick *na) override - { - if (u->fingerprint.empty()) - return EVENT_CONTINUE; - - if (cs.Matches(u, na->GetAccount())) - { - ServiceBot *NickServ = Config->GetClient("NickServ"); - u->Identify(na); - u->SendMessage(NickServ, _("SSL certificate fingerprint accepted, you are now identified.")); - Log(NickServ) << u->GetMask() << " automatically identified for account " << na->GetAccount()->GetDisplay() << " via SSL certificate fingerprint"; - return EVENT_ALLOW; - } - - return EVENT_CONTINUE; - } -}; - -MODULE_INIT(NSCert) diff --git a/modules/commands/ns_drop.cpp b/modules/commands/ns_drop.cpp deleted file mode 100644 index 62ee41970..000000000 --- a/modules/commands/ns_drop.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_drop.h" - -class CommandNSDrop : public Command -{ - EventHandlers &onnickdrop; - - public: - CommandNSDrop(Module *creator, EventHandlers &event) : Command(creator, "nickserv/drop", 1, 1), onnickdrop(event) - { - this->SetSyntax(_("\037nickname\037")); - this->SetDesc(_("Cancel the registration of a nickname")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &nick = params[0]; - - if (Anope::ReadOnly && !source.HasPriv("nickserv/drop")) - { - source.Reply(_("Sorry, nickname de-registration is temporarily disabled.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(nick); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - bool is_mine = source.GetAccount() == na->GetAccount(); - - if (!is_mine && !source.HasPriv("nickserv/drop")) - { - source.Reply(_("Access denied. You do not have the correct operator privileges to drop other user's nicknames.")); - return; - } - - if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && !is_mine && na->GetAccount()->IsServicesOper()) - { - source.Reply(_("You may not drop other Services Operators' nicknames.")); - return; - } - - this->onnickdrop(&Event::NickDrop::OnNickDrop, source, na); - - Log(!is_mine ? LOG_ADMIN : LOG_COMMAND, source, this) << "to drop nickname " << na->GetNick() << " (group: " << na->GetAccount()->GetDisplay() << ") (email: " << (!na->GetAccount()->GetEmail().empty() ? na->GetAccount()->GetEmail() : "none") << ")"; - na->Delete(); - - source.Reply(_("\002{0}\002 has been dropped."), nick); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Unregisters \037nickname\037. Once your nickname is dropped you may lose all of your access and channels that you may own. Any other user will be free to register \037nickname\037.")); - if (!source.HasPriv("nickserv/drop")) - source.Reply(_("You may drop any nickname within your group.")); - else - source.Reply(_("As a Services Operator, you may drop any nick.")); - - return true; - } -}; - -class NSDrop : public Module -{ - CommandNSDrop commandnsdrop; - EventHandlers onnickdrop; - - public: - NSDrop(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsdrop(this, onnickdrop) - , onnickdrop(this) - { - - } -}; - -MODULE_INIT(NSDrop) diff --git a/modules/commands/ns_getemail.cpp b/modules/commands/ns_getemail.cpp deleted file mode 100644 index 7b94efbea..000000000 --- a/modules/commands/ns_getemail.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - * - * A simple call to check for all emails that a user may have registered - * with. It returns the nicks that match the email you provide. Wild - * Cards are not excepted. Must use user@email-host. - */ - -#include "module.h" - -class CommandNSGetEMail : public Command -{ - public: - CommandNSGetEMail(Module *creator) : Command(creator, "nickserv/getemail", 1, 1) - { - this->SetDesc(_("Matches and returns all users that registered using given email")); - this->SetSyntax(_("\037email\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &email = params[0]; - int j = 0; - - Log(LOG_ADMIN, source, this) << "on " << email; - - for (NickServ::Account *nc : NickServ::service->GetAccountList()) - if (!nc->GetEmail().empty() && nc->GetEmail().equals_ci(email)) - { - ++j; - source.Reply(_("Email matched: \002{0}\002 to \002{1}\002."), nc->GetDisplay(), email); - } - - if (j <= 0) - { - source.Reply(_("There are no accounts with an email that matches \002{0}\002."), email); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Returns the matching accounts whose email address is \037email\037.")); - return true; - } -}; - -class NSGetEMail : public Module -{ - CommandNSGetEMail commandnsgetemail; - - public: - NSGetEMail(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsgetemail(this) - { - - } -}; - -MODULE_INIT(NSGetEMail) diff --git a/modules/commands/ns_getpass.cpp b/modules/commands/ns_getpass.cpp deleted file mode 100644 index a526986ec..000000000 --- a/modules/commands/ns_getpass.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandNSGetPass : public Command -{ - public: - CommandNSGetPass(Module *creator) : Command(creator, "nickserv/getpass", 1, 1) - { - this->SetDesc(_("Retrieve the password for a nickname")); - this->SetSyntax(_("\037account\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &nick = params[0]; - Anope::string tmp_pass; - NickServ::Nick *na = NickServ::FindNick(nick); - - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && na->GetAccount()->IsServicesOper()) - { - source.Reply(_("You may not get the password of other Services Operators.")); - return; - } - - if (!Anope::Decrypt(na->GetAccount()->GetPassword(), tmp_pass)) - { - source.Reply(_("The \002{0}\002 command is unavailable because encryption is in use."), source.command); - return; - } - - Log(LOG_ADMIN, source, this) << "for " << na->GetNick(); - source.Reply(_("Password of \002{0}\02 is \002%s\002."), na->GetNick(), tmp_pass); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Returns the password for the given account. This command may not be available if password encryption is in use.")); - return true; - } -}; - -class NSGetPass : public Module -{ - CommandNSGetPass commandnsgetpass; - - public: - NSGetPass(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsgetpass(this) - { - - Anope::string tmp_pass = "plain:tmp"; - if (!Anope::Decrypt(tmp_pass, tmp_pass)) - throw ModuleException("Incompatible with the encryption module being used"); - - } -}; - -MODULE_INIT(NSGetPass) diff --git a/modules/commands/ns_group.cpp b/modules/commands/ns_group.cpp deleted file mode 100644 index 9a9b933f4..000000000 --- a/modules/commands/ns_group.cpp +++ /dev/null @@ -1,385 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_cert.h" -#include "modules/ns_group.h" - -class NSGroupRequestListener : public NickServ::IdentifyRequestListener -{ - EventHandlers &onnickgroup; - CommandSource source; - Command *cmd; - Anope::string nick; - Reference target; - - public: - NSGroupRequestListener(EventHandlers &event, CommandSource &src, Command *c, const Anope::string &n, NickServ::Nick *targ) : onnickgroup(event), source(src), cmd(c), nick(n), target(targ) { } - - void OnSuccess(NickServ::IdentifyRequest *) override - { - if (!source.GetUser() || source.GetUser()->nick != nick || !target) - return; - - User *u = source.GetUser(); - NickServ::Nick *na = NickServ::FindNick(nick); - /* If the nick is already registered, drop it. */ - if (na) - { - Event::OnChangeCoreDisplay(&Event::ChangeCoreDisplay::OnChangeCoreDisplay, na->GetAccount(), u->nick); - delete na; - } - - na = NickServ::nick.Create(); - na->SetNick(nick); - na->SetAccount(target->GetAccount()); - na->SetLastUsermask(u->GetIdent() + "@" + u->GetDisplayedHost()); - na->SetLastRealname(u->realname); - na->SetLastSeen(Anope::CurTime); - na->SetTimeRegistered(Anope::CurTime); - - u->Login(target->GetAccount()); - this->onnickgroup(&Event::NickGroup::OnNickGroup, u, target); - - Log(LOG_COMMAND, source, cmd) << "to make " << nick << " join group of " << target->GetNick() << " (" << target->GetAccount()->GetDisplay() << ") (email: " << (!target->GetAccount()->GetEmail().empty() ? target->GetAccount()->GetEmail() : "none") << ")"; - source.Reply(_("You are now in the group of \002{0}\002."), target->GetNick()); - - u->lastnickreg = Anope::CurTime; - - } - - void OnFail(NickServ::IdentifyRequest *) override - { - if (!source.GetUser()) - return; - - Log(LOG_COMMAND, source, cmd) << "and failed to group to " << target->GetNick(); - source.Reply(_("Password incorrect.")); - source.GetUser()->BadPassword(); - } -}; - -class CommandNSGroup : public Command -{ - EventHandlers &onnickgroup; - - public: - CommandNSGroup(Module *creator, EventHandlers &event) : Command(creator, "nickserv/group", 0, 2), onnickgroup(event) - { - this->SetDesc(_("Join a group")); - this->SetSyntax(_("\037[target]\037 \037[password]\037")); - this->AllowUnregistered(true); - this->RequireUser(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - User *u = source.GetUser(); - - Anope::string nick; - if (params.empty()) - { - NickServ::Account* core = u->Account(); - if (core) - nick = core->GetDisplay(); - } - else - nick = params[0]; - - if (nick.empty()) - { - this->SendSyntax(source); - return; - } - - const Anope::string &pass = params.size() > 1 ? params[1] : ""; - - if (Anope::ReadOnly) - { - source.Reply(_("Sorry, nickname grouping is temporarily disabled.")); - return; - } - - if (!IRCD->IsNickValid(u->nick)) - { - source.Reply(_("\002{0}\002 may not be registered."), u->nick); - return; - } - - if (Config->GetModule("nickserv")->Get("restrictopernicks")) - for (Oper *o : Serialize::GetObjects(operblock)) - { - if (!u->HasMode("OPER") && u->nick.find_ci(o->GetName()) != Anope::string::npos) - { - source.Reply(_("\002{0}\002 may not be registered because it is too similar to an operator nick."), u->nick); - return; - } - } - - NickServ::Nick *target, *na = NickServ::FindNick(u->nick); - const Anope::string &guestnick = Config->GetModule("nickserv")->Get("guestnickprefix", "Guest"); - time_t reg_delay = Config->GetModule("nickserv")->Get("regdelay"); - unsigned maxaliases = Config->GetModule(this->owner)->Get("maxaliases"); - if (!(target = NickServ::FindNick(nick))) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - if (Anope::CurTime < u->lastnickreg + reg_delay) - { - source.Reply(_("Please wait \002{0}\002 seconds before using the \002{1}\002 command again."), (reg_delay + u->lastnickreg) - Anope::CurTime, source.command); - return; - } - - if (target->GetAccount()->HasFieldS("NS_SUSPENDED")) - { - Log(LOG_COMMAND, source, this) << "and tried to group to suspended nick " << target->GetNick(); - source.Reply(_("\002{0}\002 is suspended."), target->GetNick()); - return; - } - - if (na && Config->GetModule(this->owner)->Get("nogroupchange")) - { - source.Reply(_("Your nick is already registered.")); - return; - } - - if (na && target->GetAccount() == na->GetAccount()) - { - source.Reply(_("You are already a member of the group of \002{0}\002."), target->GetNick()); - return; - } - - if (na && na->GetAccount() != u->Account()) - { - source.Reply(_("\002{0}\002 is already registered."), na->GetNick()); - return; - } - - if (na && Config->GetModule(this->owner)->Get("nogroupchange")) - { - source.Reply(_("You are already registered.")); - return; - } - - if (maxaliases && target->GetAccount()->GetRefs(NickServ::nick).size() >= maxaliases && !target->GetAccount()->IsServicesOper()) - { - source.Reply(_("There are too many nicknames in your group.")); - return; - } - - if (u->nick.length() <= guestnick.length() + 7 && - u->nick.length() >= guestnick.length() + 1 && - !u->nick.find_ci(guestnick) && !u->nick.substr(guestnick.length()).find_first_not_of("1234567890")) - { - source.Reply(_("\002{0}\002 may not be registered."), u->nick); - return; - } - - bool ok = false; - if (!na && u->Account() == target->GetAccount()) - ok = true; - - if (certservice && certservice->Matches(u, target->GetAccount())) - ok = true; - - if (ok == false && !pass.empty()) - { - NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new NSGroupRequestListener(onnickgroup, source, this, u->nick, target), owner, target->GetAccount()->GetDisplay(), pass); - Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, source.GetUser(), req); - req->Dispatch(); - } - else - { - NSGroupRequestListener req(onnickgroup, source, this, u->nick, target); - - if (ok) - req.OnSuccess(nullptr); - else - req.OnFail(nullptr); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("This command makes your nickname join the \037target\037 nickname's group." - " \037password\037 is the password of the target nickname.\n" - "\n" - "Nicknames in the same group share channel privileges, memos, and most settings, including password." - "\n" - "You may be able to use this command even if you have not registered your nick yet." - " If your nick is already registered, you'll need to identify yourself before using this command.")); - return true; - } -}; - -class CommandNSUngroup : public Command -{ - public: - CommandNSUngroup(Module *creator) : Command(creator, "nickserv/ungroup", 0, 1) - { - this->SetDesc(_("Remove a nick from a group")); - this->SetSyntax(_("[\037nickname\037]")); - this->RequireUser(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - User *u = source.GetUser(); - Anope::string nick = !params.empty() ? params[0] : ""; - NickServ::Nick *na = NickServ::FindNick(!nick.empty() ? nick : u->nick); - - if (u->Account()->GetRefs(NickServ::nick).size() == 1) - { - source.Reply(_("Your nickname is not grouped to anything, so you can't ungroup it.")); - return; - } - - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), !nick.empty() ? nick : u->nick); - return; - } - - if (na->GetAccount() != u->Account()) - { - source.Reply(_("\002{0}\002 is not in your group."), na->GetNick()); - return; - } - - - NickServ::Account *oldcore = na->GetAccount(); - - if (na->GetNick().equals_ci(oldcore->GetDisplay())) - oldcore->SetDisplay(oldcore->GetRef(NickServ::nick)); - - NickServ::Account *nc = NickServ::account.Create(); - nc->SetDisplay(na->GetNick()); - na->SetAccount(nc); - - nc->SetPassword(oldcore->GetPassword()); - if (!oldcore->GetEmail().empty()) - nc->SetEmail(oldcore->GetEmail()); - nc->SetLanguage(oldcore->GetLanguage()); - - source.Reply(_("\002{0}\002 has been ungrouped from \002{1}\002."), na->GetNick(), oldcore->GetDisplay()); - - User *user = User::Find(na->GetNick()); - if (user) - /* The user on the nick who was ungrouped may be identified to the old group, set -r */ - user->RemoveMode(source.service, "REGISTERED"); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("This command ungroups your nickname, or if given, the specificed \037nickname\037, from the group it is in." - " The ungrouped nick keeps its registration time, password, email, and language. Everything else is reset." - " You may not ungroup yourself if there is only one nickname in your group.")); - return true; - } -}; - -class CommandNSGList : public Command -{ - public: - CommandNSGList(Module *creator) : Command(creator, "nickserv/glist", 0, 1) - { - this->SetDesc(_("Lists all nicknames in your group")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &nick = !params.empty() ? params[0] : ""; - NickServ::Account *nc; - - if (!nick.empty() && source.IsServicesOper()) - { - NickServ::Nick *na = NickServ::FindNick(nick); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - nc = na->GetAccount(); - } - else - nc = source.GetAccount(); - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Nick")).AddColumn(_("Expires")); - time_t nickserv_expire = Config->GetModule("nickserv")->Get("expire", "21d"), - unconfirmed_expire = Config->GetModule("nickserv")->Get("unconfirmedexpire", "1d"); - for (NickServ::Nick *na2 : nc->GetRefs(NickServ::nick)) - { - Anope::string expires; - if (na2->HasFieldS("NS_NO_EXPIRE")) - expires = _("Does not expire"); - else if (!nickserv_expire || Anope::NoExpire) - ; - else if (na2->GetAccount()->HasFieldS("UNCONFIRMED") && unconfirmed_expire) - expires = Anope::strftime(na2->GetTimeRegistered() + unconfirmed_expire, source.GetAccount()); - else - expires = Anope::strftime(na2->GetLastSeen() + nickserv_expire, source.GetAccount()); - - ListFormatter::ListEntry entry; - entry["Nick"] = na2->GetNick(); - entry["Expires"] = expires; - list.AddEntry(entry); - } - - source.Reply(nc != source.GetAccount() ? _("List of nicknames in the group of \002%s\002:") : _("List of nicknames in your group:"), nc->GetDisplay().c_str()); - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("%d nickname(s) in the group."), replies.size()); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (source.IsServicesOper()) - source.Reply(_("Without a parameter, lists all nicknames that are in your group.\n" - "\n" - "With a parameter, lists all nicknames that are in the group of the given nick.\n" - "Specifying a nick is limited to \002Services Operators\002."), - source.command); - else - source.Reply(_("Lists all nicknames in your group.")); - - return true; - } -}; - -class NSGroup : public Module -{ - CommandNSGroup commandnsgroup; - CommandNSUngroup commandnsungroup; - CommandNSGList commandnsglist; - - EventHandlers onnickgroup; - - public: - NSGroup(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsgroup(this, onnickgroup) - , commandnsungroup(this) - , commandnsglist(this) - , onnickgroup(this) - { - if (Config->GetModule("nickserv")->Get("nonicknameownership")) - throw ModuleException(modname + " can not be used with options:nonicknameownership enabled"); - } -}; - -MODULE_INIT(NSGroup) diff --git a/modules/commands/ns_identify.cpp b/modules/commands/ns_identify.cpp deleted file mode 100644 index 122378d8a..000000000 --- a/modules/commands/ns_identify.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class NSIdentifyRequestListener : public NickServ::IdentifyRequestListener -{ - CommandSource source; - Command *cmd; - - public: - NSIdentifyRequestListener(CommandSource &s, Command *c) : source(s), cmd(c) { } - - void OnSuccess(NickServ::IdentifyRequest *req) override - { - if (!source.GetUser()) - return; - - User *u = source.GetUser(); - NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); - - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), req->GetAccount()); - return; - } - - if (u->IsIdentified()) - Log(LOG_COMMAND, source, cmd) << "to log out of account " << u->Account()->GetDisplay(); - - Log(LOG_COMMAND, source, cmd) << "and identified for account " << na->GetAccount()->GetDisplay(); - source.Reply(_("Password accepted - you are now recognized as \002{0}\002."), na->GetAccount()->GetDisplay()); - u->Identify(na); - } - - void OnFail(NickServ::IdentifyRequest *req) override - { - if (!source.GetUser()) - return; - - bool accountexists = NickServ::FindNick(req->GetAccount()) != NULL; - Log(LOG_COMMAND, source, cmd) << "and failed to identify to" << (accountexists ? " " : " nonexistent ") << "account " << req->GetAccount(); - if (accountexists) - { - source.Reply(_("Password incorrect.")); - source.GetUser()->BadPassword(); - } - else - source.Reply("\002{0}\002 isn't registered.", req->GetAccount()); - } -}; - -class CommandNSIdentify : public Command -{ - public: - CommandNSIdentify(Module *creator) : Command(creator, "nickserv/identify", 1, 2) - { - this->SetDesc(_("Identify yourself with your password")); - this->SetSyntax(_("[\037account\037] \037password\037")); - this->AllowUnregistered(true); - this->RequireUser(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - User *u = source.GetUser(); - - const Anope::string &nick = params.size() == 2 ? params[0] : u->nick; - Anope::string pass = params[params.size() - 1]; - - NickServ::Nick *na = NickServ::FindNick(nick); - if (na && na->GetAccount()->HasFieldS("NS_SUSPENDED")) - { - source.Reply(_("\002{0}\002 is suspended."), na->GetNick()); - return; - } - - if (u->Account() && na && u->Account() == na->GetAccount()) - { - source.Reply(_("You are already identified.")); - return; - } - - unsigned int maxlogins = Config->GetModule(this->owner)->Get("maxlogins"); - if (na && maxlogins && na->GetAccount()->users.size() >= maxlogins) - { - source.Reply(_("Account \002{0}\002 has already reached the maximum number of simultaneous logins ({1})."), na->GetAccount()->GetDisplay(), maxlogins); - return; - } - - NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new NSIdentifyRequestListener(source, this), owner, na ? na->GetAccount()->GetDisplay() : nick, pass); - Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, u, req); - - req->Dispatch(); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Logs you in to account \037account\037. If no \037account\037 is given, your current nickname is used." - " Many commands require you to authenticate with this command before you use them. \037password\037 should be the same one you registered with.")); - return true; - } -}; - -class NSIdentify : public Module -{ - CommandNSIdentify commandnsidentify; - - public: - NSIdentify(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), - commandnsidentify(this) - { - - } -}; - -MODULE_INIT(NSIdentify) diff --git a/modules/commands/ns_info.cpp b/modules/commands/ns_info.cpp deleted file mode 100644 index 7091e53fd..000000000 --- a/modules/commands/ns_info.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_info.h" -#include "modules/ns_set.h" - -class CommandNSInfo : public Command -{ - EventHandlers &onnickinfo; - - public: - CommandNSInfo(Module *creator, EventHandlers &event) : Command(creator, "nickserv/info", 0, 2), onnickinfo(event) - { - this->SetDesc(_("Displays information about a given nickname")); - this->SetSyntax(_("[\037nickname\037]")); - this->AllowUnregistered(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - - const Anope::string &nick = params.size() ? params[0] : (source.nc ? source.nc->GetDisplay() : source.GetNick()); - NickServ::Nick *na = NickServ::FindNick(nick); - bool has_auspex = source.HasPriv("nickserv/auspex"); - - if (!na) - { - if (ServiceBot::Find(nick, true)) - source.Reply(_("\002{0}\002 is part of this Network's Services."), nick); - else - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - bool nick_online = false, show_hidden = false; - - /* Is the real owner of the nick we're looking up online? -TheShadow */ - User *u2 = User::Find(na->GetNick()); - if (u2 && u2->Account() == na->GetAccount()) - { - nick_online = true; - na->SetLastSeen(Anope::CurTime); - } - - if (has_auspex || na->GetAccount() == source.GetAccount()) - show_hidden = true; - - source.Reply(_("\002{0}\002 is \002{1}\002"), na->GetNick(), na->GetLastRealname()); - - if (na->GetAccount()->HasFieldS("UNCONFIRMED")) - source.Reply(_("\002{0}\002 has not confirmed their account."), na->GetNick()); - - if (na->GetAccount()->IsServicesOper() && (show_hidden || !na->GetAccount()->HasFieldS("HIDE_STATUS"))) - source.Reply(_("\002{0}\002 is a Services Operator of type \002{0}\002."), na->GetNick(), na->GetAccount()->o->GetType()->GetName()); - - InfoFormatter info(source.nc); - - if (nick_online) - { - bool shown = false; - if (show_hidden && !na->GetLastRealhost().empty()) - { - info[_("Online from")] = na->GetLastRealhost(); - shown = true; - } - if ((show_hidden || !na->GetAccount()->HasFieldS("HIDE_MASK")) && (!shown || na->GetLastUsermask() != na->GetLastRealhost())) - info[_("Online from")] = na->GetLastUsermask(); - else - source.Reply(_("\002{0}\002 is currently online."), na->GetNick()); - } - else - { - Anope::string shown; - if (show_hidden || !na->GetAccount()->HasFieldS("HIDE_MASK")) - { - info[_("Last seen address")] = na->GetLastUsermask(); - shown = na->GetLastUsermask(); - } - - if (show_hidden && !na->GetLastRealhost().empty() && na->GetLastRealhost() != shown) - info[_("Last seen address")] = na->GetLastRealhost(); - } - - info[_("Registered")] = Anope::strftime(na->GetTimeRegistered(), source.GetAccount()); - - if (!nick_online) - info[_("Last seen")] = Anope::strftime(na->GetLastSeen(), source.GetAccount()); - - if (!na->GetLastQuit().empty() && (show_hidden || !na->GetAccount()->HasFieldS("HIDE_QUIT"))) - info[_("Last quit message")] = na->GetLastQuit(); - - if (!na->GetAccount()->GetEmail().empty() && (show_hidden || !na->GetAccount()->HasFieldS("HIDE_EMAIL"))) - info[_("Email address")] = na->GetAccount()->GetEmail(); - - if (show_hidden) - { - if (na->HasVhost()) - { - if (IRCD->CanSetVIdent && !na->GetVhostIdent().empty()) - info[_("VHost")] = na->GetVhostIdent() + "@" + na->GetVhostHost(); - else - info[_("VHost")] = na->GetVhostHost(); - } - } - - this->onnickinfo(&Event::NickInfo::OnNickInfo, source, na, info, show_hidden); - - std::vector replies; - info.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Displays information about the given nickname, such as\n" - "the nick's owner, last seen address and time, and nick\n" - "options. If no nick is given, and you are identified,\n" - "your account name is used, else your current nickname is\n" - "used.")); - - return true; - } -}; - - -class CommandNSSetHide : public Command -{ - public: - CommandNSSetHide(Module *creator, const Anope::string &sname = "nickserv/set/hide", size_t min = 2) : Command(creator, sname, min, min + 1) - { - this->SetDesc(_("Hide certain pieces of nickname information")); - this->SetSyntax("{EMAIL | STATUS | USERMASK | QUIT} {ON | OFF}"); - } - - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m, const Anope::string &arg) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - NickServ::Account *nc = na->GetAccount(); - - EventReturn MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); - if (MOD_RESULT == EVENT_STOP) - return; - - const char *onmsg, *offmsg, *flag; - - if (param.equals_ci("EMAIL")) - { - flag = "HIDE_EMAIL"; - onmsg = _("The \002e-mail address\002 of \002{0}\002 will now be \002hidden\002."); - offmsg = _("The \002e-mail address\002 of \002{0}\002 will now be \002shown\002."); - } - else if (param.equals_ci("USERMASK")) - { - flag = "HIDE_MASK"; - onmsg = _("The \002last seen host mask\002 of \002{0}\002 will now be \002hidden\002."); - offmsg = _("The \002last seen host mask\002 of \002{0}\002 will now be \002shown\002."); - } - else if (param.equals_ci("STATUS")) - { - flag = "HIDE_STATUS"; - onmsg = _("The \002services operator status\002 of \002{0}\002 will now be \002hidden\002."); - offmsg = _("The \002services operator status\002 of \002{0}\002 will now be \002shown\002."); - } - else if (param.equals_ci("QUIT")) - { - flag = "HIDE_QUIT"; - onmsg = _("The \002last quit message\002 of \002{0}\002 will now be \002hidden\002."); - offmsg = _("The \002last quit message\002 of \002{0}\002 will now be \002shown\002."); - } - else - { - this->OnSyntaxError(source, "HIDE"); - return; - } - - if (arg.equals_ci("ON")) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change hide " << param.upper() << " to " << arg.upper() << " for " << nc->GetDisplay(); - nc->SetS(flag, true); - source.Reply(onmsg, nc->GetDisplay(), source.service->nick); - } - else if (arg.equals_ci("OFF")) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change hide " << param.upper() << " to " << arg.upper() << " for " << nc->GetDisplay(); - nc->UnsetS(flag); - source.Reply(offmsg, nc->GetDisplay(), source.service->nick); - } - else - this->OnSyntaxError(source, "HIDE"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), params[0], params[1]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Allows you to prevent certain pieces of information from being displayed when someone does a %s \002INFO\002 on you." //XXX - " You can hide the e-mail address (\002EMAIL\002), last seen hostmask (\002USERMASK\002), the services access status (\002STATUS\002) and last quit message (\002QUIT\002)." - "The second parameter specifies whether the information should\n" - "be displayed (\002OFF\002) or hidden (\002ON\002)."), source.service->nick); - return true; - } -}; - -class CommandNSSASetHide : public CommandNSSetHide -{ - public: - CommandNSSASetHide(Module *creator) : CommandNSSetHide(creator, "nickserv/saset/hide", 3) - { - this->SetSyntax(_("\037nickname\037 {EMAIL | STATUS | USERMASK | QUIT} {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->ClearSyntax(); - this->Run(source, params[0], params[1], params[2]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Allows you to prevent certain pieces of information from being displayed when someone does a %s \002INFO\002 on the \037nickname\037. " - " You can hide the e-mail address (\002EMAIL\002), last seen hostmask (\002USERMASK\002), the services access status (\002STATUS\002) and last quit message (\002QUIT\002)." - " The second parameter specifies whether the information should be displayed (\002OFF\002) or hidden (\002ON\002)."), - source.service->nick); - return true; - } -}; - -class NSInfo : public Module -{ - CommandNSInfo commandnsinfo; - - CommandNSSetHide commandnssethide; - CommandNSSASetHide commandnssasethide; - - EventHandlers onnickinfo; - - Serialize::Field hide_email, hide_usermask, hide_status, hide_quit; - - public: - NSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsinfo(this, onnickinfo) - , commandnssethide(this) - , commandnssasethide(this) - , onnickinfo(this) - , hide_email(this, NickServ::account, "HIDE_EMAIL") - , hide_usermask(this, NickServ::account, "HIDE_MASK") - , hide_status(this, NickServ::account, "HIDE_STATUS") - , hide_quit(this, NickServ::account, "HIDE_QUIT") - { - - } -}; - -MODULE_INIT(NSInfo) diff --git a/modules/commands/ns_list.cpp b/modules/commands/ns_list.cpp deleted file mode 100644 index 2820326d2..000000000 --- a/modules/commands/ns_list.cpp +++ /dev/null @@ -1,290 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_info.h" -#include "modules/ns_set.h" - -class CommandNSList : public Command -{ - public: - CommandNSList(Module *creator) : Command(creator, "nickserv/list", 1, 2) - { - this->SetDesc(_("List all registered nicknames that match a given pattern")); - this->SetSyntax(_("\037pattern\037 [SUSPENDED] [NOEXPIRE] [UNCONFIRMED]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - - Anope::string pattern = params[0]; - const NickServ::Account *mync; - unsigned nnicks; - bool is_servadmin = source.HasCommand("nickserv/list"); - int count = 0, from = 0, to = 0; - bool suspended, nsnoexpire, unconfirmed; - unsigned listmax = Config->GetModule(this->owner)->Get("listmax", "50"); - - suspended = nsnoexpire = unconfirmed = false; - - if (pattern[0] == '#') - { - Anope::string n1, n2; - sepstream(pattern.substr(1), '-').GetToken(n1, 0); - sepstream(pattern, '-').GetToken(n2, 1); - try - { - from = convertTo(n1); - to = convertTo(n2); - } - catch (const ConvertException &) - { - source.Reply(_("Incorrect range specified. The correct syntax is \002#\037from\037-\037to\037\002.")); - return; - } - - pattern = "*"; - } - - nnicks = 0; - - if (is_servadmin && params.size() > 1) - { - Anope::string keyword; - spacesepstream keywords(params[1]); - while (keywords.GetToken(keyword)) - { - if (keyword.equals_ci("NOEXPIRE")) - nsnoexpire = true; - if (keyword.equals_ci("SUSPENDED")) - suspended = true; - if (keyword.equals_ci("UNCONFIRMED")) - unconfirmed = true; - } - } - - mync = source.nc; - ListFormatter list(source.GetAccount()); - - list.AddColumn(_("Nick")).AddColumn(_("Last usermask")); - - // XXX wtf - Anope::map ordered_map; - for (NickServ::Nick *na : NickServ::service->GetNickList()) - ordered_map[na->GetNick()] = na; - - for (Anope::map::const_iterator it = ordered_map.begin(), it_end = ordered_map.end(); it != it_end; ++it) - { - NickServ::Nick *na = it->second; - - /* Don't show private nicks to non-services admins. */ - if (na->GetAccount()->HasFieldS("NS_PRIVATE") && !is_servadmin && na->GetAccount() != mync) - continue; - else if (nsnoexpire && !na->HasFieldS("NS_NO_EXPIRE")) - continue; - else if (suspended && !na->GetAccount()->HasFieldS("NS_SUSPENDED")) - continue; - else if (unconfirmed && !na->GetAccount()->HasFieldS("UNCONFIRMED")) - continue; - - /* We no longer compare the pattern against the output buffer. - * Instead we build a nice nick!user@host buffer to compare. - * The output is then generated separately. -TheShadow */ - Anope::string buf = Anope::printf("%s!%s", na->GetNick().c_str(), !na->GetLastUsermask().empty() ? na->GetLastUsermask().c_str() : "*@*"); - if (na->GetNick().equals_ci(pattern) || Anope::Match(buf, pattern, false, true)) - { - if (((count + 1 >= from && count + 1 <= to) || (!from && !to)) && ++nnicks <= listmax) - { - bool isnoexpire = false; - if (is_servadmin && na->HasFieldS("NS_NO_EXPIRE")) - isnoexpire = true; - - ListFormatter::ListEntry entry; - entry["Nick"] = (isnoexpire ? "!" : "") + na->GetNick(); - if (na->GetAccount()->HasFieldS("HIDE_MASK") && !is_servadmin && na->GetAccount() != mync) - entry["Last usermask"] = Language::Translate(source.GetAccount(), _("[Hostname hidden]")); - else if (na->GetAccount()->HasFieldS("NS_SUSPENDED")) - entry["Last usermask"] = Language::Translate(source.GetAccount(), _("[Suspended]")); - else if (na->GetAccount()->HasFieldS("UNCONFIRMED")) - entry["Last usermask"] = Language::Translate(source.GetAccount(), _("[Unconfirmed]")); - else - entry["Last usermask"] = na->GetLastUsermask(); - list.AddEntry(entry); - } - ++count; - } - } - - source.Reply(_("List of entries matching \002{0}\002:"), pattern); - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("End of list - \002{0}\002/\002{1}\002 matches shown."), nnicks > listmax ? listmax : nnicks, nnicks); - return; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Lists all registered nicknames which match the given pattern, in \037nick!user@host\037 format." - " Nicks with the \002PRIVATE\002 option set will only be displayed to Services Operators with the proper access." - " Nicks with the \002NOEXPIRE\002 option set will have a \002!\002 prefixed to the nickname for Services Operators to see.\n" - "\n" - "Note that a preceding '#' specifies a range.\n" - "\n" - "If the SUSPENDED, UNCONFIRMED or NOEXPIRE options are given, only\n" - "nicks which, respectively, are SUSPENDED, UNCONFIRMED or have the\n" - "NOEXPIRE flag set will be displayed. If multiple options are\n" - "given, all nicks matching at least one option will be displayed.\n" - "Note that these options are limited to \037Services Operators\037.\n" - "\n" - "Examples:\n" - "\n" - " {0} *!joeuser@foo.com\n" - " Lists all registered nicks owned by joeuser@foo.com.\n" - "\n" - " {0} *Bot*!*@*\n" - " Lists all registered nicks with \002Bot\002 in their names (case insensitive).\n" - "\n" - " {0} * NOEXPIRE\n" - " Lists all registered nicks which have been set to not expire.\n" - "\n" - " {0} #51-100\n" - " Lists all registered nicks within the given range (51-100).")); - - const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); - if (!regexengine.empty()) - { - source.Reply(" "); - source.Reply(_("Regex matches are also supported using the {0} engine. Enclose your pattern in // if this is desired."), regexengine); - } - - return true; - } -}; - - -class CommandNSSetPrivate : public Command -{ - public: - CommandNSSetPrivate(Module *creator, const Anope::string &sname = "nickserv/set/private", size_t min = 1) : Command(creator, sname, min, min + 1) - { - this->SetDesc(_("Prevent your account from appearing in the LIST command")); - this->SetSyntax("{ON | OFF}"); - } - - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - NickServ::Account *nc = na->GetAccount(); - - EventReturn MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (param.equals_ci("ON")) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable private for " << nc->GetDisplay(); - nc->SetS("NS_PRIVATE", true); - source.Reply(_("Private option is now \002on\002 for \002{0}\002."), nc->GetDisplay()); - } - else if (param.equals_ci("OFF")) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable private for " << nc->GetDisplay(); - nc->UnsetS("NS_PRIVATE"); - source.Reply(_("Private option is now \002off\002 for \002{0}\002."), nc->GetDisplay()); - } - else - this->OnSyntaxError(source, "PRIVATE"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), params[0]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Turns the privacy option on or off for your account." - " When \002PRIVATE\002 is set, your account will not appear in the account list." - " However, anyone who knows your account can still request information about it.")); - return true; - } -}; - -class CommandNSSASetPrivate : public CommandNSSetPrivate -{ - public: - CommandNSSASetPrivate(Module *creator) : CommandNSSetPrivate(creator, "nickserv/saset/private", 2) - { - this->ClearSyntax(); - this->SetSyntax(_("\037account\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params[1]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Turns the privacy option on or off for \037account\037." - " When \002PRIVATE\002 is set, the account will not appear in the account list." - " However, anyone who knows your account can still request information about it.")); - return true; - } -}; - - -class NSList : public Module - , public EventHook -{ - CommandNSList commandnslist; - - CommandNSSetPrivate commandnssetprivate; - CommandNSSASetPrivate commandnssasetprivate; - - Serialize::Field priv; - - public: - NSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnslist(this) - , commandnssetprivate(this) - , commandnssasetprivate(this) - , priv(this, NickServ::account, "NS_PRIVATE") - { - } - - void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_all) override - { - if (!show_all) - return; - - if (priv.HasExt(na->GetAccount())) - info.AddOption(_("Private")); - } -}; - -MODULE_INIT(NSList) diff --git a/modules/commands/ns_logout.cpp b/modules/commands/ns_logout.cpp deleted file mode 100644 index 9bcb543fc..000000000 --- a/modules/commands/ns_logout.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/nickserv.h" - -class CommandNSLogout : public Command -{ - public: - CommandNSLogout(Module *creator) : Command(creator, "nickserv/logout", 0, 2) - { - this->SetDesc(_("Reverses the effect of the IDENTIFY command")); - this->SetSyntax(_("[\037nickname\037 [REVALIDATE]]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - - const Anope::string &nick = !params.empty() ? params[0] : ""; - const Anope::string ¶m = params.size() > 1 ? params[1] : ""; - - if (!source.IsServicesOper() && !nick.empty()) - { - this->OnSyntaxError(source, ""); - return; - } - - User *u2 = !nick.empty() ? User::Find(nick, true) : source.GetUser(); - if (!u2) - { - source.Reply(_("\002{0}\002 isn't currently online."), !nick.empty() ? nick : source.GetNick()); - return; - } - - if (!nick.empty() && u2->IsServicesOper()) - { - source.Reply(_("You can't logout \002{0}\002, they are a Services Operator."), nick); - return; - } - -#if 0 - if (!nick.empty() && !param.empty() && param.equals_ci("REVALIDATE") && NickServ::service) - NickServ::service->Validate(u2); -#endif - - u2->super_admin = false; /* Dont let people logout and remain a SuperAdmin */ - Log(LOG_COMMAND, source, this) << "to logout " << u2->nick; - - if (!nick.empty()) - source.Reply(_("\002{0}\002 has been logged out."), nick); - else - source.Reply(_("You have been logged out.")); - - IRCD->SendLogout(u2); - u2->RemoveMode(source.service, "REGISTERED"); - u2->Logout(); - - /* Send out an event */ - Event::OnNickLogout(&Event::NickLogout::OnNickLogout, u2); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Logs you out of your account")); - #if 0 - source.Reply(_("Without a parameter, reverses the effect of the \002IDENTIFY\002\n" - "command, i.e. make you not recognized as the real owner of the nick\n" - "anymore. Note, however, that you won't be asked to reidentify\n" - "yourself.\n" - " \n" - "With a parameter, does the same for the given nick. If you\n" - "specify \002REVALIDATE\002 as well, Services will ask the given nick\n" - "to re-identify. This is limited to \002Services Operators\002.")); - #endif - - return true; - } -}; - -class NSLogout : public Module -{ - CommandNSLogout commandnslogout; - - public: - NSLogout(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnslogout(this) - { - - } -}; - -MODULE_INIT(NSLogout) diff --git a/modules/commands/ns_recover.cpp b/modules/commands/ns_recover.cpp deleted file mode 100644 index 65033bab9..000000000 --- a/modules/commands/ns_recover.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_cert.h" -#include "modules/nickserv.h" - -typedef std::map NSRecoverInfo; - -class NSRecoverRequestListener : public NickServ::IdentifyRequestListener -{ - CommandSource source; - Command *cmd; - Anope::string user; - Anope::string pass; - - public: - NSRecoverRequestListener(CommandSource &src, Command *c, const Anope::string &nick, const Anope::string &p) : source(src), cmd(c), user(nick), pass(p) { } - - void OnSuccess(NickServ::IdentifyRequest *) override - { - User *u = User::Find(user, true); - if (!source.GetUser() || !source.service) - return; - - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - return; - - Log(LOG_COMMAND, source, cmd) << "for " << na->GetNick(); - - /* Nick is being held by us, release it */ - if (na->HasFieldS("HELD")) - { - NickServ::service->Release(na); - source.Reply(_("Service's hold on \002{0}\002 has been released."), na->GetNick()); - } - else if (!u) - { - source.Reply(_("No one is using your nick, and services are not holding it.")); - } - // If the user being recovered is identified for the account of the nick then the user is the - // same person that is executing the command, so kill them off (old GHOST command). - else if (u->Account() == na->GetAccount()) - { - if (!source.GetAccount() && na->GetAccount()->HasFieldS("NS_SECURE")) - { - source.GetUser()->Login(u->Account()); - Log(LOG_COMMAND, source, cmd) << "and was automatically identified to " << u->Account()->GetDisplay(); - } - - if (Config->GetModule("ns_recover")->Get("restoreonrecover")) - { - if (!u->chans.empty()) - { - NSRecoverInfo i; - for (User::ChanUserList::iterator it = u->chans.begin(), it_end = u->chans.end(); it != it_end; ++it) - i[it->first->name] = it->second->status; - source.GetUser()->Extend("recover", i); - } - } - - u->SendMessage(*source.service, _("This nickname has been recovered by \002{0}\002. If you did not do this, then \002{0}\002 may have your password, and you should change it."), - source.GetNick()); - - Anope::string buf = source.command.upper() + " command used by " + source.GetNick(); - u->Kill(*source.service, buf); - - source.Reply(_("Ghost with your nick has been killed.")); - - if (IRCD->CanSVSNick) - IRCD->SendForceNickChange(source.GetUser(), user, Anope::CurTime); - } - /* User is not identified or not identified to the same account as the person using this command */ - else - { - if (!source.GetAccount() && na->GetAccount()->HasFieldS("NS_SECURE")) - { - source.GetUser()->Login(na->GetAccount()); // Identify the user using the command if they arent identified - Log(LOG_COMMAND, source, cmd) << "and was automatically identified to " << na->GetNick() << " (" << na->GetAccount()->GetDisplay() << ")"; - } - - u->SendMessage(*source.service, _("This nickname has been recovered by \002{0}\002."), source.GetNick()); - if (NickServ::service) - NickServ::service->Collide(u, na); - - if (IRCD->CanSVSNick) - { - /* If we can svsnick then release our hold and svsnick the user using the command */ - if (NickServ::service) - NickServ::service->Release(na); - IRCD->SendForceNickChange(source.GetUser(), user, Anope::CurTime); - source.Reply(_("You have regained control of \002%s\002 and are now identified as \002%s\002."), user, na->GetAccount()->GetDisplay().c_str()); - } - else - source.Reply(_("The user with your nick has been removed. Use this command again to release services's hold on your nick.")); - } - } - - void OnFail(NickServ::IdentifyRequest *) override - { - if (NickServ::FindNick(user) != NULL) - { - source.Reply(_("Access denied.")); - if (!pass.empty()) - { - Log(LOG_COMMAND, source, cmd) << "with an invalid password for " << user; - if (source.GetUser()) - source.GetUser()->BadPassword(); - } - } - else - source.Reply(_("\002{0}\002 isn't registered."), user); - } -}; - -class CommandNSRecover : public Command -{ - public: - CommandNSRecover(Module *creator) : Command(creator, "nickserv/recover", 1, 2) - { - this->SetDesc(_("Regains control of your nick")); - this->SetSyntax(_("\037nickname\037 [\037password\037]")); - this->AllowUnregistered(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &nick = params[0]; - const Anope::string &pass = params.size() > 1 ? params[1] : ""; - - User *user = User::Find(nick, true); - - if (user && source.GetUser() == user) - { - source.Reply(_("You can't %s yourself!"), source.command.lower().c_str()); - return; - } - - NickServ::Nick *na = NickServ::FindNick(nick); - - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - if (na->GetAccount()->HasFieldS("NS_SUSPENDED")) - { - source.Reply(_("\002{0}\002 is suspended."), na->GetNick()); - return; - } - - bool ok = false; - if (source.GetAccount() == na->GetAccount()) - ok = true; - else if (!na->GetAccount()->HasFieldS("NS_SECURE") && source.GetUser() && na->GetAccount()->IsOnAccess(source.GetUser())) - ok = true; - - if (certservice && source.GetUser() && certservice->Matches(source.GetUser(), na->GetAccount())) - ok = true; - - if (ok == false && !pass.empty()) - { - NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new NSRecoverRequestListener(source, this, na->GetNick(), pass), owner, na->GetNick(), pass); - Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, source.GetUser(), req); - req->Dispatch(); - } - else - { - NSRecoverRequestListener req(source, this, na->GetNick(), pass); - - if (ok) - req.OnSuccess(nullptr); - else - req.OnFail(nullptr); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Recovers your nickname from another user or from services." - "If services are currently holding your nickname, the hold will be released." - " If another user is holding your nickname and is identified they will be killed." - " If they are not identified they will be forced off of the nickname.")); - return true; - } -}; - -class NSRecover : public Module - , public EventHook - , public EventHook -{ - CommandNSRecover commandnsrecover; - ExtensibleItem recover; - - public: - NSRecover(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsrecover(this) - , recover(this, "recover") - { - - if (Config->GetModule("nickserv")->Get("nonicknameownership")) - throw ModuleException(modname + " can not be used with options:nonicknameownership enabled"); - - } - - void OnUserNickChange(User *u, const Anope::string &oldnick) override - { - if (Config->GetModule(this)->Get("restoreonrecover")) - { - NSRecoverInfo *ei = recover.Get(u); - ServiceBot *NickServ = Config->GetClient("NickServ"); - - if (ei != NULL && NickServ != NULL) - for (NSRecoverInfo::iterator it = ei->begin(), it_end = ei->end(); it != it_end;) - { - Channel *c = Channel::Find(it->first); - const Anope::string &cname = it->first; - ++it; - - /* User might already be on the channel */ - if (u->FindChannel(c)) - this->OnJoinChannel(u, c); - else if (IRCD->CanSVSJoin) - IRCD->SendSVSJoin(NickServ, u, cname, ""); - } - } - } - - void OnJoinChannel(User *u, Channel *c) override - { - if (Config->GetModule(this)->Get("restoreonrecover")) - { - NSRecoverInfo *ei = recover.Get(u); - - if (ei != NULL) - { - NSRecoverInfo::iterator it = ei->find(c->name); - if (it != ei->end()) - { - for (size_t i = 0; i < it->second.Modes().length(); ++i) - c->SetMode(c->ci->WhoSends(), ModeManager::FindChannelModeByChar(it->second.Modes()[i]), u->GetUID()); - - ei->erase(it); - if (ei->empty()) - recover.Unset(u); - } - } - } - } -}; - -MODULE_INIT(NSRecover) diff --git a/modules/commands/ns_register.cpp b/modules/commands/ns_register.cpp deleted file mode 100644 index c7e6e5af2..000000000 --- a/modules/commands/ns_register.cpp +++ /dev/null @@ -1,426 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/nickserv.h" - -static bool SendRegmail(User *u, NickServ::Nick *na, ServiceBot *bi); - -class CommandNSConfirm : public Command -{ - public: - CommandNSConfirm(Module *creator) : Command(creator, "nickserv/confirm", 1, 2) - { - this->SetDesc(_("Confirm a passcode")); - this->SetSyntax(_("\037passcode\037")); - this->AllowUnregistered(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &passcode = params[0]; - - if (source.nc && !source.nc->HasFieldS("UNCONFIRMED") && source.HasPriv("nickserv/confirm")) - { - NickServ::Nick *na = NickServ::FindNick(passcode); - if (na == NULL) - { - source.Reply(_("\002{0}\002 isn't registered."), passcode); - return; - } - - if (na->GetAccount()->HasFieldS("UNCONFIRMED") == false) - { - source.Reply(_("\002{0}\002 is already confirmed."), na->GetNick()); - return; - } - - na->GetAccount()->UnsetS("UNCONFIRMED"); - if (NickServ::Event::OnNickConfirm) - NickServ::Event::OnNickConfirm(&NickServ::Event::NickConfirm::OnNickConfirm, source.GetUser(), na->GetAccount()); - Log(LOG_ADMIN, source, this) << "to confirm nick " << na->GetNick() << " (" << na->GetAccount()->GetDisplay() << ")"; - source.Reply(_("\002{0}\002 has been confirmed."), na->GetNick()); - } - else if (source.nc) - { - Anope::string *code = source.nc->GetExt("passcode"); - if (code == nullptr || *code != passcode) - { - source.Reply(_("Invalid passcode.")); - return; - } - - NickServ::Account *nc = source.nc; - nc->ShrinkOK("passcode"); - Log(LOG_COMMAND, source, this) << "to confirm their email"; - source.Reply(_("Your email address of \002{0}\002 has been confirmed."), source.nc->GetEmail()); - nc->UnsetS("UNCONFIRMED"); - if (NickServ::Event::OnNickConfirm) - NickServ::Event::OnNickConfirm(&NickServ::Event::NickConfirm::OnNickConfirm, source.GetUser(), nc); - - if (source.GetUser()) - { - NickServ::Nick *na = NickServ::FindNick(source.GetNick()); - if (na) - { - IRCD->SendLogin(source.GetUser(), na); - if (!Config->GetModule("nickserv")->Get("nonicknameownership") && na->GetAccount() == source.GetAccount() && !na->GetAccount()->HasFieldS("UNCONFIRMED")) - source.GetUser()->SetMode(source.service, "REGISTERED"); - } - } - } - else - source.Reply(_("Invalid passcode.")); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("This command is used by several commands as a way to confirm changes made to your account.\n" - "\n" - "This is most commonly used to confirm your email address once you register or change it.\n" - "\n" - "This is also used after when resetting your password to force identify you to your account so you may change your password.")); - if (source.HasPriv("nickserv/confirm")) - source.Reply(_("Additionally, Services Operators with the \037nickserv/confirm\037 permission can\n" - "replace \037passcode\037 with a users nick to force validate them.")); - return true; - } -}; - -class CommandNSRegister : public Command -{ - public: - CommandNSRegister(Module *creator) : Command(creator, "nickserv/register", 1, 2) - { - this->SetDesc(_("Register a nickname")); - if (Config->GetModule("nickserv")->Get("forceemail", "yes")) - this->SetSyntax(_("\037password\037 \037email\037")); - else - this->SetSyntax(_("\037password\037 \037[email]\037")); - this->AllowUnregistered(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - User *u = source.GetUser(); - Anope::string u_nick = source.GetNick(); - size_t nicklen = u_nick.length(); - Anope::string pass = params[0]; - Anope::string email = params.size() > 1 ? params[1] : ""; - const Anope::string &nsregister = Config->GetModule(this->owner)->Get("registration"); - - if (Anope::ReadOnly) - { - source.Reply(_("Sorry, nickname registration is temporarily disabled.")); - return; - } - - if (nsregister.equals_ci("disable")) - { - source.Reply(_("Registration is currently disabled.")); - return; - } - - time_t nickregdelay = Config->GetModule(this->owner)->Get("nickregdelay"); - time_t reg_delay = Config->GetModule("nickserv")->Get("regdelay"); - if (u && !u->HasMode("OPER") && nickregdelay && Anope::CurTime - u->timestamp < nickregdelay) - { - source.Reply(_("You must have been using this nickname for at least {0} seconds to register."), nickregdelay); - return; - } - - /* Prevent "Guest" nicks from being registered. -TheShadow */ - - /* Guest nick can now have a series of between 1 and 7 digits. - * --lara - */ - const Anope::string &guestnick = Config->GetModule("nickserv")->Get("guestnickprefix", "Guest"); - if (nicklen <= guestnick.length() + 7 && nicklen >= guestnick.length() + 1 && !u_nick.find_ci(guestnick) && u_nick.substr(guestnick.length()).find_first_not_of("1234567890") == Anope::string::npos) - { - source.Reply(_("\002{0}\002 may not be registered."), u_nick); - return; - } - - if (!IRCD->IsNickValid(u_nick)) - { - source.Reply(_("\002{0}\002 may not be registered."), u_nick); - return; - } - - if (ServiceBot::Find(u_nick, true)) - { - source.Reply(_("\002{0}\002 may not be registered."), u_nick); - return; - } - - if (Config->GetModule("nickserv")->Get("restrictopernicks")) - for (Oper *o : Serialize::GetObjects(operblock)) - { - if (!source.IsOper() && u_nick.find_ci(o->GetName()) != Anope::string::npos) - { - source.Reply(_("\002{0}\002 may not be registered."), u_nick); - return; - } - } - - unsigned int passlen = Config->GetModule("nickserv")->Get("passlen", "32"); - - if (Config->GetModule("nickserv")->Get("forceemail", "yes") && email.empty()) - { - this->OnSyntaxError(source, ""); - return; - } - - if (u && Anope::CurTime < u->lastnickreg + reg_delay) - { - source.Reply(_("Please wait \002{0}\002 seconds before using the {1} command again."), (u->lastnickreg + reg_delay) - Anope::CurTime, source.command); - return; - } - - if (NickServ::FindNick(u_nick) != NULL) - { - source.Reply(_("\002{0}\002 is already registered."), u_nick); - return; - } - - if (pass.equals_ci(u_nick) || (Config->GetBlock("options")->Get("strictpasswords") && pass.length() < 5)) - { - source.Reply(_("Please try again with a more obscure password. Passwords should be at least five characters long, should not be something easily guessed" - " (e.g. your real name or your nickname), and cannot contain the space or tab characters.")); - return; - } - - if (pass.length() > Config->GetModule("nickserv")->Get("passlen", "32")) - { - source.Reply(_("Your password is too long, it can not contain more than \002{0}\002 characters."), Config->GetModule("nickserv")->Get("passlen", "32")); - return; - } - - if (!email.empty() && !Mail::Validate(email)) - { - source.Reply(_("\002{0}\002 is not a valid e-mail address."), email); - return; - } - - NickServ::Account *nc = NickServ::account.Create(); - nc->SetDisplay(u_nick); - - NickServ::Nick *na = NickServ::nick.Create(); - na->SetNick(u_nick); - na->SetAccount(nc); - Anope::string epass; - Anope::Encrypt(pass, epass); - nc->SetPassword(epass); - if (!email.empty()) - nc->SetEmail(email); - - if (u) - { - na->SetLastUsermask(u->GetIdent() + "@" + u->GetDisplayedHost()); - na->SetLastRealname(u->realname); - } - else - na->SetLastRealname(source.GetNick()); - - Log(LOG_COMMAND, source, this) << "to register " << na->GetNick() << " (email: " << (!na->GetAccount()->GetEmail().empty() ? na->GetAccount()->GetEmail() : "none") << ")"; - - source.Reply(_("\002{0}\002 has been registered."), u_nick); - - Anope::string tmp_pass; - if (Anope::Decrypt(na->GetAccount()->GetPassword(), tmp_pass)) - source.Reply(_("Your password is \002{0}\002 - remember this for later use."), tmp_pass); - - if (nsregister.equals_ci("admin")) - { - nc->SetS("UNCONFIRMED", true); - // User::Identify() called below will notify the user that their registration is pending - } - else if (nsregister.equals_ci("mail")) - { - if (!email.empty()) - { - nc->SetS("UNCONFIRMED", true); - SendRegmail(NULL, na, source.service); - } - } - - if (NickServ::Event::OnNickRegister) - NickServ::Event::OnNickRegister(&NickServ::Event::NickRegister::OnNickRegister, source.GetUser(), na, pass); - - if (u) - { - u->Identify(na); - u->lastnickreg = Anope::CurTime; - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Registers your nickname. Once your nickname is registered, you will be able to use most features of services, including owning and managing channels." - "Make sure you remember the password - you'll need it to identify yourself later. Your email address will only be used if you forget your password.")); - - if (!Config->GetModule("nickserv")->Get("forceemail", "yes")) - { - source.Reply(" "); - source.Reply(_("The \037email\037 parameter is optional and will set the email\n" - "for your nick immediately.\n" - "Your privacy is respected; this e-mail won't be given to\n" - "any third-party person. You may also wish to \002SET HIDE\002 it\n" - "after registering if it isn't the default setting already.")); - } - - if (!Config->GetModule("nickserv")->Get("nonicknameownership")) - { - source.Reply(" "); - source.Reply(_("This command also creates a new group for your nickname, which will allow you to group other nicknames later, which share the same configuration, the same set of memos and the same channel privileges.")); - } - return true; - } -}; - -class CommandNSResend : public Command -{ - public: - CommandNSResend(Module *creator) : Command(creator, "nickserv/resend", 0, 0) - { - this->SetDesc(_("Resend registration confirmation email")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (!Config->GetModule(this->owner)->Get("registration").equals_ci("mail")) - { - source.Reply(_("Access denied.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(source.GetNick()); - - if (na == NULL) - { - source.Reply(_("Your nickname isn't registered.")); - return; - } - - if (na->GetAccount() != source.GetAccount() || !source.nc->HasFieldS("UNCONFIRMED")) - { - source.Reply(_("Your account is already confirmed.")); - return; - } - - if (Anope::CurTime < source.nc->lastmail + Config->GetModule(this->owner)->Get("resenddelay")) - { - source.Reply(_("Cannot send mail now; please retry a little later.")); - return; - } - - if (!SendRegmail(source.GetUser(), na, source.service)) - { - Log(this->owner) << "Unable to resend registration verification code for " << source.GetNick(); - return; - } - - na->GetAccount()->lastmail = Anope::CurTime; - source.Reply(_("Your passcode has been re-sent to \002{0}\002."), na->GetAccount()->GetEmail()); - Log(LOG_COMMAND, source, this) << "to resend registration verification code"; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (!Config->GetModule(this->owner)->Get("registration").equals_ci("mail")) - return false; - - source.Reply(_("This command will resend you the registration confirmation email.")); - return true; - } - - void OnServHelp(CommandSource &source) override - { - if (Config->GetModule(this->owner)->Get("registration").equals_ci("mail")) - Command::OnServHelp(source); - } -}; - -class NSRegister : public Module - , public EventHook - , public EventHook -{ - CommandNSRegister commandnsregister; - CommandNSConfirm commandnsconfirm; - CommandNSResend commandnsrsend; - - Serialize::Field unconfirmed; - Serialize::Field passcode; - - public: - NSRegister(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsregister(this) - , commandnsconfirm(this) - , commandnsrsend(this) - , unconfirmed(this, NickServ::account, "UNCONFIRMED") - , passcode(this, NickServ::account, "passcode") - { - if (Config->GetModule(this)->Get("registration").equals_ci("disable")) - throw ModuleException("Module " + this->name + " will not load with registration disabled."); - } - - void OnNickIdentify(User *u) override - { - ServiceBot *NickServ; - if (unconfirmed.HasExt(u->Account()) && (NickServ = Config->GetClient("NickServ"))) - { - const Anope::string &nsregister = Config->GetModule(this)->Get("registration"); - if (nsregister.equals_ci("admin")) - u->SendMessage(NickServ, _("All new accounts must be validated by an administrator. Please wait for your registration to be confirmed.")); - else - u->SendMessage(NickServ, _("Your email address is not confirmed. To confirm it, follow the instructions that were emailed to you.")); - NickServ::Nick *this_na = NickServ::FindNick(u->Account()->GetDisplay()); - time_t time_registered = Anope::CurTime - this_na->GetTimeRegistered(); - time_t unconfirmed_expire = Config->GetModule(this)->Get("unconfirmedexpire", "1d"); - if (unconfirmed_expire > time_registered) - u->SendMessage(NickServ, _("Your account will expire, if not confirmed, in %s."), Anope::Duration(unconfirmed_expire - time_registered, u->Account()).c_str()); - } - } - - void OnPreNickExpire(NickServ::Nick *na, bool &expire) override - { - if (unconfirmed.HasExt(na->GetAccount())) - { - time_t unconfirmed_expire = Config->GetModule(this)->Get("unconfirmedexpire", "1d"); - if (unconfirmed_expire && Anope::CurTime - na->GetTimeRegistered() >= unconfirmed_expire) - expire = true; - } - } -}; - -static bool SendRegmail(User *u, NickServ::Nick *na, ServiceBot *bi) -{ - NickServ::Account *nc = na->GetAccount(); - - Anope::string *code = na->GetAccount()->GetExt("passcode"); - if (code == NULL) - code = na->GetAccount()->Extend("passcode", Anope::Random(9)); - - Anope::string subject = Language::Translate(na->GetAccount(), Config->GetBlock("mail")->Get("registration_subject").c_str()), - message = Language::Translate(na->GetAccount(), Config->GetBlock("mail")->Get("registration_message").c_str()); - - subject = subject.replace_all_cs("%n", na->GetNick()); - subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); - subject = subject.replace_all_cs("%c", *code); - - message = message.replace_all_cs("%n", na->GetNick()); - message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); - message = message.replace_all_cs("%c", *code); - - return Mail::Send(u, nc, bi, subject, message); -} - -MODULE_INIT(NSRegister) diff --git a/modules/commands/ns_resetpass.cpp b/modules/commands/ns_resetpass.cpp deleted file mode 100644 index 3be5a4c06..000000000 --- a/modules/commands/ns_resetpass.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -static bool SendResetEmail(User *u, NickServ::Nick *na, ServiceBot *bi); - -class CommandNSResetPass : public Command -{ - public: - CommandNSResetPass(Module *creator) : Command(creator, "nickserv/resetpass", 2, 2) - { - this->SetDesc(_("Helps you reset lost passwords")); - this->SetSyntax(_("\037account\037 \037email\037")); - this->AllowUnregistered(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - NickServ::Nick *na = NickServ::FindNick(params[0]); - - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), params[0]); - return; - } - - if (!na->GetAccount()->GetEmail().equals_ci(params[1])) - { - source.Reply(_("Incorrect email address.")); - return; - } - - if (SendResetEmail(source.GetUser(), na, source.service)) - { - Log(LOG_COMMAND, source, this) << "for " << na->GetNick() << " (group: " << na->GetAccount()->GetDisplay() << ")"; - source.Reply(_("Password reset email for \002{0}\002 has been sent."), na->GetNick()); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sends a passcode to the email address of \037account\037 with instructions on how to reset their password. \037email\037 must be the email address associated to \037account\037.")); - return true; - } -}; - -struct ResetInfo -{ - Anope::string code; - time_t time; -}; - -class NSResetPass : public Module - , public EventHook -{ - CommandNSResetPass commandnsresetpass; - ExtensibleItem reset; - - public: - NSResetPass(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsresetpass(this), reset(this, "reset") - { - if (!Config->GetBlock("mail")->Get("usemail")) - throw ModuleException("Not using mail."); - } - - EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override - { - if (command->name == "nickserv/confirm" && params.size() > 1) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return EVENT_STOP; - } - - NickServ::Nick *na = NickServ::FindNick(params[0]); - - ResetInfo *ri = na ? reset.Get(na->GetAccount()) : NULL; - if (na && ri) - { - NickServ::Account *nc = na->GetAccount(); - const Anope::string &passcode = params[1]; - if (ri->time < Anope::CurTime - 3600) - { - reset.Unset(nc); - source.Reply(_("Your password reset request has expired.")); - } - else if (passcode.equals_cs(ri->code)) - { - reset.Unset(nc); - nc->UnsetS("UNCONFIRMED"); - - Log(LOG_COMMAND, source, &commandnsresetpass) << "confirmed RESETPASS to forcefully identify as " << na->GetNick(); - - if (source.GetUser()) - { - source.GetUser()->Identify(na); - source.Reply(_("You are now identified for \002{0}\002. Change your password now."), na->GetAccount()->GetDisplay()); - } - } - else - return EVENT_CONTINUE; - - return EVENT_STOP; - } - } - - return EVENT_CONTINUE; - } -}; - -static bool SendResetEmail(User *u, NickServ::Nick *na, ServiceBot *bi) -{ - Anope::string subject = Language::Translate(na->GetAccount(), Config->GetBlock("mail")->Get("reset_subject").c_str()), - message = Language::Translate(na->GetAccount(), Config->GetBlock("mail")->Get("reset_message").c_str()), - passcode = Anope::Random(20); - - subject = subject.replace_all_cs("%n", na->GetNick()); - subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); - subject = subject.replace_all_cs("%c", passcode); - - message = message.replace_all_cs("%n", na->GetNick()); - message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); - message = message.replace_all_cs("%c", passcode); - - na->GetAccount()->Extend("reset", ResetInfo{passcode, Anope::CurTime}); - - return Mail::Send(u, na->GetAccount(), bi, subject, message); -} - -MODULE_INIT(NSResetPass) diff --git a/modules/commands/ns_set.cpp b/modules/commands/ns_set.cpp deleted file mode 100644 index a75386743..000000000 --- a/modules/commands/ns_set.cpp +++ /dev/null @@ -1,1244 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_info.h" -#include "modules/ns_set.h" -#include "modules/nickserv.h" - -class CommandNSSet : public Command -{ - public: - CommandNSSet(Module *creator) : Command(creator, "nickserv/set", 1, 3) - { - this->SetDesc(_("Set options, including kill protection")); - this->SetSyntax(_("\037option\037 \037parameters\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets various options on your account\n" - "\n" - "Available options:")); - - Anope::string this_name = source.command; - bool hide_privileged_commands = Config->GetBlock("options")->Get("hideprivilegedcommands"), - hide_registered_commands = Config->GetBlock("options")->Get("hideregisteredcommands"); - for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) - { - const Anope::string &c_name = it->first; - const CommandInfo &info = it->second; - - if (c_name.find_ci(this_name + " ") == 0) - { - ServiceReference c("Command", info.name); - // XXX dup - if (!c) - continue; - else if (hide_registered_commands && !c->AllowUnregistered() && !source.GetAccount()) - continue; - else if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission)) - continue; - - source.command = c_name; - c->OnServHelp(source); - } - } - - CommandInfo *help = source.service->FindCommand("generic/help"); - if (help) - source.Reply(_("Type \002{0}{1} {2} {3} \037option\037\002 for more information on a particular option."), - Config->StrictPrivmsg, source.service->nick, help->cname, this_name); - - return true; - } -}; - -class CommandNSSASet : public Command -{ - public: - CommandNSSASet(Module *creator) : Command(creator, "nickserv/saset", 2, 4) - { - this->SetDesc(_("Set SET-options on another nickname")); - this->SetSyntax(_("\037option\037 \037nickname\037 \037parameters\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->OnSyntaxError(source, ""); - return; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Sets various options on other users accounts\n" - "\n" - "Available options:")); - - Anope::string this_name = source.command; - for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) - { - const Anope::string &c_name = it->first; - const CommandInfo &info = it->second; - - if (c_name.find_ci(this_name + " ") == 0) - { - ServiceReference command("Command", info.name); - if (command) - { - source.command = c_name; - command->OnServHelp(source); - } - } - } - - CommandInfo *help = source.service->FindCommand("generic/help"); - if (help) - source.Reply(_("Type \002{0}{1} {2} {3} \037option\037\002 for more information on a particular option."), - Config->StrictPrivmsg, source.service->nick, help->cname, this_name); - - return true; - } -}; - -class CommandNSSetPassword : public Command -{ - public: - CommandNSSetPassword(Module *creator) : Command(creator, "nickserv/set/password", 1) - { - this->SetDesc(_("Changes your password")); - this->SetSyntax(_("\037new-password\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string ¶m = params[0]; - unsigned len = param.length(); - - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - if (source.GetNick().equals_ci(param) || (Config->GetBlock("options")->Get("strictpasswords") && len < 5)) - { - source.Reply(_("Please try again with a more obscure password. Passwords should be at least five characters long, should not be something easily guessed (e.g. your real name or your nick), and cannot contain the space or tab characters.")); - return; - } - - if (len > Config->GetModule("nickserv")->Get("passlen", "32")) - { - source.Reply(_("Your password is too long, it can not contain more than \002{0}\002 characters."), Config->GetModule("nickserv")->Get("passlen", "32")); - return; - } - - Log(LOG_COMMAND, source, this) << "to change their password"; - - Anope::string tmp_pass; - Anope::Encrypt(param, tmp_pass); - source.nc->SetPassword(tmp_pass); - - if (Anope::Decrypt(source.nc->GetPassword(), tmp_pass)) - source.Reply(_("Password for \002{0}\002 changed to \002{1]\002."), source.nc->GetDisplay(), tmp_pass); - else - source.Reply(_("Password for \002{0}\002 changed."), source.nc->GetDisplay()); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Changes your password to \037new-password\037.")); - return true; - } -}; - -class CommandNSSASetPassword : public Command -{ - public: - CommandNSSASetPassword(Module *creator) : Command(creator, "nickserv/saset/password", 2, 2) - { - this->SetDesc(_("Changes the password of another user")); - this->SetSyntax(_("\037account\037 \037new-password\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *setter_na = NickServ::FindNick(params[0]); - if (setter_na == NULL) - { - source.Reply(_("\002{0}\002 isn't registered."), params[0]); - return; - } - NickServ::Account *nc = setter_na->GetAccount(); - - size_t len = params[1].length(); - - if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && source.nc != nc && nc->IsServicesOper()) - { - source.Reply(_("You may not change the password of other Services Operators.")); - return; - } - - if (nc->GetDisplay().equals_ci(params[1]) || (Config->GetBlock("options")->Get("strictpasswords") && len < 5)) - { - source.Reply(_("Please try again with a more obscure password. Passwords should be at least five characters long, should not be something easily guessed (e.g. your real name or your nick), and cannot contain the space or tab characters.")); - return; - } - - if (len > Config->GetModule("nickserv")->Get("passlen", "32")) - { - source.Reply(_("Your password is too long, it can not contain more than \002{0}\002 characters."), Config->GetModule("nickserv")->Get("passlen", "32")); - return; - } - - Log(LOG_ADMIN, source, this) << "to change the password of " << nc->GetDisplay(); - - Anope::string tmp_pass; - Anope::Encrypt(params[1], tmp_pass); - nc->SetPassword(tmp_pass); - if (Anope::Decrypt(nc->GetPassword(), tmp_pass) == 1) - source.Reply(_("Password for \002{0}\002 changed to \002{1}\002."), nc->GetDisplay(), tmp_pass); - else - source.Reply(_("Password for \002{0}\002 changed."), nc->GetDisplay()); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Changes the password of \037account\037 to \037new-password\037.")); - return true; - } -}; - -class CommandNSSetAutoOp : public Command -{ - public: - CommandNSSetAutoOp(Module *creator, const Anope::string &sname = "nickserv/set/autoop", size_t min = 1) : Command(creator, sname, min, min + 1) - { - this->SetDesc(_("Sets whether services should set channel status modes on you automatically.")); - this->SetSyntax("{ON | OFF}"); - } - - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(user); - if (na == NULL) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - NickServ::Account *nc = na->GetAccount(); - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (param.equals_ci("ON")) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable autoop for " << na->GetAccount()->GetDisplay(); - nc->SetS("AUTOOP", true); - source.Reply(_("Services will from now on set status modes on \002{0}\002 in channels."), nc->GetDisplay()); - } - else if (param.equals_ci("OFF")) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable autoop for " << na->GetAccount()->GetDisplay(); - nc->UnsetS("AUTOOP"); - source.Reply(_("Services will no longer set status modes on \002{0}\002 in channels."), nc->GetDisplay()); - } - else - this->OnSyntaxError(source, "AUTOOP"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), params[0]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Sets whether you will be given your channel status modes automatically when you join a channel." - " Note that depending on channel settings some modes may not get set automatically.")); - return true; - } -}; - -class CommandNSSASetAutoOp : public CommandNSSetAutoOp -{ - public: - CommandNSSASetAutoOp(Module *creator) : CommandNSSetAutoOp(creator, "nickserv/saset/autoop", 2) - { - this->ClearSyntax(); - this->SetSyntax(_("\037nickname\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params[1]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Sets whether the given nickname will be given their status modes automatically when they join a channel." - " Note that depending on channel settings some modes may not get set automatically.")); - return true; - } -}; - -class CommandNSSetDisplay : public Command -{ - public: - CommandNSSetDisplay(Module *creator, const Anope::string &sname = "nickserv/set/display", size_t min = 1) : Command(creator, sname, min, min + 1) - { - this->SetDesc(_("Set the display of your group in Services")); - this->SetSyntax(_("\037new-display\037")); - } - - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *user_na = NickServ::FindNick(user), *na = NickServ::FindNick(param); - - if (Config->GetModule("nickserv")->Get("nonicknameownership")) - { - source.Reply(_("This command may not be used on this network because nickname ownership is disabled.")); - return; - } - - if (user_na == NULL) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - - if (!na || na->GetAccount() != user_na->GetAccount()) - { - source.Reply(_("The new display must be a nickname of the nickname group \002{0}\02."), user_na->GetAccount()->GetDisplay()); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, user_na->GetAccount(), param); - if (MOD_RESULT == EVENT_STOP) - return; - - Log(user_na->GetAccount() == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the display of " << user_na->GetAccount()->GetDisplay() << " to " << na->GetNick(); - - user_na->GetAccount()->SetDisplay(na); - if (source.GetUser()) - IRCD->SendLogin(source.GetUser(), na); - source.Reply(_("The new display is now \002{0}\002."), user_na->GetAccount()->GetDisplay()); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), params[0]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Changes the display used to refer to your nickname group in services. The new display nickname must be a nickname of your group.")); - return true; - } -}; - -class CommandNSSASetDisplay : public CommandNSSetDisplay -{ - public: - CommandNSSASetDisplay(Module *creator) : CommandNSSetDisplay(creator, "nickserv/saset/display", 2) - { - this->ClearSyntax(); - this->SetSyntax(_("\037account\037 \037new-display\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params[1]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Changes the display used to refer to the nickname group \037account\037 in services. The new display nickname must be a nickname in the group of \037account\037.")); - return true; - } -}; - -class CommandNSSetEmail : public Command -{ - static bool SendConfirmMail(User *u, ServiceBot *bi, const Anope::string &new_email) - { - Anope::string code = Anope::Random(9); - - u->Account()->Extend >("ns_set_email", std::make_pair(new_email, code)); - - Anope::string subject = Config->GetBlock("mail")->Get("emailchange_subject"), - message = Config->GetBlock("mail")->Get("emailchange_message"); - - subject = subject.replace_all_cs("%e", u->Account()->GetEmail()); - subject = subject.replace_all_cs("%E", new_email); - subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); - subject = subject.replace_all_cs("%c", code); - - message = message.replace_all_cs("%e", u->Account()->GetEmail()); - message = message.replace_all_cs("%E", new_email); - message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); - message = message.replace_all_cs("%c", code); - - Anope::string old = u->Account()->GetEmail(); - u->Account()->SetEmail(new_email); - bool b = Mail::Send(u, u->Account(), bi, subject, message); - u->Account()->SetEmail(old); - return b; - } - - public: - CommandNSSetEmail(Module *creator, const Anope::string &cname = "nickserv/set/email", size_t min = 0) : Command(creator, cname, min, min + 1) - { - this->SetDesc(_("Associate an E-mail address with your nickname")); - this->SetSyntax(_("\037address\037")); - } - - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - NickServ::Account *nc = na->GetAccount(); - - if (nc->HasFieldS("UNCONFIRMED")) - { - source.Reply(_("You may not change the email of an unconfirmed account.")); - return; - } - - if (param.empty() && Config->GetModule("nickserv")->Get("forceemail", "yes")) - { - source.Reply(_("You cannot unset the e-mail on this network.")); - return; - } - - if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && source.nc != nc && nc->IsServicesOper()) - { - source.Reply(_("You may not change the e-mail of other Services Operators.")); - return; - } - - if (!param.empty() && !Mail::Validate(param)) - { - source.Reply(_("\002{0}\002 is not a valid e-mail address."), param); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (param.empty()) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to unset the email of " << nc->GetDisplay(); - nc->SetEmail(""); - source.Reply(_("E-mail address for \002{0}\002 unset."), nc->GetDisplay()); - } - else if (Config->GetModule("nickserv")->Get("confirmemailchanges") && !source.IsServicesOper()) - { - if (SendConfirmMail(source.GetUser(), source.service, param)) - source.Reply(_("A confirmation e-mail has been sent to \002{0}\002. Follow the instructions in it to change your e-mail address."), param); - } - else - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the email of " << nc->GetDisplay() << " to " << param; - nc->SetEmail(param); - source.Reply(_("E-mail address for \002{0}\002 changed to \002{1}\002."), nc->GetDisplay(), param); - } - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), params.size() ? params[0] : ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Changes your email address to \037address\037.")); - return true; - } -}; - -class CommandNSSASetEmail : public CommandNSSetEmail -{ - public: - CommandNSSASetEmail(Module *creator) : CommandNSSetEmail(creator, "nickserv/saset/email", 2) - { - this->ClearSyntax(); - this->SetSyntax(_("\037account\037 \037address\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params.size() > 1 ? params[1] : ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Changes the email address of \037account\037 to \037address\037.")); - return true; - } -}; - -class CommandNSSetKeepModes : public Command -{ - public: - CommandNSSetKeepModes(Module *creator, const Anope::string &sname = "nickserv/set/keepmodes", size_t min = 1) : Command(creator, sname, min, min + 1) - { - this->SetDesc(_("Enable or disable keep modes")); - this->SetSyntax("{ON | OFF}"); - } - - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - NickServ::Account *nc = na->GetAccount(); - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (param.equals_ci("ON")) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable keepmodes for " << nc->GetDisplay(); - nc->SetS("NS_KEEP_MODES", true); - source.Reply(_("Keep modes for \002{0}\002 is now \002on\002."), nc->GetDisplay()); - } - else if (param.equals_ci("OFF")) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable keepmodes for " << nc->GetDisplay(); - nc->UnsetS("NS_KEEP_MODES"); - source.Reply(_("Keep modes for \002{0}\002 is now \002off\002."), nc->GetDisplay()); - } - else - this->OnSyntaxError(source, ""); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), params[0]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables keepmodes for your account. If keepmodes is enabled, services will remember your usermodes and attempt to re-set them the next time you log on.")); - return true; - } -}; - -class CommandNSSASetKeepModes : public CommandNSSetKeepModes -{ - public: - CommandNSSASetKeepModes(Module *creator) : CommandNSSetKeepModes(creator, "nickserv/saset/keepmodes", 2) - { - this->ClearSyntax(); - this->SetSyntax(_("\037account\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params[1]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Enables or disables keepmodes for \037account\037. If keep modes is enabled, services will remember users' usermodes and attempt to re-set them the next time they log pn.")); - return true; - } -}; - -class CommandNSSetKill : public Command -{ - public: - CommandNSSetKill(Module *creator, const Anope::string &sname = "nickserv/set/kill", size_t min = 1) : Command(creator, sname, min, min + 1) - { - this->SetDesc(_("Turn protection on or off")); - this->SetSyntax("{ON | QUICK | IMMED | OFF}"); - } - - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - if (Config->GetModule("nickserv")->Get("nonicknameownership")) - { - source.Reply(_("This command may not be used on this network because nickname ownership is disabled.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - NickServ::Account *nc = na->GetAccount(); - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (param.equals_ci("ON")) - { - nc->SetS("KILLPROTECT", true); - nc->UnsetS("KILL_QUICK"); - nc->UnsetS("KILL_IMMED"); - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to set kill on for " << nc->GetDisplay(); - source.Reply(_("Protection is now \002on\002 for \002{0}\002."), nc->GetDisplay()); - } - else if (param.equals_ci("QUICK")) - { - nc->SetS("KILLPROTECT", true); - nc->SetS("KILL_QUICK", true); - nc->UnsetS("KILL_IMMED"); - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to set kill quick for " << nc->GetDisplay(); - source.Reply(_("Protection is now \002on\002 for \002{0}\002, with a reduced delay."), nc->GetDisplay()); - } - else if (param.equals_ci("IMMED")) - { - if (Config->GetModule(this->owner)->Get("allowkillimmed")) - { - nc->SetS("KILLPROTECT",true); - nc->UnsetS("KILL_QUICK"); - nc->SetS("KILL_IMMED", true); - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to set kill immed for " << nc->GetDisplay(); - source.Reply(_("Protection is now \002on\002 for \002{0}\002, with no delay."), nc->GetDisplay()); - } - else - source.Reply(_("The \002IMMED\002 option is not available on this network.")); - } - else if (param.equals_ci("OFF")) - { - nc->UnsetS("KILLPROTECT"); - nc->UnsetS("KILL_QUICK"); - nc->UnsetS("KILL_IMMED"); - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable kill for " << nc->GetDisplay(); - source.Reply(_("Protection is now \002off\002 for \002{0}\002."), nc->GetDisplay()); - } - else - this->OnSyntaxError(source, "KILL"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), params[0]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Turns the automatic protection option for your account on or off." - " With protection on, if another user tries to use one of your nicknames, they will be given {0} to change to another their nickname, after which {1} will forcibly change their nickname.\n" - "\n" - "If you select \002QUICK\002, the user will be given only {1} to change their nick instead {0}." - " If you select \002IMMED\002, the user's nickname will be changed immediately \037without\037 being warned first or given a chance to change their nick." - " With this set, the only way to use the nickname is to match an entry on the account's access list.")); - return true; - } -}; - -class CommandNSSASetKill : public CommandNSSetKill -{ - public: - CommandNSSASetKill(Module *creator) : CommandNSSetKill(creator, "nickserv/saset/kill", 2) - { - this->ClearSyntax(); - this->SetSyntax(_("\037account\037 {ON | QUICK | IMMED | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params[1]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Turns the automatic protection option for \037account\037 on or off." - " With protection on, if another user tries to use one of the nicknames in the group, they will be given {0} to change to another their nickname, after which {1} will forcibly change their nickname.\n" - "\n" - "If you select \002QUICK\002, the user will be given only {1} to change their nick instead {0}." - " If you select \002IMMED\002, the user's nickname will be changed immediately \037without\037 being warned first or given a chance to change their nick." - " With this set, the only way to use the nickname is to match an entry on the account's access list.")); - return true; - } -}; - -class CommandNSSetLanguage : public Command -{ - public: - CommandNSSetLanguage(Module *creator, const Anope::string &sname = "nickserv/set/language", size_t min = 1) : Command(creator, sname, min, min + 1) - { - this->SetDesc(_("Set the language Services will use when messaging you")); - this->SetSyntax(_("\037language\037")); - } - - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - NickServ::Account *nc = na->GetAccount(); - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (param != "en_US") - for (unsigned j = 0; j < Language::Languages.size(); ++j) - { - if (Language::Languages[j] == param) - break; - else if (j + 1 == Language::Languages.size()) - { - this->OnSyntaxError(source, ""); - return; - } - } - - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the language of " << nc->GetDisplay() << " to " << param; - - nc->SetLanguage(param); - source.Reply(_("Language changed to \002English\002.")); - } - - void Execute(CommandSource &source, const std::vector ¶m) override - { - this->Run(source, source.nc->GetDisplay(), param[0]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Changes the language services will use when sending messages to you (for example, when responding to a command you send). \037language\037 should be chosen from the following list of supported languages:")); - - source.Reply(" en_US (English)"); - for (unsigned j = 0; j < Language::Languages.size(); ++j) - { - const Anope::string &langname = Language::Translate(Language::Languages[j].c_str(), _("English")); - if (langname == "English") - continue; - source.Reply(" %s (%s)", Language::Languages[j].c_str(), langname.c_str()); - } - - return true; - } -}; - -class CommandNSSASetLanguage : public CommandNSSetLanguage -{ - public: - CommandNSSASetLanguage(Module *creator) : CommandNSSetLanguage(creator, "nickserv/saset/language", 2) - { - this->ClearSyntax(); - this->SetSyntax(_("\037account\037 \037language\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params[1]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Changes the language services will use when sending messages to the given user (for example, when responding to a command they send). \037language\037 should be chosen from the following list of supported languages:")); - source.Reply(" en (English)"); - for (unsigned j = 0; j < Language::Languages.size(); ++j) - { - const Anope::string &langname = Language::Translate(Language::Languages[j].c_str(), _("English")); - if (langname == "English") - continue; - source.Reply(" %s (%s)", Language::Languages[j].c_str(), langname.c_str()); - } - return true; - } -}; - -class CommandNSSetMessage : public Command -{ - public: - CommandNSSetMessage(Module *creator, const Anope::string &sname = "nickserv/set/message", size_t min = 1) : Command(creator, sname, min, min + 1) - { - this->SetDesc(_("Change the communication method of Services")); - this->SetSyntax("{ON | OFF}"); - } - - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - NickServ::Account *nc = na->GetAccount(); - - if (!Config->GetBlock("options")->Get("useprivmsg")) - { - source.Reply(_("You cannot %s on this network."), source.command); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (param.equals_ci("ON")) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable " << source.command << " for " << nc->GetDisplay(); - nc->SetS("MSG", true); - source.Reply(_("Services will now reply to \002{0}\002 with \002messages\002."), nc->GetDisplay()); - } - else if (param.equals_ci("OFF")) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable " << source.command << " for " << nc->GetDisplay(); - nc->UnsetS("MSG"); - source.Reply(_("Services will now reply to \002{0}\002 with \002notices\002."), nc->GetDisplay()); - } - else - this->OnSyntaxError(source, "MSG"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), params[0]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - Anope::string cmd = source.command; - size_t i = cmd.find_last_of(' '); - if (i != Anope::string::npos) - cmd = cmd.substr(i + 1); - - source.Reply(_("Allows you to choose the way services communicate with you. With \002{0}\002 set, services will use messages instead of notices."), cmd); - return true; - } - - void OnServHelp(CommandSource &source) override - { - if (!Config->GetBlock("options")->Get("useprivmsg")) - Command::OnServHelp(source); - } -}; - -class CommandNSSASetMessage : public CommandNSSetMessage -{ - public: - CommandNSSASetMessage(Module *creator) : CommandNSSetMessage(creator, "nickserv/saset/message", 2) - { - this->ClearSyntax(); - this->SetSyntax(_("\037account\037 {ON | OFF}")); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - Anope::string cmd = source.command; - size_t i = cmd.find_last_of(' '); - if (i != Anope::string::npos) - cmd = cmd.substr(i + 1); - - source.Reply(_("Allows you to choose the way services communicate with the given user. With \002{0}\002 set, services will use messages instead of notices."), cmd); - return true; - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params[1]); - } -}; - -class CommandNSSetSecure : public Command -{ - public: - CommandNSSetSecure(Module *creator, const Anope::string &sname = "nickserv/set/secure", size_t min = 1) : Command(creator, sname, min, min + 1) - { - this->SetDesc(_("Turn nickname security on or off")); - this->SetSyntax("{ON | OFF}"); - } - - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - NickServ::Account *nc = na->GetAccount(); - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (param.equals_ci("ON")) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable secure for " << nc->GetDisplay(); - nc->SetS("NS_SECURE", true); - source.Reply(_("Secure option is now \002on\002 for \002{0}\002."), nc->GetDisplay()); - } - else if (param.equals_ci("OFF")) - { - Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable secure for " << nc->GetDisplay(); - nc->UnsetS("NS_SECURE"); - source.Reply(_("Secure option is now \002off\002 for \002{0}\002."), nc->GetDisplay()); - } - else - this->OnSyntaxError(source, "SECURE"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), params[0]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Turns the security feature on or off for your account." - " With \002SECURE\002 set, you must enter your password before you will be recognized as the owner of the account," - " regardless of whether your address is on the access list or not." - " However, if you are on the access list, services will not force you to change your nickname, regardless of the setting of the \002kill\002 option.")); - return true; - } -}; - -class CommandNSSASetSecure : public CommandNSSetSecure -{ - public: - CommandNSSASetSecure(Module *creator) : CommandNSSetSecure(creator, "nickserv/saset/secure", 2) - { - this->ClearSyntax(); - this->SetSyntax(_("\037account\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params[1]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Turns the security feature on or off for \037account\037." - " With \002SECURE\002 set, you the user must enter their password before they will be recognized as the owner of the account," - " regardless of whether their address address is on the access list or not." - " However, if they are on the access list, services will not force them to change your nickname, regardless of the setting of the \002kill\002 option.")); - return true; - } -}; - -class CommandNSSASetNoexpire : public Command -{ - public: - CommandNSSASetNoexpire(Module *creator) : Command(creator, "nickserv/saset/noexpire", 1, 2) - { - this->SetDesc(_("Prevent the nickname from expiring")); - this->SetSyntax(_("\037nickname\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - const Anope::string &user = params[0]; - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - - Anope::string param = params.size() > 1 ? params[1] : ""; - - if (param.equals_ci("ON")) - { - Log(LOG_ADMIN, source, this) << "to enable noexpire for " << na->GetAccount()->GetDisplay(); - na->SetS("NS_NO_EXPIRE", true); - source.Reply(_("\002{0}\002 \002will not\002 expire."), na->GetNick()); - } - else if (param.equals_ci("OFF")) - { - Log(LOG_ADMIN, source, this) << "to disable noexpire for " << na->GetAccount()->GetDisplay(); - na->UnsetS("NS_NO_EXPIRE"); - source.Reply(_("\002{0}\002 \002will\002 expire."), na->GetNick()); - } - else - this->OnSyntaxError(source, "NOEXPIRE"); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - source.Reply(_("Sets whether the given nickname will expire. Setting this to \002ON\002 prevents the nickname from expiring.")); - return true; - } -}; - -class NSSet : public Module - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - CommandNSSet commandnsset; - CommandNSSASet commandnssaset; - - CommandNSSetAutoOp commandnssetautoop; - CommandNSSASetAutoOp commandnssasetautoop; - - CommandNSSetDisplay commandnssetdisplay; - CommandNSSASetDisplay commandnssasetdisplay; - - CommandNSSetEmail commandnssetemail; - CommandNSSASetEmail commandnssasetemail; - - CommandNSSetKeepModes commandnssetkeepmodes; - CommandNSSASetKeepModes commandnssasetkeepmodes; - - CommandNSSetKill commandnssetkill; - CommandNSSASetKill commandnssasetkill; - - CommandNSSetLanguage commandnssetlanguage; - CommandNSSASetLanguage commandnssasetlanguage; - - CommandNSSetMessage commandnssetmessage; - CommandNSSASetMessage commandnssasetmessage; - - CommandNSSetPassword commandnssetpassword; - CommandNSSASetPassword commandnssasetpassword; - - CommandNSSetSecure commandnssetsecure; - CommandNSSASetSecure commandnssasetsecure; - - CommandNSSASetNoexpire commandnssasetnoexpire; - - Serialize::Field autoop, keep_modes, killprotect, kill_quick, kill_immed, message, secure; - Serialize::Field noexpire; - - /* email, passcode */ - ExtensibleItem > ns_set_email; - - EventHandlers onsetnickoption; - - public: - NSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsset(this) - , commandnssaset(this) - , commandnssetautoop(this) - , commandnssasetautoop(this) - , commandnssetdisplay(this) - , commandnssasetdisplay(this) - , commandnssetemail(this) - , commandnssasetemail(this) - , commandnssetkeepmodes(this) - , commandnssasetkeepmodes(this) - , commandnssetkill(this) - , commandnssasetkill(this) - , commandnssetlanguage(this) - , commandnssasetlanguage(this) - , commandnssetmessage(this) - , commandnssasetmessage(this) - , commandnssetpassword(this) - , commandnssasetpassword(this) - , commandnssetsecure(this) - , commandnssasetsecure(this) - , commandnssasetnoexpire(this) - - , autoop(this, NickServ::account, "AUTOOP") - , keep_modes(this, NickServ::account, "NS_KEEP_MODES") - , killprotect(this, NickServ::account, "KILLPROTECT") - , kill_quick(this, NickServ::account, "KILL_QUICK") - , kill_immed(this, NickServ::account, "KILL_IMMED") - , message(this, NickServ::account, "MSG") - , secure(this, NickServ::account, "NS_SECURE") - , noexpire(this, NickServ::nick, "NS_NO_EXPIRE") - - , ns_set_email(this, "ns_set_email") - - , onsetnickoption(this) - { - - } - - EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override - { - NickServ::Account *uac = source.nc; - - if (command->name == "nickserv/confirm" && !params.empty() && uac) - { - std::pair *n = ns_set_email.Get(uac); - if (n) - { - if (params[0] == n->second) - { - uac->SetEmail(n->first); - Log(LOG_COMMAND, source, command) << "to confirm their email address change to " << uac->GetEmail(); - source.Reply(_("Your email address has been changed to \002%s\002."), uac->GetEmail().c_str()); - ns_set_email.Unset(uac); - return EVENT_STOP; - } - } - } - - return EVENT_CONTINUE; - } - - void OnSetCorrectModes(User *user, Channel *chan, ChanServ::AccessGroup &access, bool &give_modes, bool &take_modes) override - { - if (chan->ci) - { - /* Only give modes if autoop is set */ - give_modes &= !user->Account() || autoop.HasExt(user->Account()); - } - } - - void OnPreNickExpire(NickServ::Nick *na, bool &expire) override - { - if (noexpire.HasExt(na)) - expire = false; - } - - void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override - { - if (!show_hidden) - return; - - if (kill_immed.HasExt(na->GetAccount())) - info.AddOption(_("Immediate protection")); - else if (kill_quick.HasExt(na->GetAccount())) - info.AddOption(_("Quick protection")); - else if (killprotect.HasExt(na->GetAccount())) - info.AddOption(_("Protection")); - if (secure.HasExt(na->GetAccount())) - info.AddOption(_("Security")); - if (message.HasExt(na->GetAccount())) - info.AddOption(_("Message mode")); - if (autoop.HasExt(na->GetAccount())) - info.AddOption(_("Auto-op")); - if (noexpire.HasExt(na)) - info.AddOption(_("No expire")); - if (keep_modes.HasExt(na->GetAccount())) - info.AddOption(_("Keep modes")); - } - - void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override - { - if (u->Account() && setter.GetUser() == u && NickServ::mode) - { - NickServ::Mode *m = NickServ::mode.Create(); - m->SetAccount(u->Account()); - m->SetMode(mname); - } - } - - void OnUserModeUnset(const MessageSource &setter, User *u, const Anope::string &mname) override - { - if (u->Account() && setter.GetUser() == u) - { - for (NickServ::Mode *m : u->Account()->GetRefs(NickServ::mode)) - if (m->GetMode() == mname) - m->Delete(); - } - } - - void OnUserLogin(User *u) override - { - if (keep_modes.HasExt(u->Account())) - for (NickServ::Mode *mode : u->Account()->GetRefs(NickServ::mode)) - { - UserMode *um = ModeManager::FindUserModeByName(mode->GetMode()); - /* if the null user can set the mode, then it's probably safe */ - if (um && um->CanSet(NULL)) - u->SetMode(NULL, mode->GetMode()); - } - } -}; - -MODULE_INIT(NSSet) diff --git a/modules/commands/ns_set_misc.cpp b/modules/commands/ns_set_misc.cpp deleted file mode 100644 index 5d03797b1..000000000 --- a/modules/commands/ns_set_misc.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/* - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_set_misc.h" -#include "modules/ns_info.h" -#include "modules/ns_set.h" - -static Anope::map descriptions; - -class NSMiscDataImpl : public NSMiscData -{ - public: - NSMiscDataImpl(Serialize::TypeBase *type) : NSMiscData(type) { } - NSMiscDataImpl(Serialize::TypeBase *type, Serialize::ID id) : NSMiscData(type, id) { } - - NickServ::Account *GetAccount() override; - void SetAccount(NickServ::Account *s) override; - - Anope::string GetName() override; - void SetName(const Anope::string &n) override; - - Anope::string GetData() override; - void SetData(const Anope::string &d) override; -}; - -class NSMiscDataType : public Serialize::Type -{ - public: - Serialize::ObjectField owner; - Serialize::Field name, data; - - NSMiscDataType(Module *me) : Serialize::Type(me, "NSMiscData") - , owner(this, "nc", true) - , name(this, "name") - , data(this, "data") - { - } -}; - -NickServ::Account *NSMiscDataImpl::GetAccount() -{ - return Get(&NSMiscDataType::owner); -} - -void NSMiscDataImpl::SetAccount(NickServ::Account *s) -{ - Set(&NSMiscDataType::owner, s); -} - -Anope::string NSMiscDataImpl::GetName() -{ - return Get(&NSMiscDataType::name); -} - -void NSMiscDataImpl::SetName(const Anope::string &n) -{ - Set(&NSMiscDataType::name, n); -} - -Anope::string NSMiscDataImpl::GetData() -{ - return Get(&NSMiscDataType::data); -} - -void NSMiscDataImpl::SetData(const Anope::string &d) -{ - Set(&NSMiscDataType::data, d); -} - -class CommandNSSetMisc : public Command -{ - Anope::string GetAttribute(const Anope::string &command) - { - size_t sp = command.rfind(' '); - if (sp != Anope::string::npos) - return command.substr(sp + 1); - return command; - } - - public: - CommandNSSetMisc(Module *creator, const Anope::string &cname = "nickserv/set/misc", size_t min = 0) : Command(creator, cname, min, min + 1) - { - this->SetSyntax(_("[\037parameter\037]")); - } - - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) - { - if (Anope::ReadOnly) - { - source.Reply(_("Services are in read-only mode.")); - return; - } - - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), user); - return; - } - NickServ::Account *nc = na->GetAccount(); - - EventReturn MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); - if (MOD_RESULT == EVENT_STOP) - return; - - Anope::string scommand = GetAttribute(source.command); - - /* remove existing */ - for (NSMiscData *data : nc->GetRefs(nsmiscdata)) - if (data->GetName() == scommand) - { - data->Delete(); - break; - } - - if (!param.empty()) - { - NSMiscData *data = nsmiscdata.Create(); - data->SetAccount(nc); - data->SetName(scommand); - data->SetData(param); - - source.Reply(_("\002{0}\002 for \002{1}\002 set to \002{2}\002."), scommand, nc->GetDisplay(), param); - } - else - { - source.Reply(_("\002{0}\002 for \002{1}\002 unset."), scommand, nc->GetDisplay()); - } - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), !params.empty() ? params[0] : ""); - } - - void OnServHelp(CommandSource &source) override - { - if (descriptions.count(source.command)) - { - this->SetDesc(descriptions[source.command]); - Command::OnServHelp(source); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (descriptions.count(source.command)) - { - source.Reply(Language::Translate(source.nc, descriptions[source.command].c_str())); - return true; - } - return false; - } -}; - -class CommandNSSASetMisc : public CommandNSSetMisc -{ - public: - CommandNSSASetMisc(Module *creator) : CommandNSSetMisc(creator, "nickserv/saset/misc", 1) - { - this->ClearSyntax(); - this->SetSyntax(_("\037nickname\037 [\037parameter\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params.size() > 1 ? params[1] : ""); - } -}; - -class NSSetMisc : public Module - , public EventHook -{ - CommandNSSetMisc commandnssetmisc; - CommandNSSASetMisc commandnssasetmisc; - NSMiscDataType type; - - public: - NSSetMisc(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnssetmisc(this) - , commandnssasetmisc(this) - , type(this) - { - } - - void OnReload(Configuration::Conf *conf) override - { - descriptions.clear(); - - for (int i = 0; i < conf->CountBlock("command"); ++i) - { - Configuration::Block *block = conf->GetBlock("command", i); - - const Anope::string &cmd = block->Get("command"); - - if (cmd != "nickserv/set/misc" && cmd != "nickserv/saset/misc") - continue; - - Anope::string cname = block->Get("name"); - Anope::string desc = block->Get("misc_description"); - - if (cname.empty() || desc.empty()) - continue; - - descriptions[cname] = desc; - } - } - - void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool) override - { - for (NSMiscData *data : na->GetAccount()->GetRefs(nsmiscdata)) - info[data->GetName()] = data->GetData(); - } -}; - -MODULE_INIT(NSSetMisc) diff --git a/modules/commands/ns_status.cpp b/modules/commands/ns_status.cpp deleted file mode 100644 index 7dc751d8b..000000000 --- a/modules/commands/ns_status.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandNSStatus : public Command -{ - public: - CommandNSStatus(Module *creator) : Command(creator, "nickserv/status", 0, 16) - { - this->SetDesc(_("Returns the owner status of the given nickname")); - this->SetSyntax(_("[\037nickname\037]")); - this->AllowUnregistered(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &nick = !params.empty() ? params[0] : source.GetNick(); - const NickServ::Nick *na = NickServ::FindNick(nick); - spacesepstream sep(nick); - Anope::string nickbuf; - - while (sep.GetToken(nickbuf)) - { - #if 0 - User *u2 = User::Find(nickbuf, true); - if (!u2) /* Nick is not online */ - source.Reply("STATUS %s %d %s", nickbuf.c_str(), 0, ""); - else if (u2->IsIdentified() && na && na->GetAccount() == u2->Account()) /* Nick is identified */ - source.Reply("STATUS %s %d %s", nickbuf.c_str(), 3, u2->Account()->GetDisplay().c_str()); - else if (u2->IsRecognized()) /* Nick is recognised, but NOT identified */ - source.Reply("STATUS %s %d %s", nickbuf.c_str(), 2, u2->Account() ? u2->Account()->GetDisplay().c_str() : ""); - else if (!na) /* Nick is online, but NOT a registered */ - source.Reply("STATUS %s %d %s", nickbuf.c_str(), 0, ""); - else - /* Nick is not identified for the nick, but they could be logged into an account, - * so we tell the user about it - */ - source.Reply("STATUS %s %d %s", nickbuf.c_str(), 1, u2->Account() ? u2->Account()->GetDisplay().c_str() : ""); - #endif - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Returns whether the user using the given nickname is\n" - "recognized as the owner of the nickname. The response has\n" - "this format:\n" - " \n" - " \037nickname\037 \037status-code\037 \037account\037\n" - " \n" - "where \037nickname\037 is the nickname sent with the command,\n" - "\037status-code\037 is one of the following, and \037account\037\n" - "is the account they are logged in as.\n" - " \n" - " 0 - no such user online \002or\002 nickname not registered\n" - " 1 - user not recognized as nickname's owner\n" - " 2 - user recognized as owner via access list only\n" - " 3 - user recognized as owner via password identification\n" - " \n" - "If no nickname is given, your status will be returned.")); - return true; - } -}; - -class NSStatus : public Module -{ - CommandNSStatus commandnsstatus; - - public: - NSStatus(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsstatus(this) - { - throw ModuleException("Remind Adam to fix this"); - } -}; - -MODULE_INIT(NSStatus) diff --git a/modules/commands/ns_suspend.cpp b/modules/commands/ns_suspend.cpp deleted file mode 100644 index 2859eacc1..000000000 --- a/modules/commands/ns_suspend.cpp +++ /dev/null @@ -1,339 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_suspend.h" -#include "modules/ns_info.h" -#include "modules/nickserv.h" - -class NSSuspendInfoImpl : public NSSuspendInfo -{ - public: - NSSuspendInfoImpl(Serialize::TypeBase *type) : NSSuspendInfo(type) { } - NSSuspendInfoImpl(Serialize::TypeBase *type, Serialize::ID id) : NSSuspendInfo(type, id) { } - - NickServ::Account *GetAccount() override; - void SetAccount(NickServ::Account *) override; - - Anope::string GetBy() override; - void SetBy(const Anope::string &by) override; - - Anope::string GetReason() override; - void SetReason(const Anope::string &reason) override; - - time_t GetWhen() override; - void SetWhen(const time_t &w) override; - - time_t GetExpires() override; - void SetExpires(const time_t &e) override; -}; - -class NSSuspendType : public Serialize::Type -{ - public: - Serialize::ObjectField account; - Serialize::Field by, reason; - Serialize::Field when, expires; - - NSSuspendType(Module *me) : Serialize::Type(me, "NSSuspendInfo") - , account(this, "nick", true) - , by(this, "by") - , reason(this, "reason") - , when(this, "time") - , expires(this, "expires") - { - } -}; - -NickServ::Account *NSSuspendInfoImpl::GetAccount() -{ - return Get(&NSSuspendType::account); -} - -void NSSuspendInfoImpl::SetAccount(NickServ::Account *s) -{ - Set(&NSSuspendType::account, s); -} - -Anope::string NSSuspendInfoImpl::GetBy() -{ - return Get(&NSSuspendType::by); -} - -void NSSuspendInfoImpl::SetBy(const Anope::string &by) -{ - Set(&NSSuspendType::by, by); -} - -Anope::string NSSuspendInfoImpl::GetReason() -{ - return Get(&NSSuspendType::reason); -} - -void NSSuspendInfoImpl::SetReason(const Anope::string &reason) -{ - Set(&NSSuspendType::reason, reason); -} - -time_t NSSuspendInfoImpl::GetWhen() -{ - return Get(&NSSuspendType::when); -} - -void NSSuspendInfoImpl::SetWhen(const time_t &w) -{ - Set(&NSSuspendType::when, w); -} - -time_t NSSuspendInfoImpl::GetExpires() -{ - return Get(&NSSuspendType::expires); -} - -void NSSuspendInfoImpl::SetExpires(const time_t &e) -{ - Set(&NSSuspendType::expires, e); -} - -class CommandNSSuspend : public Command -{ - EventHandlers &onnicksuspend; - - public: - CommandNSSuspend(Module *creator, EventHandlers &event) : Command(creator, "nickserv/suspend", 2, 3), onnicksuspend(event) - { - this->SetDesc(_("Suspend a given nick")); - this->SetSyntax(_("\037account\037 [+\037expiry\037] [\037reason\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - - const Anope::string &nick = params[0]; - Anope::string expiry = params[1]; - Anope::string reason = params.size() > 2 ? params[2] : ""; - time_t expiry_secs = Config->GetModule(this->owner)->Get("suspendexpire"); - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - if (expiry[0] != '+') - { - reason = expiry + " " + reason; - reason.trim(); - expiry.clear(); - } - else - { - expiry_secs = Anope::DoTime(expiry); - if (expiry_secs == -1) - { - source.Reply(_("Invalid expiry time \002{0}\002."), expiry); - return; - } - } - - NickServ::Nick *na = NickServ::FindNick(nick); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && na->GetAccount()->IsServicesOper()) - { - source.Reply(_("You may not suspend other Services Operators' nicknames.")); - return; - } - - NSSuspendInfo *si = na->GetAccount()->GetRef(nssuspendinfo); - if (!si) - { - source.Reply(_("\002%s\002 is already suspended."), na->GetAccount()->GetDisplay().c_str()); - return; - } - - NickServ::Account *nc = na->GetAccount(); - - si = nssuspendinfo.Create(); - si->SetAccount(nc); - si->SetBy(source.GetNick()); - si->SetReason(reason); - si->SetWhen(Anope::CurTime); - si->SetExpires(expiry_secs ? expiry_secs + Anope::CurTime : 0); - - for (NickServ::Nick *na2 : nc->GetRefs(NickServ::nick)) - { - na2->SetLastQuit(reason); - - User *u2 = User::Find(na2->GetNick(), true); - if (u2) - { - u2->Logout(); - if (NickServ::service) - NickServ::service->Collide(u2, na2); - } - } - - Log(LOG_ADMIN, source, this) << "for " << nick << " (" << (!reason.empty() ? reason : "No reason") << "), expires on " << (expiry_secs ? Anope::strftime(Anope::CurTime + expiry_secs) : "never"); - source.Reply(_("\002{0}\002 is now suspended."), na->GetNick()); - - this->onnicksuspend(&Event::NickSuspend::OnNickSuspend, na); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Suspends \037account\037, which prevents it from being used while keeping all the data for it." - " If an expiry is given the account will be unsuspended after that period of time, otherwise the default expiry from the configuration is used.")); // XXX - return true; - } -}; - -class CommandNSUnSuspend : public Command -{ - EventHandlers &onnickunsuspend; - - public: - CommandNSUnSuspend(Module *creator, EventHandlers &event) : Command(creator, "nickserv/unsuspend", 1, 1), onnickunsuspend(event) - { - this->SetDesc(_("Unsuspend a given nick")); - this->SetSyntax(_("\037account\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &nick = params[0]; - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - NickServ::Nick *na = NickServ::FindNick(nick); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), nick); - return; - } - - NSSuspendInfo *si = na->GetAccount()->GetRef(nssuspendinfo); - if (!si) - { - source.Reply(_("\002{0}\002 is not suspended."), na->GetNick()); - return; - } - - Log(LOG_ADMIN, source, this) << "for " << na->GetNick() << " which was suspended by " << (!si->GetBy().empty() ? si->GetBy() : "(none)") << " for: " << (!si->GetReason().empty() ? si->GetReason() : "No reason"); - - si->Delete(); - - source.Reply(_("\002{0}\002 is now released."), na->GetNick()); - - this->onnickunsuspend(&Event::NickUnsuspend::OnNickUnsuspend, na); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Unsuspends \037account\037, which allows it to be used again.")); - return true; - } -}; - -class NSSuspend : public Module - , public EventHook - , public EventHook - , public EventHook -{ - CommandNSSuspend commandnssuspend; - CommandNSUnSuspend commandnsunsuspend; - EventHandlers onnicksuspend; - EventHandlers onnickunsuspend; - std::vector show; - NSSuspendType nst; - - struct trim - { - Anope::string operator()(Anope::string s) const - { - return s.trim(); - } - }; - - bool Show(CommandSource &source, const Anope::string &what) const - { - return source.IsOper() || std::find(show.begin(), show.end(), what) != show.end(); - } - - public: - NSSuspend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnssuspend(this, onnicksuspend) - , commandnsunsuspend(this, onnickunsuspend) - , onnicksuspend(this) - , onnickunsuspend(this) - , nst(this) - { - } - - void OnReload(Configuration::Conf *conf) override - { - Anope::string s = conf->GetModule(this)->Get("show"); - commasepstream(s).GetTokens(show); - std::transform(show.begin(), show.end(), show.begin(), trim()); - } - - void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override - { - NSSuspendInfo *s = na->GetAccount()->GetRef(nssuspendinfo); - if (!s) - return; - - if (show_hidden || Show(source, "suspended")) - info[_("Suspended")] = _("This nickname is \002suspended\002."); - if (!s->GetBy().empty() && (show_hidden || Show(source, "by"))) - info[_("Suspended by")] = s->GetBy(); - if (!s->GetReason().empty() && (show_hidden || Show(source, "reason"))) - info[_("Suspend reason")] = s->GetReason(); - if (s->GetWhen() && (show_hidden || Show(source, "on"))) - info[_("Suspended on")] = Anope::strftime(s->GetWhen(), source.GetAccount()); - if (s->GetExpires() && (show_hidden || Show(source, "expires"))) - info[_("Suspension expires")] = Anope::strftime(s->GetExpires(), source.GetAccount()); - } - - void OnPreNickExpire(NickServ::Nick *na, bool &expire) override - { - NSSuspendInfo *s = na->GetAccount()->GetRef(nssuspendinfo); - if (!s) - return; - - expire = false; - - if (!s->GetExpires()) - return; - - if (s->GetExpires() < Anope::CurTime) - { - na->SetLastSeen(Anope::CurTime); - s->Delete(); - - Log(LOG_NORMAL, "nickserv/expire", Config->GetClient("NickServ")) << "Expiring suspend for " << na->GetNick(); - } - } - - EventReturn OnNickValidate(User *u, NickServ::Nick *na) override - { - NSSuspendInfo *s = na->GetAccount()->GetRef(nssuspendinfo); - if (!s) - return EVENT_CONTINUE; - - u->SendMessage(Config->GetClient("NickServ"), _("\002{0}\002 is suspended."), u->nick); - return EVENT_STOP; - } -}; - -MODULE_INIT(NSSuspend) diff --git a/modules/commands/ns_update.cpp b/modules/commands/ns_update.cpp deleted file mode 100644 index 0022f389d..000000000 --- a/modules/commands/ns_update.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_update.h" - -class CommandNSUpdate : public Command -{ - EventHandlers &onnickupdate; - - public: - CommandNSUpdate(Module *creator, EventHandlers &event) : Command(creator, "nickserv/update", 0, 0), onnickupdate(event) - { - this->SetDesc(_("Updates your current status, i.e. it checks for new memos")); - this->RequireUser(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - User *u = source.GetUser(); - NickServ::Nick *na = NickServ::FindNick(u->nick); - - if (na && na->GetAccount() == source.GetAccount()) - { - na->SetLastRealname(u->realname); - na->SetLastSeen(Anope::CurTime); - } - - this->onnickupdate(&Event::NickUpdate::OnNickUpdate, u); - - source.Reply(_("Status updated (memos, vhost, chmodes, flags).")); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Updates your current status, i.e. it checks for new memos,\n" - "sets needed channel modes and updates your vhost and\n" - "your userflags (lastseentime, etc).")); - return true; - } -}; - -class NSUpdate : public Module -{ - CommandNSUpdate commandnsupdate; - EventHandlers onnickupdate; - - public: - NSUpdate(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandnsupdate(this, onnickupdate) - , onnickupdate(this) - { - - } -}; - -MODULE_INIT(NSUpdate) diff --git a/modules/commands/os_akill.cpp b/modules/commands/os_akill.cpp deleted file mode 100644 index 7f366e0c9..000000000 --- a/modules/commands/os_akill.cpp +++ /dev/null @@ -1,437 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -static ServiceReference akills("XLineManager", "xlinemanager/sgline"); - -class CommandOSAKill : public Command -{ - private: - void DoAdd(CommandSource &source, const std::vector ¶ms) - { - Anope::string expiry, mask; - - if (params.size() < 2) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - spacesepstream sep(params[1]); - sep.GetToken(mask); - - if (mask[0] == '+') - { - expiry = mask; - sep.GetToken(mask); - } - - time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get("autokillexpiry", "30d"); - /* If the expiry given does not contain a final letter, it's in days, - * said the doc. Ah well. - */ - if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) - expires *= 86400; - /* Do not allow less than a minute expiry time */ - if (expires && expires < 60) - { - source.Reply(_("Invalid expiry time \002{0}\002."), expiry); - return; - } - else if (expires > 0) - expires += Anope::CurTime; - - if (sep.StreamEnd()) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - Anope::string reason; - if (mask.find('#') != Anope::string::npos) - { - Anope::string remaining = sep.GetRemaining(); - - size_t co = remaining[0] == ':' ? 0 : remaining.rfind(" :"); - if (co == Anope::string::npos) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - if (co != 0) - ++co; - - reason = remaining.substr(co + 1); - mask += " " + remaining.substr(0, co); - mask.trim(); - } - else - reason = sep.GetRemaining(); - - if (mask[0] == '/' && mask[mask.length() - 1] == '/') - { - if (!Config->regex_flags) - { - source.Reply(_("Regex is disabled.")); - return; - } - - Anope::string stripped_mask = mask.substr(1, mask.length() - 2); - try - { - std::regex(stripped_mask.str(), Config->regex_flags); - } - catch (const std::regex_error &ex) - { - source.Reply("%s", ex.what()); - return; - } - } - - User *targ = User::Find(mask, true); - if (targ) - mask = "*@" + targ->host; - - if (Config->GetModule("operserv")->Get("addakiller", "yes") && !source.GetNick().empty()) - reason = "[" + source.GetNick() + "] " + reason; - - if (!akills->CanAdd(source, mask, expires, reason)) - return; - - if (mask.find_first_not_of("/~@.*?") == Anope::string::npos) - { - source.Reply(_("\002{0}\002 coverage is too wide; Please use a more specific mask."), mask); - return; - } - - if (mask.find('@') == Anope::string::npos) - { - source.Reply(_("Mask must be in the form \037user\037@\037host\037.")); - return; - } - - XLine *x = new XLine(mask, source.GetNick(), expires, reason); - if (Config->GetModule("operserv")->Get("akillids")) - x->SetID(XLineManager::GenerateUID()); - - unsigned int affected = 0; - for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) - if (akills->Check(it->second, x)) - ++affected; - float percent = static_cast(affected) / static_cast(UserListByNick.size()) * 100.0; - - if (percent > 95) - { - source.Reply(_("\002{0}\002 coverage is too wide; Please use a more specific mask."), mask); - Log(LOG_ADMIN, source, this) << "tried to akill " << percent << "% of the network (" << affected << " users)"; - delete x; - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnAddXLine(&Event::AddXLine::OnAddXLine, source, x, akills); - if (MOD_RESULT == EVENT_STOP) - { - delete x; - return; - } - - akills->AddXLine(x); - if (Config->GetModule("operserv")->Get("akillonadd")) - akills->Send(NULL, x); - - source.Reply(_("\002{0}\002 added to the akill list."), mask); - - Log(LOG_ADMIN, source, this) << "on " << mask << " (" << x->GetReason() << "), expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]"; - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - } - - void DoDel(CommandSource &source, const std::vector ¶ms) - { - const Anope::string &mask = params.size() > 1 ? params[1] : ""; - - if (mask.empty()) - { - this->OnSyntaxError(source, "DEL"); - return; - } - - if (akills->GetXLines().empty()) - { - source.Reply(_("The akill list is empty.")); - return; - } - - if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) - { - unsigned int deleted = 0; - - NumberList(mask, true, - [&](unsigned int number) - { - XLine *x = akills->GetEntry(number - 1); - - if (!x) - return; - - Log(LOG_ADMIN, source, this) << "to remove " << x->GetMask() << " from the list"; - - ++deleted; - x->Delete(); - }, - [&]() - { - if (!deleted) - source.Reply(_("No matching entries on the akill list.")); - else if (deleted == 1) - source.Reply(_("Deleted \0021\002 entry from the akill list.")); - else - source.Reply(_("Deleted \002{0}\002 entries from the akill list."), deleted); - }); - } - else - { - XLine *x = akills->HasEntry(mask); - - if (!x) - { - source.Reply(_("\002{0}\002 was not found on the akill list."), mask); - return; - } - - do - { - Event::OnDelXLine(&Event::DelXLine::OnDelXLine, source, x, akills); - - Log(LOG_ADMIN, source, this) << "to remove " << x->GetMask() << " from the list"; - source.Reply(_("\002{0}\002 deleted from the akill list."), x->GetMask()); - x->Delete(); - } - while ((x = akills->HasEntry(mask))); - - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - } - - void ProcessList(CommandSource &source, const std::vector ¶ms, ListFormatter &list) - { - const Anope::string &mask = params.size() > 1 ? params[1] : ""; - - if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) - { - NumberList(mask, false, - [&](unsigned int number) - { - XLine *x = akills->GetEntry(number - 1); - - if (!x) - return; - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(number); - entry["Mask"] = x->GetMask(); - entry["Creator"] = x->GetBy(); - entry["Created"] = Anope::strftime(x->GetCreated(), NULL, true); - entry["Expires"] = Anope::Expires(x->GetExpires(), source.nc); - entry["Reason"] = x->GetReason(); - list.AddEntry(entry); - }, - [&]{}); - } - else - { - unsigned int i = 0; - for (XLine *x : akills->GetXLines()) - { - if (mask.empty() || mask.equals_ci(x->GetMask()) || mask == x->id || Anope::Match(x->GetMask(), mask, false, true)) - { - ListFormatter::ListEntry entry; - entry["Number"] = stringify(++i); - entry["Mask"] = x->GetMask(); - entry["Creator"] = x->GetBy(); - entry["Created"] = Anope::strftime(x->GetCreated(), NULL, true); - entry["Expires"] = Anope::Expires(x->GetExpires(), source.nc); - entry["Reason"] = x->GetReason(); - list.AddEntry(entry); - } - } - } - - if (list.IsEmpty()) - { - source.Reply(_("There are no matching entries on the akill list.")); - return; - } - - source.Reply(_("Current akill list:")); - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("End of akill list.")); - } - - void DoList(CommandSource &source, const std::vector ¶ms) - { - if (akills->GetXLines().empty()) - { - source.Reply(_("The akill list is empty.")); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Reason")); - - this->ProcessList(source, params, list); - } - - void DoView(CommandSource &source, const std::vector ¶ms) - { - if (akills->GetXLines().empty()) - { - source.Reply(_("The akill list is empty.")); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Expires")).AddColumn(_("Reason")); - - this->ProcessList(source, params, list); - } - - void DoClear(CommandSource &source) - { - for (XLine *x : akills->GetXLines()) - { - Event::OnDelXLine(&Event::DelXLine::OnDelXLine, source, x, akills); - x->Delete(); - } - - Log(LOG_ADMIN, source, this) << "to CLEAR the list"; - source.Reply(_("The akill list has been cleared.")); - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - } - public: - CommandOSAKill(Module *creator) : Command(creator, "operserv/akill", 1, 2) - { - this->SetDesc(_("Manipulate the AKILL list")); - this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037 \037reason\037")); - this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}")); - this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]")); - this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]")); - this->SetSyntax("CLEAR"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &cmd = params[0]; - - if (!akills) - return; - - if (cmd.equals_ci("ADD")) - return this->DoAdd(source, params); - else if (cmd.equals_ci("DEL")) - return this->DoDel(source, params); - else if (cmd.equals_ci("LIST")) - return this->DoList(source, params); - else if (cmd.equals_ci("VIEW")) - return this->DoView(source, params); - else if (cmd.equals_ci("CLEAR")) - return this->DoClear(source); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (subcommand.equals_ci("ADD")) - { - source.Reply(_("The \002{0} ADD\002 command adds \037mask\037 to the auto kill list with the given \037reason\037." - "\037mask\037 can be in the format of nickname!username@hostname#realname." - " If a real name is specified, the reason must be prefixed with a :." - " \037expiry\037 is optional, and if specified must be an integer followed by one of \037d\037 (days), \037h\037 (hours), or \037m\037 (minutes)." - " If a unit specifier is not included, the default is days, so \037+30\037 means 30 days." - " To add an auto kill which does not expire, use \037+0\037. " - " The default auto kill expiry time is \002{1}\002"), - source.command, Anope::Duration(Config->GetModule("operserv")->Get("autokillexpiry", "30d"), source.GetAccount())); - - const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); - if (!regexengine.empty()) - { - source.Reply(" "); - source.Reply(_("Regex matches are also supported using the %s engine. Enclose your mask in // if this is desired."), regexengine); - } - } - else if (subcommand.equals_ci("DEL")) - source.Reply(_("The \002{0} DEL\002 command removes the given \037mask\037 from the auto kill list, if present." - " If a list of entry numbers is given, those entries are deleted."), - source.command); - else if (subcommand.equals_ci("LIST") || subcommand.equals_ci("VIEW")) - source.Reply(_("The \002{0} LIST\002 and \002{0} VIEW\002 commands display the auto kill list." - " If a wildcard \037mask\037 is given, only those entries matching the mask are displayed." - " If a list of entry numbers is given, only those entries are shown." - " \002VIEW\002 is similar to \002LIST\002 but also shows who created the auto kill, when it was created, and when it expires.\n" - "\n" - "Example:\n" - "\n" - " {0} LIST 2-5,7-9\n" - " Lists auto kill entries numbered 2 through 5 and 7 through 9."), - source.command); - else if (subcommand.equals_ci("CLEAR")) - source.Reply(_("The \002{0} CLEAR\002 command removes all auto kills from the auto kill list."), - source.command); - else - { - CommandInfo *help = source.service->FindCommand("generic/help"); - source.Reply(_("Allows manipulating the AKILL list." - " If a user matching an AKILL mask connects, services will issue a kill the user and prevent them from reconnecting.\n" - "\n" - "The \002ADD\002 command adds \037mask\037 to the auto kill list.\n" - "\002{msg}{service} {help} {command} ADD\002 for more information.\n" - "\n" - "The \002DEL\002 command removes \037mask\037 from the auto kill list.\n" - "\002{msg}{service} {help} {command} DEL\002 for more information.\n" - "\n" - "The \002LIST\002 and \002VIEW\002 commands both show the auto kill list, but \002VIEW\002 also shows who created the auto kill entry, when it was created, and when it expires.\n" - "\002{msg}{service} {help} {command} [LIST | VIEW]\002 for more information.\n" - "\n" - "The \002CLEAR\002 command clears th auto kill list." - "\002{msg}{service} {help} {command} CLEAR\002 for more information.\n"), - "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "help"_kw = help->cname, "command"_kw = source.command); - } - return true; - } -}; - -class OSAKill : public Module -{ - CommandOSAKill commandosakill; - - public: - OSAKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosakill(this) - { - - } -}; - -MODULE_INIT(OSAKill) diff --git a/modules/commands/os_chankill.cpp b/modules/commands/os_chankill.cpp deleted file mode 100644 index 8d3773e9d..000000000 --- a/modules/commands/os_chankill.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -static ServiceReference akills("XLineManager", "xlinemanager/sgline"); - -class CommandOSChanKill : public Command -{ - public: - CommandOSChanKill(Module *creator) : Command(creator, "operserv/chankill", 2, 3) - { - this->SetDesc(_("AKILL all users on a specific channel")); - this->SetSyntax(_("[+\037expiry\037] \037channel\037 \037reason\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (!akills) - return; - - Anope::string expiry, channel; - unsigned last_param = 1; - - channel = params[0]; - if (!channel.empty() && channel[0] == '+') - { - expiry = channel; - channel = params[1]; - last_param = 2; - } - - time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get("autokillexpiry", "30d"); - if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) - expires *= 86400; - - if (expires) - { - if (expires < 60) - { - source.Reply(_("Invalid expiry time \002{0}\002."), expiry); - return; - } - - expires += Anope::CurTime; - } - - if (params.size() <= last_param) - { - this->OnSyntaxError(source, ""); - return; - } - - Anope::string reason = params[last_param]; - if (params.size() > last_param + 1) - reason += params[last_param + 1]; - - Anope::string realreason; - if (Config->GetModule("operserv")->Get("addakiller") && !source.GetNick().empty()) - realreason = "[" + source.GetNick() + "] " + reason; - else - realreason = reason; - - Channel *c = Channel::Find(channel); - if (!c) - { - source.Reply(_("Channel \002{0}\002 doesn't exist."), channel); - return; - } - - for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ++it) - { - ChanUserContainer *uc = it->second; - - if (uc->user->server == Me || uc->user->HasMode("OPER")) - continue; - - XLine *x = new XLine("*@" + uc->user->host, source.GetNick(), expires, realreason, XLineManager::GenerateUID()); - akills->AddXLine(x); - akills->OnMatch(uc->user, x); - } - - Log(LOG_ADMIN, source, this) << "on " << c->name << " (" << realreason << ")"; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Adds an auto kill for every user on \037channel\037, except for IRC operators.")); - return true; - } -}; - -class OSChanKill : public Module -{ - CommandOSChanKill commandoschankill; - - public: - OSChanKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandoschankill(this) - { - - } -}; - -MODULE_INIT(OSChanKill) diff --git a/modules/commands/os_config.cpp b/modules/commands/os_config.cpp deleted file mode 100644 index 257f8fee4..000000000 --- a/modules/commands/os_config.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSConfig : public Command -{ - public: - CommandOSConfig(Module *creator) : Command(creator, "operserv/config", 1, 4) - { - this->SetDesc(_("View and change configuration file settings")); - this->SetSyntax(_("{\037MODIFY\037|\037VIEW\037} [\037block name\037 \037item name\037 \037item value\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &what = params[0]; - - if (what.equals_ci("MODIFY") && params.size() > 3) - { - if (!source.HasPriv("operserv/config")) - { - source.Reply(_("Access denied. You do not have the operator privilege \002{0}\002."), "operserv/config"); - return; - } - - Configuration::Block *block = Config->GetBlock(params[1]); - if (!block) - block = Config->GetModule(params[1]); - - if (!block) - { - source.Reply(_("There is no such configuration block \002{0}\002."), params[1]); - return; - } - - block->Set(params[2], params[3]); - - Log(LOG_ADMIN, source, this) << "to change the configuration value of " << params[1] << ":" << params[2] << " to " << params[3]; - source.Reply(_("Value of \002{0}:{1}\002 changed to \002{2}\002."), params[1], params[2], params[3]); - } - else if (what.equals_ci("VIEW")) - { - /* Blocks we should show */ - const Anope::string show_blocks[] = { "serverinfo", "networkinfo", "options", "" }; - - Log(LOG_ADMIN, source, this) << "VIEW"; - - for (unsigned i = 0; !show_blocks[i].empty(); ++i) - { - Configuration::Block *block = Config->GetBlock(show_blocks[i]); - const Configuration::Block::item_map *items = block->GetItems(); - - if (!items) - continue; - - ListFormatter lflist(source.GetAccount()); - lflist.AddColumn(_("Name")).AddColumn(_("Value")); - - for (Configuration::Block::item_map::const_iterator it = items->begin(), it_end = items->end(); it != it_end; ++it) - { - ListFormatter::ListEntry entry; - entry["Name"] = it->first; - entry["Value"] = it->second; - lflist.AddEntry(entry); - } - - std::vector replies; - lflist.Process(replies); - - source.Reply(_("%s settings:"), block->GetName()); - - for (unsigned j = 0; j < replies.size(); ++j) - source.Reply(replies[j]); - - source.Reply(" "); - } - - ListFormatter lflist(source.GetAccount()); - lflist.AddColumn(_("Module Name")).AddColumn(_("Name")).AddColumn(_("Value")); - - for (int i = 0; i < Config->CountBlock("module"); ++i) - { - Configuration::Block *block = Config->GetBlock("module", i); - const Configuration::Block::item_map *items = block->GetItems(); - - if (!items || items->size() <= 1) - continue; - - ListFormatter::ListEntry entry; - entry["Module Name"] = block->Get("name"); - - for (Configuration::Block::item_map::const_iterator it = items->begin(), it_end = items->end(); it != it_end; ++it) - { - entry["Name"] = it->first; - entry["Value"] = it->second; - lflist.AddEntry(entry); - } - } - - std::vector replies; - lflist.Process(replies); - - source.Reply(_("Module settings:")); - - for (unsigned j = 0; j < replies.size(); ++j) - source.Reply(replies[j]); - - source.Reply(_("End of configuration.")); - } - else - this->OnSyntaxError(source, what); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Allows you to change and view configuration settings. Settings changed by this command are temporary and will not be reflected back into the configuration file, and will be lost if Anope is shut down, restarted, or the configuration is reloaded.\n" - "Use of the \002MODIFY\002 command requires the \002{0}\002 operator privilege.\n" - "\n" - "Example:\n" - " {0} MODIFY nickserv forcemail no" - " Changes the \"forceemail\" setting of the module configuration for \"nickserv\" to \"no\"")); - return true; - } -}; - -class OSConfig : public Module -{ - CommandOSConfig commandosconfig; - - public: - OSConfig(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosconfig(this) - { - - } -}; - -MODULE_INIT(OSConfig) diff --git a/modules/commands/os_defcon.cpp b/modules/commands/os_defcon.cpp deleted file mode 100644 index 804b66c27..000000000 --- a/modules/commands/os_defcon.cpp +++ /dev/null @@ -1,619 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/os_session.h" -#include "modules/os_defcon.h" -#include "modules/global.h" - -enum DefconLevel -{ - DEFCON_NO_NEW_CHANNELS, - DEFCON_NO_NEW_NICKS, - DEFCON_NO_MLOCK_CHANGE, - DEFCON_FORCE_CHAN_MODES, - DEFCON_REDUCE_SESSION, - DEFCON_NO_NEW_CLIENTS, - DEFCON_OPER_ONLY, - DEFCON_SILENT_OPER_ONLY, - DEFCON_AKILL_NEW_CLIENTS, - DEFCON_NO_NEW_MEMOS -}; - -bool DefConModesSet = false; - -struct DefconConfig -{ - std::vector > DefCon; - std::set DefConModesOn, DefConModesOff; - std::map DefConModesOnParams; - - int defaultlevel, sessionlimit; - Anope::string chanmodes, message, offmessage, akillreason; - std::vector defcons; - time_t akillexpire, timeout; - bool globalondefcon; - - unsigned max_session_kill; - time_t session_autokill_expiry; - Anope::string sle_reason, sle_detailsloc; - - DefconConfig() - { - this->DefCon.resize(6); - this->defcons.resize(5); - } - - bool Check(DefconLevel level) - { - return this->Check(this->defaultlevel, level); - } - - bool Check(int dlevel, DefconLevel level) - { - return this->DefCon[dlevel].test(level); - } - - void Add(int dlevel, DefconLevel level) - { - this->DefCon[dlevel][level] = true; - } - - void Del(int dlevel, DefconLevel level) - { - this->DefCon[dlevel][level] = false; - } - - bool SetDefConParam(const Anope::string &name, const Anope::string &buf) - { - return DefConModesOnParams.insert(std::make_pair(name, buf)).second; - } - - void UnsetDefConParam(const Anope::string &name) - { - DefConModesOnParams.erase(name); - } - - bool GetDefConParam(const Anope::string &name, Anope::string &buf) - { - std::map::iterator it = DefConModesOnParams.find(name); - - buf.clear(); - - if (it != DefConModesOnParams.end()) - { - buf = it->second; - return true; - } - - return false; - } -}; - -static DefconConfig DConfig; - -static void runDefCon(); -static Anope::string defconReverseModes(const Anope::string &modes); - -static Timer *timeout; - -class DefConTimeout : public Timer -{ - EventHandlers &eventdefcon; - int level; - - public: - DefConTimeout(EventHandlers &ev, Module *mod, int newlevel) : Timer(mod, DConfig.timeout), eventdefcon(ev), level(newlevel) - { - timeout = this; - } - - ~DefConTimeout() - { - timeout = NULL; - } - - void Tick(time_t) override - { - if (DConfig.defaultlevel != level) - { - DConfig.defaultlevel = level; - this->eventdefcon(&Event::DefconLevel::OnDefconLevel, level); - Log(Config->GetClient("OperServ"), "operserv/defcon") << "Defcon level timeout, returning to level " << level; - - if (DConfig.globalondefcon && Global::service) - { - if (!DConfig.offmessage.empty()) - Global::service->SendGlobal(NULL, "", DConfig.offmessage); - else - Global::service->SendGlobal(NULL, "", Anope::printf(Language::Translate(_("The Defcon level is now at: \002%d\002")), DConfig.defaultlevel)); - - if (!DConfig.message.empty()) - Global::service->SendGlobal(NULL, "", DConfig.message); - } - - runDefCon(); - } - } -}; - -class CommandOSDefcon : public Command -{ - void SendLevels(CommandSource &source) - { - if (DConfig.Check(DEFCON_NO_NEW_CHANNELS)) - source.Reply(_("* No new channel registrations")); - if (DConfig.Check(DEFCON_NO_NEW_NICKS)) - source.Reply(_("* No new nick registrations")); - if (DConfig.Check(DEFCON_NO_MLOCK_CHANGE)) - source.Reply(_("* No mode lock changes")); - if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && !DConfig.chanmodes.empty()) - source.Reply(_("* Force channel modes (\002{0}\002) to be set on all channels"), DConfig.chanmodes); - if (DConfig.Check(DEFCON_REDUCE_SESSION)) - source.Reply(_("* Use the reduced session limit of \002{0}\002"), DConfig.sessionlimit); - if (DConfig.Check(DEFCON_NO_NEW_CLIENTS)) - source.Reply(_("* Kill any new clients connecting")); - if (DConfig.Check(DEFCON_OPER_ONLY)) - source.Reply(_("* Ignore non-opers with a message")); - if (DConfig.Check(DEFCON_SILENT_OPER_ONLY)) - source.Reply(_("* Silently ignore non-opers")); - if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) - source.Reply(_("* AKILL any new clients connecting")); - if (DConfig.Check(DEFCON_NO_NEW_MEMOS)) - source.Reply(_("* No new memos sent")); - } - - EventHandlers &ondefconlevel; - - public: - CommandOSDefcon(Module *creator, EventHandlers &event) : Command(creator, "operserv/defcon", 1, 1), ondefconlevel(event) - { - this->SetDesc(_("Manipulate the DefCon system")); - this->SetSyntax(_("[\0021\002|\0022\002|\0023\002|\0024\002|\0025\002]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &lvl = params[0]; - - if (lvl.empty()) - { - source.Reply(_("Services are now at defcon \002{0}\002."), DConfig.defaultlevel); - this->SendLevels(source); - return; - } - - int newLevel = 0; - try - { - newLevel = convertTo(lvl); - } - catch (const ConvertException &) { } - - if (newLevel < 1 || newLevel > 5) - { - this->OnSyntaxError(source, ""); - return; - } - - DConfig.defaultlevel = newLevel; - - this->ondefconlevel(&Event::DefconLevel::OnDefconLevel, newLevel); - - delete timeout; - - if (DConfig.timeout) - timeout = new DefConTimeout(this->ondefconlevel, this->module, 5); - - source.Reply(_("Services are now at defcon \002{0}\002."), DConfig.defaultlevel); - this->SendLevels(source); - Log(LOG_ADMIN, source, this) << "to change defcon level to " << newLevel; - - /* Global notice the user what is happening. Also any Message that - the Admin would like to add. Set in config file. */ - if (DConfig.globalondefcon && Global::service) - { - if (DConfig.defaultlevel == 5 && !DConfig.offmessage.empty()) - Global::service->SendGlobal(NULL, "", DConfig.offmessage); - else if (DConfig.defaultlevel != 5) - { - Global::service->SendGlobal(NULL, "", Anope::printf(_("The defcon level is now at \002%d\002"), DConfig.defaultlevel)); - if (!DConfig.message.empty()) - Global::service->SendGlobal(NULL, "", DConfig.message); - } - } - - /* Run any defcon functions, e.g. FORCE CHAN MODE */ - runDefCon(); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("The defcon system can be used to implement a pre-defined set of restrictions to services useful during an attempted attack on the network.")); - // XXX actually explain what this does - return true; - } -}; - -class OSDefcon : public Module - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - ServiceReference session_service; - ServiceReference akills; - CommandOSDefcon commandosdefcon; - EventHandlers ondefconlevel; - - void ParseModeString() - { - int add = -1; /* 1 if adding, 0 if deleting, -1 if neither */ - unsigned char mode; - ChannelMode *cm; - ChannelModeParam *cmp; - Anope::string modes, param; - - spacesepstream ss(DConfig.chanmodes); - - DConfig.DefConModesOn.clear(); - DConfig.DefConModesOff.clear(); - ss.GetToken(modes); - - /* Loop while there are modes to set */ - for (unsigned i = 0, end = modes.length(); i < end; ++i) - { - mode = modes[i]; - - switch (mode) - { - case '+': - add = 1; - continue; - case '-': - add = 0; - continue; - default: - if (add < 0) - continue; - } - - if ((cm = ModeManager::FindChannelModeByChar(mode))) - { - if (cm->type == MODE_STATUS || cm->type == MODE_LIST) - { - Log(this) << "DefConChanModes mode character '" << mode << "' cannot be locked"; - continue; - } - else if (add) - { - DConfig.DefConModesOn.insert(cm->name); - DConfig.DefConModesOff.erase(cm->name); - - if (cm->type == MODE_PARAM) - { - cmp = anope_dynamic_static_cast(cm); - - if (!ss.GetToken(param)) - { - Log(this) << "DefConChanModes mode character '" << mode << "' has no parameter while one is expected"; - continue; - } - - if (!cmp->IsValid(param)) - continue; - - DConfig.SetDefConParam(cmp->name, param); - } - } - else if (DConfig.DefConModesOn.count(cm->name)) - { - DConfig.DefConModesOn.erase(cm->name); - - if (cm->type == MODE_PARAM) - DConfig.UnsetDefConParam(cm->name); - } - } - } - - /* We can't mlock +L if +l is not mlocked as well. */ - if ((cm = ModeManager::FindChannelModeByName("REDIRECT")) && DConfig.DefConModesOn.count(cm->name) && !DConfig.DefConModesOn.count("LIMIT")) - { - DConfig.DefConModesOn.erase("REDIRECT"); - - Log(this) << "DefConChanModes must lock mode +l as well to lock mode +L"; - } - } - - public: - OSDefcon(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , session_service("SessionService", "session") - , akills("XLineManager", "xlinemanager/sgline") - , commandosdefcon(this, ondefconlevel) - , ondefconlevel(this) - { - - } - - ~OSDefcon() - { - delete timeout; - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - DefconConfig dconfig; - - dconfig.defaultlevel = block->Get("defaultlevel"); - dconfig.defcons[4] = block->Get("level4"); - dconfig.defcons[3] = block->Get("level3"); - dconfig.defcons[2] = block->Get("level2"); - dconfig.defcons[1] = block->Get("level1"); - dconfig.sessionlimit = block->Get("sessionlimit"); - dconfig.akillreason = block->Get("akillreason"); - dconfig.akillexpire = block->Get("akillexpire"); - dconfig.chanmodes = block->Get("chanmodes"); - dconfig.timeout = block->Get("timeout"); - dconfig.globalondefcon = block->Get("globalondefcon"); - dconfig.message = block->Get("message"); - dconfig.offmessage = block->Get("offmessage"); - - Module *session = ModuleManager::FindModule("os_session"); - block = conf->GetModule(session); - - dconfig.max_session_kill = block->Get("maxsessionkill"); - dconfig.session_autokill_expiry = block->Get("sessionautokillexpiry"); - dconfig.sle_reason = block->Get("sessionlimitexceeded"); - dconfig.sle_detailsloc = block->Get("sessionlimitdetailsloc"); - - if (dconfig.defaultlevel < 1 || dconfig.defaultlevel > 5) - throw ConfigException("The value for must be between 1 and 5"); - else if (dconfig.akillexpire <= 0) - throw ConfigException("The value for must be greater than zero!"); - - for (unsigned level = 1; level < 5; ++level) - { - spacesepstream operations(dconfig.defcons[level]); - Anope::string operation; - while (operations.GetToken(operation)) - { - if (operation.equals_ci("nonewchannels")) - dconfig.Add(level, DEFCON_NO_NEW_CHANNELS); - else if (operation.equals_ci("nonewnicks")) - dconfig.Add(level, DEFCON_NO_NEW_NICKS); - else if (operation.equals_ci("nomlockchanges")) - dconfig.Add(level, DEFCON_NO_MLOCK_CHANGE); - else if (operation.equals_ci("forcechanmodes")) - dconfig.Add(level, DEFCON_FORCE_CHAN_MODES); - else if (operation.equals_ci("reducedsessions")) - dconfig.Add(level, DEFCON_REDUCE_SESSION); - else if (operation.equals_ci("nonewclients")) - dconfig.Add(level, DEFCON_NO_NEW_CLIENTS); - else if (operation.equals_ci("operonly")) - dconfig.Add(level, DEFCON_OPER_ONLY); - else if (operation.equals_ci("silentoperonly")) - dconfig.Add(level, DEFCON_SILENT_OPER_ONLY); - else if (operation.equals_ci("akillnewclients")) - dconfig.Add(level, DEFCON_AKILL_NEW_CLIENTS); - else if (operation.equals_ci("nonewmemos")) - dconfig.Add(level, DEFCON_NO_NEW_MEMOS); - } - - if (dconfig.Check(level, DEFCON_REDUCE_SESSION) && dconfig.sessionlimit <= 0) - throw ConfigException("The value for must be greater than zero!"); - else if (dconfig.Check(level, DEFCON_AKILL_NEW_CLIENTS) && dconfig.akillreason.empty()) - throw ConfigException("The value for must not be empty!"); - else if (dconfig.Check(level, DEFCON_FORCE_CHAN_MODES) && dconfig.chanmodes.empty()) - throw ConfigException("The value for must not be empty!"); - } - - DConfig = dconfig; - this->ParseModeString(); - } - - EventReturn OnChannelModeSet(Channel *c, const MessageSource &source, ChannelMode *mode, const Anope::string ¶m) override - { - if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && DConfig.DefConModesOff.count(mode->name) && source.GetUser() && !source.GetBot()) - { - c->RemoveMode(Config->GetClient("OperServ"), mode, param); - - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } - - EventReturn OnChannelModeUnset(Channel *c, const MessageSource &source, ChannelMode *mode, const Anope::string &) override - { - if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && DConfig.DefConModesOn.count(mode->name) && source.GetUser() && !source.GetBot()) - { - Anope::string param; - - if (DConfig.GetDefConParam(mode->name, param)) - c->SetMode(Config->GetClient("OperServ"), mode, param); - else - c->SetMode(Config->GetClient("OperServ"), mode); - - return EVENT_STOP; - - } - - return EVENT_CONTINUE; - } - - EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override - { - if (DConfig.Check(DEFCON_OPER_ONLY) && !source.IsOper()) - { - source.Reply(_("Services are in DefCon mode, please try again later.")); - return EVENT_STOP; - } - else if (DConfig.Check(DEFCON_SILENT_OPER_ONLY) && !source.IsOper()) - { - return EVENT_STOP; - } - else if (command->name == "nickserv/register" || command->name == "nickserv/group") - { - if (DConfig.Check(DEFCON_NO_NEW_NICKS)) - { - source.Reply(_("Services are in defcon mode, please try again later.")); - return EVENT_STOP; - } - } - else if (command->name == "chanserv/mode" && params.size() > 1 && params[1].equals_ci("LOCK")) - { - if (DConfig.Check(DEFCON_NO_MLOCK_CHANGE)) - { - source.Reply(_("Services are in defcon mode, please try again later.")); - return EVENT_STOP; - } - } - else if (command->name == "chanserv/register") - { - if (DConfig.Check(DEFCON_NO_NEW_CHANNELS)) - { - source.Reply(_("Services are in defcon mode, please try again later.")); - return EVENT_STOP; - } - } - else if (command->name == "memoserv/send") - { - if (DConfig.Check(DEFCON_NO_NEW_MEMOS)) - { - source.Reply(_("Services are in defcon mode, please try again later.")); - return EVENT_STOP; - } - } - - return EVENT_CONTINUE; - } - - void OnUserConnect(User *u, bool &exempt) override - { - if (exempt || u->Quitting() || !u->server->IsSynced() || u->server->IsULined()) - return; - - ServiceBot *OperServ = Config->GetClient("OperServ"); - if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS) && akills) - { - Log(OperServ, "operserv/defcon") << "DEFCON: adding akill for *@" << u->host; - XLine x("*@" + u->host, OperServ ? OperServ->nick : "defcon", Anope::CurTime + DConfig.akillexpire, DConfig.akillreason, XLineManager::GenerateUID()); - akills->Send(NULL, &x); - } - - if (DConfig.Check(DEFCON_NO_NEW_CLIENTS) || DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) - { - u->Kill(OperServ, DConfig.akillreason); - return; - } - - if (DConfig.sessionlimit <= 0 || !session_service) - return; - - Session *session = session_service->FindSession(u->ip.addr()); - Exception *e = session_service->FindException(u); - - if (DConfig.Check(DEFCON_REDUCE_SESSION) && !e) - { - if (session && session->count > static_cast(DConfig.sessionlimit)) - { - if (!DConfig.sle_reason.empty()) - { - Anope::string message = DConfig.sle_reason.replace_all_cs("%IP%", u->ip.addr()); - u->SendMessage(OperServ, message); - } - if (!DConfig.sle_detailsloc.empty()) - u->SendMessage(OperServ, DConfig.sle_detailsloc); - - ++session->hits; - if (akills && DConfig.max_session_kill && session->hits >= DConfig.max_session_kill) - { - XLine x("*@" + session->addr.mask(), OperServ ? OperServ->nick : "", Anope::CurTime + DConfig.session_autokill_expiry, "Defcon session limit exceeded", XLineManager::GenerateUID()); - akills->Send(NULL, &x); - Log(OperServ, "akill/defcon") << "[DEFCON] Added a temporary AKILL for \002*@" << session->addr.mask() << "\002 due to excessive connections"; - } - else - { - u->Kill(OperServ, "Defcon session limit exceeded"); - } - } - } - } - - void OnChannelModeAdd(ChannelMode *cm) override - { - if (DConfig.chanmodes.find(cm->mchar) != Anope::string::npos) - this->ParseModeString(); - } - - void OnChannelSync(Channel *c) override - { - if (DConfig.Check(DEFCON_FORCE_CHAN_MODES)) - c->SetModes(Config->GetClient("OperServ"), false, "%s", DConfig.chanmodes.c_str()); - } -}; - -static void runDefCon() -{ - ServiceBot *OperServ = Config->GetClient("OperServ"); - if (DConfig.Check(DEFCON_FORCE_CHAN_MODES)) - { - if (!DConfig.chanmodes.empty() && !DefConModesSet) - { - if (DConfig.chanmodes[0] == '+' || DConfig.chanmodes[0] == '-') - { - Log(OperServ, "operserv/defcon") << "DEFCON: setting " << DConfig.chanmodes << " on all channels"; - DefConModesSet = true; - for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) - it->second->SetModes(OperServ, false, "%s", DConfig.chanmodes.c_str()); - } - } - } - else - { - if (!DConfig.chanmodes.empty() && DefConModesSet) - { - if (DConfig.chanmodes[0] == '+' || DConfig.chanmodes[0] == '-') - { - DefConModesSet = false; - Anope::string newmodes = defconReverseModes(DConfig.chanmodes); - if (!newmodes.empty()) - { - Log(OperServ, "operserv/defcon") << "DEFCON: setting " << newmodes << " on all channels"; - for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) - it->second->SetModes(OperServ, true, "%s", newmodes.c_str()); - } - } - } - } -} - -static Anope::string defconReverseModes(const Anope::string &modes) -{ - if (modes.empty()) - return ""; - Anope::string newmodes; - for (unsigned i = 0, end = modes.length(); i < end; ++i) - { - if (modes[i] == '+') - newmodes += '-'; - else if (modes[i] == '-') - newmodes += '+'; - else - newmodes += modes[i]; - } - return newmodes; -} - -MODULE_INIT(OSDefcon) diff --git a/modules/commands/os_dns.cpp b/modules/commands/os_dns.cpp deleted file mode 100644 index a231a1e74..000000000 --- a/modules/commands/os_dns.cpp +++ /dev/null @@ -1,1012 +0,0 @@ -/* - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - */ - -#include "module.h" -#include "modules/dns.h" -#include "modules/os_dns.h" - -static std::map > server_quit_times; - -class DNSZoneImpl : public DNSZone -{ - public: - DNSZoneImpl(Serialize::TypeBase *type) : DNSZone(type) { } - DNSZoneImpl(Serialize::TypeBase *type, Serialize::ID id) : DNSZone(type, id) { } - - Anope::string GetName() override; - void SetName(const Anope::string &) override; - - static DNSZone *Find(const Anope::string &name) - { - for (DNSZone *zone : Serialize::GetObjects(dnszone)) - if (zone->GetName().equals_ci(name)) - return zone; - return nullptr; - } -}; - -class DNSZoneType : public Serialize::Type -{ - public: - Serialize::Field name; - - DNSZoneType(Module *creator) : Serialize::Type(creator, "DNSZone") - , name(this, "name") - { - } -}; - -Anope::string DNSZoneImpl::GetName() -{ - return Get(&DNSZoneType::name); -} - -void DNSZoneImpl::SetName(const Anope::string &name) -{ - Set(&DNSZoneType::name, name); -} - -class DNSServerImpl : public DNSServer -{ - /* is actually in the pool */ - bool active = false; - - public: - std::set zones; - time_t repool = 0; - - DNSServerImpl(Serialize::TypeBase *type) : DNSServer(type) { } - DNSServerImpl(Serialize::TypeBase *type, Serialize::ID id) : DNSServer(type, id) { } - - DNSZone *GetZone() override; - void SetZone(DNSZone *) override; - - Anope::string GetName() override; - void SetName(const Anope::string &) override; - - unsigned int GetLimit() override; - void SetLimit(const unsigned int &) override; - - bool GetPooled() override; - void SetPool(const bool &) override; - - bool Active() - { - return GetPooled() && active; - } - - void SetActive(bool p) - { - if (p) - this->SetPool(p); - active = p; - - if (DNS::manager) - { - DNS::manager->UpdateSerial(); - for (std::set::iterator it = zones.begin(), it_end = zones.end(); it != it_end; ++it) - DNS::manager->Notify(*it); - } - } - - static DNSServerImpl *Find(const Anope::string &s) - { - for (DNSServerImpl *server : Serialize::GetObjects(dnsserver)) - if (server->GetName().equals_ci(s)) - return server; - return nullptr; - } -}; - -class DNSServerType : public Serialize::Type -{ - public: - Serialize::ObjectField zone; - Serialize::Field name; - Serialize::Field limit; - Serialize::Field pooled; - - DNSServerType(Module *creator) : Serialize::Type(creator, "DNSServer") - , zone(this, "zone") - , name(this, "name") - , limit(this, "limit") - , pooled(this, "pooled") - { - } -}; - -DNSZone *DNSServerImpl::GetZone() -{ - return Get(&DNSServerType::zone); -} - -void DNSServerImpl::SetZone(DNSZone *z) -{ - Set(&DNSServerType::zone, z); -} - -Anope::string DNSServerImpl::GetName() -{ - return Get(&DNSServerType::name); -} - -void DNSServerImpl::SetName(const Anope::string &n) -{ - Set(&DNSServerType::name, n); -} - -unsigned int DNSServerImpl::GetLimit() -{ - return Get(&DNSServerType::limit); -} - -void DNSServerImpl::SetLimit(const unsigned int &l) -{ - Set(&DNSServerType::limit, l); -} - -bool DNSServerImpl::GetPooled() -{ - return Get(&DNSServerType::pooled); -} - -void DNSServerImpl::SetPool(const bool &p) -{ - Set(&DNSServerType::pooled, p); -} - -class DNSZoneMembershipImpl : public DNSZoneMembership -{ - public: - DNSZoneMembershipImpl(Serialize::TypeBase *type) : DNSZoneMembership(type) { } - DNSZoneMembershipImpl(Serialize::TypeBase *type, Serialize::ID id) : DNSZoneMembership(type, id) { } - - DNSServer *GetServer() override; - void SetServer(DNSServer *) override; - - DNSZone *GetZone() override; - void SetZone(DNSZone *) override; - - static DNSZoneMembership *Find(DNSServer *server, DNSZone *zone) - { - for (DNSZoneMembership *mem : Serialize::GetObjects(dnszonemembership)) - if (mem->GetServer() == server && mem->GetZone() == zone) - return mem; - return nullptr; - } -}; - -class DNSZoneMembershipType : public Serialize::Type -{ - public: - Serialize::ObjectField server; - Serialize::ObjectField zone; - - DNSZoneMembershipType(Module *creator) : Serialize::Type(creator, "DNSZoneMembership") - , server(this, "server") - , zone(this, "zone") - { - } -}; - -DNSServer *DNSZoneMembershipImpl::GetServer() -{ - return Get(&DNSZoneMembershipType::server); -} - -void DNSZoneMembershipImpl::SetServer(DNSServer *s) -{ - Set(&DNSZoneMembershipType::server, s); -} - -DNSZone *DNSZoneMembershipImpl::GetZone() -{ - return Get(&DNSZoneMembershipType::zone); -} - -void DNSZoneMembershipImpl::SetZone(DNSZone *z) -{ - Set(&DNSZoneMembershipType::zone, z); -} - -class DNSIPImpl : public DNSIP -{ - public: - DNSIPImpl(Serialize::TypeBase *type) : DNSIP(type) { } - DNSIPImpl(Serialize::TypeBase *type, Serialize::ID id) : DNSIP(type, id) { } - - DNSServer *GetServer() override; - void SetServer(DNSServer *) override; - - Anope::string GetIP() override; - void SetIP(const Anope::string &) override; -}; - -class DNSIPType : public Serialize::Type -{ - public: - Serialize::ObjectField server; - Serialize::Field ip; - - DNSIPType(Module *creator) : Serialize::Type(creator, "DNSIP") - , server(this, "server") - , ip(this, "ip") - { - } -}; - -DNSServer *DNSIPImpl::GetServer() -{ - return Get(&DNSIPType::server); -} - -void DNSIPImpl::SetServer(DNSServer *s) -{ - Set(&DNSIPType::server, s); -} - -Anope::string DNSIPImpl::GetIP() -{ - return Get(&DNSIPType::ip); -} - -void DNSIPImpl::SetIP(const Anope::string &ip) -{ - Set(&DNSIPType::ip, ip); -} - -class CommandOSDNS : public Command -{ - void DisplayPoolState(CommandSource &source) - { - std::vector servers = Serialize::GetObjects(dnsserver); - - if (servers.empty()) - { - source.Reply(_("There are no configured servers.")); - return; - } - - ListFormatter lf(source.GetAccount()); - lf.AddColumn(_("Server")).AddColumn(_("IP")).AddColumn(_("Limit")).AddColumn(_("State")); - for (DNSServerImpl *s : servers) - { - Server *srv = Server::Find(s->GetName(), true); - - ListFormatter::ListEntry entry; - entry["Server"] = s->GetName(); - entry["Limit"] = s->GetLimit() ? stringify(s->GetLimit()) : Language::Translate(source.GetAccount(), _("None")); - - Anope::string ip_str; - for (DNSIP *ip : s->GetRefs(dnsip)) - ip_str += ip->GetIP() + " "; - ip_str.trim(); - if (ip_str.empty()) - ip_str = "None"; - entry["IP"] = ip_str; - - if (s->Active()) - entry["State"] = Language::Translate(source.GetAccount(), _("Pooled/Active")); - else if (s->GetPooled()) - entry["State"] = Language::Translate(source.GetAccount(), _("Pooled/Not Active")); - else - entry["State"] = Language::Translate(source.GetAccount(), _("Unpooled")); - - if (!srv) - entry["State"] += Anope::string(" ") + Language::Translate(source.GetAccount(), _("(Split)")); - - lf.AddEntry(entry); - } - - std::vector replies; - lf.Process(replies); - - std::vector zones = Serialize::GetObjects(dnszone); - if (!zones.empty()) - { - ListFormatter lf2(source.GetAccount()); - lf2.AddColumn(_("Zone")).AddColumn(_("Servers")); - - for (DNSZone *z : zones) - { - ListFormatter::ListEntry entry; - entry["Zone"] = z->GetName(); - - Anope::string server_str; - for (DNSServer *s : z->GetRefs(dnsserver)) - server_str += s->GetName() + " "; - server_str.trim(); - - if (server_str.empty()) - server_str = "None"; - - entry["Servers"] = server_str; - - lf2.AddEntry(entry); - } - - lf2.Process(replies); - } - - for (const Anope::string &r : replies) - source.Reply(r); - } - - void AddZone(CommandSource &source, const std::vector ¶ms) - { - const Anope::string &zone = params[1]; - - if (DNSZoneImpl::Find(zone)) - { - source.Reply(_("Zone \002{0}\002 already exists."), zone); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - Log(LOG_ADMIN, source, this) << "to add zone " << zone; - - DNSZone *z = dnszone.Create(); - z->SetName(zone); - source.Reply(_("Added zone \002{0}\002."), zone); - } - - void DelZone(CommandSource &source, const std::vector ¶ms) - { - const Anope::string &zone = params[1]; - - DNSZone *z = DNSZoneImpl::Find(zone); - if (!z) - { - source.Reply(_("Zone \002{0}\002 does not exist."), zone); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - Log(LOG_ADMIN, source, this) << "to delete zone " << z->GetName(); - - for (DNSZoneMembership *mem : z->GetRefs(dnszonemembership)) - mem->Delete(); - - if (DNS::manager) - { - DNS::manager->UpdateSerial(); - DNS::manager->Notify(z->GetName()); - } - - source.Reply(_("Zone \002{0}\002 removed."), z->GetName()); - z->Delete(); - } - - void AddServer(CommandSource &source, const std::vector ¶ms) - { - DNSServer *s = DNSServerImpl::Find(params[1]); - const Anope::string &zone = params.size() > 2 ? params[2] : ""; - - if (s) - { - if (zone.empty()) - { - source.Reply(_("Server \002{0}\002 already exists."), s->GetName()); - } - else - { - DNSZone *z = DNSZoneImpl::Find(zone); - if (!z) - { - source.Reply(_("Zone \002{0}\002 does not exist."), zone); - return; - } - - DNSZoneMembership *mem = DNSZoneMembershipImpl::Find(s, z); - if (mem) - { - source.Reply(_("Server \002{0}\002 is already in zone \002{1}\002."), s->GetName(), z->GetName()); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - mem = dnszonemembership.Create(); - mem->SetZone(z); - mem->SetServer(s); - - if (DNS::manager) - { - DNS::manager->UpdateSerial(); - DNS::manager->Notify(zone); - } - - Log(LOG_ADMIN, source, this) << "to add server " << s->GetName() << " to zone " << z->GetName(); - - source.Reply(_("Server \002{0}\002 added to zone \002{1}\002."), s->GetName(), z->GetName()); - } - - return; - } - - Server *serv = Server::Find(params[1], true); - if (!serv || serv == Me || serv->IsJuped()) - { - source.Reply(_("Server \002{0}\002 is not linked to the network."), params[1]); - return; - } - - s = dnsserver.Create(); - s->SetName(params[1]); - if (zone.empty()) - { - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - Log(LOG_ADMIN, source, this) << "to add server " << s->GetName(); - source.Reply(_("Added server \002{0}\002."), s->GetName()); - } - else - { - DNSZone *z = DNSZoneImpl::Find(zone); - if (!z) - { - source.Reply(_("Zone \002{0}\002 does not exist."), zone); - s->Delete(); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - Log(LOG_ADMIN, source, this) << "to add server " << s->GetName() << " to zone " << zone; - - DNSZoneMembership *mem = dnszonemembership.Create(); - mem->SetServer(s); - mem->SetZone(z); - - if (DNS::manager) - { - DNS::manager->UpdateSerial(); - DNS::manager->Notify(z->GetName()); - } - } - } - - void DelServer(CommandSource &source, const std::vector ¶ms) - { - DNSServer *s = DNSServerImpl::Find(params[1]); - const Anope::string &zone = params.size() > 2 ? params[2] : ""; - - if (!s) - { - source.Reply(_("Server \002{0}\002 does not exist."), params[1]); - return; - } - - if (!zone.empty()) - { - DNSZone *z = DNSZoneImpl::Find(zone); - if (!z) - { - source.Reply(_("Zone \002{0}\002 does not exist."), zone); - return; - } - - DNSZoneMembership *mem = DNSZoneMembershipImpl::Find(s, z); - if (!mem) - { - source.Reply(_("Server \002{0}\002 is not in zone \002{1}\002."), s->GetName(), z->GetName()); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - Log(LOG_ADMIN, source, this) << "to remove server " << s->GetName() << " from zone " << z->GetName(); - - if (DNS::manager) - { - DNS::manager->UpdateSerial(); - DNS::manager->Notify(z->GetName()); - } - - mem->Delete(); - source.Reply(_("Removed server \002{0}\002 from zone \002{1}\002."), s->GetName(), z->GetName()); - return; - } - - if (Server::Find(s->GetName(), true)) - { - source.Reply(_("Server \002{0}\002 must be quit before it can be deleted."), s->GetName()); - return; - } - - for (DNSZoneMembership *mem : s->GetRefs(dnszonemembership)) - mem->Delete(); - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - if (DNS::manager) - DNS::manager->UpdateSerial(); - - Log(LOG_ADMIN, source, this) << "to delete server " << s->GetName(); - source.Reply(_("Removed server \002{0}\002."), s->GetName()); - s->Delete(); - } - - void AddIP(CommandSource &source, const std::vector ¶ms) - { - DNSServerImpl *s = DNSServerImpl::Find(params[1]); - - if (!s) - { - source.Reply(_("Server \002{0}\002 does not exist."), params[1]); - return; - } - - for (DNSIP *ip : s->GetRefs(dnsip)) - if (params[2].equals_ci(ip->GetIP())) - { - source.Reply(_("IP \002{0}\002 already exists for \002{1}\002."), ip->GetIP(), s->GetName()); - return; - } - - sockaddrs addr(params[2]); - if (!addr.valid()) - { - source.Reply(_("\002{0}\002 is not a valid IP address."), params[2]); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - DNSIP *ip = dnsip.Create(); - ip->SetServer(s); - ip->SetIP(params[2]); - - source.Reply(_("Added IP \002{0}\002 to \002{1}\002."), params[2], s->GetName()); - Log(LOG_ADMIN, source, this) << "to add IP " << params[2] << " to " << s->GetName(); - - if (s->Active() && DNS::manager) - { - DNS::manager->UpdateSerial(); - for (DNSZone *zone : s->GetRefs(dnszone)) - DNS::manager->Notify(zone->GetName()); - } - } - - void DelIP(CommandSource &source, const std::vector ¶ms) - { - DNSServerImpl *s = DNSServerImpl::Find(params[1]); - - if (!s) - { - source.Reply(_("Server \002{0}\002 does not exist."), params[1]); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - for (DNSIP *ip : s->GetRefs(dnsip)) - if (params[2].equals_ci(ip->GetIP())) - { - ip->Delete(); - - source.Reply(_("Removed IP \002{0}\002 from \002{1}\002."), params[2], s->GetName()); - Log(LOG_ADMIN, source, this) << "to remove IP " << params[2] << " from " << s->GetName(); - - if (s->GetRefs(dnsip).empty()) - { - s->repool = 0; - s->SetPool(false); - } - - if (s->Active() && DNS::manager) - { - DNS::manager->UpdateSerial(); - for (DNSZone *zone : s->GetRefs(dnszone)) - DNS::manager->Notify(zone->GetName()); - } - - return; - } - - source.Reply(_("IP \002{0}\002 does not exist for \002{1}\002."), params[2], s->GetName()); - } - - void OnSet(CommandSource &source, const std::vector ¶ms) - { - DNSServer *s = DNSServerImpl::Find(params[1]); - - if (!s) - { - source.Reply(_("Server \002{0}\002 does not exist."), params[1]); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - if (params[2].equals_ci("LIMIT")) - { - try - { - unsigned l = convertTo(params[3]); - s->SetLimit(l); - if (l) - source.Reply(_("User limit for \002{0}\002 set to \002{1}\002."), s->GetName(), l); - else - source.Reply(_("User limit for \002{0}\002 removed."), s->GetName()); - } - catch (const ConvertException &ex) - { - source.Reply(_("Invalid value for limit. Must be numerical.")); - } - } - else - source.Reply(_("Unknown SET option.")); - } - - void OnPool(CommandSource &source, const std::vector ¶ms) - { - DNSServerImpl *s = DNSServerImpl::Find(params[1]); - - if (!s) - { - source.Reply(_("Server \002{0}\002 does not exist."), params[1]); - return; - } - - if (!Server::Find(s->GetName(), true)) - { - source.Reply(_("Server \002{0}\002 is not currently linked."), s->GetName()); - return; - } - - if (s->GetPooled()) - { - source.Reply(_("Server \002{0}\002 is already pooled."), s->GetName()); - return; - } - - if (s->GetRefs(dnsip).empty()) - { - source.Reply(_("Server \002{0}\002 has no configured IPs."), s->GetName()); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - s->SetActive(true); - - source.Reply(_("Pooled \002{0}\002."), s->GetName()); - Log(LOG_ADMIN, source, this) << "to pool " << s->GetName(); - } - - - void OnDepool(CommandSource &source, const std::vector ¶ms) - { - DNSServer *s = DNSServerImpl::Find(params[1]); - - if (!s) - { - source.Reply(_("Server \002{0}\002 does not exist."), params[1]); - return; - } - - if (!s->GetPooled()) - { - source.Reply(_("Server \002{0}\002 is not pooled."), s->GetName()); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - s->SetPool(false); - - source.Reply(_("Depooled \002{0}\002."), s->GetName()); - Log(LOG_ADMIN, source, this) << "to depool " << s->GetName(); - } - - public: - CommandOSDNS(Module *creator) : Command(creator, "operserv/dns", 0, 4) - { - this->SetDesc(_("Manage DNS zones for this network")); - this->SetSyntax(_("ADDZONE \037zone.name\037")); - this->SetSyntax(_("DELZONE \037zone.name\037")); - this->SetSyntax(_("ADDSERVER \037server.name\037 [\037zone.name\037]")); - this->SetSyntax(_("DELSERVER \037server.name\037 [\037zone.name\037]")); - this->SetSyntax(_("ADDIP \037server.name\037 \037ip\037")); - this->SetSyntax(_("DELIP \037server.name\037 \037ip\037")); - this->SetSyntax(_("SET \037server.name\037 \037option\037 \037value\037")); - this->SetSyntax(_("POOL \037server.name\037")); - this->SetSyntax(_("DEPOOL \037server.name\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (params.empty()) - this->DisplayPoolState(source); - else if (params[0].equals_ci("ADDZONE") && params.size() > 1) - this->AddZone(source, params); - else if (params[0].equals_ci("DELZONE") && params.size() > 1) - this->DelZone(source, params); - else if (params[0].equals_ci("ADDSERVER") && params.size() > 1) - this->AddServer(source, params); - else if (params[0].equals_ci("DELSERVER") && params.size() > 1) - this->DelServer(source, params); - else if (params[0].equals_ci("ADDIP") && params.size() > 2) - this->AddIP(source, params); - else if (params[0].equals_ci("DELIP") && params.size() > 2) - this->DelIP(source, params); - else if (params[0].equals_ci("SET") && params.size() > 3) - this->OnSet(source, params); - else if (params[0].equals_ci("POOL") && params.size() > 1) - this->OnPool(source, params); - else if (params[0].equals_ci("DEPOOL") && params.size() > 1) - this->OnDepool(source, params); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("This command allows managing DNS zones used for controlling what servers users are directed to when connecting." - " Omitting all parameters prints out the status of the DNS zone.\n" - "\n" - "\002{0} ADDZONE\002 adds a zone, eg. us.example.com. Servers can then be added to this zone with the \002ADDSERVER\002 command.\n" - "\n" - "The \002{0} ADDSERVER\002 command adds a server to the given zone." - " When a DNS query is done, the zone in question is served if it exists, otherwise all servers in all zones are served." - " A server may be in more than one zone.\n" - "\n" - "The \002{0} ADDIP\002 command associates an IP with a server.\n" - " \n" - "The \002{0} POOL\002 and \002{0} DEPOOL\002 commands actually add and remove servers to their given zones."), - source.command); - return true; - } -}; - -class ModuleDNS : public Module - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - DNSZoneType zonetype; - DNSServerType servertype; - DNSZoneMembershipType zonemembtype; - DNSIPType iptype; - CommandOSDNS commandosdns; - - time_t ttl; - int user_drop_mark; - time_t user_drop_time; - time_t user_drop_readd_time; - bool remove_split_servers; - bool readd_connected_servers; - - time_t last_warn; - - public: - ModuleDNS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - , zonetype(this) - , servertype(this) - , zonemembtype(this) - , iptype(this) - , commandosdns(this) - , last_warn(0) - { -#if 0 - for (unsigned j = 0; j < dns_servers->size(); ++j) - { - DNSServer *s = dns_servers->at(j); - if (s->Pooled() && Server::Find(s->GetName(), true)) - s->SetActive(true); - } -#endif - } - - ~ModuleDNS() - { - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - this->ttl = block->Get("ttl"); - this->user_drop_mark = block->Get("user_drop_mark"); - this->user_drop_time = block->Get("user_drop_time"); - this->user_drop_readd_time = block->Get("user_drop_readd_time"); - this->remove_split_servers = block->Get("remove_split_servers"); - this->readd_connected_servers = block->Get("readd_connected_servers"); - } - - void OnNewServer(Server *s) override - { - if (s == Me || s->IsJuped()) - return; - if (!Me->IsSynced() || this->readd_connected_servers) - { - DNSServerImpl *dns = DNSServerImpl::Find(s->GetName()); - if (dns && dns->GetPooled() && !dns->Active() && !dns->GetRefs(dnsip).empty()) - { - dns->SetActive(true); - Log(this) << "Pooling server " << s->GetName(); - } - } - } - - void OnServerQuit(Server *s) override - { - DNSServerImpl *dns = DNSServerImpl::Find(s->GetName()); - if (remove_split_servers && dns && dns->GetPooled() && dns->Active()) - { - if (readd_connected_servers) - dns->SetActive(false); // Will be reactivated when it comes back - else - dns->SetPool(false); // Otherwise permanently pull this - Log(this) << "Depooling delinked server " << s->GetName(); - } - } - - void OnUserConnect(User *u, bool &exempt) override - { - if (!u->Quitting() && u->server) - { - DNSServerImpl *s = DNSServerImpl::Find(u->server->GetName()); - /* Check for user limit reached */ - if (s && s->GetPooled() && s->Active() && s->GetLimit() && u->server->users >= s->GetLimit()) - { - Log(this) << "Depooling full server " << s->GetName() << ": " << u->server->users << " users"; - s->SetActive(false); - } - } - } - - void OnPreUserLogoff(User *u) override - { - if (u && u->server) - { - DNSServerImpl *s = DNSServerImpl::Find(u->server->GetName()); - if (!s || !s->GetPooled()) - return; - - /* Check for dropping under userlimit */ - if (s->GetLimit() && !s->Active() && s->GetLimit() > u->server->users) - { - Log(this) << "Pooling server " << s->GetName(); - s->SetActive(true); - } - - if (this->user_drop_mark > 0) - { - std::list& times = server_quit_times[u->server->GetName()]; - times.push_back(Anope::CurTime); - if (times.size() > static_cast(this->user_drop_mark)) - times.pop_front(); - - if (times.size() == static_cast(this->user_drop_mark)) - { - time_t diff = Anope::CurTime - *times.begin(); - - /* Check for very fast user drops */ - if (s->Active() && diff <= this->user_drop_time) - { - Log(this) << "Depooling server " << s->GetName() << ": dropped " << this->user_drop_mark << " users in " << diff << " seconds"; - s->repool = Anope::CurTime + this->user_drop_readd_time; - s->SetActive(false); - } - /* Check for needing to re-pool a server that dropped users */ - else if (!s->Active() && s->repool && s->repool <= Anope::CurTime) - { - s->SetActive(true); - s->repool = 0; - Log(this) << "Pooling server " << s->GetName(); - } - } - } - } - } - - void OnDnsRequest(DNS::Query &req, DNS::Query *packet) override - { - if (req.questions.empty()) - return; - /* Currently we reply to any QR for A/AAAA */ - const DNS::Question& q = req.questions[0]; - if (q.type != DNS::QUERY_A && q.type != DNS::QUERY_AAAA && q.type != DNS::QUERY_AXFR && q.type != DNS::QUERY_ANY) - return; - - DNSZone *zone = DNSZoneImpl::Find(q.name); - size_t answer_size = packet->answers.size(); - if (zone) - { - for (DNSServerImpl *s : zone->GetRefs(dnsserver)) - { - if (!s->Active()) - continue; - - for (DNSIP *ip : s->GetRefs(dnsip)) - { - DNS::QueryType q_type = ip->GetIP().find(':') != Anope::string::npos ? DNS::QUERY_AAAA : DNS::QUERY_A; - - if (q.type == DNS::QUERY_AXFR || q.type == DNS::QUERY_ANY || q_type == q.type) - { - DNS::ResourceRecord rr(q.name, q_type); - rr.ttl = this->ttl; - rr.rdata = ip->GetIP(); - packet->answers.push_back(rr); - } - } - } - } - - if (packet->answers.size() == answer_size) - { - /* Default zone */ - for (DNSServerImpl *s : Serialize::GetObjects(dnsserver)) - { - if (!s->Active()) - continue; - - for (DNSIP *ip : s->GetRefs(dnsip)) - { - DNS::QueryType q_type = ip->GetIP().find(':') != Anope::string::npos ? DNS::QUERY_AAAA : DNS::QUERY_A; - - if (q.type == DNS::QUERY_AXFR || q.type == DNS::QUERY_ANY || q_type == q.type) - { - DNS::ResourceRecord rr(q.name, q_type); - rr.ttl = this->ttl; - rr.rdata = ip->GetIP(); - packet->answers.push_back(rr); - } - } - } - } - - if (packet->answers.size() == answer_size) - { - if (last_warn + 60 < Anope::CurTime) - { - last_warn = Anope::CurTime; - Log(this) << "Warning! There are no pooled servers!"; - } - - /* Something messed up, just return them all and hope one is available */ - for (DNSServer *s : Serialize::GetObjects(dnsserver)) - for (DNSIP *ip : s->GetRefs(dnsip)) - { - DNS::QueryType q_type = ip->GetIP().find(':') != Anope::string::npos ? DNS::QUERY_AAAA : DNS::QUERY_A; - - if (q.type == DNS::QUERY_AXFR || q.type == DNS::QUERY_ANY || q_type == q.type) - { - DNS::ResourceRecord rr(q.name, q_type); - rr.ttl = this->ttl; - rr.rdata = ip->GetIP(); - packet->answers.push_back(rr); - } - } - - if (packet->answers.size() == answer_size) - { - Log(this) << "Error! There are no servers with any IPs of type " << q.type; - /* Send back an empty answer anyway */ - } - } - } -}; - -MODULE_INIT(ModuleDNS) diff --git a/modules/commands/os_forbid.cpp b/modules/commands/os_forbid.cpp deleted file mode 100644 index 0a7b1c209..000000000 --- a/modules/commands/os_forbid.cpp +++ /dev/null @@ -1,577 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/os_forbid.h" -#include "modules/nickserv.h" -#include "modules/chanserv.h" - -class ForbidDataImpl : public ForbidData -{ - public: - ForbidDataImpl(Serialize::TypeBase *type) : ForbidData(type) { } - ForbidDataImpl(Serialize::TypeBase *type, Serialize::ID id) : ForbidData(type, id) { } - - Anope::string GetMask() override; - void SetMask(const Anope::string &) override; - - Anope::string GetCreator() override; - void SetCreator(const Anope::string &) override; - - Anope::string GetReason() override; - void SetReason(const Anope::string &) override; - - time_t GetCreated() override; - void SetCreated(const time_t &) override; - - time_t GetExpires() override; - void SetExpires(const time_t &) override; - - ForbidType GetType() override; - void SetType(const ForbidType &) override; -}; - -class ForbidDataType : public Serialize::Type -{ - public: - Serialize::Field mask, creator, reason; - Serialize::Field created, expires; - Serialize::Field type; - - ForbidDataType(Module *me) : Serialize::Type(me, "ForbidData") - , mask(this, "mask") - , creator(this, "creator") - , reason(this, "reason") - , created(this, "created") - , expires(this, "expires") - , type(this, "type") - { - } -}; - -Anope::string ForbidDataImpl::GetMask() -{ - return Get(&ForbidDataType::mask); -} - -void ForbidDataImpl::SetMask(const Anope::string &m) -{ - Set(&ForbidDataType::mask, m); -} - -Anope::string ForbidDataImpl::GetCreator() -{ - return Get(&ForbidDataType::creator); -} - -void ForbidDataImpl::SetCreator(const Anope::string &c) -{ - Set(&ForbidDataType::creator, c); -} - -Anope::string ForbidDataImpl::GetReason() -{ - return Get(&ForbidDataType::reason); -} - -void ForbidDataImpl::SetReason(const Anope::string &r) -{ - Set(&ForbidDataType::reason, r); -} - -time_t ForbidDataImpl::GetCreated() -{ - return Get(&ForbidDataType::created); -} - -void ForbidDataImpl::SetCreated(const time_t &t) -{ - Set(&ForbidDataType::created, t); -} - -time_t ForbidDataImpl::GetExpires() -{ - return Get(&ForbidDataType::expires); -} - -void ForbidDataImpl::SetExpires(const time_t &t) -{ - Set(&ForbidDataType::expires, t); -} - -ForbidType ForbidDataImpl::GetType() -{ - return Get(&ForbidDataType::type); -} - -void ForbidDataImpl::SetType(const ForbidType &t) -{ - Set(&ForbidDataType::type, t); -} - -class MyForbidService : public ForbidService -{ - public: - MyForbidService(Module *m) : ForbidService(m) - { - } - - ForbidData *FindForbid(const Anope::string &mask, ForbidType ftype) override - { - for (ForbidData *d : GetForbids()) - if (d->GetType() == ftype && Anope::Match(mask, d->GetMask(), false, true)) - return d; - return NULL; - } - - std::vector GetForbids() override - { - for (ForbidData *d : Serialize::GetObjects(forbiddata)) - if (d->GetExpires() && !Anope::NoExpire && Anope::CurTime >= d->GetExpires()) - { - Anope::string ftype = "none"; - if (d->GetType() == FT_NICK) - ftype = "nick"; - else if (d->GetType() == FT_CHAN) - ftype = "chan"; - else if (d->GetType() == FT_EMAIL) - ftype = "email"; - - Log(LOG_NORMAL, "expire/forbid", Config->GetClient("OperServ")) << "Expiring forbid for " << d->GetMask() << " type " << ftype; - d->Delete(); - } - return Serialize::GetObjects(forbiddata); - } -}; - -class CommandOSForbid : public Command -{ - ServiceReference fs; - public: - CommandOSForbid(Module *creator) : Command(creator, "operserv/forbid", 1, 5), fs("ForbidService", "forbid") - { - this->SetDesc(_("Forbid usage of nicknames, channels, and emails")); - this->SetSyntax(_("ADD {NICK|CHAN|EMAIL|REGISTER} [+\037expiry\037] \037entry\037 \037reason\037")); - this->SetSyntax(_("DEL {NICK|CHAN|EMAIL|REGISTER} \037entry\037")); - this->SetSyntax("LIST [NICK|CHAN|EMAIL|REGISTER]"); - } - - void OnUserNickChange(User *u) - { - if (u->HasMode("OPER")) - return; - - ForbidData *d = fs->FindForbid(u->nick, FT_NICK); - if (d != NULL) - { - ServiceBot *bi = Config->GetClient("NickServ"); - if (!bi) - bi = Config->GetClient("OperServ"); - if (bi) - u->SendMessage(bi, _("This nickname has been forbidden: \002{0}\002"), d->GetReason()); - if (NickServ::service) - NickServ::service->Collide(u, NULL); - } - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (!this->fs) - return; - - const Anope::string &command = params[0]; - const Anope::string &subcommand = params.size() > 1 ? params[1] : ""; - - ForbidType ftype = FT_SIZE; - if (subcommand.equals_ci("NICK")) - ftype = FT_NICK; - else if (subcommand.equals_ci("CHAN")) - ftype = FT_CHAN; - else if (subcommand.equals_ci("EMAIL")) - ftype = FT_EMAIL; - else if (subcommand.equals_ci("REGISTER")) - ftype = FT_REGISTER; - - if (command.equals_ci("ADD") && params.size() > 3 && ftype != FT_SIZE) - { - const Anope::string &expiry = params[2][0] == '+' ? params[2] : ""; - const Anope::string &entry = !expiry.empty() ? params[3] : params[2]; - Anope::string reason; - if (expiry.empty()) - reason = params[3] + " "; - if (params.size() > 4) - reason += params[4]; - reason.trim(); - - if (entry.replace_all_cs("?*", "").empty()) - { - source.Reply(_("The mask must contain at least one non wildcard character.")); - return; - } - - time_t expiryt = 0; - - if (!expiry.empty()) - { - expiryt = Anope::DoTime(expiry); - if (expiryt == -1) - { - source.Reply(_("Invalid expiry time \002{0}\002."), expiry); - return; - } - else if (expiryt) - expiryt += Anope::CurTime; - } - - NickServ::Nick *target = NickServ::FindNick(entry); - if (target != NULL && Config->GetModule("nickserv")->Get("secureadmins", "yes") && target->GetAccount()->IsServicesOper()) - { - source.Reply(_("Access denied.")); - return; - } - - ForbidData *d = this->fs->FindForbid(entry, ftype); - bool created = false; - if (d == NULL) - { - d = forbiddata.Create(); - created = true; - } - - d->SetMask(entry); - d->SetCreator(source.GetNick()); - d->SetReason(reason); - d->SetCreated(Anope::CurTime); - d->SetExpires(expiryt); - d->SetType(ftype); - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - Log(LOG_ADMIN, source, this) << "to add a forbid on " << entry << " of type " << subcommand; - source.Reply(_("Added a forbid on \002{0}\002 of type \002{1}\002 to expire on \002{2}\002."), entry, subcommand.lower(), expiryt ? Anope::strftime(expiryt, source.GetAccount()) : "never"); - - /* apply forbid */ - switch (ftype) - { - case FT_NICK: - { - int na_matches = 0; - - for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) - this->OnUserNickChange(it->second); - - for (auto it = NickServ::service->GetNickList().begin(); it != NickServ::service->GetNickList().end();) - { - NickServ::Nick *na = *it; - ++it; - - d = this->fs->FindForbid(na->GetNick(), FT_NICK); - if (d == NULL) - continue; - - ++na_matches; - - delete na; - } - - source.Reply(_("\002{0}\002 nickname(s) dropped."), na_matches); - break; - } - case FT_CHAN: - { - int chan_matches = 0, ci_matches = 0; - - for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end;) - { - Channel *c = it->second; - ++it; - - d = this->fs->FindForbid(c->name, FT_CHAN); - if (d == NULL) - continue; - - ServiceBot *OperServ = Config->GetClient("OperServ"); - if (IRCD->CanSQLineChannel && OperServ) - { - time_t inhabit = Config->GetModule("chanserv")->Get("inhabit", "15s"); - XLine x(c->name, OperServ->nick, Anope::CurTime + inhabit, d->GetReason()); - IRCD->SendSQLine(NULL, &x); - } - else if (ChanServ::service) - { - ChanServ::service->Hold(c); - } - - ++chan_matches; - - for (Channel::ChanUserList::const_iterator cit = c->users.begin(), cit_end = c->users.end(); cit != cit_end;) - { - User *u = cit->first; - ++cit; - - if (u->server == Me || u->HasMode("OPER")) - continue; - - reason = Anope::printf(Language::Translate(u, _("This channel has been forbidden: \002%s\002")), d->GetReason().c_str()); - - c->Kick(source.service, u, "%s", reason.c_str()); - } - } - - for (auto it = ChanServ::service->GetChannels().begin(); it != ChanServ::service->GetChannels().end();) - { - ChanServ::Channel *ci = it->second; - ++it; - - d = this->fs->FindForbid(ci->GetName(), FT_CHAN); - if (d == NULL) - continue; - - ++ci_matches; - - delete ci; - } - - source.Reply(_("\002{0}\002 channel(s) cleared, and \002{1}\002 channel(s) dropped."), chan_matches, ci_matches); - - break; - } - default: - break; - } - - } - else if (command.equals_ci("DEL") && params.size() > 2 && ftype != FT_SIZE) - { - const Anope::string &entry = params[2]; - - ForbidData *d = this->fs->FindForbid(entry, ftype); - if (d == nullptr) - { - source.Reply(_("Forbid on \002{0}\002 was not found."), entry); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - Log(LOG_ADMIN, source, this) << "to remove forbid on " << d->GetMask() << " of type " << subcommand; - source.Reply(_("\002{0}\002 deleted from the \002{1}\002 forbid list."), d->GetMask(), subcommand); - d->Delete(); - } - else if (command.equals_ci("LIST")) - { - const std::vector &forbids = this->fs->GetForbids(); - if (forbids.empty()) - { - source.Reply(_("Forbid list is empty.")); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Mask")).AddColumn(_("Type")).AddColumn(_("Creator")).AddColumn(_("Expires")).AddColumn(_("Reason")); - - unsigned shown = 0; - for (unsigned i = 0; i < forbids.size(); ++i) - { - ForbidData *d = forbids[i]; - - if (ftype != FT_SIZE && ftype != d->GetType()) - continue; - - Anope::string stype; - if (d->GetType() == FT_NICK) - stype = "NICK"; - else if (d->GetType() == FT_CHAN) - stype = "CHAN"; - else if (d->GetType() == FT_EMAIL) - stype = "EMAIL"; - else if (d->GetType() == FT_REGISTER) - stype = "REGISTER"; - else - continue; - - ListFormatter::ListEntry entry; - entry["Mask"] = d->GetMask(); - entry["Type"] = stype; - entry["Creator"] = d->GetCreator(); - entry["Expires"] = d->GetExpires() ? Anope::strftime(d->GetExpires(), NULL, true).c_str() : Language::Translate(source.GetAccount(), _("Never")); - entry["Reason"] = d->GetReason(); - list.AddEntry(entry); - ++shown; - } - - if (!shown) - { - source.Reply(_("There are no forbids of type \002{0}\002."), subcommand.upper()); - return; - } - - source.Reply(_("Forbid list:")); - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - if (shown >= forbids.size()) - source.Reply(_("End of forbid list.")); - else - source.Reply(_("End of forbid list - \002{0}\002/\002{1}\002 entries shown."), shown, forbids.size()); - } - else - this->OnSyntaxError(source, command); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Forbid allows you to forbid usage of certain nicknames, channels, and email addresses. Wildcards are accepted for all entries.")); - - const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); - if (!regexengine.empty()) - { - source.Reply(" "); - source.Reply(_("Regex matches are also supported using the %s engine. Enclose your pattern in // if this is desired."), regexengine); - } - - return true; - } -}; - -class OSForbid : public Module - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - MyForbidService forbid_service; - ForbidDataType type; - CommandOSForbid commandosforbid; - - public: - OSForbid(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , forbid_service(this) - , type(this) - , commandosforbid(this) - { - } - - void OnUserConnect(User *u, bool &exempt) override - { - if (u->Quitting() || exempt) - return; - - commandosforbid.OnUserNickChange(u); - } - - void OnUserNickChange(User *u, const Anope::string &) override - { - commandosforbid.OnUserNickChange(u); - } - - EventReturn OnCheckKick(User *u, Channel *c, Anope::string &mask, Anope::string &reason) override - { - ServiceBot *OperServ = Config->GetClient("OperServ"); - if (u->HasMode("OPER") || !OperServ) - return EVENT_CONTINUE; - - ForbidData *d = this->forbid_service.FindForbid(c->name, FT_CHAN); - if (d != NULL) - { - if (IRCD->CanSQLineChannel) - { - time_t inhabit = Config->GetModule("chanserv")->Get("inhabit", "15s"); - XLine x(c->name, OperServ->nick, Anope::CurTime + inhabit, d->GetReason()); - IRCD->SendSQLine(NULL, &x); - } - else if (ChanServ::service) - { - ChanServ::service->Hold(c); - } - - reason = Anope::printf(Language::Translate(u, _("This channel has been forbidden: %s")), d->GetReason().c_str()); - - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } - - EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override - { - if (command->name == "nickserv/info" && params.size() > 0) - { - ForbidData *d = this->forbid_service.FindForbid(params[0], FT_NICK); - if (d != NULL) - { - if (source.IsOper()) - source.Reply(_("Nick \002%s\002 is forbidden by %s: %s"), params[0], d->GetCreator(), d->GetReason()); - else - source.Reply(_("Nick \002%s\002 is forbidden."), params[0]); - return EVENT_STOP; - } - } - else if (command->name == "chanserv/info" && params.size() > 0) - { - ForbidData *d = this->forbid_service.FindForbid(params[0], FT_CHAN); - if (d != NULL) - { - if (source.IsOper()) - source.Reply(_("Channel \002%s\002 is forbidden by %s: %s"), params[0], d->GetCreator(), d->GetReason()); - else - source.Reply(_("Channel \002%s\002 is forbidden."), params[0]); - return EVENT_STOP; - } - } - else if (source.IsOper()) - return EVENT_CONTINUE; - else if (command->name == "nickserv/register" && params.size() > 1) - { - ForbidData *d = this->forbid_service.FindForbid(source.GetNick(), FT_REGISTER); - if (d != NULL) - { - source.Reply(_("\002{0}\002 may not be registered."), source.GetNick()); - return EVENT_STOP; - } - - d = this->forbid_service.FindForbid(params[1], FT_EMAIL); - if (d != NULL) - { - source.Reply(_("Your email address is not allowed, choose a different one.")); - return EVENT_STOP; - } - } - else if (command->name == "nickserv/set/email" && params.size() > 0) - { - ForbidData *d = this->forbid_service.FindForbid(params[0], FT_EMAIL); - if (d != NULL) - { - source.Reply(_("Your email address is not allowed, choose a different one.")); - return EVENT_STOP; - } - } - else if (command->name == "chanserv/register" && !params.empty()) - { - ForbidData *d = this->forbid_service.FindForbid(params[0], FT_REGISTER); - if (d != NULL) - { - source.Reply(_("Channel \002{0}\002 is currently suspended."), params[0]); - return EVENT_STOP; - } - } - - return EVENT_CONTINUE; - } -}; - -MODULE_INIT(OSForbid) diff --git a/modules/commands/os_ignore.cpp b/modules/commands/os_ignore.cpp deleted file mode 100644 index d9563cc29..000000000 --- a/modules/commands/os_ignore.cpp +++ /dev/null @@ -1,393 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/os_ignore.h" - -class IgnoreImpl : public Ignore -{ - public: - IgnoreImpl(Serialize::TypeBase *type) : Ignore(type) { } - IgnoreImpl(Serialize::TypeBase *type, Serialize::ID id) : Ignore(type, id) { } - - Anope::string GetMask(); - void SetMask(const Anope::string &); - - Anope::string GetCreator(); - void SetCreator(const Anope::string &); - - Anope::string GetReason(); - void SetReason(const Anope::string &); - - time_t GetTime(); - void SetTime(const time_t &); -}; - -class IgnoreType : public Serialize::Type -{ - public: - Serialize::Field mask, creator, reason; - Serialize::Field time; - - IgnoreType(Module *me) : Serialize::Type(me, "IgnoreData") - , mask(this, "mask") - , creator(this, "creator") - , reason(this, "reason") - , time(this, "time") - { - } -}; - -Anope::string IgnoreImpl::GetMask() -{ - return Get(&IgnoreType::mask); -} - -void IgnoreImpl::SetMask(const Anope::string &m) -{ - Set(&IgnoreType::mask, m); -} - -Anope::string IgnoreImpl::GetCreator() -{ - return Get(&IgnoreType::creator); -} - -void IgnoreImpl::SetCreator(const Anope::string &c) -{ - Set(&IgnoreType::creator, c); -} - -Anope::string IgnoreImpl::GetReason() -{ - return Get(&IgnoreType::reason); -} - -void IgnoreImpl::SetReason(const Anope::string &r) -{ - Set(&IgnoreType::reason, r); -} - -time_t IgnoreImpl::GetTime() -{ - return Get(&IgnoreType::time); -} - -void IgnoreImpl::SetTime(const time_t &t) -{ - Set(&IgnoreType::time, t); -} - -class OSIgnoreService : public IgnoreService -{ - public: - OSIgnoreService(Module *o) : IgnoreService(o) - { - } - - Ignore *Find(const Anope::string &mask) override - { - User *u = User::Find(mask, true); - std::vector ignores = Serialize::GetObjects(ignoretype); - std::vector::iterator ign = ignores.begin(), ign_end = ignores.end(); - - if (u) - { - for (; ign != ign_end; ++ign) - { - Entry ignore_mask("", (*ign)->GetMask()); - if (ignore_mask.Matches(u, true)) - break; - } - } - else - { - size_t user, host; - Anope::string tmp; - /* We didn't get a user.. generate a valid mask. */ - if ((host = mask.find('@')) != Anope::string::npos) - { - if ((user = mask.find('!')) != Anope::string::npos) - { - /* this should never happen */ - if (user > host) - return NULL; - tmp = mask; - } - else - /* We have user@host. Add nick wildcard. */ - tmp = "*!" + mask; - } - /* We only got a nick.. */ - else - tmp = mask + "!*@*"; - - for (; ign != ign_end; ++ign) - if (Anope::Match(tmp, (*ign)->GetMask(), false, true)) - break; - } - - /* Check whether the entry has timed out */ - if (ign != ign_end) - { - Ignore *id = *ign; - - if (id->GetTime() && !Anope::NoExpire && id->GetTime() <= Anope::CurTime) - { - Log(LOG_NORMAL, "expire/ignore", Config->GetClient("OperServ")) << "Expiring ignore entry " << id->GetMask(); - id->Delete(); - } - else - return id; - } - - return NULL; - } -}; - -class CommandOSIgnore : public Command -{ - private: - Anope::string RealMask(const Anope::string &mask) - { - /* If it s an existing user, we ignore the hostmask. */ - User *u = User::Find(mask, true); - if (u) - return "*!*@" + u->host; - - size_t host = mask.find('@'); - /* Determine whether we get a nick or a mask. */ - if (host != Anope::string::npos) - { - size_t user = mask.find('!'); - /* Check whether we have a nick too.. */ - if (user != Anope::string::npos) - { - if (user > host) - /* this should never happen */ - return ""; - else - return mask; - } - else - /* We have user@host. Add nick wildcard. */ - return "*!" + mask; - } - - /* We only got a nick.. */ - return mask + "!*@*"; - } - - void DoAdd(CommandSource &source, const std::vector ¶ms) - { - const Anope::string &time = params.size() > 1 ? params[1] : ""; - const Anope::string &nick = params.size() > 2 ? params[2] : ""; - const Anope::string &reason = params.size() > 3 ? params[3] : ""; - - if (time.empty() || nick.empty()) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - time_t t = Anope::DoTime(time); - - if (t <= -1) - { - source.Reply(_("Invalid expiry time \002{0}\002."), time); - return; - } - - Anope::string mask = RealMask(nick); - if (mask.empty()) - { - source.Reply(_("Mask must be in the form \037user\037@\037host\037.")); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - Ignore *ign = ignoretype.Create(); - ign->SetMask(mask); - ign->SetCreator(source.GetNick()); - ign->SetReason(reason); - ign->SetTime(t ? Anope::CurTime + t : 0); - - if (!t) - { - source.Reply(_("\002{0}\002 will now permanently be ignored."), mask); - Log(LOG_ADMIN, source, this) << "to add a permanent ignore for " << mask; - } - else - { - source.Reply(_("\002{0}\002 will now be ignored for \002{1}\002."), mask, Anope::Duration(t, source.GetAccount())); - Log(LOG_ADMIN, source, this) << "to add an ignore on " << mask << " for " << Anope::Duration(t); - } - } - - void DoList(CommandSource &source) - { - std::vector ignores = Serialize::GetObjects(ignoretype); - - for (Ignore *id : ignores) - { - if (id->GetTime() && !Anope::NoExpire && id->GetTime() <= Anope::CurTime) - { - Log(LOG_NORMAL, "expire/ignore", Config->GetClient("OperServ")) << "Expiring ignore entry " << id->GetMask(); - id->Delete(); - } - } - - ignores = Serialize::GetObjects(ignoretype); - if (ignores.empty()) - { - source.Reply(_("Ignore list is empty.")); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Mask")).AddColumn(_("Creator")).AddColumn(_("Reason")).AddColumn(_("Expires")); - - for (Ignore *ignore : ignores) - { - ListFormatter::ListEntry entry; - entry["Mask"] = ignore->GetMask(); - entry["Creator"] = ignore->GetCreator(); - entry["Reason"] = ignore->GetReason(); - entry["Expires"] = Anope::Expires(ignore->GetTime(), source.GetAccount()); - list.AddEntry(entry); - } - - source.Reply(_("Services ignore list:")); - - std::vector replies; - list.Process(replies); - - for (const Anope::string &r : replies) - source.Reply(r); - } - - void DoDel(CommandSource &source, const std::vector ¶ms) - { - const Anope::string nick = params.size() > 1 ? params[1] : ""; - if (nick.empty()) - { - this->OnSyntaxError(source, "DEL"); - return; - } - - Anope::string mask = RealMask(nick); - if (mask.empty()) - { - source.Reply(_("Mask must be in the form \037user\037@\037host\037.")); - return; - } - - Ignore *ign = ignore_service->Find(mask); - if (!ign) - { - source.Reply(_("\002{0}\002 not found on ignore list."), mask); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - Log(LOG_ADMIN, source, this) << "to remove an ignore on " << mask; - source.Reply(_("\002{0}\002 will no longer be ignored."), mask); - ign->Delete(); - } - - void DoClear(CommandSource &source) - { - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - for (Ignore *ign : Serialize::GetObjects(ignoretype)) - ign->Delete(); - - Log(LOG_ADMIN, source, this) << "to CLEAR the list"; - source.Reply(_("Ignore list has been cleared.")); - } - - public: - CommandOSIgnore(Module *creator) : Command(creator, "operserv/ignore", 1, 4) - { - this->SetDesc(_("Modify the Services ignore list")); - this->SetSyntax(_("ADD \037expiry\037 {\037nick\037|\037mask\037} [\037reason\037]")); - this->SetSyntax(_("DEL {\037nick\037|\037mask\037}")); - this->SetSyntax("LIST"); - this->SetSyntax("CLEAR"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &cmd = params[0]; - - if (cmd.equals_ci("ADD")) - return this->DoAdd(source, params); - else if (cmd.equals_ci("LIST")) - return this->DoList(source); - else if (cmd.equals_ci("DEL")) - return this->DoDel(source, params); - else if (cmd.equals_ci("CLEAR")) - return this->DoClear(source); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("The \002{0}\002 command allows you to make services ignore a user or hostmask." - " \037expiry\037 must be an integer and can be followed by one of \037d\037 (days), \037h\037 (hours), or \037m\037 (minutes)." - " If a unit specifier is not included, the default is seconds." - " To make services permanently ignore the user, use 0 as the expiry time." - " When adding a \037mask\037, it should be in the format nick!user@host, everything else will be considered a nicknames. Wildcards are permitted."), - source.command); - - const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); - if (!regexengine.empty()) - { - source.Reply(" "); - source.Reply(_("Regex matches are also supported using the \002{0}\002 engine. Enclose your pattern in // if this is desired."), regexengine); - } - - return true; - } -}; - -class OSIgnore : public Module - , public EventHook -{ - IgnoreType ignoretype; - OSIgnoreService osignoreservice; - CommandOSIgnore commandosignore; - - public: - OSIgnore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , ignoretype(this) - , osignoreservice(this) - , commandosignore(this) - { - - } - - EventReturn OnBotPrivmsg(User *u, ServiceBot *bi, Anope::string &message) override - { - if (!u->HasMode("OPER") && this->osignoreservice.Find(u->nick)) - return EVENT_STOP; - - return EVENT_CONTINUE; - } -}; - -MODULE_INIT(OSIgnore) diff --git a/modules/commands/os_info.cpp b/modules/commands/os_info.cpp deleted file mode 100644 index 7b292d4b2..000000000 --- a/modules/commands/os_info.cpp +++ /dev/null @@ -1,260 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - */ - -#include "module.h" -#include "modules/ns_info.h" -#include "modules/cs_info.h" -#include "modules/os_info.h" - -class OperInfoImpl : public OperInfo -{ - public: - OperInfoImpl(Serialize::TypeBase *type) : OperInfo(type) { } - OperInfoImpl(Serialize::TypeBase *type, Serialize::ID id) : OperInfo(type, id) { } - - Serialize::Object *GetTarget(); - void SetTarget(Serialize::Object *); - - Anope::string GetInfo(); - void SetInfo(const Anope::string &); - - Anope::string GetCreator(); - void SetCreator(const Anope::string &); - - time_t GetCreated(); - void SetCreated(const time_t &); -}; - -class OperInfoType : public Serialize::Type -{ - public: - Serialize::ObjectField target; - Serialize::Field info, creator; - Serialize::Field created; - - OperInfoType(Module *c) : Serialize::Type(c, "OperInfo") - , target(this, "target", true) - , info(this, "info") - , creator(this, "adder") - , created(this, "created") - { - } -}; - -Serialize::Object *OperInfoImpl::GetTarget() -{ - return Get(&OperInfoType::target); -} - -void OperInfoImpl::SetTarget(Serialize::Object *t) -{ - Set(&OperInfoType::target, t); -} - -Anope::string OperInfoImpl::GetInfo() -{ - return Get(&OperInfoType::info); -} - -void OperInfoImpl::SetInfo(const Anope::string &i) -{ - Set(&OperInfoType::info, i); -} - -Anope::string OperInfoImpl::GetCreator() -{ - return Get(&OperInfoType::creator); -} - -void OperInfoImpl::SetCreator(const Anope::string &c) -{ - Set(&OperInfoType::creator, c); -} - -time_t OperInfoImpl::GetCreated() -{ - return Get(&OperInfoType::created); -} - -void OperInfoImpl::SetCreated(const time_t &t) -{ - Set(&OperInfoType::created, t); -} - -class CommandOSInfo : public Command -{ - public: - CommandOSInfo(Module *creator) : Command(creator, "operserv/info", 2, 3) - { - this->SetDesc(_("Associate oper info with a nick or channel")); - this->SetSyntax(_("ADD \037target\037 \037info\037")); - this->SetSyntax(_("DEL \037target\037 \037info\037")); - this->SetSyntax(_("CLEAR \037target\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &cmd = params[0], target = params[1], info = params.size() > 2 ? params[2] : ""; - - Serialize::Object *e; - if (IRCD->IsChannelValid(target)) - { - ChanServ::Channel *ci = ChanServ::Find(target); - if (!ci) - { - source.Reply(_("Channel \002{0}\002 isn't registered."), target); - return; - } - - e = ci; - } - else - { - NickServ::Nick *na = NickServ::FindNick(target); - if (!na) - { - source.Reply(_("\002{0}\002 isn't registered."), target); - return; - } - - e = na->GetAccount(); - } - - if (cmd.equals_ci("ADD")) - { - if (info.empty()) - { - this->OnSyntaxError(source, cmd); - return; - } - - std::vector oinfos = e->GetRefs(operinfo); - if (oinfos.size() >= Config->GetModule(this->module)->Get("max", "10")) - { - source.Reply(_("The oper info list for \002{0}\002 is full."), target); - return; - } - - for (OperInfo *o : oinfos) - if (o->GetInfo().equals_ci(info)) - { - source.Reply(_("The oper info already exists on \002{0}\002."), target); - return; - } - - OperInfo *o = operinfo.Create(); - o->SetTarget(e); - o->SetInfo(info); - o->SetCreator(source.GetNick()); - o->SetCreated(Anope::CurTime); - - source.Reply(_("Added info to \002{0}\002."), target); - Log(LOG_ADMIN, source, this) << "to add information to " << target; - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - } - else if (cmd.equals_ci("DEL")) - { - if (info.empty()) - { - this->OnSyntaxError(source, cmd); - return; - } - - std::vector oinfos = e->GetRefs(operinfo); - if (oinfos.empty()) - { - source.Reply(_("Oper info list for \002{0}\002 is empty."), target); - return; - } - - for (OperInfo *o : oinfos) - { - if (o->GetInfo().equals_ci(info)) - { - o->Delete(); - - source.Reply(_("Deleted info from \002{0}\002."), target); - Log(LOG_ADMIN, source, this) << "to remove information from " << target; - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - return; - } - } - - source.Reply(_("No such info \002{0}\002 on \002{1}\002."), info, target); - } - else if (cmd.equals_ci("CLEAR")) - { - std::vector oinfos = e->GetRefs(operinfo); - - if (oinfos.empty()) - { - source.Reply(_("Oper info list for \002{0}\002 is empty."), target); - return; - } - - for (OperInfo *o : oinfos) - o->Delete(); - - source.Reply(_("Cleared info from \002{0}\002."), target); - Log(LOG_ADMIN, source, this) << "to clear information for " << target; - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - } - else - { - this->OnSyntaxError(source, cmd); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Add or delete oper information for a given accpint or channel. This informaition will show to opers in the respective info command for the account or channel.")); - return true; - } -}; - -class OSInfo : public Module - , public EventHook - , public EventHook -{ - CommandOSInfo commandosinfo; - OperInfoType oinfotype; - - void OnInfo(CommandSource &source, Serialize::Object *e, InfoFormatter &info) - { - if (!source.IsOper()) - return; - - for (OperInfo *o : e->GetRefs(operinfo)) - info[_("Oper Info")] = Anope::printf(_("(by %s on %s) %s"), o->GetCreator().c_str(), Anope::strftime(o->GetCreated(), source.GetAccount(), true).c_str(), o->GetInfo().c_str()); - } - - public: - OSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosinfo(this) - , oinfotype(this) - { - } - - void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override - { - OnInfo(source, na->GetAccount(), info); - } - - void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_hidden) override - { - OnInfo(source, ci, info); - } -}; - -MODULE_INIT(OSInfo) diff --git a/modules/commands/os_jupe.cpp b/modules/commands/os_jupe.cpp deleted file mode 100644 index e2dd30b3a..000000000 --- a/modules/commands/os_jupe.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSJupe : public Command -{ - public: - CommandOSJupe(Module *creator) : Command(creator, "operserv/jupe", 1, 2) - { - this->SetDesc(_("\"Jupiter\" a server")); - this->SetSyntax(_("\037server\037 [\037reason\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &jserver = params[0]; - const Anope::string &reason = params.size() > 1 ? params[1] : ""; - Server *server = Server::Find(jserver, true); - - if (!IRCD->IsHostValid(jserver) || jserver.find('.') == Anope::string::npos) - source.Reply(_("\002{0}\002 is not a valid server name."), jserver); - else if (server == Me || server == Servers::GetUplink() || server->IsULined()) - source.Reply(_("You can not jupe Servoces or its uplink server.")); - else if (server && server->IsJuped()) - source.Reply(_("You can not jupe an already juped server.")); - else - { - Anope::string rbuf = "Juped by " + source.GetNick() + (!reason.empty() ? ": " + reason : ""); - /* Generate the new sid before quitting the old server, so they can't collide */ - Anope::string sid = IRCD->SID_Retrieve(); - if (server) - { - IRCD->SendSquit(server, rbuf); - server->Delete(rbuf); - } - Server *juped_server = new Server(Me, jserver, 1, rbuf, sid, true); - IRCD->SendServer(juped_server); - - Log(LOG_ADMIN, source, this) << "on " << jserver << " (" << rbuf << ")"; - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Tells services to jupiter a server -- that is, to create a fake \"server\" connected to Services which prevents the real server of that name from connecting." - " The jupe may be removed using a standard \002SQUIT\002. If a reason is given, it is placed in the server information field; otherwise, the server information field will contain the text \"Juped by \", showing the nickname of the person who jupitered the server.")); - return true; - } -}; - -class OSJupe : public Module -{ - CommandOSJupe commandosjupe; - - public: - OSJupe(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosjupe(this) - { - - } -}; - -MODULE_INIT(OSJupe) diff --git a/modules/commands/os_kick.cpp b/modules/commands/os_kick.cpp deleted file mode 100644 index d97e21602..000000000 --- a/modules/commands/os_kick.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSKick : public Command -{ - public: - CommandOSKick(Module *creator) : Command(creator, "operserv/kick", 3, 3) - { - this->SetDesc(_("Kick a user from a channel")); - this->SetSyntax(_("\037channel\037 \037user\037 \037reason\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &chan = params[0]; - const Anope::string &nick = params[1]; - const Anope::string &s = params[2]; - Channel *c; - User *u2; - - if (!(c = Channel::Find(chan))) - { - source.Reply(_("Channel \002%s\002 doesn't exist."), chan); - return; - } - - if (c->bouncy_modes) - { - source.Reply(_("Services is unable to change modes. Are your servers' U:lines configured correctly?")); - return; - } - - if (!(u2 = User::Find(nick, true))) - { - source.Reply(_("\002{0}\002 isn't currently online."), nick); - return; - } - - if (!c->Kick(source.service, u2, "%s (%s)", source.GetNick().c_str(), s.c_str())) - { - source.Reply(_("Access denied.")); - return; - } - - Log(LOG_ADMIN, source, this) << "on " << u2->nick << " in " << c->name << " (" << s << ")"; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Allows you to kick a user from any channel. Parameters are the same as for the standard /KICK command." - " The kick message will have the nickname of the IRCop sending the KICK command prepended; for example:\n" - "\n" - "*** SpamMan has been kicked off channel #my_channel by {0} (Alcan (Flood))"), - source.service->nick); - return true; - } -}; - -class OSKick : public Module -{ - CommandOSKick commandoskick; - - public: - OSKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandoskick(this) - { - - } -}; - -MODULE_INIT(OSKick) diff --git a/modules/commands/os_kill.cpp b/modules/commands/os_kill.cpp deleted file mode 100644 index ef6f11ebe..000000000 --- a/modules/commands/os_kill.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSKill : public Command -{ - public: - CommandOSKill(Module *creator) : Command(creator, "operserv/kill", 1, 2) - { - this->SetDesc(_("Kill a user")); - this->SetSyntax(_("\037user\037 [\037reason\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &nick = params[0]; - Anope::string reason = params.size() > 1 ? params[1] : ""; - - User *u2 = User::Find(nick, true); - if (u2 == NULL) - source.Reply(_("\002{0}\002 isn't currently online."), nick); - else if (u2->IsProtected() || u2->server == Me) - source.Reply(_("\002{0}\002 is protected and cannot be killed."), u2->nick); - else - { - if (reason.empty()) - reason = "No reason specified"; - if (Config->GetModule("operserv")->Get("addakiller")) - reason = "(" + source.GetNick() + ") " + reason; - Log(LOG_ADMIN, source, this) << "on " << u2->nick << " for " << reason; - u2->Kill(*source.service, reason); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Allows you to kill a user from the network. Parameters are the same as for the standard /KILL command.")); - return true; - } -}; - -class OSKill : public Module -{ - CommandOSKill commandoskill; - - public: - OSKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandoskill(this) - { - - } -}; - -MODULE_INIT(OSKill) diff --git a/modules/commands/os_list.cpp b/modules/commands/os_list.cpp deleted file mode 100644 index ed825f373..000000000 --- a/modules/commands/os_list.cpp +++ /dev/null @@ -1,238 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSChanList : public Command -{ - public: - CommandOSChanList(Module *creator) : Command(creator, "operserv/chanlist", 0, 2) - { - this->SetDesc(_("Lists all channel records")); - this->SetSyntax(_("[{\037pattern\037 | \037nick\037} [\037SECRET\037]]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &pattern = !params.empty() ? params[0] : ""; - const Anope::string &opt = params.size() > 1 ? params[1] : ""; - std::set modes; - User *u2; - - if (!pattern.empty()) - Log(LOG_ADMIN, source, this) << "for " << pattern; - else - Log(LOG_ADMIN, source, this); - - if (!opt.empty() && opt.equals_ci("SECRET")) - { - modes.insert("SECRET"); - modes.insert("PRIVATE"); - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Name")).AddColumn(_("Users")).AddColumn(_("Modes")).AddColumn(_("Topic")); - - if (!pattern.empty() && (u2 = User::Find(pattern, true))) - { - source.Reply(_("Channel list for \002{0}\002:"), u2->nick); - - for (User::ChanUserList::iterator uit = u2->chans.begin(), uit_end = u2->chans.end(); uit != uit_end; ++uit) - { - ChanUserContainer *cc = uit->second; - - if (!modes.empty()) - for (std::set::iterator it = modes.begin(), it_end = modes.end(); it != it_end; ++it) - if (!cc->chan->HasMode(*it)) - continue; - - ListFormatter::ListEntry entry; - entry["Name"] = cc->chan->name; - entry["Users"] = stringify(cc->chan->users.size()); - entry["Modes"] = cc->chan->GetModes(true, true); - entry["Topic"] = cc->chan->topic; - list.AddEntry(entry); - } - } - else - { - source.Reply(_("Channel list:")); - - for (channel_map::const_iterator cit = ChannelList.begin(), cit_end = ChannelList.end(); cit != cit_end; ++cit) - { - Channel *c = cit->second; - - if (!pattern.empty() && !Anope::Match(c->name, pattern, false, true)) - continue; - if (!modes.empty()) - for (std::set::iterator it = modes.begin(), it_end = modes.end(); it != it_end; ++it) - if (!c->HasMode(*it)) - continue; - - ListFormatter::ListEntry entry; - entry["Name"] = c->name; - entry["Users"] = stringify(c->users.size()); - entry["Modes"] = c->GetModes(true, true); - entry["Topic"] = c->topic; - list.AddEntry(entry); - } - } - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("End of channel list.")); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Lists all channels currently in use on the IRC network, whether they are registered or not.\n" - "\n" - "If \002pattern\002 is given, lists only channels that match it. If a nicknam is given, lists only the channels the user using it is on." - " If SECRET is specified, lists only channels matching \002pattern\002 that have the +s or +p mode.")); - - const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); - if (!regexengine.empty()) - { - source.Reply(" "); - source.Reply(_("Regex matches are also supported using the {0} engine. Enclose your pattern in // if this is desired."), regexengine); - } - - return true; - } -}; - -class CommandOSUserList : public Command -{ - public: - CommandOSUserList(Module *creator) : Command(creator, "operserv/userlist", 0, 2) - { - this->SetDesc(_("Lists all user records")); - this->SetSyntax(_("[{\037pattern\037 | \037channel\037} [\037INVISIBLE\037]]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &pattern = !params.empty() ? params[0] : ""; - const Anope::string &opt = params.size() > 1 ? params[1] : ""; - Channel *c; - std::set modes; - - if (!pattern.empty()) - Log(LOG_ADMIN, source, this) << "for " << pattern; - else - Log(LOG_ADMIN, source, this); - - if (!opt.empty() && opt.equals_ci("INVISIBLE")) - modes.insert("INVIS"); - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Name")).AddColumn(_("Mask")); - - if (!pattern.empty() && (c = Channel::Find(pattern))) - { - source.Reply(_("User list for \002{0}\002:"), pattern); - - for (Channel::ChanUserList::iterator cuit = c->users.begin(), cuit_end = c->users.end(); cuit != cuit_end; ++cuit) - { - ChanUserContainer *uc = cuit->second; - - if (!modes.empty()) - for (std::set::iterator it = modes.begin(), it_end = modes.end(); it != it_end; ++it) - if (!uc->user->HasMode(*it)) - continue; - - ListFormatter::ListEntry entry; - entry["Name"] = uc->user->nick; - entry["Mask"] = uc->user->GetIdent() + "@" + uc->user->GetDisplayedHost(); - list.AddEntry(entry); - } - } - else - { - /* Historically this has been ordered, so... */ - Anope::map ordered_map; - for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) - ordered_map[it->first] = it->second; - - source.Reply(_("Users list:")); - - for (Anope::map::const_iterator it = ordered_map.begin(); it != ordered_map.end(); ++it) - { - User *u2 = it->second; - - if (!pattern.empty()) - { - Anope::string mask = u2->nick + "!" + u2->GetIdent() + "@" + u2->GetDisplayedHost(), mask2 = u2->nick + "!" + u2->GetIdent() + "@" + u2->host, mask3 = u2->nick + "!" + u2->GetIdent() + "@" + u2->ip.addr(); - if (!Anope::Match(mask, pattern) && !Anope::Match(mask2, pattern) && !Anope::Match(mask3, pattern)) - continue; - if (!modes.empty()) - for (std::set::iterator mit = modes.begin(), mit_end = modes.end(); mit != mit_end; ++mit) - if (!u2->HasMode(*mit)) - continue; - } - - ListFormatter::ListEntry entry; - entry["Name"] = u2->nick; - entry["Mask"] = u2->GetIdent() + "@" + u2->GetDisplayedHost(); - list.AddEntry(entry); - } - } - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("End of users list.")); - return; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Lists all users currently online on the IRC network, whether their nickname is registered or not.\n" - "\n" - "If \002pattern\002 is given, lists only users that match it (it must be in the format nick!user@host)." - " If \002channel\002 is given, lists only users that are on the given channel. If INVISIBLE is specified, only users with the +i flag will be listed.")); - - const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); - if (!regexengine.empty()) - { - source.Reply(" "); - source.Reply(_("Regex matches are also supported using the %s engine.\n" - "Enclose your pattern in // if this is desired."), regexengine.c_str()); - } - - return true; - } -}; - -class OSList : public Module -{ - CommandOSChanList commandoschanlist; - CommandOSUserList commandosuserlist; - - public: - OSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandoschanlist(this) - , commandosuserlist(this) - { - - } -}; - -MODULE_INIT(OSList) diff --git a/modules/commands/os_login.cpp b/modules/commands/os_login.cpp deleted file mode 100644 index 650c48e3d..000000000 --- a/modules/commands/os_login.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - * - * - */ -#include "module.h" - -class CommandOSLogin : public Command -{ - public: - CommandOSLogin(Module *creator) : Command(creator, "operserv/login", 1, 1) - { - this->SetSyntax(_("\037password\037")); - this->RequireUser(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &password = params[0]; - - User *u = source.GetUser(); - Oper *o = source.nc->o; - if (o == NULL) - { - source.Reply(_("No oper block for your nickname.")); - return; - } - - if (o->GetPassword().empty()) - { - source.Reply(_("Your oper block doesn't require logging in.")); - return; - } - - if (u->HasExtOK("os_login")) - { - source.Reply(_("You are already logged in.")); - return; - } - - if (o->GetPassword() != password) - { - source.Reply(_("Password incorrect.")); - u->BadPassword(); - return; - } - - Log(LOG_ADMIN, source, this) << "and successfully identified to " << source.service->nick; - u->Extend("os_login", true); - source.Reply(_("Password accepted.")); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Logs you in to {0} so you gain Services Operator privileges. This command is unnecessary if your oper block is configured without a password."), source.service->nick); - return true; - } - - const Anope::string GetDesc(CommandSource &source) const override - { - return Anope::printf(Language::Translate(source.GetAccount(), _("Login to %s")), source.service->nick.c_str()); - } -}; - -class CommandOSLogout : public Command -{ - public: - CommandOSLogout(Module *creator) : Command(creator, "operserv/logout", 0, 0) - { - this->RequireUser(true); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - User *u = source.GetUser(); - Oper *o = source.nc->o; - if (o == NULL) - { - source.Reply(_("No oper block for your nick.")); - return; - } - - if (o->GetPassword().empty()) - { - source.Reply(_("Your oper block doesn't require logging in.")); - return; - } - - if (!u->HasExtOK("os_login")) - { - source.Reply(_("You are not identified.")); - return; - } - - Log(LOG_ADMIN, source, this); - u->ShrinkOK("os_login"); - source.Reply(_("You have been logged out.")); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Logs you out from %s so you lose Services Operator privileges. This command is only useful if your oper block is configured with a password."), source.service->nick); - return true; - } - - const Anope::string GetDesc(CommandSource &source) const override - { - return Anope::printf(Language::Translate(source.GetAccount(), _("Logout from %s")), source.service->nick.c_str()); - } -}; - -class OSLogin : public Module - , public EventHook -{ - CommandOSLogin commandoslogin; - CommandOSLogout commandoslogout; - ExtensibleItem os_login; - - public: - OSLogin(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandoslogin(this) - , commandoslogout(this) - , os_login(this, "os_login") - { - - } - - EventReturn IsServicesOper(User *u) override - { - if (!u->Account()->o->GetPassword().empty()) - { - if (os_login.HasExt(u)) - return EVENT_ALLOW; - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } -}; - -MODULE_INIT(OSLogin) diff --git a/modules/commands/os_logsearch.cpp b/modules/commands/os_logsearch.cpp deleted file mode 100644 index 0fa425aba..000000000 --- a/modules/commands/os_logsearch.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSLogSearch : public Command -{ - static inline Anope::string CreateLogName(const Anope::string &file, time_t t = Anope::CurTime) - { - char timestamp[32]; - - tm *tm = localtime(&t); - - strftime(timestamp, sizeof(timestamp), "%Y%m%d", tm); - - return Anope::LogDir + "/" + file + "." + timestamp; - } - - public: - CommandOSLogSearch(Module *creator) : Command(creator, "operserv/logsearch", 1, 3) - { - this->SetDesc(_("Searches logs for a matching pattern")); - this->SetSyntax(_("[+\037days\037d] [+\037limit\037l] \037pattern\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - int days = 7, replies = 50; - - unsigned i; - for (i = 0; i < params.size() && params[i][0] == '+'; ++i) - { - switch (params[i][params[i].length() - 1]) - { - case 'd': - if (params[i].length() > 2) - { - Anope::string dur = params[i].substr(1, params[i].length() - 2); - try - { - days = convertTo(dur); - if (days <= 0) - throw ConvertException(); - } - catch (const ConvertException &) - { - source.Reply(_("Invalid duration \002{0}\002, using \002{1}\002 days."), dur, days); - } - } - break; - case 'l': - if (params[i].length() > 2) - { - Anope::string dur = params[i].substr(1, params[i].length() - 2); - try - { - replies = convertTo(dur); - if (replies <= 0) - throw ConvertException(); - } - catch (const ConvertException &) - { - source.Reply(_("Invalid limit \002{0}\002, using \002{1}\002."), dur, replies); - } - } - break; - default: - source.Reply(_("Unknown parameter: {0}"), params[i]); - } - } - - if (i >= params.size()) - { - this->OnSyntaxError(source, ""); - return; - } - - Anope::string search_string = params[i++]; - for (; i < params.size(); ++i) - search_string += " " + params[i]; - - Log(LOG_ADMIN, source, this) << "for " << search_string; - - const Anope::string &logfile_name = Config->GetModule(this->owner)->Get("logname"); - std::list matches; - for (int d = days - 1; d >= 0; --d) - { - Anope::string lf_name = CreateLogName(logfile_name, Anope::CurTime - (d * 86400)); - Log(LOG_DEBUG) << "Searching " << lf_name; - std::fstream fd(lf_name.c_str(), std::ios_base::in); - if (!fd.is_open()) - continue; - - for (Anope::string buf, token; std::getline(fd, buf.str());) - if (Anope::Match(buf, "*" + search_string + "*")) - matches.push_back(buf); - - fd.close(); - } - - unsigned found = matches.size(); - if (!found) - { - source.Reply(_("No matches for \002{0}\002 found."), search_string); - return; - } - - while (matches.size() > static_cast(replies)) - matches.pop_front(); - - source.Reply(_("Matches for \002{0}\002:"), search_string); - unsigned count = 0; - for (std::list::iterator it = matches.begin(), it_end = matches.end(); it != it_end; ++it) - source.Reply("#%d: %s", ++count, it->c_str()); - source.Reply(_("Showed \002{0}/{1}\002 matches for \002{2}\002."), matches.size(), found, search_string); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("This command searches the Services logfiles for messages that match the given pattern." - " The day and limit argument may be used to specify how many days of logs to search and the number of replies to limit to." - " By default this command searches one week of logs, and limits replies to 50.\n" - "\n" - "Example:\n" - " {0} +21d +500l Anope\n" - " Searches the last 21 days worth of logs for messages containing \"Anope\" and lists the most recent 500 of them."), - source.command); - return true; - } -}; - -class OSLogSearch : public Module -{ - CommandOSLogSearch commandoslogsearch; - - public: - OSLogSearch(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandoslogsearch(this) - { - } -}; - -MODULE_INIT(OSLogSearch) diff --git a/modules/commands/os_mode.cpp b/modules/commands/os_mode.cpp deleted file mode 100644 index 30e1d502b..000000000 --- a/modules/commands/os_mode.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSMode : public Command -{ - public: - CommandOSMode(Module *creator) : Command(creator, "operserv/mode", 2, 3) - { - this->SetDesc(_("Change channel modes")); - this->SetSyntax(_("\037channel\037 \037modes\037")); - this->SetSyntax(_("\037channel\037 CLEAR [ALL]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &target = params[0]; - const Anope::string &modes = params[1]; - - Reference c = Channel::Find(target); - if (!c) - source.Reply(_("Channel \002{0}\002 doesn't exist."), target); - else if (c->bouncy_modes) - source.Reply(_("Services is unable to change modes. Are your servers' U:lines configured correctly?")); - else if (modes.equals_ci("CLEAR")) - { - bool all = params.size() > 2 && params[2].equals_ci("ALL"); - - const Channel::ModeList chmodes = c->GetModes(); - for (Channel::ModeList::const_iterator it = chmodes.begin(), it_end = chmodes.end(); it != it_end && c; ++it) - c->RemoveMode(c->ci->WhoSends(), it->first, it->second, false); - - if (!c) - { - source.Reply(_("Modes cleared on %s and the channel destroyed."), target.c_str()); - return; - } - - if (all) - { - for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ++it) - { - ChanUserContainer *uc = it->second; - - if (uc->user->HasMode("OPER")) - continue; - - for (size_t i = uc->status.Modes().length(); i > 0; --i) - c->RemoveMode(c->ci->WhoSends(), ModeManager::FindChannelModeByChar(uc->status.Modes()[i - 1]), uc->user->GetUID(), false); - } - - source.Reply(_("All modes cleared on \002{0}\002."), c->name); - } - else - source.Reply(_("Non-status modes cleared on \002{0}\002."), c->name); - } - else - { - spacesepstream sep(modes + (params.size() > 2 ? " " + params[2] : "")); - Anope::string mode; - int add = 1; - Anope::string log_modes, log_params; - - sep.GetToken(mode); - for (unsigned i = 0; i < mode.length() && c; ++i) - { - char ch = mode[i]; - - if (ch == '+') - { - add = 1; - log_modes += "+"; - continue; - } - else if (ch == '-') - { - add = 0; - log_modes += "-"; - continue; - } - - ChannelMode *cm = ModeManager::FindChannelModeByChar(ch); - if (!cm) - continue; - - Anope::string param, param_log; - if (cm->type != MODE_REGULAR) - { - if (cm->type == MODE_PARAM && !add && anope_dynamic_static_cast(cm)->minus_no_arg) - ; - else if (!sep.GetToken(param)) - continue; - - param_log = param; - - if (cm->type == MODE_STATUS) - { - User *targ = User::Find(param, true); - if (targ == NULL || c->FindUser(targ) == NULL) - continue; - param = targ->GetUID(); - } - } - - log_modes += cm->mchar; - if (!param.empty()) - log_params += " " + param_log; - - if (add) - c->SetMode(source.service, cm, param, false); - else - c->RemoveMode(source.service, cm, param, false); - } - - if (!log_modes.replace_all_cs("+", "").replace_all_cs("-", "").empty()) - Log(LOG_ADMIN, source, this) << log_modes << log_params << " on " << (c ? c->name : target); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Allows Services Operators to change modes for any channel. Parameters are the same as for the standard /MODE command.\n" - "Alternatively, CLEAR may be given to clear all modes on the channel.\n" - "If CLEAR ALL is given then all modes, including user status, is removed.")); - return true; - } -}; - -class CommandOSUMode : public Command -{ - public: - CommandOSUMode(Module *creator) : Command(creator, "operserv/umode", 2, 2) - { - this->SetDesc(_("Change user modes")); - this->SetSyntax(_("\037user\037 \037modes\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &target = params[0]; - const Anope::string &modes = params[1]; - - User *u2 = User::Find(target, true); - if (!u2) - source.Reply(_("\002{0}\002 isn't currently online."), target); - else - { - u2->SetModes(source.service, "%s", modes.c_str()); - source.Reply(_("Changed usermodes of \002{0}\002 to \002{1}\002."), u2->nick.c_str(), modes.c_str()); - - u2->SendMessage(*source.service, _("\002{0}\002 changed your usermodes to \002{1}\002."), source.GetNick(), modes); - - Log(LOG_ADMIN, source, this) << modes << " on " << target; - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Allows Services Operators to change modes for any user. Parameters are the same as for the standard /MODE command.")); - return true; - } -}; - -class OSMode : public Module -{ - CommandOSMode commandosmode; - CommandOSUMode commandosumode; - - public: - OSMode(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosmode(this) - , commandosumode(this) - { - - } -}; - -MODULE_INIT(OSMode) diff --git a/modules/commands/os_modinfo.cpp b/modules/commands/os_modinfo.cpp deleted file mode 100644 index f3c6db00f..000000000 --- a/modules/commands/os_modinfo.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSModInfo : public Command -{ - public: - CommandOSModInfo(Module *creator) : Command(creator, "operserv/modinfo", 1, 1) - { - this->SetDesc(_("Info about a loaded module")); - this->SetSyntax(_("\037modname\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &file = params[0]; - - Log(LOG_ADMIN, source, this) << "on " << file; - - Module *m = ModuleManager::FindModule(file); - if (m) - { - source.Reply(_("Module: \002{0}\002 Version: \002{1}\002 Author: \002{2}\002 Loaded: \002{3}\002"), m->name, !m->version.empty() ? m->version : "?", !m->author.empty() ? m->author : "Unknown", Anope::strftime(m->created, source.GetAccount())); - if (Anope::Debug) - source.Reply(_(" Loaded at: {0}"), Anope::printf("%o", m->handle)); - - std::vector servicekeys = Service::GetServiceKeys("Command"); - for (unsigned i = 0; i < servicekeys.size(); ++i) - { - ServiceReference c("Command", servicekeys[i]); - if (!c || c->owner != m) - continue; - - source.Reply(_(" Providing service: \002{0}\002"), c->name); - - for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) - { - User *u = it->second; - - if (u->type != UserType::BOT) - continue; - - ServiceBot *bi = anope_dynamic_static_cast(u); - - for (CommandInfo::map::const_iterator cit = bi->commands.begin(), cit_end = bi->commands.end(); cit != cit_end; ++cit) - { - const Anope::string &c_name = cit->first; - const CommandInfo &info = cit->second; - if (info.name != c->name) - continue; - source.Reply(_(" Command \002{0}\002 on \002{1}\002 is linked to \002{2}\002"), c_name, bi->nick, c->name); - } - } - } - } - else - source.Reply(_("No information about module \002{0}\002 is available."), file); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("This command lists information about the specified loaded module.")); - return true; - } -}; - -class CommandOSModList : public Command -{ - public: - CommandOSModList(Module *creator) : Command(creator, "operserv/modlist", 0, 1) - { - this->SetDesc(_("List loaded modules")); - this->SetSyntax("[all|third|vendor|extra|database|encryption|pseudoclient|protocol]"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string ¶m = !params.empty() ? params[0] : ""; - - if (!param.empty()) - Log(LOG_ADMIN, source, this) << "for " << param; - else - Log(LOG_ADMIN, source, this); - - bool third = false, vendor = false, extra = false, database = false, encryption = false, pseudoclient = false, protocol = false; - - if (param.equals_ci("all")) - third = vendor = extra = database = encryption = pseudoclient = protocol = true; - else if (param.equals_ci("third")) - third = true; - else if (param.equals_ci("vendor")) - vendor = true; - else if (param.equals_ci("extra")) - extra = true; - else if (param.equals_ci("database")) - database = true; - else if (param.equals_ci("encryption")) - encryption = true; - else if (param.equals_ci("pseudoclient")) - pseudoclient = true; - else if (param.equals_ci("protocol")) - protocol = true; - else - third = extra = database = encryption = protocol = true; - - Module *protomod = ModuleManager::FindFirstOf(PROTOCOL); - - source.Reply(_("Current module list:")); - - int count = 0; - for (std::list::iterator it = ModuleManager::Modules.begin(), it_end = ModuleManager::Modules.end(); it != it_end; ++it) - { - Module *m = *it; - - bool show = false; - Anope::string mtype; - - if (m->type & PROTOCOL) - { - show |= protocol; - if (!mtype.empty()) - mtype += ", "; - mtype += "Protocol"; - } - if (m->type & PSEUDOCLIENT) - { - show |= pseudoclient; - if (!mtype.empty()) - mtype += ", "; - mtype += "Pseudoclient"; - } - if (m->type & ENCRYPTION) - { - show |= encryption; - if (!mtype.empty()) - mtype += ", "; - mtype += "Encryption"; - } - if (m->type & DATABASE) - { - show |= database; - if (!mtype.empty()) - mtype += ", "; - mtype += "Database"; - } - if (m->type & EXTRA) - { - show |= extra; - if (!mtype.empty()) - mtype += ", "; - mtype += "Extra"; - } - if (m->type & VENDOR) - { - show |= vendor; - if (!mtype.empty()) - mtype += ", "; - mtype += "Vendor"; - } - if (m->type & THIRD) - { - show |= third; - if (!mtype.empty()) - mtype += ", "; - mtype += "Third"; - } - - if (!show) - continue; - else if (m->type & PROTOCOL && param.empty() && m != protomod) - continue; - - ++count; - - source.Reply(_("Module: \002{0}\002 [{1}] [{2}]"), m->name, m->version, mtype); - } - - if (!count) - source.Reply(_("No modules currently loaded matching that criteria.")); - else - source.Reply(_("\002{0}\002 modules loaded."), count); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Lists currently loaded modules.")); - return true; - } -}; - -class OSModInfo : public Module -{ - CommandOSModInfo commandosmodinfo; - CommandOSModList commandosmodlist; - - public: - OSModInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosmodinfo(this) - , commandosmodlist(this) - { - - } -}; - -MODULE_INIT(OSModInfo) diff --git a/modules/commands/os_module.cpp b/modules/commands/os_module.cpp deleted file mode 100644 index 14bc897e3..000000000 --- a/modules/commands/os_module.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSModLoad : public Command -{ - public: - CommandOSModLoad(Module *creator) : Command(creator, "operserv/modload", 1, 1) - { - this->SetDesc(_("Load a module")); - this->SetSyntax(_("\037modname\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &mname = params[0]; - - ModuleReturn status = ModuleManager::LoadModule(mname, source.GetUser()); - if (status == MOD_ERR_OK) - { - Log(LOG_ADMIN, source, this) << "to load module " << mname; - source.Reply(_("Module \002{0}\002 loaded."), mname); - } - else if (status == MOD_ERR_EXISTS) - source.Reply(_("Module \002{0}\002 is already loaded."), mname); - else - source.Reply(_("Unable to load module \002{0}\002."), mname); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("This command loads the module named \037modname\037.")); - return true; - } -}; - -class CommandOSModReLoad : public Command -{ - public: - CommandOSModReLoad(Module *creator) : Command(creator, "operserv/modreload", 1, 1) - { - this->SetDesc(_("Reload a module")); - this->SetSyntax(_("\037modname\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &mname = params[0]; - - Module *m = ModuleManager::FindModule(mname); - if (!m) - { - source.Reply(_("Module \002{0}\002 isn't loaded."), mname); - return; - } - - if (!m->handle || m->GetPermanent()) - { - source.Reply(_("Unable to remove module \002{0}\002."), m->name); - return; - } - - Module *protocol = ModuleManager::FindFirstOf(PROTOCOL); - if (m->type == PROTOCOL && m != protocol) - { - source.Reply(_("You can not reload this module directly, instead reload {0}."), protocol ? protocol->name : "(unknown)"); - return; - } - - /* Unrecoverable */ - bool fatal = m->type == PROTOCOL; - ModuleReturn status = ModuleManager::UnloadModule(m, source.GetUser()); - - if (status != MOD_ERR_OK) - { - source.Reply(_("Unable to remove module \002{0}\002."), mname); - return; - } - - status = ModuleManager::LoadModule(mname, source.GetUser()); - if (status == MOD_ERR_OK) - { - Log(LOG_ADMIN, source, this) << "to reload module " << mname; - source.Reply(_("Module \002{0}\002 reloaded."), mname); - } - else - { - if (fatal) - { - Anope::QuitReason = "Unable to reload module " + mname; - Anope::Quitting = true; - } - else - source.Reply(_("Unable to load module \002{0}\002."), mname); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("This command reloads the module named \037modname\037.")); - return true; - } -}; - -class CommandOSModUnLoad : public Command -{ - public: - CommandOSModUnLoad(Module *creator) : Command(creator, "operserv/modunload", 1, 1) - { - this->SetDesc(_("Un-Load a module")); - this->SetSyntax(_("\037modname\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &mname = params[0]; - - Module *m = ModuleManager::FindModule(mname); - if (!m) - { - source.Reply(_("Module \002{0}\002 isn't loaded."), mname); - return; - } - - if (!m->handle || m->GetPermanent() || m->type == PROTOCOL) - { - source.Reply(_("Unable to remove module \002{0}\002."), m->name); - return; - } - - Log(this->owner) << "Trying to unload module [" << mname << "]"; - - ModuleReturn status = ModuleManager::UnloadModule(m, source.GetUser()); - - if (status == MOD_ERR_OK) - { - Log(LOG_ADMIN, source, this) << "to unload module " << mname; - source.Reply(_("Module \002{0}\002 unloaded."), mname); - } - else - source.Reply(_("Unable to remove module \002{0}\002."), mname); - - return; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("This command unloads the module named \037modname\037.")); - return true; - } -}; - -class OSModule : public Module -{ - CommandOSModLoad commandosmodload; - CommandOSModReLoad commandosmodreload; - CommandOSModUnLoad commandosmodunload; - - public: - OSModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosmodload(this) - , commandosmodreload(this) - , commandosmodunload(this) - { - this->SetPermanent(true); - - } -}; - -MODULE_INIT(OSModule) diff --git a/modules/commands/os_news.cpp b/modules/commands/os_news.cpp deleted file mode 100644 index 03a1e3a57..000000000 --- a/modules/commands/os_news.cpp +++ /dev/null @@ -1,459 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at info@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/os_news.h" - -/* List of messages for each news type. This simplifies message sending. */ - -enum -{ - MSG_SYNTAX, - MSG_LIST_HEADER, - MSG_LIST_NONE, - MSG_ADDED, - MSG_DEL_NOT_FOUND, - MSG_DELETED, - MSG_DEL_NONE, - MSG_DELETED_ALL, - MSG_SIZE -}; - -static struct NewsMessages -{ - NewsType type; - Anope::string name; - const char *msgs[MSG_SIZE]; -} msgarray[] = { - {NEWS_LOGON, "LOGON", - {_("LOGONNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"), - _("Logon news items:"), - _("There is no logon news."), - _("Added new logon news item."), - _("Logon news item #{0} not found!"), - _("Logon news item #{0} deleted."), - _("No logon news items to delete!"), - _("All logon news items deleted.")} - }, - {NEWS_OPER, "OPER", - {_("OPERNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"), - _("Oper news items:"), - _("There is no oper news."), - _("Added new oper news item."), - _("Oper news item #{0} not found!"), - _("Oper news item #{0} deleted."), - _("No oper news items to delete!"), - _("All oper news items deleted.")} - }, - {NEWS_RANDOM, "RANDOM", - {_("RANDOMNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"), - _("Random news items:"), - _("There is no random news."), - _("Added new random news item."), - _("Random news item #{0} not found!"), - _("Random news item #{0} deleted."), - _("No random news items to delete!"), - _("All random news items deleted.")} - } -}; - -class NewsItemImpl : public NewsItem -{ - public: - NewsItemImpl(Serialize::TypeBase *type) : NewsItem(type) { } - NewsItemImpl(Serialize::TypeBase *type, Serialize::ID id) : NewsItem(type, id) { } - - NewsType GetNewsType() override; - void SetNewsType(const NewsType &) override; - - Anope::string GetText() override; - void SetText(const Anope::string &) override; - - Anope::string GetWho() override; - void SetWho(const Anope::string &) override; - - time_t GetTime() override; - void SetTime(const time_t &) override; -}; - -class NewsItemType : public Serialize::Type -{ - public: - Serialize::Field type; - Serialize::Field text; - Serialize::Field who; - Serialize::Field time; - - NewsItemType(Module *me) : Serialize::Type(me, "NewsItem") - , type(this, "type") - , text(this, "text") - , who(this, "who") - , time(this, "time") - { - } -}; - -NewsType NewsItemImpl::GetNewsType() -{ - return Get(&NewsItemType::type); -} - -void NewsItemImpl::SetNewsType(const NewsType &t) -{ - Set(&NewsItemType::type, t); -} - -Anope::string NewsItemImpl::GetText() -{ - return Get(&NewsItemType::text); -} - -void NewsItemImpl::SetText(const Anope::string &t) -{ - Set(&NewsItemType::text, t); -} - -Anope::string NewsItemImpl::GetWho() -{ - return Get(&NewsItemType::who); -} - -void NewsItemImpl::SetWho(const Anope::string &w) -{ - Set(&NewsItemType::who, w); -} - -time_t NewsItemImpl::GetTime() -{ - return Get(&NewsItemType::time); -} - -void NewsItemImpl::SetTime(const time_t &t) -{ - Set(&NewsItemType::time, t); -} - -static const char **findmsgs(NewsType type) -{ - for (unsigned i = 0; i < sizeof(msgarray) / sizeof(*msgarray); ++i) - if (msgarray[i].type == type) - return msgarray[i].msgs; - return NULL; -} - -class NewsBase : public Command -{ - protected: - void DoList(CommandSource &source, NewsType ntype, const char **msgs) - { - std::vector list = Serialize::GetObjects(newsitem); - - if (list.empty()) - { - source.Reply(msgs[MSG_LIST_NONE]); - return; - } - - ListFormatter lflist(source.GetAccount()); - lflist.AddColumn(_("Number")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Text")); - - unsigned int i = 1; - for (NewsItem *n : list) - { - ListFormatter::ListEntry entry; - entry["Number"] = stringify(i++ + 1); - entry["Creator"] = n->GetWho(); - entry["Created"] = Anope::strftime(n->GetTime(), NULL, true); - entry["Text"] = n->GetText(); - lflist.AddEntry(entry); - } - - source.Reply(msgs[MSG_LIST_HEADER]); - - std::vector replies; - lflist.Process(replies); - - for (i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - - source.Reply(_("End of news list.")); - } - - void DoAdd(CommandSource &source, const std::vector ¶ms, NewsType ntype, const char **msgs) - { - const Anope::string text = params.size() > 1 ? params[1] : ""; - - if (text.empty()) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - NewsItem *ni = newsitem.Create(); - ni->SetNewsType(ntype); - ni->SetText(text); - ni->SetTime(Anope::CurTime); - ni->SetWho(source.GetNick()); - - source.Reply(msgs[MSG_ADDED]); - Log(LOG_ADMIN, source, this) << "to add a news item"; - } - - void DoDel(CommandSource &source, const std::vector ¶ms, NewsType ntype, const char **msgs) - { - const Anope::string &text = params.size() > 1 ? params[1] : ""; - std::vector list = Serialize::GetObjects(newsitem); - - if (text.empty()) - { - this->OnSyntaxError(source, "DEL"); - return; - } - - if (list.empty()) - { - source.Reply(msgs[MSG_LIST_NONE]); - return; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - if (!text.equals_ci("ALL")) - { - try - { - unsigned num = convertTo(text); - if (num > 0 && num <= list.size()) - { - list[num - 1]->Delete(); - source.Reply(msgs[MSG_DELETED], num); - Log(LOG_ADMIN, source, this) << "to delete a news item"; - return; - } - } - catch (const ConvertException &) { } - - source.Reply(msgs[MSG_DEL_NOT_FOUND], text.c_str()); - } - else - { - for (NewsItem *n : list) - n->Delete(); - source.Reply(msgs[MSG_DELETED_ALL]); - Log(LOG_ADMIN, source, this) << "to delete all news items"; - } - } - - void DoNews(CommandSource &source, const std::vector ¶ms, NewsType ntype) - { - const Anope::string &cmd = params[0]; - - const char **msgs = findmsgs(ntype); - if (!msgs) - throw CoreException("news: Invalid type to do_news()"); - - if (cmd.equals_ci("LIST")) - return this->DoList(source, ntype, msgs); - else if (cmd.equals_ci("ADD")) - return this->DoAdd(source, params, ntype, msgs); - else if (cmd.equals_ci("DEL")) - return this->DoDel(source, params, ntype, msgs); - else - this->OnSyntaxError(source, ""); - } - public: - NewsBase(Module *creator, const Anope::string &newstype) : Command(creator, newstype, 1, 2) - { - this->SetSyntax(_("ADD \037text\037")); - this->SetSyntax(_("DEL {\037num\037 | ALL}")); - this->SetSyntax("LIST"); - } - - virtual void Execute(CommandSource &source, const std::vector ¶ms) anope_abstract; - - virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_abstract; -}; - -class CommandOSLogonNews : public NewsBase -{ - public: - CommandOSLogonNews(Module *creator) : NewsBase(creator, "operserv/logonnews") - { - this->SetDesc(_("Define messages to be shown to users at logon")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - return this->DoNews(source, params, NEWS_LOGON); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Edits or displays the list of logon news messages. When a user connects to the network, these messages will be sent to them." - " However, no more than \002{0}\002 messages will be sent in order to avoid flooding the user." - " If there are more news messages, only the most recent will be sent."), - Config->GetModule(this->owner)->Get("newscount", "3")); - return true; - } -}; - -class CommandOSOperNews : public NewsBase -{ - public: - CommandOSOperNews(Module *creator) : NewsBase(creator, "operserv/opernews") - { - this->SetDesc(_("Define messages to be shown to users who oper")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - return this->DoNews(source, params, NEWS_OPER); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Edits or displays the list of oper news messages." - " When a user opers up (with the /OPER command), these messages will be sent to them." - " However, no more than \002{0}\002 messages will be sent in order to avoid flooding the user." - " If there are more news messages, only the most recent will be sent."), - Config->GetModule(this->owner)->Get("newscount", "3")); - return true; - } -}; - -class CommandOSRandomNews : public NewsBase -{ - public: - CommandOSRandomNews(Module *creator) : NewsBase(creator, "operserv/randomnews") - { - this->SetDesc(_("Define messages to be randomly shown to users at logon")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - return this->DoNews(source, params, NEWS_RANDOM); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Edits or displays the list of random news messages. When a user connects to the network, one (and only one) of the random news will be randomly chosen and sent to them.")); - return true; - } -}; - -class OSNews : public Module - , public EventHook - , public EventHook -{ - NewsItemType newsitemtype; - - CommandOSLogonNews commandoslogonnews; - CommandOSOperNews commandosopernews; - CommandOSRandomNews commandosrandomnews; - - Anope::string oper_announcer, announcer; - unsigned news_count; - unsigned cur_rand_news = 0; - - void DisplayNews(User *u, NewsType Type) - { - std::vector list = Serialize::GetObjects(newsitem); - if (list.empty()) - return; - - ServiceBot *bi = NULL; - if (Type == NEWS_OPER) - bi = ServiceBot::Find(Config->GetModule(this)->Get("oper_announcer", "OperServ"), true); - else - bi = ServiceBot::Find(Config->GetModule(this)->Get("announcer", "Global"), true); - if (bi == NULL) - return; - - Anope::string msg; - if (Type == NEWS_LOGON) - msg = _("[\002Logon News\002 - {0}] {1}"); - else if (Type == NEWS_OPER) - msg = _("[\002Oper News\002 - {0}] {1}"); - else if (Type == NEWS_RANDOM) - msg = _("[\002Random News\002 - {0}] {1]"); - else - return; - - unsigned int randnews = 0; - for (NewsItem *n : list) - if (n->GetNewsType() == NEWS_RANDOM) - ++randnews; - - /* Reset to head of list to get first random news value */ - if (Type == NEWS_RANDOM && cur_rand_news >= randnews) - cur_rand_news = 0; - - unsigned i = 0, displayed = 0; - for (NewsItem *n : list) - { - if (n->GetNewsType() != Type) - continue; - - if (Type == NEWS_RANDOM && i++ != cur_rand_news) - continue; - - u->SendMessage(bi, msg.c_str(), Anope::strftime(n->GetTime(), u->Account(), true), n->GetText()); - - ++displayed; - - if (Type == NEWS_RANDOM) - { - ++cur_rand_news; - break; - } - - if (displayed >= news_count) - break; - } - } - - public: - OSNews(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , newsitemtype(this) - , commandoslogonnews(this) - , commandosopernews(this) - , commandosrandomnews(this) - { - } - - void OnReload(Configuration::Conf *conf) override - { - oper_announcer = conf->GetModule(this)->Get("oper_announcer", "OperServ"); - announcer = conf->GetModule(this)->Get("announcer", "Global"); - news_count = conf->GetModule(this)->Get("newscount", "3"); - } - - void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override - { - if (mname == "OPER") - DisplayNews(u, NEWS_OPER); - } - - void OnUserConnect(User *user, bool &) override - { - if (user->Quitting() || !user->server->IsSynced()) - return; - - DisplayNews(user, NEWS_LOGON); - DisplayNews(user, NEWS_RANDOM); - } -}; - -MODULE_INIT(OSNews) diff --git a/modules/commands/os_noop.cpp b/modules/commands/os_noop.cpp deleted file mode 100644 index a12bef6a2..000000000 --- a/modules/commands/os_noop.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSNOOP : public Command -{ - public: - CommandOSNOOP(Module *creator) : Command(creator, "operserv/noop", 2, 2) - { - this->SetDesc(_("Remove all operators from a server remotely")); - this->SetSyntax(_("SET \037server\037")); - this->SetSyntax(_("REVOKE \037server\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &cmd = params[0]; - const Anope::string &server = params[1]; - - Server *s = Server::Find(server, true); - if (s == NULL) - source.Reply(_("Server \002{0}\002 does not exist."), server); - else if (s == Me || s->IsJuped() || s->IsULined()) - source.Reply(_("You can not NOOP Services.")); - else if (cmd.equals_ci("SET")) - { - /* Remove the O:lines */ - IRCD->SendSVSNOOP(s, true); - s->Extend("noop", source.GetNick()); - - Log(LOG_ADMIN, source, this) << "SET on " << s->GetName(); - source.Reply(_("All operators from \002{0}\002 have been removed."), s->GetName()); - - Anope::string reason = "NOOP command used by " + source.GetNick(); - /* Kill all the IRCops of the server */ - for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) - { - User *u2 = it->second; - - if (u2->server == s && u2->HasMode("OPER")) - u2->Kill(*source.service, reason); - } - } - else if (cmd.equals_ci("REVOKE")) - { - s->ShrinkOK("noop"); - IRCD->SendSVSNOOP(s, false); - Log(LOG_ADMIN, source, this) << "REVOKE on " << s->GetName(); - source.Reply(_("All O:lines of \002{0}\002 have been reset."), s->GetName()); - } - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("\002{0} SET\002 kills all operators from the given \002server\002 and prevents operators from opering up on the given server." - " \002{0} REVOKE\002 removes this restriction.")); - return true; - } -}; - -class OSNOOP : public Module - , public EventHook -{ - CommandOSNOOP commandosnoop; - ExtensibleItem noop; - - public: - OSNOOP(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosnoop(this) - , noop(this, "noop") - { - - } - - void OnUserModeSet(const MessageSource &, User *u, const Anope::string &mname) override - { - Anope::string *setter; - if (mname == "OPER" && (setter = noop.Get(u->server))) - { - Anope::string reason = "NOOP command used by " + *setter; - ServiceBot *OperServ = Config->GetClient("OperServ"); - u->Kill(OperServ, reason); - } - } -}; - -MODULE_INIT(OSNOOP) diff --git a/modules/commands/os_oline.cpp b/modules/commands/os_oline.cpp deleted file mode 100644 index 34c13a4a3..000000000 --- a/modules/commands/os_oline.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSOLine : public Command -{ - public: - CommandOSOLine(Module *creator) : Command(creator, "operserv/oline", 2, 2) - { - this->SetDesc(_("Give Operflags to a certain user")); - this->SetSyntax(_("\037nick\037 \037flags\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &nick = params[0]; - const Anope::string &flag = params[1]; - User *u2 = NULL; - - /* let's check whether the user is online */ - if (!(u2 = User::Find(nick, true))) - source.Reply(_("\002{0}\002 isn't currently online."), nick); - else if (u2 && flag[0] == '+') - { - IRCD->SendSVSO(source.service, nick, flag); - u2->SetMode(source.service, "OPER"); - u2->SendMessage(*source.service, _("You are now an IRC Operator.")); - source.Reply(_("Operflags \002{0}\002 have been added for \002{1}\002."), flag, nick); - Log(LOG_ADMIN, source, this) << "for " << nick; - } - else if (u2 && flag[0] == '-') - { - IRCD->SendSVSO(source.service, nick, flag); - source.Reply(_("Operflags \002{0}\002 have been removed from \002{1}\002."), flag, nick); - Log(LOG_ADMIN, source, this) << "for " << nick; - } - else - this->OnSyntaxError(source, ""); - - return; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Allows Services Operators to give Operflags to any user. Flags have to be prefixed with a \"+\" or a \"-\". To remove all flags simply type a \"-\" instead of any flags.")); - return true; - } -}; - -class OSOLine : public Module -{ - CommandOSOLine commandosoline; - - public: - OSOLine(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosoline(this) - { - - if (!IRCD || !IRCD->CanSVSO) - throw ModuleException("Your IRCd does not support OMODE."); - - } -}; - -MODULE_INIT(OSOLine) diff --git a/modules/commands/os_oper.cpp b/modules/commands/os_oper.cpp deleted file mode 100644 index 923137013..000000000 --- a/modules/commands/os_oper.cpp +++ /dev/null @@ -1,241 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSOper : public Command -{ - bool HasPrivs(CommandSource &source, OperType *ot) const - { - std::list commands = ot->GetCommands(), privs = ot->GetPrivs(); - - for (std::list::iterator it = commands.begin(); it != commands.end(); ++it) - if (!source.HasCommand(*it)) - return false; - - for (std::list::iterator it = privs.begin(); it != privs.end(); ++it) - if (!source.HasPriv(*it)) - return false; - - return true; - } - - public: - CommandOSOper(Module *creator) : Command(creator, "operserv/oper", 1, 3) - { - this->SetDesc(_("View and change Services Operators")); - this->SetSyntax(_("ADD \037oper\037 \037type\037")); - this->SetSyntax(_("DEL \037oper\037")); - this->SetSyntax(_("INFO [\037type\037]")); - this->SetSyntax("LIST"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &subcommand = params[0]; - - if (subcommand.equals_ci("ADD") && params.size() > 2) - { - const Anope::string &oper = params[1]; - const Anope::string &otype = params[2]; - - if (!source.HasPriv("operserv/oper/modify")) - { - source.Reply(_("Access denied. You do not have the operator privilege \002{0}\002."), "operserv/oper/modify"); - return; - } - - NickServ::Nick *na = NickServ::FindNick(oper); - if (na == NULL) - { - source.Reply(_("\002{0}\002 isn't currently online."), oper); - return; - } - - OperType *ot = OperType::Find(otype); - if (ot == NULL) - { - source.Reply(_("Oper type \002{0}\002 has not been configured."), otype); - return; - } - - if (!HasPrivs(source, ot)) - { - source.Reply(_("Access denied.")); - return; - } - - if (na->GetAccount()->o) - { - na->GetAccount()->o->Delete(); - na->GetAccount()->o = nullptr; - } - - Oper *o = operblock.Create(); - o->SetName(na->GetAccount()->GetDisplay()); - o->SetType(ot); - o->SetRequireOper(true); - - na->GetAccount()->o = o; - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - Log(LOG_ADMIN, source, this) << "ADD " << na->GetNick() << " as type " << ot->GetName(); - source.Reply("\002{0}\002 (\002{1}\002) added to the \002{2}\002 list.", na->GetNick(), na->GetAccount()->GetDisplay(), ot->GetName()); - } - else if (subcommand.equals_ci("DEL") && params.size() > 1) - { - const Anope::string &oper = params[1]; - - if (!source.HasPriv("operserv/oper/modify")) - { - source.Reply(_("Access denied. You do not have the operator privilege \002{0}\002."), "operserv/oper/modify"); - return; - } - - NickServ::Nick *na = NickServ::FindNick(oper); - if (na == NULL) - { - source.Reply(_("\002{0}\002 isn't registered."), oper); - return; - } - - if (!na->GetAccount() || !na->GetAccount()->o) - { - source.Reply(_("Nick \002{0}\002 is not a Services Operator."), oper); - return; - } - - if (!HasPrivs(source, na->GetAccount()->o->GetType())) - { - source.Reply(_("Access denied.")); - return; - } - - na->GetAccount()->o->Delete(); - na->GetAccount()->o = NULL; - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - - Log(LOG_ADMIN, source, this) << "DEL " << na->GetNick(); - source.Reply(_("Oper privileges removed from \002{0}\002 (\002{1}\002)."), na->GetNick(), na->GetAccount()->GetDisplay()); - } - else if (subcommand.equals_ci("LIST")) - { - source.Reply(_("Name Type")); - for (NickServ::Account *nc : NickServ::service->GetAccountList()) - { - if (!nc->o) - continue; - - source.Reply(Anope::printf("%-8s %s", nc->o->GetName().c_str(), nc->o->GetType()->GetName().c_str())); - if (nc->o->conf) - source.Reply(_(" This oper is configured in the configuration file.")); - for (User *u : nc->users) - source.Reply(_(" \002{0}\002 is online using this oper block."), u->nick); - } - } - else if (subcommand.equals_ci("INFO")) - { - if (params.size() < 2) - { - source.Reply(_("Available opertypes:")); - for (unsigned i = 0; i < Config->MyOperTypes.size(); ++i) - { - OperType *ot = Config->MyOperTypes[i]; - source.Reply("%s", ot->GetName().c_str()); - } - return; - } - - Anope::string fulltype = params[1]; - if (params.size() > 2) - fulltype += " " + params[2]; - OperType *ot = OperType::Find(fulltype); - if (ot == NULL) - { - source.Reply(_("Oper type \002{0}\002 has not been configured."), fulltype); - return; - } - - if (ot->GetCommands().empty()) - source.Reply(_("Opertype \002{0}\002 has no allowed commands."), ot->GetName()); - else - { - source.Reply(_("Available commands for \002{0}\002:"), ot->GetName()); - Anope::string buf; - std::list cmds = ot->GetCommands(); - for (std::list::const_iterator it = cmds.begin(), it_end = cmds.end(); it != it_end; ++it) - { - buf += *it + " "; - if (buf.length() > 400) - { - source.Reply("%s", buf.c_str()); - buf.clear(); - } - } - if (!buf.empty()) - { - source.Reply("%s", buf.c_str()); - buf.clear(); - } - } - if (ot->GetPrivs().empty()) - source.Reply(_("Opertype \002{0}\002 has no allowed privileges."), ot->GetName()); - else - { - source.Reply(_("Available privileges for \002{0}\002:"), ot->GetName()); - Anope::string buf; - std::list privs = ot->GetPrivs(); - for (std::list::const_iterator it = privs.begin(), it_end = privs.end(); it != it_end; ++it) - { - buf += *it + " "; - if (buf.length() > 400) - { - source.Reply("%s", buf.c_str()); - buf.clear(); - } - } - if (!buf.empty()) - { - source.Reply("%s", buf.c_str()); - buf.clear(); - } - } - if (!ot->modes.empty()) - source.Reply(_("Opertype \002{0}\002 receives modes \002{1}\002 once identified."), ot->GetName(), ot->modes); - } - else - this->OnSyntaxError(source, subcommand); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Allows you to change and view Services Operators.\n" - " Note that operators removed by this command but are still set in the configuration file are not permanently affected by this.")); - return true; - } -}; - -class OSOper : public Module -{ - CommandOSOper commandosoper; - - public: - OSOper(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosoper(this) - { - } -}; - -MODULE_INIT(OSOper) diff --git a/modules/commands/os_reload.cpp b/modules/commands/os_reload.cpp deleted file mode 100644 index 217194e18..000000000 --- a/modules/commands/os_reload.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSReload : public Command -{ - public: - CommandOSReload(Module *creator) : Command(creator, "operserv/reload", 0, 0) - { - this->SetDesc(_("Reload services' configuration file")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - try - { - Log(LOG_ADMIN, source, this); - - Configuration::Conf *new_config = new Configuration::Conf(); - Configuration::Conf *old = Config; - Config = new_config; - Config->Post(old); - delete old; - - source.Reply(_("Services' configuration has been reloaded.")); - } - catch (const ConfigException &ex) - { - Log(this->owner) << "Error reloading configuration file: " << ex.GetReason(); - source.Reply(_("Error reloading configuration file: {0}"), ex.GetReason()); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Causes Services to reload the configuration file.")); - return true; - } -}; - -class OSReload : public Module -{ - CommandOSReload commandosreload; - - public: - OSReload(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosreload(this) - { - - } -}; - -MODULE_INIT(OSReload) diff --git a/modules/commands/os_session.cpp b/modules/commands/os_session.cpp deleted file mode 100644 index 997914360..000000000 --- a/modules/commands/os_session.cpp +++ /dev/null @@ -1,768 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/os_session.h" - -namespace -{ - /* The default session limit */ - unsigned session_limit; - /* How many times to kill before adding an AKILL */ - unsigned max_session_kill; - /* How long session akills should last */ - time_t session_autokill_expiry; - /* Reason to use for session kills */ - Anope::string sle_reason; - /* Optional second reason */ - Anope::string sle_detailsloc; - - /* Max limit that can be used for exceptions */ - unsigned max_exception_limit; - /* How long before exceptions expire by default */ - time_t exception_expiry; - - /* Number of bits to use when comparing session IPs */ - unsigned ipv4_cidr; - unsigned ipv6_cidr; - - EventHandlers *events; -} - -class ExceptionImpl : public Exception -{ - public: - ExceptionImpl(Serialize::TypeBase *type) : Exception(type) { } - ExceptionImpl(Serialize::TypeBase *type, Serialize::ID id) : Exception(type, id) { } - - Anope::string GetMask() override; - void SetMask(const Anope::string &) override; - - unsigned int GetLimit() override; - void SetLimit(unsigned int) override; - - Anope::string GetWho() override; - void SetWho(const Anope::string &) override; - - Anope::string GetReason() override; - void SetReason(const Anope::string &) override; - - time_t GetTime() override; - void SetTime(const time_t &) override; - - time_t GetExpires() override; - void SetExpires(const time_t &) override; -}; - -class ExceptionType : public Serialize::Type -{ - public: - Serialize::Field mask, who, reason; - Serialize::Field limit; - Serialize::Field time, expires; - - ExceptionType(Module *me) : Serialize::Type(me, "Exception") - , mask(this, "mask") - , who(this, "who") - , reason(this, "reason") - , limit(this, "limit") - , time(this, "time") - , expires(this, "expires") - { - } -}; - -Anope::string ExceptionImpl::GetMask() -{ - return Get(&ExceptionType::mask); -} - -void ExceptionImpl::SetMask(const Anope::string &m) -{ - Set(&ExceptionType::mask, m); -} - -unsigned int ExceptionImpl::GetLimit() -{ - return Get(&ExceptionType::limit); -} - -void ExceptionImpl::SetLimit(unsigned int l) -{ - Set(&ExceptionType::limit, l); -} - -Anope::string ExceptionImpl::GetWho() -{ - return Get(&ExceptionType::who); -} - -void ExceptionImpl::SetWho(const Anope::string &w) -{ - Set(&ExceptionType::who, w); -} - -Anope::string ExceptionImpl::GetReason() -{ - return Get(&ExceptionType::reason); -} - -void ExceptionImpl::SetReason(const Anope::string &r) -{ - Set(&ExceptionType::reason, r); -} - -time_t ExceptionImpl::GetTime() -{ - return Get(&ExceptionType::time); -} - -void ExceptionImpl::SetTime(const time_t &t) -{ - Set(&ExceptionType::time, t); -} - -time_t ExceptionImpl::GetExpires() -{ - return Get(&ExceptionType::expires); -} - -void ExceptionImpl::SetExpires(const time_t &e) -{ - Set(&ExceptionType::expires, e); -} - -class MySessionService : public SessionService -{ - SessionMap Sessions; - - public: - MySessionService(Module *m) : SessionService(m) { } - - Exception *FindException(User *u) override - { - for (Exception *e : Serialize::GetObjects(exception)) - { - if (Anope::Match(u->host, e->GetMask()) || Anope::Match(u->ip.addr(), e->GetMask())) - return e; - - if (cidr(e->GetMask()).match(u->ip)) - return e; - } - return nullptr; - } - - Exception *FindException(const Anope::string &host) override - { - for (Exception *e : Serialize::GetObjects(exception)) - { - if (Anope::Match(host, e->GetMask())) - return e; - - if (cidr(e->GetMask()).match(sockaddrs(host))) - return e; - } - - return nullptr; - } - - void DelSession(Session *s) - { - this->Sessions.erase(s->addr); - } - - Session *FindSession(const Anope::string &ip) override - { - cidr c(ip, ip.find(':') != Anope::string::npos ? ipv6_cidr : ipv4_cidr); - if (!c.valid()) - return NULL; - SessionMap::iterator it = this->Sessions.find(c); - if (it != this->Sessions.end()) - return it->second; - return NULL; - } - - SessionMap::iterator FindSessionIterator(const sockaddrs &ip) - { - cidr c(ip, ip.ipv6() ? ipv6_cidr : ipv4_cidr); - if (!c.valid()) - return this->Sessions.end(); - return this->Sessions.find(c); - } - - Session* &FindOrCreateSession(const cidr &ip) - { - return this->Sessions[ip]; - } - - SessionMap &GetSessions() override - { - return this->Sessions; - } -}; - -class CommandOSSession : public Command -{ - private: - void DoList(CommandSource &source, const std::vector ¶ms) - { - Anope::string param = params[1]; - - unsigned mincount = 0; - try - { - mincount = convertTo(param); - } - catch (const ConvertException &) { } - - if (mincount <= 1) - { - source.Reply(_("Invalid threshold value. It must be a valid integer greater than 1.")); - return; - } - - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Session")).AddColumn(_("Host")); - - for (SessionService::SessionMap::iterator it = session_service->GetSessions().begin(), it_end = session_service->GetSessions().end(); it != it_end; ++it) - { - Session *session = it->second; - - if (session->count >= mincount) - { - ListFormatter::ListEntry entry; - entry["Session"] = stringify(session->count); - entry["Host"] = session->addr.mask(); - list.AddEntry(entry); - } - } - - source.Reply(_("Hosts with at least \002{0}\002 sessions:"), mincount); - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - - void DoView(CommandSource &source, const std::vector ¶ms) - { - Anope::string param = params[1]; - Session *session = session_service->FindSession(param); - - Exception *e = session_service->FindException(param); - Anope::string entry = "no entry"; - unsigned limit = session_limit; - if (e) - { - if (!e->GetLimit()) - limit = 0; - else if (e->GetLimit() > limit) - limit = e->GetLimit(); - entry = e->GetMask(); - } - - if (!session) - source.Reply(_("\002{0}\002 not found on the session list, but has a limit of \002{1}\002 because it matches entry: \002{2}\002."), param, limit, entry); - else - source.Reply(_("The host \002{0}\002 currently has \002{1}\002 sessions with a limit of \002{2}\002 because it matches entry: \002{3}\002."), session->addr.mask(), session->count, limit, entry); - } - - public: - CommandOSSession(Module *creator) : Command(creator, "operserv/session", 2, 2) - { - this->SetDesc(_("View the list of host sessions")); - this->SetSyntax(_("LIST \037threshold\037")); - this->SetSyntax(_("VIEW \037host\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &cmd = params[0]; - - Log(LOG_ADMIN, source, this) << cmd << " " << params[1]; - - if (!session_limit) - source.Reply(_("Session limiting is disabled.")); - else if (cmd.equals_ci("LIST")) - this->DoList(source, params); - else if (cmd.equals_ci("VIEW")) - this->DoView(source, params); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Allows Services Operators to view the session list.\n" - "\n" - "\002{0} LIST\002 lists hosts with at least \037threshold\037 sessions. The threshold must be a number greater than 1.\n" - "\n" - "\002{0} VIEW\002 displays detailed information about a specific host - including the current session count and session limit.\n" - "\n" - "See the \002EXCEPTION\002 help for more information about session limiting and how to set session limits specific to certain hosts and groups thereof."), // XXX - source.command); - return true; - } -}; - -class CommandOSException : public Command -{ - static void DoDel(CommandSource &source, Exception *e) - { - (*events)(&Event::Exception::OnExceptionDel, source, e); - e->Delete(); - } - - void DoAdd(CommandSource &source, const std::vector ¶ms) - { - Anope::string mask, expiry, limitstr; - unsigned last_param = 3; - - mask = params.size() > 1 ? params[1] : ""; - if (!mask.empty() && mask[0] == '+') - { - expiry = mask; - mask = params.size() > 2 ? params[2] : ""; - last_param = 4; - } - - limitstr = params.size() > last_param - 1 ? params[last_param - 1] : ""; - - if (params.size() <= last_param) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - Anope::string reason = params[last_param]; - if (last_param == 3 && params.size() > 4) - reason += " " + params[4]; - if (reason.empty()) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : exception_expiry; - if (expires < 0) - { - source.Reply(_("Invalid expiry time \002{0}\002."), expiry); - return; - } - else if (expires > 0) - expires += Anope::CurTime; - - unsigned limit = -1; - try - { - limit = convertTo(limitstr); - } - catch (const ConvertException &) { } - - if (limit > max_exception_limit) - { - source.Reply(_("Invalid session limit. It must be a valid integer greater than or equal to zero and less than \002{0}\002."), max_exception_limit); - return; - } - else - { - if (mask.find('!') != Anope::string::npos || mask.find('@') != Anope::string::npos) - { - source.Reply(_("Invalid hostmask. Only real hostmasks are valid, as exceptions are not matched against nicks or usernames.")); - return; - } - - for (Exception *e : Serialize::GetObjects(exception)) - if (e->GetMask().equals_ci(mask)) - { - if (e->GetLimit() != limit) - { - e->SetLimit(limit); - source.Reply(_("Exception for \002{0}\002 has been updated to \002{1}\002."), mask, e->GetLimit()); - } - else - source.Reply(_("\002{0}\002 already exists on the session-limit exception list."), mask); - return; - } - - Exception *e = exception.Create(); - e->SetMask(mask); - e->SetLimit(limit); - e->SetReason(reason); - e->SetTime(Anope::CurTime); - e->SetWho(source.GetNick()); - e->SetExpires(expires); - - EventReturn MOD_RESULT; - MOD_RESULT = (*events)(&Event::Exception::OnExceptionAdd, e); - if (MOD_RESULT == EVENT_STOP) - return; - - Log(LOG_ADMIN, source, this) << "to set the session limit for " << mask << " to " << limit; - source.Reply(_("Session limit for \002{0}\002 set to \002{1}\002."), mask, limit); - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - } - } - - void DoDel(CommandSource &source, const std::vector ¶ms) - { - const Anope::string &mask = params.size() > 1 ? params[1] : ""; - - if (mask.empty()) - { - this->OnSyntaxError(source, "DEL"); - return; - } - - if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) - { - unsigned int deleted = 0; - - NumberList(mask, true, - [&](unsigned int number) - { - std::vector exceptions = Serialize::GetObjects(exception); - if (!number || number > exceptions.size()) - return; - - Exception *e = exceptions[number - 1]; - - Log(LOG_ADMIN, source, this) << "to remove the session limit exception for " << e->GetMask(); - - ++deleted; - DoDel(source, e); - }, - [&]() - { - if (!deleted) - source.Reply(_("No matching entries on session-limit exception list.")); - else if (deleted == 1) - source.Reply(_("Deleted \0021\002 entry from session-limit exception list.")); - else - source.Reply(_("Deleted \002{0}\002 entries from session-limit exception list."), deleted); - }); - } - else - { - bool found = false; - for (Exception *e : Serialize::GetObjects(exception)) - if (mask.equals_ci(e->GetMask())) - { - Log(LOG_ADMIN, source, this) << "to remove the session limit exception for " << mask; - DoDel(source, e); - source.Reply(_("\002{0}\002 deleted from session-limit exception list."), mask); - found = true; - break; - } - if (!found) - source.Reply(_("\002{0}\002 not found on session-limit exception list."), mask); - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - } - - void ProcessList(CommandSource &source, const std::vector ¶ms, ListFormatter &list) - { - const Anope::string &mask = params.size() > 1 ? params[1] : ""; - std::vector exceptions = Serialize::GetObjects(exception); - - if (exceptions.empty()) - { - source.Reply(_("The session exception list is empty.")); - return; - } - - if (!mask.empty() && mask.find_first_not_of("1234567890,-") == Anope::string::npos) - { - NumberList(mask, false, - [&](unsigned int number) - { - if (!number || number > exceptions.size()) - return; - - Exception *e = exceptions[number - 1]; - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(number); - entry["Mask"] = e->GetMask(); - entry["By"] = e->GetWho(); - entry["Created"] = Anope::strftime(e->GetTime(), NULL, true); - entry["Expires"] = Anope::Expires(e->GetExpires(), source.GetAccount()); - entry["Limit"] = stringify(e->GetLimit()); - entry["Reason"] = e->GetReason(); - list.AddEntry(entry); - }, - []{}); - } - else - { - unsigned int i = 0; - for (Exception *e : exceptions) - { - if (mask.empty() || Anope::Match(e->GetMask(), mask)) - { - ListFormatter::ListEntry entry; - entry["Number"] = stringify(++i); - entry["Mask"] = e->GetMask(); - entry["By"] = e->GetWho(); - entry["Created"] = Anope::strftime(e->GetTime(), NULL, true); - entry["Expires"] = Anope::Expires(e->GetExpires(), source.GetAccount()); - entry["Limit"] = stringify(e->GetLimit()); - entry["Reason"] = e->GetReason(); - list.AddEntry(entry); - } - } - } - - if (list.IsEmpty()) - { - source.Reply(_("No matching entries on session-limit exception list.")); - return; - } - - source.Reply(_("Session-limit exception list:")); - - std::vector replies; - list.Process(replies); - - for (const Anope::string &r : replies) - source.Reply(r); - } - - void DoList(CommandSource &source, const std::vector ¶ms) - { - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Limit")).AddColumn(_("Mask")); - - this->ProcessList(source, params, list); - } - - void DoView(CommandSource &source, const std::vector ¶ms) - { - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("By")).AddColumn(_("Created")).AddColumn(_("Expires")).AddColumn(_("Limit")).AddColumn(_("Reason")); - - this->ProcessList(source, params, list); - } - - public: - CommandOSException(Module *creator) : Command(creator, "operserv/exception", 1, 5) - { - this->SetDesc(_("Modify the session-limit exception list")); - this->SetSyntax(_("ADD [\037+expiry\037] \037mask\037 \037limit\037 \037reason\037")); - this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037}")); - this->SetSyntax(_("LIST [\037mask\037 | \037list\037]")); - this->SetSyntax(_("VIEW [\037mask\037 | \037list\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &cmd = params[0]; - - if (!session_limit) - source.Reply(_("Session limiting is disabled.")); - else if (cmd.equals_ci("ADD")) - return this->DoAdd(source, params); - else if (cmd.equals_ci("DEL")) - return this->DoDel(source, params); - else if (cmd.equals_ci("LIST")) - return this->DoList(source, params); - else if (cmd.equals_ci("VIEW")) - return this->DoView(source, params); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (subcommand.equals_ci("ADD")) - source.Reply(_("\002{0} ADD\002 adds the given hostmask to the exception list." - " Note that \002nick!user@host\002 and \002user@host\002 masks are invalid!\n" - " Only real host masks, such as \002box.host.dom\002 and \002*.host.dom\002, are allowed because sessions limiting does not take nickname or user names into account." - " \037limit\037 must be a number greater than or equal to zero." - " This determines how many sessions this host may carry at a time." - " A value of zero means the host has an unlimited session limit." - " If more than one entry matches a client, the first matching enty will be used."), - source.command); - else - source.Reply(_("Allows you to manipulate the list of hosts that have specific session limits - allowing certain machines, such as shell servers, to carry more than the default number of clients at a time." - " Once a host reaches its session limit, all clients attempting to connect from that host will be killed." - " Before the user is killed, they are notified, of a source of help regarding session limiting. The content of this notice is a config setting.\n" - "\n" - "\002{0} ADD\002 adds the given host mask to the exception list.\n" - "\002{msg}{service} {help} {command} ADD\002 for more information.\n" - "\n" - "\002{0} DEL\002 removes the given mask from the exception list.\n" - "\n" - "\002{0} LIST\002 and \002{0} VIEW\002 show all current sessions if the optional mask is given, the list is limited to those sessions matching the mask." - " The difference is that \002{0} VIEW\002 is more verbose, displaying the name of the person who added the exception, its session limit, reason, host mask and the expiry date and time.\n")); - return true; - } -}; - -class OSSession : public Module - , public EventHook - , public EventHook - , public EventHook -{ - MySessionService ss; - CommandOSSession commandossession; - CommandOSException commandosexception; - ServiceReference akills; - EventHandlers exceptionevents; - ExceptionType etype; - - public: - OSSession(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , EventHook(EventHook::Priority::FIRST) - , EventHook(EventHook::Priority::FIRST) - , EventHook(EventHook::Priority::FIRST) - , ss(this) - , commandossession(this) - , commandosexception(this) - , akills("XLineManager", "xlinemanager/sgline") - , exceptionevents(this) - , etype(this) - { - this->SetPermanent(true); - - events = &exceptionevents; - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = Config->GetModule(this); - - session_limit = block->Get("defaultsessionlimit"); - max_session_kill = block->Get("maxsessionkill"); - session_autokill_expiry = block->Get("sessionautokillexpiry"); - sle_reason = block->Get("sessionlimitexceeded"); - sle_detailsloc = block->Get("sessionlimitdetailsloc"); - - max_exception_limit = block->Get("maxsessionlimit"); - exception_expiry = block->Get("exceptionexpiry"); - - ipv4_cidr = block->Get("session_ipv4_cidr", "32"); - ipv6_cidr = block->Get("session_ipv6_cidr", "128"); - - if (ipv4_cidr > 32 || ipv6_cidr > 128) - throw ConfigException(this->name + ": session CIDR value out of range"); - } - - void OnUserConnect(User *u, bool &exempt) override - { - if (u->Quitting() || !session_limit || exempt || !u->server || u->server->IsULined()) - return; - - cidr u_ip(u->ip, u->ip.ipv6() ? ipv6_cidr : ipv4_cidr); - if (!u_ip.valid()) - return; - - Session* &session = this->ss.FindOrCreateSession(u_ip); - - if (session) - { - bool kill = false; - if (session->count >= session_limit) - { - kill = true; - Exception *e = this->ss.FindException(u); - if (e) - { - kill = false; - if (e->GetLimit() && session->count >= e->GetLimit()) - kill = true; - } - } - - ++session->count; - - if (kill && !exempt) - { - ServiceBot *OperServ = Config->GetClient("OperServ"); - if (OperServ) - { - if (!sle_reason.empty()) - { - Anope::string message = sle_reason.replace_all_cs("%IP%", u->ip.addr()); - u->SendMessage(OperServ, message); - } - if (!sle_detailsloc.empty()) - u->SendMessage(OperServ, sle_detailsloc); - } - - ++session->hits; - - const Anope::string &akillmask = "*@" + session->addr.mask(); - if (max_session_kill && session->hits >= max_session_kill && akills && !akills->HasEntry(akillmask)) - { - XLine *x = new XLine(akillmask, OperServ ? OperServ->nick : "", Anope::CurTime + session_autokill_expiry, "Session limit exceeded", XLineManager::GenerateUID()); - akills->AddXLine(x); - akills->Send(NULL, x); - Log(OperServ, "akill/session") << "Added a temporary AKILL for \002" << akillmask << "\002 due to excessive connections"; - } - else - { - u->Kill(OperServ, "Session limit exceeded"); - } - } - } - else - { - session = new Session(u->ip, u->ip.ipv6() ? ipv6_cidr : ipv4_cidr); - } - } - - void OnUserQuit(User *u, const Anope::string &msg) override - { - if (!session_limit || !u->server || u->server->IsULined()) - return; - - SessionService::SessionMap &sessions = this->ss.GetSessions(); - SessionService::SessionMap::iterator sit = this->ss.FindSessionIterator(u->ip); - - if (sit == sessions.end()) - return; - - Session *session = sit->second; - - if (session->count > 1) - { - --session->count; - return; - } - - delete session; - sessions.erase(sit); - } - - void OnExpireTick() override - { - if (Anope::NoExpire) - return; - - for (Exception *e : Serialize::GetObjects(exception)) - { - if (!e->GetExpires() || e->GetExpires() > Anope::CurTime) - continue; - - ServiceBot *OperServ = Config->GetClient("OperServ"); - Log(OperServ, "expire/exception") << "Session exception for " << e->GetMask() << " has expired."; - e->Delete(); - } - } -}; - -MODULE_INIT(OSSession) diff --git a/modules/commands/os_set.cpp b/modules/commands/os_set.cpp deleted file mode 100644 index 45515fa4b..000000000 --- a/modules/commands/os_set.cpp +++ /dev/null @@ -1,243 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSSet : public Command -{ - private: - void DoList(CommandSource &source) - { - Log(LOG_ADMIN, source, this) << "LIST"; - - Anope::string index; - - index = Anope::ReadOnly ? _("%s is enabled") : _("%s is disabled"); - source.Reply(index.c_str(), "READONLY"); - index = Anope::Debug ? _("%s is enabled") : _("%s is disabled"); - source.Reply(index.c_str(), "DEBUG"); - index = Anope::NoExpire ? _("%s is enabled") : _("%s is disabled"); - source.Reply(index.c_str(), "NOEXPIRE"); - - return; - } - - void DoSetReadOnly(CommandSource &source, const std::vector ¶ms) - { - const Anope::string &setting = params.size() > 1 ? params[1] : ""; - - if (setting.empty()) - { - this->OnSyntaxError(source, "READONLY"); - return; - } - - if (setting.equals_ci("ON")) - { - Anope::ReadOnly = true; - Log(LOG_ADMIN, source, this) << "READONLY ON"; - source.Reply(_("Services are now in \002read-only\002 mode.")); - } - else if (setting.equals_ci("OFF")) - { - Anope::ReadOnly = false; - Log(LOG_ADMIN, source, this) << "READONLY OFF"; - source.Reply(_("Services are now in \002read-write\002 mode.")); - } - else - source.Reply(_("Setting for READONLY must be \002ON\002 or \002OFF\002.")); - } - - void DoSetSuperAdmin(CommandSource &source, const std::vector ¶ms) - { - const Anope::string &setting = params.size() > 1 ? params[1] : ""; - - if (!source.GetUser()) - return; - - if (setting.empty()) - { - this->OnSyntaxError(source, "SUPERADMIN"); - return; - } - - /** - * Allow the user to turn super admin on/off - * - * Rob - **/ - bool super_admin = Config->GetModule(this->owner)->Get("superadmin"); - if (!super_admin) - source.Reply(_("Super admin can not be set because it is not enabled in the configuration.")); - else if (setting.equals_ci("ON")) - { - source.GetUser()->super_admin = true; - source.Reply(_("You are now a super admin.")); - Log(LOG_ADMIN, source, this) << "SUPERADMIN ON"; - } - else if (setting.equals_ci("OFF")) - { - source.GetUser()->super_admin = false; - source.Reply(_("You are no longer a super admin.")); - Log(LOG_ADMIN, source, this) << "SUPERADMIN OFF"; - } - else - source.Reply(_("Setting for super admin must be \002ON\002 or \002OFF\002.")); - } - - void DoSetDebug(CommandSource &source, const std::vector ¶ms) - { - const Anope::string &setting = params.size() > 1 ? params[1] : ""; - - if (setting.empty()) - { - this->OnSyntaxError(source, "DEBUG"); - return; - } - - if (setting.equals_ci("ON")) - { - Anope::Debug = 1; - Log(LOG_ADMIN, source, this) << "DEBUG ON"; - source.Reply(_("Services are now in \002debug\002 mode.")); - } - else if (setting.equals_ci("OFF") || setting == "0") - { - Log(LOG_ADMIN, source, this) << "DEBUG OFF"; - Anope::Debug = 0; - source.Reply(_("Services are now in \002non-debug\002 mode.")); - } - else - { - try - { - Anope::Debug = convertTo(setting); - Log(LOG_ADMIN, source, this) << "DEBUG " << Anope::Debug; - source.Reply(_("Services are now in \002debug\002 mode (level %d)."), Anope::Debug); - return; - } - catch (const ConvertException &) { } - - source.Reply(_("Setting for DEBUG must be \002ON\002, \002OFF\002, or a positive number.")); - } - - return; - } - - void DoSetNoExpire(CommandSource &source, const std::vector ¶ms) - { - const Anope::string &setting = params.size() > 1 ? params[1] : ""; - - if (setting.empty()) - { - this->OnSyntaxError(source, "NOEXPIRE"); - return; - } - - if (setting.equals_ci("ON")) - { - Anope::NoExpire = true; - Log(LOG_ADMIN, source, this) << "NOEXPIRE ON"; - source.Reply(_("Services are now in \002no expire\002 mode.")); - } - else if (setting.equals_ci("OFF")) - { - Anope::NoExpire = false; - Log(LOG_ADMIN, source, this) << "NOEXPIRE OFF"; - source.Reply(_("Services are now in \002expire\002 mode.")); - } - else - source.Reply(_("Setting for NOEXPIRE must be \002ON\002 or \002OFF\002.")); - } - public: - CommandOSSet(Module *creator) : Command(creator, "operserv/set", 1, 2) - { - this->SetDesc(_("Set various global Services options")); - this->SetSyntax(_("\037option\037 \037setting\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &option = params[0]; - - if (option.equals_ci("LIST")) - return this->DoList(source); - else if (option.equals_ci("READONLY")) - return this->DoSetReadOnly(source, params); - else if (option.equals_ci("DEBUG")) - return this->DoSetDebug(source, params); - else if (option.equals_ci("NOEXPIRE")) - return this->DoSetNoExpire(source, params); - else if (option.equals_ci("SUPERADMIN")) - return this->DoSetSuperAdmin(source, params); - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (subcommand.empty()) - { - source.Reply(_("Sets various global Services options. Option names currently defined are:\n" - " READONLY Set read-only or read-write mode\n" - " DEBUG Activate or deactivate debug mode\n" - " NOEXPIRE Activate or deactivate no expire mode\n" - " SUPERADMIN Activate or deactivate super admin mode\n" - " LIST List the options")); - } - else if (subcommand.equals_ci("LIST")) - //source.Reply(_("Syntax: \002LIST\002\n" - // " \n" - source.Reply(("Display the various {0} settings."), source.service->nick); - else if (subcommand.equals_ci("READONLY")) - //source.Reply(_("Syntax: \002READONLY {ON | OFF}\002\n" - // " \n" - source.Reply(_("Sets read-only mode on or off. In read-only mode, norma users will not be allowed to modify any Services data, including channel and nickname access lists, etc." - "Services Operators will still be able to do most tasks, but should understand any changes they do may not be permanent.\n" - "\n" - "This option is equivalent to the command-line option \002--readonly\002.")); - else if (subcommand.equals_ci("DEBUG")) - //source.Reply(_("Syntax: \002DEBUG {ON | OFF}\002\n" - // " \n" - source.Reply(_("Sets debug mode on or off.\n" - "\n" - "This option is equivalent to the command-line option \002--debug\002.")); - else if (subcommand.equals_ci("NOEXPIRE")) - //source.Reply(_("Syntax: \002NOEXPIRE {ON | OFF}\002\n" - // " \n" - source.Reply(_("Sets no expire mode on or off. In no expire mode, nicks, channels, akills and exceptions won't expire until the option is unset.\n" - "\n" - "This option is equivalent to the command-line option \002--noexpire\002.")); - else if (subcommand.equals_ci("SUPERADMIN")) - //source.Reply(_("Syntax: \002SUPERADMIN {ON | OFF}\002\n" - // " \n" - source.Reply(_("Setting this will grant you extra privileges, such as the ability to be \"founder\" on all channels." - "\n" - "This option is \002not\002 persistent, and should only be used when needed, and set back to OFF when no longer needed.")); - else - return false; - - return true; - } -}; - -class OSSet : public Module -{ - CommandOSSet commandosset; - - public: - OSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosset(this) - { - } -}; - -MODULE_INIT(OSSet) diff --git a/modules/commands/os_shutdown.cpp b/modules/commands/os_shutdown.cpp deleted file mode 100644 index 642be5d84..000000000 --- a/modules/commands/os_shutdown.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSQuit : public Command -{ - public: - CommandOSQuit(Module *creator) : Command(creator, "operserv/quit", 0, 0) - { - this->SetDesc(_("Terminate Services without saving")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - Log(LOG_ADMIN, source, this); - Anope::QuitReason = source.command + " command received from " + source.GetNick(); - Anope::Quitting = true; - return; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Causes Services to do an immediate shutdown; databases may \002not\002 saved." - " If you are using a real time database such as SQL or Redis, this command is not useful." - " This command should not be used unless damage to the in-memory copies of the databases is feared and they should not be saved.")); - return true; - } -}; - -class CommandOSRestart : public Command -{ - public: - CommandOSRestart(Module *creator) : Command(creator, "operserv/restart", 0, 0) - { - this->SetDesc(_("Save databases and restart Services")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - Log(LOG_ADMIN, source, this); - Anope::QuitReason = source.command + " command received from " + source.GetNick(); - Anope::Quitting = Anope::Restarting = true; - Anope::SaveDatabases(); - return; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Causes Services to restart.")); - return true; - } -}; - -class CommandOSShutdown : public Command -{ - public: - CommandOSShutdown(Module *creator) : Command(creator, "operserv/shutdown", 0, 0) - { - this->SetDesc(_("Terminate services with save")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - Log(LOG_ADMIN, source, this); - Anope::QuitReason = source.command + " command received from " + source.GetNick(); - Anope::Quitting = true; - Anope::SaveDatabases(); - return; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Causes Services to shut down")); - return true; - } -}; - -class OSShutdown : public Module -{ - CommandOSQuit commandosquit; - CommandOSRestart commandosrestart; - CommandOSShutdown commandosshutdown; - - public: - OSShutdown(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), - commandosquit(this), commandosrestart(this), commandosshutdown(this) - { - - } -}; - -MODULE_INIT(OSShutdown) diff --git a/modules/commands/os_stats.cpp b/modules/commands/os_stats.cpp deleted file mode 100644 index 6e74303ce..000000000 --- a/modules/commands/os_stats.cpp +++ /dev/null @@ -1,271 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/os_session.h" - -#if 0 -struct Stats : Serialize::Object -{ - static Stats *me; - - Stats() : Serialize::Object("Stats") - { - me = this; - } - - void Serialize(Serialize::Data &data) const override - { - data["maxusercnt"] << MaxUserCount; - data["maxusertime"] << MaxUserTime; - } - - static Serializable* Unserialize(Serializable *obj, Serialize::Data &data) - { - data["maxusercnt"] >> MaxUserCount; - data["maxusertime"] >> MaxUserTime; - return me; - } -}; - -Stats *Stats::me; - -/** - * Count servers connected to server s - * @param s The server to start counting from - * @return Amount of servers connected to server s - **/ -static int stats_count_servers(Server *s) -{ - if (!s) - return 0; - - int count = 1; - - if (!s->GetLinks().empty()) - for (unsigned i = 0, j = s->GetLinks().size(); i < j; ++i) - count += stats_count_servers(s->GetLinks()[i]); - - return count; -} - -class CommandOSStats : public Command -{ - ServiceReference akills, snlines, sqlines; - private: - void DoStatsAkill(CommandSource &source) - { - int timeout; - if (akills) - { - /* AKILLs */ - source.Reply(_("Current number of AKILLs: \002{0}\002"), akills->GetCount()); - timeout = Config->GetModule("operserv")->Get("autokillexpiry", "30d") + 59; - if (timeout >= 172800) - source.Reply(_("Default AKILL expiry time: \002{0} days\002"), timeout / 86400); - else if (timeout >= 86400) - source.Reply(_("Default AKILL expiry time: \0021 day\002")); - else if (timeout >= 7200) - source.Reply(_("Default AKILL expiry time: \002{0} hours\002"), timeout / 3600); - else if (timeout >= 3600) - source.Reply(_("Default AKILL expiry time: \0021 hour\002")); - else if (timeout >= 120) - source.Reply(_("Default AKILL expiry time: \002{0} minutes\002"), timeout / 60); - else if (timeout >= 60) - source.Reply(_("Default AKILL expiry time: \0021 minute\002")); - else - source.Reply(_("Default AKILL expiry time: \002No expiration\002")); - } - if (snlines) - { - /* SNLINEs */ - source.Reply(_("Current number of SNLINEs: \002{0}\002"), snlines->GetCount()); - timeout = Config->GetModule("operserv")->Get("snlineexpiry", "30d") + 59; - if (timeout >= 172800) - source.Reply(_("Default SNLINE expiry time: \002{0} days\002"), timeout / 86400); - else if (timeout >= 86400) - source.Reply(_("Default SNLINE expiry time: \0021 day\002")); - else if (timeout >= 7200) - source.Reply(_("Default SNLINE expiry time: \002{0} hours\002"), timeout / 3600); - else if (timeout >= 3600) - source.Reply(_("Default SNLINE expiry time: \0021 hour\002")); - else if (timeout >= 120) - source.Reply(_("Default SNLINE expiry time: \002{0} minutes\002"), timeout / 60); - else if (timeout >= 60) - source.Reply(_("Default SNLINE expiry time: \0021 minute\002")); - else - source.Reply(_("Default SNLINE expiry time: \002No expiration\002")); - } - if (sqlines) - { - /* SQLINEs */ - source.Reply(_("Current number of SQLINEs: \002{0}\002"), sqlines->GetCount()); - timeout = Config->GetModule("operserv")->Get("sglineexpiry", "30d") + 59; - if (timeout >= 172800) - source.Reply(_("Default SQLINE expiry time: \002{0} days\002"), timeout / 86400); - else if (timeout >= 86400) - source.Reply(_("Default SQLINE expiry time: \0021 day\002")); - else if (timeout >= 7200) - source.Reply(_("Default SQLINE expiry time: \002{0} hours\002"), timeout / 3600); - else if (timeout >= 3600) - source.Reply(_("Default SQLINE expiry time: \0021 hour\002")); - else if (timeout >= 120) - source.Reply(_("Default SQLINE expiry time: \002{0} minutes\002"), timeout / 60); - else if (timeout >= 60) - source.Reply(_("Default SQLINE expiry time: \0021 minute\002")); - else - source.Reply(_("Default SQLINE expiry time: \002No expiration\002")); - } - } - - void DoStatsReset(CommandSource &source) - { - MaxUserCount = UserListByNick.size(); - source.Reply(_("Statistics reset.")); - return; - } - - void DoStatsUptime(CommandSource &source) - { - time_t uptime = Anope::CurTime - Anope::StartTime; - source.Reply(_("Current users: \002{0}\002 (\002{1}\002 ops)"), UserListByNick.size(), OperCount); - source.Reply(_("Maximum users: \002{0}\002 ({1})"), MaxUserCount, Anope::strftime(MaxUserTime, source.GetAccount())); - source.Reply(_("Services up \002{0}\002."), Anope::Duration(uptime, source.GetAccount())); - } - - void DoStatsUplink(CommandSource &source) - { - Anope::string buf; - for (std::set::iterator it = Servers::Capab.begin(); it != Servers::Capab.end(); ++it) - buf += " " + *it; - if (!buf.empty()) - buf.erase(buf.begin()); - - source.Reply(_("Uplink server: \002{0}\002"), Me->GetLinks().front()->GetName()); - source.Reply(_("Uplink capab: {0}"), buf); - source.Reply(_("Servers found: {0}"), stats_count_servers(Me->GetLinks().front())); - } - - template void GetHashStats(const T& map, size_t& entries, size_t& buckets, size_t& max_chain) - { - entries = map.size(), buckets = map.bucket_count(), max_chain = 0; - for (size_t i = 0; i < buckets; ++i) - if (map.bucket_size(i) > max_chain) - max_chain = map.bucket_size(i); - } - - void DoStatsHash(CommandSource &source) - { - size_t entries, buckets, max_chain; - - GetHashStats(UserListByNick, entries, buckets, max_chain); - source.Reply(_("Users (nick): {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); - - if (!UserListByUID.empty()) - { - GetHashStats(UserListByUID, entries, buckets, max_chain); - source.Reply(_("Users (uid): {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); - } - - GetHashStats(ChannelList, entries, buckets, max_chain); - source.Reply(_("Channels: {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); - - GetHashStats(ChanServ::service->GetChannels(), entries, buckets, max_chain); - source.Reply(_("Registered channels: {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); - - GetHashStats(NickServ::service->GetNickMap(), entries, buckets, max_chain); - source.Reply(_("Registered nicknames: {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); - - GetHashStats(NickServ::service->GetAccountMap(), entries, buckets, max_chain); - source.Reply(_("Registered nick groups: {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); - - if (session_service) - { - GetHashStats(session_service->GetSessions(), entries, buckets, max_chain); - source.Reply(_("Sessions: {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); - } - } - - public: - CommandOSStats(Module *creator) : Command(creator, "operserv/stats", 0, 1), - akills("XLineManager", "xlinemanager/sgline"), snlines("XLineManager", "xlinemanager/snline"), sqlines("XLineManager", "xlinemanager/sqline") - { - this->SetDesc(_("Show status of Services and network")); - this->SetSyntax("[AKILL | HASH | UPLINK | UPTIME | ALL | RESET]"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - Anope::string extra = !params.empty() ? params[0] : ""; - - Log(LOG_ADMIN, source, this) << extra; - - if (extra.equals_ci("RESET")) - return this->DoStatsReset(source); - - if (extra.equals_ci("ALL") || extra.equals_ci("AKILL")) - this->DoStatsAkill(source); - - if (extra.equals_ci("ALL") || extra.equals_ci("HASH")) - this->DoStatsHash(source); - - if (extra.equals_ci("ALL") || extra.equals_ci("UPLINK")) - this->DoStatsUplink(source); - - if (extra.empty() || extra.equals_ci("ALL") || extra.equals_ci("UPTIME")) - this->DoStatsUptime(source); - - if (!extra.empty() && !extra.equals_ci("ALL") && !extra.equals_ci("AKILL") && !extra.equals_ci("HASH") && !extra.equals_ci("UPLINK") && !extra.equals_ci("UPTIME")) - source.Reply(_("Unknown STATS option: \002{0}\002"), extra); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Without any option, shows the current number of users online, and the highest number of users online since Services was started, and the length of time Services has been running.\n" - "\n" - "With the \002AKILL\002 option, displays the current size of the AKILL list and the current default expiry time.\n" - "\n" - "The \002RESET\002 option currently resets the maximum user count to the number of users currently present on the network.\n" - "\n" - "The \002UPLINK\002 option displays information about the current server Anope uses as an uplink to the network.\n" - "\n" - "The \002HASH\002 option displays information about the hash maps.\n" - "\n" - "The \002ALL\002 option displays all of the above statistics.")); - return true; - } -}; - -class OSStats : public Module -{ - CommandOSStats commandosstats; - //Serialize::TypeBase stats_type; - Stats stats_saver; - - public: - OSStats(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosstats(this) -// , stats_type("Stats", Stats::Unserialize) - { - - } - -#if 0 - void OnUserConnect(User *u, bool &exempt) override - { - if (UserListByNick.size() == MaxUserCount && Anope::CurTime == MaxUserTime) - stats_saver.QueueUpdate(); - } -#endif -}; - -MODULE_INIT(OSStats) -#endif diff --git a/modules/commands/os_svs.cpp b/modules/commands/os_svs.cpp deleted file mode 100644 index 35c3136a8..000000000 --- a/modules/commands/os_svs.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSSVSNick : public Command -{ - public: - CommandOSSVSNick(Module *creator) : Command(creator, "operserv/svsnick", 2, 2) - { - this->SetDesc(_("Forcefully change a user's nickname")); - this->SetSyntax(_("\037nick\037 \037newnick\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &nick = params[0]; - Anope::string newnick = params[1]; - User *u2; - - if (!IRCD->CanSVSNick) - { - source.Reply(_("Your IRCd does not support SVSNICK.")); - return; - } - - /* Truncate long nicknames to nicklen characters */ - unsigned nicklen = Config->GetBlock("networkinfo")->Get("nicklen"); - if (newnick.length() > nicklen) - { - source.Reply(_("Nick \002{0}\002 was truncated to \002{1}\002 characters."), newnick, nicklen); - newnick = params[1].substr(0, nicklen); - } - - /* Check for valid characters */ - if (!IRCD->IsNickValid(newnick)) - { - source.Reply(_("Nick \002{0}\002 is an illegal nickname and cannot be used."), newnick); - return; - } - - /* Check for a nick in use or a forbidden/suspended nick */ - if (!(u2 = User::Find(nick, true))) - source.Reply(_("\002{0}\002 isn't currently online."), nick); - else if (!nick.equals_ci(newnick) && User::Find(newnick)) - source.Reply(_("\002{0}\002 is currently in use."), newnick); - else - { - source.Reply(_("\002{0}\002 is now being changed to \002{1}\002."), nick, newnick); - Log(LOG_ADMIN, source, this) << "to change " << nick << " to " << newnick; - IRCD->SendForceNickChange(u2, newnick, Anope::CurTime); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Forcefully changes a user's nickname from \037nick\037 to \037newnick\037.")); - return true; - } -}; - -class CommandOSSVSJoin : public Command -{ - public: - CommandOSSVSJoin(Module *creator) : Command(creator, "operserv/svsjoin", 2, 2) - { - this->SetDesc(_("Forcefully join a user to a channel")); - this->SetSyntax(_("\037user\037 \037channel\037")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (!IRCD->CanSVSJoin) - { - source.Reply(_("Your IRCd does not support SVSJOIN.")); - return; - } - - User *target = User::Find(params[0], true); - Channel *c = Channel::Find(params[1]); - if (target == NULL) - source.Reply(_("\002{0}\002 isn't currently online."), params[0]); - else if (source.GetUser() != target && (target->IsProtected() || target->server == Me)) - source.Reply(_("Access denied.")); - else if (!c && !IRCD->IsChannelValid(params[1])) - source.Reply(_("\002{0}\002 isn't a valid channel."), params[1]); - else if (c && c->FindUser(target)) - source.Reply(_("\002{0}\002 is already in \002{1}\002."), target->nick, c->name); - else - { - IRCD->SendSVSJoin(*source.service, target, params[1], ""); - Log(LOG_ADMIN, source, this) << "to force " << target->nick << " to join " << params[1]; - source.Reply(_("\002{0}\002 has been joined to \002{1}\002."), target->nick, params[1]); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Forcefully join \037user\037 to \037channel\037.")); - return true; - } -}; - -class CommandOSSVSPart : public Command -{ - public: - CommandOSSVSPart(Module *creator) : Command(creator, "operserv/svspart", 2, 3) - { - this->SetDesc(_("Forcefully part a user from a channel")); - this->SetSyntax(_("\037nick\037 \037channel\037 [\037reason\037]")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - if (!IRCD->CanSVSJoin) - { - source.Reply(_("Your IRCd does not support SVSPART.")); - return; - } - - User *target = User::Find(params[0], true); - Channel *c = Channel::Find(params[1]); - const Anope::string &reason = params.size() > 2 ? params[2] : ""; - if (target == NULL) - source.Reply(_("\002{0}\002 isn't currently online."), params[0]); - else if (source.GetUser() != target && (target->IsProtected() || target->server == Me)) - source.Reply(_("Access denied.")); - else if (!c) - source.Reply(_("Channel \002{0}\002 doesn't exist."), params[1]); - else if (!c->FindUser(target)) - source.Reply(_("\002{0}\002 is not in \002{1}\002."), target->nick, c->name); - else - { - IRCD->SendSVSPart(*source.service, target, params[1], reason); - if (!reason.empty()) - Log(LOG_ADMIN, source, this) << "to force " << target->nick << " to part " << c->name << " with reason " << reason; - else - Log(LOG_ADMIN, source, this) << "to force " << target->nick << " to part " << c->name; - source.Reply(_("\002{0}\002 has been parted from \002{1}\002."), target->nick, c->name); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Forcefully part \037user\037 from \037channel\037.")); - return true; - } -}; - -class OSSVS : public Module -{ - CommandOSSVSNick commandossvsnick; - CommandOSSVSJoin commandossvsjoin; - CommandOSSVSPart commandossvspart; - - public: - OSSVS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), - commandossvsnick(this), commandossvsjoin(this), commandossvspart(this) - { - } -}; - -MODULE_INIT(OSSVS) diff --git a/modules/commands/os_sxline.cpp b/modules/commands/os_sxline.cpp deleted file mode 100644 index 3465faae7..000000000 --- a/modules/commands/os_sxline.cpp +++ /dev/null @@ -1,673 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSSXLineBase : public Command -{ - private: - virtual XLineManager* xlm() anope_abstract; - - virtual void OnAdd(CommandSource &source, const std::vector ¶ms) anope_abstract; - - void OnDel(CommandSource &source, const std::vector ¶ms) - { - if (!this->xlm() || this->xlm()->GetXLines().empty()) - { - source.Reply(_("{0} list is empty."), source.command); - return; - } - - const Anope::string &mask = params.size() > 1 ? params[1] : ""; - - if (mask.empty()) - { - this->OnSyntaxError(source, "DEL"); - return; - } - - if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) - { - unsigned int deleted = 0; - - NumberList(mask, true, - [&](unsigned int number) - { - XLine *x = this->xlm()->GetEntry(number - 1); - - if (!x) - return; - - Log(LOG_ADMIN, source, this) << "to remove " << x->GetMask() << " from the list"; - - ++deleted; - x->Delete(); - }, - [&]() - { - if (!deleted) - source.Reply(_("No matching entries on the {0} list."), source.command); - else if (deleted == 1) - source.Reply(_("Deleted \0021\002 entry from the {0} list."), source.command); - else - source.Reply(_("Deleted \002{0}\002 entries from the {1} list."), deleted, source.command); - }); - } - else - { - XLine *x = this->xlm()->HasEntry(mask); - - if (!x) - { - source.Reply(_("\002{0}\002 not found on the {1] list."), mask, source.command); - return; - } - - Event::OnDelXLine(&Event::DelXLine::OnDelXLine, source, x, this->xlm()); - - x->Delete(); - source.Reply(_("\002{0}\002 deleted from the {1} list."), mask, source.command); - Log(LOG_ADMIN, source, this) << "to remove " << mask << " from the list"; - } - - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - } - - void ProcessList(CommandSource &source, const std::vector ¶ms, ListFormatter &list) - { - if (!this->xlm() || this->xlm()->GetXLines().empty()) - { - source.Reply(_("{0} list is empty."), source.command); - return; - } - - const Anope::string &mask = params.size() > 1 ? params[1] : ""; - - if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) - { - NumberList(mask, false, - [&](unsigned int number) - { - XLine *x = this->xlm()->GetEntry(number - 1); - - if (!x) - return; - - ListFormatter::ListEntry entry; - entry["Number"] = stringify(number); - entry["Mask"] = x->GetMask(); - entry["By"] = x->GetBy(); - entry["Created"] = Anope::strftime(x->GetCreated(), NULL, true); - entry["Expires"] = Anope::Expires(x->GetExpires(), source.nc); - entry["Reason"] = x->GetReason(); - list.AddEntry(entry); - }, - []{}); - } - else - { - unsigned int i = 0; - for (XLine *x : this->xlm()->GetXLines()) - { - if (mask.empty() || mask.equals_ci(x->GetMask()) || mask == x->id || Anope::Match(x->GetMask(), mask, false, true)) - { - ListFormatter::ListEntry entry; - entry["Number"] = stringify(i + 1); - entry["Mask"] = x->GetMask(); - entry["By"] = x->GetBy(); - entry["Created"] = Anope::strftime(x->GetCreated(), NULL, true); - entry["Expires"] = Anope::Expires(x->GetExpires(), source.nc); - entry["Reason"] = x->GetReason(); - list.AddEntry(entry); - } - } - } - - if (list.IsEmpty()) - source.Reply(_("No matching entries on the {0} list."), source.command); - else - { - source.Reply(_("{0} list:"), source.command); - - std::vector replies; - list.Process(replies); - - for (unsigned i = 0; i < replies.size(); ++i) - source.Reply(replies[i]); - } - } - - void OnList(CommandSource &source, const std::vector ¶ms) - { - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Reason")); - - this->ProcessList(source, params, list); - } - - void OnView(CommandSource &source, const std::vector ¶ms) - { - ListFormatter list(source.GetAccount()); - list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("By")).AddColumn(_("Created")).AddColumn(_("Expires")).AddColumn(_("Reason")); - this->ProcessList(source, params, list); - } - - void OnClear(CommandSource &source) - { - Event::OnDelXLine(&Event::DelXLine::OnDelXLine, source, nullptr, this->xlm()); - - for (XLine *x : this->xlm()->GetXLines()) - x->Delete(); - - Log(LOG_ADMIN, source, this) << "to CLEAR the list"; - source.Reply(_("The {0} list has been cleared."), source.command); - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - } - public: - CommandOSSXLineBase(Module *creator, const Anope::string &cmd) : Command(creator, cmd, 1, 4) - { - } - - const Anope::string GetDesc(CommandSource &source) const override - { - return Anope::printf(Language::Translate(source.GetAccount(), _("Manipulate the %s list")), source.command.upper().c_str()); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - const Anope::string &cmd = params[0]; - - if (cmd.equals_ci("ADD")) - return this->OnAdd(source, params); - else if (cmd.equals_ci("DEL")) - return this->OnDel(source, params); - else if (cmd.equals_ci("LIST")) - return this->OnList(source, params); - else if (cmd.equals_ci("VIEW")) - return this->OnView(source, params); - else if (cmd.equals_ci("CLEAR")) - return this->OnClear(source); - else - this->OnSyntaxError(source, ""); - } - - virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) = 0; -}; - -class CommandOSSNLine : public CommandOSSXLineBase -{ - XLineManager *xlm() - { - return this->snlines; - } - - void OnAdd(CommandSource &source, const std::vector ¶ms) override - { - if (!this->xlm()) - return; - - unsigned last_param = 2; - Anope::string param, expiry; - - param = params.size() > 1 ? params[1] : ""; - if (!param.empty() && param[0] == '+') - { - expiry = param; - param = params.size() > 2 ? params[2] : ""; - last_param = 3; - } - - time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get("snlineexpiry", "30d"); - /* If the expiry given does not contain a final letter, it's in days, - * said the doc. Ah well. - */ - if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) - expires *= 86400; - /* Do not allow less than a minute expiry time */ - if (expires && expires < 60) - { - source.Reply(_("Invalid expiry time \002{0}\002."), expiry); - return; - } - else if (expires > 0) - expires += Anope::CurTime; - - if (param.empty()) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - Anope::string rest = param; - if (params.size() > last_param) - rest += " " + params[last_param]; - - if (rest.find(':') == Anope::string::npos) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - sepstream sep(rest, ':'); - Anope::string mask; - sep.GetToken(mask); - Anope::string reason = sep.GetRemaining(); - - if (mask.empty() || reason.empty()) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - if (mask[0] == '/' && mask[mask.length() - 1] == '/') - { - if (!Config->regex_flags) - { - source.Reply(_("Regex is disabled.")); - return; - } - - Anope::string stripped_mask = mask.substr(1, mask.length() - 2); - try - { - std::regex(stripped_mask.str(), Config->regex_flags); - } - catch (const std::regex_error &ex) - { - source.Reply(ex.what()); - return; - } - } - - /* Clean up the last character of the mask if it is a space - * See bug #761 - */ - unsigned masklen = mask.length(); - if (mask[masklen - 1] == ' ') - mask.erase(masklen - 1); - - if (Config->GetModule("operserv")->Get("addakiller", "yes") && !source.GetNick().empty()) - reason = "[" + source.GetNick() + "] " + reason; - - if (!this->xlm()->CanAdd(source, mask, expires, reason)) - return; - else if (mask.find_first_not_of("/.*?") == Anope::string::npos) - { - source.Reply(_("\002{0}\002 coverage is too wide; please use a more specific mask."), mask); - return; - } - - XLine *x = new XLine(mask, source.GetNick(), expires, reason); - if (Config->GetModule("operserv")->Get("akillids")) - x->SetID(XLineManager::GenerateUID()); - - unsigned int affected = 0; - for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) - if (this->xlm()->Check(it->second, x)) - ++affected; - float percent = static_cast(affected) / static_cast(UserListByNick.size()) * 100.0; - - if (percent > 95) - { - source.Reply(_("\002{0}\002 coverage is too wide; please use a more specific mask."), mask); - Log(LOG_ADMIN, source, this) << "tried to " << source.command << " " << percent << "% of the network (" << affected << " users)"; - delete x; - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnAddXLine(&Event::AddXLine::OnAddXLine, source, x, this->xlm()); - if (MOD_RESULT == EVENT_STOP) - { - delete x; - return; - } - - this->xlm()->AddXLine(x); - - if (Config->GetModule("operserv")->Get("killonsnline", "yes")) - { - Anope::string rreason = "G-Lined: " + reason; - - for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) - { - User *user = it->second; - - if (!user->HasMode("OPER") && user->server != Me && this->xlm()->Check(user, x)) - user->Kill(Me, rreason); - } - - this->xlm()->Send(NULL, x); - } - - source.Reply(_("\002{0}\002 added to the {1} list."), mask, source.command); - Log(LOG_ADMIN, source, this) << "on " << mask << " (" << reason << "), expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]"; - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - } - - ServiceReference snlines; - public: - CommandOSSNLine(Module *creator) : CommandOSSXLineBase(creator, "operserv/snline"), snlines("XLineManager", "xlinemanager/snline") - { - this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037:\037reason\037")); - this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}")); - this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]")); - this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]")); - this->SetSyntax("CLEAR"); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (subcommand.equals_ci("ADD")) - { - source.Reply(_("\002{0} ADD\002 adds the given \037mask\037 to the {0} list for the given \037reason\037." - " \037expiry\037 is specified as an integer followed by one of \037d\037 (days), \037h\037 (hours), or \037m\037 (minutes)." - " If a unit specifier is not included, the default is days, so \037+30\037 by itself means 30 days." - " To add a {0} which does not expire, use \037+0\037." - " The default {0} expiry time is \002{1}\002." - " Because the real name may contain spaces, the separator between it and the reason is a \002colon\002."), - source.command, Anope::Duration(Config->GetModule("operserv")->Get("snlineexpiry", "30d"), source.GetAccount())); - - const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); - if (!regexengine.empty()) - { - source.Reply(" "); - source.Reply(_("Regex matches are also supported using the {0} engine. Enclose your mask in // if this is desired."), regexengine); - } - } - else if (subcommand.equals_ci("DEL")) - source.Reply(_("The \002{0} DEL\002 command removes the given \037mask\037 from the {0} list if it is present." - " If a list of entry numbers is given, those entries are deleted.")); - else if (subcommand.equals_ci("LIST") || subcommand.equals_ci("VIEW")) - source.Reply(_("The \002{0} LIST\002 and \002{0} VIEW\002 commands displays the {0} list.\n" - "If a wildcard \037mask\037 is given, only those entries matching the \037mask\037 are displayed." - " If a list of entry numbers is given, only those entries are shown." - " \002VIEW\002 is similar to \002LIST\002 but also shows who created the {0}, when it was created, and when it expires.\n" - "\n" - "Example:\n" - "\n" - " {0} LIST 2-5,7-9\n" - " Lists {0} entries numbered 2 through 5 and 7 through 9.\n"), - source.command); - else if (subcommand.equals_ci("CLEAR")) - source.Reply(_("\002{0} CLEAR\002 removes all entries from the {0} list."), - source.command); - else - { - CommandInfo *help = source.service->FindCommand("generic/help"); - - source.Reply(_("Allows you to manipulate the {0} list." - " If a user attempts to use a realname that matches a {0} mask, services will kill the user." - "\n" - "The \002ADD\002 command adds \037mask\037 to the {0} list.\n" - "\002{msg}{service} {help} {command} ADD\002 for more information.\n" - "\n" - "The \002DEL\002 command removes \037mask\037 from the {0} list.\n" - "\002{msg}{service} {help} {command} DEL\002 for more information.\n" - "\n" - "The \002LIST\002 and \002VIEW\002 commands both show the {0} list, but \002VIEW\002 also shows who created the {0} entry, when it was created, and when it expires.\n" - "\002{msg}{service} {help} {command} [LIST | VIEW]\002 for more information.\n" - "\n" - "The \002CLEAR\002 command clears th auto kill list." - "\002{msg}{service} {help} {command} CLEAR\002 for more information.\n"), - "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "help"_kw = help->cname, "command"_kw = source.command); - } - return true; - } -}; - -class CommandOSSQLine : public CommandOSSXLineBase -{ - XLineManager *xlm() - { - return this->sqlines; - } - - void OnAdd(CommandSource &source, const std::vector ¶ms) override - { - if (!this->xlm()) - return; - - unsigned last_param = 2; - Anope::string expiry, mask; - - mask = params.size() > 1 ? params[1] : ""; - if (!mask.empty() && mask[0] == '+') - { - expiry = mask; - mask = params.size() > 2 ? params[2] : ""; - last_param = 3; - } - - time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get("sqlineexpiry", "30d"); - /* If the expiry given does not contain a final letter, it's in days, - * said the doc. Ah well. - */ - if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) - expires *= 86400; - /* Do not allow less than a minute expiry time */ - if (expires && expires < 60) - { - source.Reply(_("Invalid expiry time \002{0}\002."), expiry); - return; - } - else if (expires > 0) - expires += Anope::CurTime; - - if (params.size() <= last_param) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - Anope::string reason = params[last_param]; - if (last_param == 2 && params.size() > 3) - reason += " " + params[3]; - - if (mask.empty() || reason.empty()) - { - this->OnSyntaxError(source, "ADD"); - return; - } - - if (mask[0] == '/' && mask[mask.length() - 1] == '/') - { - if (!Config->regex_flags) - { - source.Reply(_("Regex is disabled.")); - return; - } - - Anope::string stripped_mask = mask.substr(1, mask.length() - 2); - try - { - std::regex(stripped_mask.str(), Config->regex_flags); - } - catch (const std::regex_error &ex) - { - source.Reply(ex.what()); - return; - } - } - - if (Config->GetModule("operserv")->Get("addakiller", "yes") && !source.GetNick().empty()) - reason = "[" + source.GetNick() + "] " + reason; - - if (!this->sqlines->CanAdd(source, mask, expires, reason)) - return; - else if (mask.find_first_not_of("./?*") == Anope::string::npos) - { - source.Reply(_("\002{0}\002 coverage is too wide; please use a more specific mask."), mask); - return; - } - - XLine *x = new XLine(mask, source.GetNick(), expires, reason); - if (Config->GetModule("operserv")->Get("akillids")) - x->SetID(XLineManager::GenerateUID()); - - unsigned int affected = 0; - for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) - if (this->xlm()->Check(it->second, x)) - ++affected; - float percent = static_cast(affected) / static_cast(UserListByNick.size()) * 100.0; - - if (percent > 95) - { - source.Reply(_("\002{0}\002 coverage is too wide; please use a more specific mask."), mask); - Log(LOG_ADMIN, source, this) << "tried to SQLine " << percent << "% of the network (" << affected << " users)"; - delete x; - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnAddXLine(&Event::AddXLine::OnAddXLine, source, x, this->xlm()); - if (MOD_RESULT == EVENT_STOP) - { - delete x; - return; - } - - this->xlm()->AddXLine(x); - - if (Config->GetModule("operserv")->Get("killonsqline", "yes")) - { - Anope::string rreason = "Q-Lined: " + reason; - - if (mask[0] == '#') - { - for (channel_map::const_iterator cit = ChannelList.begin(), cit_end = ChannelList.end(); cit != cit_end; ++cit) - { - Channel *c = cit->second; - - if (!Anope::Match(c->name, mask, false, true)) - continue; - - std::vector users; - for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ++it) - { - ChanUserContainer *uc = it->second; - User *user = uc->user; - - if (!user->HasMode("OPER") && user->server != Me) - users.push_back(user); - } - - for (unsigned i = 0; i < users.size(); ++i) - c->Kick(NULL, users[i], "%s", reason.c_str()); - } - } - else - { - for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) - { - User *user = it->second; - - if (!user->HasMode("OPER") && user->server != Me && this->xlm()->Check(user, x)) - user->Kill(Me, rreason); - } - } - - this->xlm()->Send(NULL, x); - } - - source.Reply(_("\002{0}\002 added to the {1} list."), mask, source.command); - Log(LOG_ADMIN, source, this) << "on " << mask << " (" << reason << "), expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]"; - if (Anope::ReadOnly) - source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); - } - - ServiceReference sqlines; - public: - CommandOSSQLine(Module *creator) : CommandOSSXLineBase(creator, "operserv/sqline"), sqlines("XLineManager", "xlinemanager/sqline") - { - this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037 \037reason\037")); - this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}")); - this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]")); - this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]")); - this->SetSyntax("CLEAR"); - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - if (subcommand.equals_ci("ADD")) - { - source.Reply(_("\002{0} ADD\002 adds the given \037mask\037 to the {0} list for the given \037reason\037." - " \037expiry\037 is specified as an integer followed by one of \037d\037 (days), \037h\037 (hours), or \037m\037 (minutes)." - " If a unit specifier is not included, the default is days, so \037+30\037 by itself means 30 days." - " To add a {0} which does not expire, use \037+0\037." - " The default {0} expiry time is \002{1}\002."), - source.command, Anope::Duration(Config->GetModule("operserv")->Get("sqlineexpiry", "30d"), source.GetAccount())); - - const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); - if (!regexengine.empty()) - { - source.Reply(" "); - source.Reply(_("Regex matches are also supported using the {0} engine. Enclose your mask in // if this is desired."), regexengine); - } - } - else if (subcommand.equals_ci("DEL")) - source.Reply(_("The \002{0} DEL\002 command removes the given \037mask\037 from the {0} list if it is present." - " If a list of entry numbers is given, those entries are deleted.")); - else if (subcommand.equals_ci("LIST") || subcommand.equals_ci("VIEW")) - source.Reply(_("The \002{0} LIST\002 and \002{0} VIEW\002 commands displays the {0} list.\n" - "If a wildcard \037mask\037 is given, only those entries matching the \037mask\037 are displayed." - " If a list of entry numbers is given, only those entries are shown." - " \002VIEW\002 is similar to \002LIST\002 but also shows who created the {0}, when it was created, and when it expires.\n" - "\n" - "Example:\n" - "\n" - " {0} LIST 2-5,7-9\n" - " Lists {0} entries numbered 2 through 5 and 7 through 9.\n"), - source.command); - else if (subcommand.equals_ci("CLEAR")) - source.Reply(_("\002{0} CLEAR\002 removes all entries from the {0} list."), - source.command); - else - { - CommandInfo *help = source.service->FindCommand("generic/help"); - - source.Reply(_("Allows you to manipulate the {0} list." - " If a user attempts to use a nickname that matches a {0} mask, services will force the user off of the nickname." - " If the first character of the mask is a #, services will prevent the use of matching channels." - "\n" - "The \002ADD\002 command adds \037mask\037 to the {0} list.\n" - "\002{msg}{service} {help} {command} ADD\002 for more information.\n" - "\n" - "The \002DEL\002 command removes \037mask\037 from the {0} list.\n" - "\002{msg}{service} {help} {command} DEL\002 for more information.\n" - "\n" - "The \002LIST\002 and \002VIEW\002 commands both show the {0} list, but \002VIEW\002 also shows who created the {0} entry, when it was created, and when it expires.\n" - "\002{msg}{service} {help} {command} [LIST | VIEW]\002 for more information.\n" - "\n" - "The \002CLEAR\002 command clears th auto kill list." - "\002{msg}{service} {help} {command} CLEAR\002 for more information.\n"), - "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "help"_kw = help->cname, "command"_kw = source.command); - } - return true; - } -}; - -class OSSXLine : public Module -{ - CommandOSSNLine commandossnline; - CommandOSSQLine commandossqline; - - public: - OSSXLine(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), - commandossnline(this), commandossqline(this) - { - } -}; - -MODULE_INIT(OSSXLine) diff --git a/modules/commands/os_update.cpp b/modules/commands/os_update.cpp deleted file mode 100644 index 8ff39fb69..000000000 --- a/modules/commands/os_update.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class CommandOSUpdate : public Command -{ - public: - CommandOSUpdate(Module *creator) : Command(creator, "operserv/update", 0, 0) - { - this->SetDesc(_("Force the Services databases to be updated immediately")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - Log(LOG_ADMIN, source, this); - source.Reply(_("Updating databases.")); - Anope::SaveDatabases(); - return; - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - source.Reply(_("Causes Services to update all database files.")); - return true; - } -}; - -class OSUpdate : public Module -{ - CommandOSUpdate commandosupdate; - - public: - OSUpdate(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandosupdate(this) - { - - } -}; - -MODULE_INIT(OSUpdate) diff --git a/modules/cs_statusupdate.cpp b/modules/cs_statusupdate.cpp deleted file mode 100644 index e2e37c60d..000000000 --- a/modules/cs_statusupdate.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - */ - -#include "module.h" - -class StatusUpdate : public Module - , public EventHook - , public EventHook -{ - public: - StatusUpdate(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - { - - } - - void OnAccessAdd(ChanServ::Channel *ci, CommandSource &, ChanServ::ChanAccess *access) override - { - if (ci->c) - for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) - { - User *user = it->second->user; - - ChanServ::ChanAccess::Path p; - if (user->server != Me && access->Matches(user, user->Account(), p)) - { - ChanServ::AccessGroup ag = ci->AccessFor(user); - - for (unsigned i = 0; i < ModeManager::GetStatusChannelModesByRank().size(); ++i) - { - ChannelModeStatus *cms = ModeManager::GetStatusChannelModesByRank()[i]; - if (!ag.HasPriv("AUTO" + cms->name)) - ci->c->RemoveMode(NULL, cms, user->GetUID()); - } - ci->c->SetCorrectModes(user, true); - } - } - } - - // XXX this relies on the access entry already being removed from the list? - void OnAccessDel(ChanServ::Channel *ci, CommandSource &, ChanServ::ChanAccess *access) override - { - if (ci->c) - for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) - { - User *user = it->second->user; - - ChanServ::ChanAccess::Path p; - if (user->server != Me && access->Matches(user, user->Account(), p)) - { - ChanServ::AccessGroup ag = ci->AccessFor(user); - - for (unsigned i = 0; i < ModeManager::GetStatusChannelModesByRank().size(); ++i) - { - ChannelModeStatus *cms = ModeManager::GetStatusChannelModesByRank()[i]; - if (!ag.HasPriv("AUTO" + cms->name)) - ci->c->RemoveMode(NULL, cms, user->GetUID()); - } - } - } - } -}; - -MODULE_INIT(StatusUpdate) diff --git a/modules/database/CMakeLists.txt b/modules/database/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/database/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/database/db_flatfile.cpp b/modules/database/db_flatfile.cpp deleted file mode 100644 index 122134960..000000000 --- a/modules/database/db_flatfile.cpp +++ /dev/null @@ -1,206 +0,0 @@ -/* - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" - -class DBFlatFile : public Module - , public EventHook - , public EventHook -{ - /* Day the last backup was on */ - int last_day; - /* Backup file names */ - std::map > backups; - bool loaded; - - void BackupDatabase() - { - tm *tm = localtime(&Anope::CurTime); - - if (tm->tm_mday != last_day) - { - last_day = tm->tm_mday; - - const std::map &types = Serialize::TypeBase::GetTypes(); - - std::set dbs; - dbs.insert(Config->GetModule(this)->Get("database", "anope.db")); - - for (const std::pair &p : types) - { - Serialize::TypeBase *stype = p.second; - - if (stype->GetOwner()) - dbs.insert("module_" + stype->GetOwner()->name + ".db"); - } - - - for (std::set::const_iterator it = dbs.begin(), it_end = dbs.end(); it != it_end; ++it) - { - const Anope::string &oldname = Anope::DataDir + "/" + *it; - Anope::string newname = Anope::DataDir + "/backups/" + *it + "-" + stringify(tm->tm_year + 1900) + Anope::printf("-%02i-", tm->tm_mon + 1) + Anope::printf("%02i", tm->tm_mday); - - /* Backup already exists or no database to backup */ - if (Anope::IsFile(newname) || !Anope::IsFile(oldname)) - continue; - - Log(LOG_DEBUG) << "db_flatfile: Attempting to rename " << *it << " to " << newname; - if (rename(oldname.c_str(), newname.c_str())) - { - Anope::string err = Anope::LastError(); - Log(this) << "Unable to back up database " << *it << " (" << err << ")!"; - - if (!Config->GetModule(this)->Get("nobackupokay")) - { - Anope::Quitting = true; - Anope::QuitReason = "Unable to back up database " + *it + " (" + err + ")"; - } - - continue; - } - - backups[*it].push_back(newname); - - unsigned keepbackups = Config->GetModule(this)->Get("keepbackups"); - if (keepbackups > 0 && backups[*it].size() > keepbackups) - { - unlink(backups[*it].front().c_str()); - backups[*it].pop_front(); - } - } - } - } - - public: - DBFlatFile(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, DATABASE | VENDOR) - , last_day(0) - , loaded(false) - { - - } - - EventReturn OnLoadDatabase() override - { - const Anope::string &db_name = Anope::DataDir + "/" + Config->GetModule(this)->Get("database", "anope.db"); - - std::fstream fd(db_name.c_str(), std::ios_base::in | std::ios_base::binary); - if (!fd.is_open()) - { - Log(this) << "Unable to open " << db_name << " for reading!"; - return EVENT_STOP; - } - - Serialize::TypeBase *type = nullptr; - Serialize::Object *obj = nullptr; - for (Anope::string buf; std::getline(fd, buf.str());) - { - if (buf.find("OBJECT ") == 0) - { - Anope::string t = buf.substr(7); - if (obj) - Log(LOG_DEBUG) << "obj != null but got OBJECT"; - if (type) - Log(LOG_DEBUG) << "type != null but got OBJECT"; - type = Serialize::TypeBase::Find(t); - obj = nullptr; - } - else if (buf.find("ID ") == 0) - { - if (!type || obj) - continue; - - try - { - Serialize::ID id = convertTo(buf.substr(3)); - obj = type->Require(id); - } - catch (const ConvertException &) - { - Log(LOG_DEBUG) << "Unable to parse object id " << buf.substr(3); - } - } - else if (buf.find("DATA ") == 0) - { - if (!type) - continue; - - if (!obj) - obj = type->Create(); - - size_t sp = buf.find(' ', 5); // Skip DATA - if (sp == Anope::string::npos) - continue; - - Anope::string key = buf.substr(5, sp - 5), value = buf.substr(sp + 1); - - Serialize::FieldBase *field = type->GetField(key); - if (field) - field->UnserializeFromString(obj, value); - } - else if (buf.find("END") == 0) - { - type = nullptr; - obj = nullptr; - } - } - - fd.close(); - - loaded = true; - return EVENT_STOP; - } - - - void OnSaveDatabase() override - { - BackupDatabase(); - - Anope::string db_name = Anope::DataDir + "/" + Config->GetModule(this)->Get("database", "anope.db"); - - if (Anope::IsFile(db_name)) - rename(db_name.c_str(), (db_name + ".tmp").c_str()); - - std::fstream f(db_name.c_str(), std::ios_base::out | std::ios_base::trunc | std::ios_base::binary); - - if (!f.is_open()) - { - Log(this) << "Unable to open " << db_name << " for writing"; - } - else - { - for (std::pair p : Serialize::objects) - { - Serialize::Object *object = p.second; - Serialize::TypeBase *s_type = object->GetSerializableType(); - - f << "OBJECT " << s_type->GetName() << "\n"; - f << "ID " << object->id << "\n"; - for (Serialize::FieldBase *field : s_type->fields) - f << "DATA " << field->GetName() << " " << field->SerializeToString(object) << "\n"; - f << "END\n"; - } - } - - if (!f.is_open() || !f.good()) - { - f.close(); - rename((db_name + ".tmp").c_str(), db_name.c_str()); - } - else - { - f.close(); - unlink((db_name + ".tmp").c_str()); - } - } -}; - -MODULE_INIT(DBFlatFile) - - diff --git a/modules/database/db_old.cpp b/modules/database/db_old.cpp deleted file mode 100644 index 3d9b92922..000000000 --- a/modules/database/db_old.cpp +++ /dev/null @@ -1,1428 +0,0 @@ -/* - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/os_session.h" -#include "modules/bs_kick.h" -#include "modules/cs_mode.h" -#include "modules/bs_badwords.h" -#include "modules/os_news.h" -#include "modules/os_forbid.h" -#include "modules/cs_entrymsg.h" -#include "modules/ns_suspend.h" -#include "modules/cs_suspend.h" -#include "modules/cs_access.h" -#include "modules/ns_access.h" - -#define READ(x) \ -if (true) \ -{ \ - if ((x) < 0) \ - printf("Error, the database is broken, line %d, trying to continue... no guarantee.\n", __LINE__); \ -} \ -else \ - static_cast(0) - -#define getc_db(f) (fgetc((f)->fp)) -#define read_db(f, buf, len) (fread((buf), 1, (len), (f)->fp)) -#define read_buffer(buf, f) (read_db((f), (buf), sizeof(buf)) == sizeof(buf)) - -#define OLD_BI_PRIVATE 0x0001 - -#define OLD_NI_KILLPROTECT 0x00000001 /* Kill others who take this nick */ -#define OLD_NI_SECURE 0x00000002 /* Don't recognize unless IDENTIFY'd */ -#define OLD_NI_MSG 0x00000004 /* Use PRIVMSGs instead of NOTICEs */ -#define OLD_NI_MEMO_HARDMAX 0x00000008 /* Don't allow user to change memo limit */ -#define OLD_NI_MEMO_SIGNON 0x00000010 /* Notify of memos at signon and un-away */ -#define OLD_NI_MEMO_RECEIVE 0x00000020 /* Notify of new memos when sent */ -#define OLD_NI_PRIVATE 0x00000040 /* Don't show in LIST to non-servadmins */ -#define OLD_NI_HIDE_EMAIL 0x00000080 /* Don't show E-mail in INFO */ -#define OLD_NI_HIDE_MASK 0x00000100 /* Don't show last seen address in INFO */ -#define OLD_NI_HIDE_QUIT 0x00000200 /* Don't show last quit message in INFO */ -#define OLD_NI_KILL_QUICK 0x00000400 /* Kill in 20 seconds instead of 60 */ -#define OLD_NI_KILL_IMMED 0x00000800 /* Kill immediately instead of in 60 sec */ -#define OLD_NI_MEMO_MAIL 0x00010000 /* User gets email on memo */ -#define OLD_NI_HIDE_STATUS 0x00020000 /* Don't show services access status */ -#define OLD_NI_SUSPENDED 0x00040000 /* Nickname is suspended */ -#define OLD_NI_AUTOOP 0x00080000 /* Autoop nickname in channels */ - -#define OLD_NS_NO_EXPIRE 0x0004 /* nick won't expire */ -#define OLD_NS_VERBOTEN 0x0002 - -#define OLD_CI_KEEPTOPIC 0x00000001 -#define OLD_CI_SECUREOPS 0x00000002 -#define OLD_CI_PRIVATE 0x00000004 -#define OLD_CI_TOPICLOCK 0x00000008 -#define OLD_CI_RESTRICTED 0x00000010 -#define OLD_CI_PEACE 0x00000020 -#define OLD_CI_SECURE 0x00000040 -#define OLD_CI_VERBOTEN 0x00000080 -#define OLD_CI_ENCRYPTEDPW 0x00000100 -#define OLD_CI_NO_EXPIRE 0x00000200 -#define OLD_CI_MEMO_HARDMAX 0x00000400 -#define OLD_CI_OPNOTICE 0x00000800 -#define OLD_CI_SECUREFOUNDER 0x00001000 -#define OLD_CI_SIGNKICK 0x00002000 -#define OLD_CI_SIGNKICK_LEVEL 0x00004000 -#define OLD_CI_XOP 0x00008000 -#define OLD_CI_SUSPENDED 0x00010000 - -/* BotServ SET flags */ -#define OLD_BS_DONTKICKOPS 0x00000001 -#define OLD_BS_DONTKICKVOICES 0x00000002 -#define OLD_BS_FANTASY 0x00000004 -#define OLD_BS_SYMBIOSIS 0x00000008 -#define OLD_BS_GREET 0x00000010 -#define OLD_BS_NOBOT 0x00000020 - -/* BotServ Kickers flags */ -#define OLD_BS_KICK_BOLDS 0x80000000 -#define OLD_BS_KICK_COLORS 0x40000000 -#define OLD_BS_KICK_REVERSES 0x20000000 -#define OLD_BS_KICK_UNDERLINES 0x10000000 -#define OLD_BS_KICK_BADWORDS 0x08000000 -#define OLD_BS_KICK_CAPS 0x04000000 -#define OLD_BS_KICK_FLOOD 0x02000000 -#define OLD_BS_KICK_REPEAT 0x01000000 - -#define OLD_NEWS_LOGON 0 -#define OLD_NEWS_OPER 1 -#define OLD_NEWS_RANDOM 2 - -enum -{ - TTB_BOLDS, - TTB_COLORS, - TTB_REVERSES, - TTB_UNDERLINES, - TTB_BADWORDS, - TTB_CAPS, - TTB_FLOOD, - TTB_REPEAT, -}; - -static struct mlock_info -{ - char c; - uint32_t m; -} mlock_infos[] = { - {'i', 0x00000001}, - {'m', 0x00000002}, - {'n', 0x00000004}, - {'p', 0x00000008}, - {'s', 0x00000010}, - {'t', 0x00000020}, - {'R', 0x00000100}, - {'r', 0x00000200}, - {'c', 0x00000400}, - {'A', 0x00000800}, - {'K', 0x00002000}, - {'O', 0x00008000}, - {'Q', 0x00010000}, - {'S', 0x00020000}, - {'G', 0x00100000}, - {'C', 0x00200000}, - {'u', 0x00400000}, - {'z', 0x00800000}, - {'N', 0x01000000}, - {'M', 0x04000000} -}; - -static Anope::string hashm; - -enum -{ - LANG_EN_US, /* United States English */ - LANG_JA_JIS, /* Japanese (JIS encoding) */ - LANG_JA_EUC, /* Japanese (EUC encoding) */ - LANG_JA_SJIS, /* Japanese (SJIS encoding) */ - LANG_ES, /* Spanish */ - LANG_PT, /* Portugese */ - LANG_FR, /* French */ - LANG_TR, /* Turkish */ - LANG_IT, /* Italian */ - LANG_DE, /* German */ - LANG_CAT, /* Catalan */ - LANG_GR, /* Greek */ - LANG_NL, /* Dutch */ - LANG_RU, /* Russian */ - LANG_HUN, /* Hungarian */ - LANG_PL /* Polish */ -}; - -static void process_mlock(ChanServ::Channel *ci, uint32_t lock, bool status, uint32_t *limit, Anope::string *key) -{ - if (!mlocks) - return; - - for (unsigned i = 0; i < (sizeof(mlock_infos) / sizeof(mlock_info)); ++i) - if (lock & mlock_infos[i].m) - { - ChannelMode *cm = ModeManager::FindChannelModeByChar(mlock_infos[i].c); - if (cm) - { - if (limit && mlock_infos[i].c == 'l') - mlocks->SetMLock(ci, cm, status, stringify(*limit)); - else if (key && mlock_infos[i].c == 'k') - mlocks->SetMLock(ci, cm, status, *key); - else - mlocks->SetMLock(ci, cm, status); - } - } -} - -static const char Base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -static const char Pad64 = '='; - -static void my_b64_encode(const Anope::string &src, Anope::string &target) -{ - size_t src_pos = 0, src_len = src.length(); - unsigned char input[3]; - - target.clear(); - - while (src_len - src_pos > 2) - { - input[0] = src[src_pos++]; - input[1] = src[src_pos++]; - input[2] = src[src_pos++]; - - target += Base64[input[0] >> 2]; - target += Base64[((input[0] & 0x03) << 4) + (input[1] >> 4)]; - target += Base64[((input[1] & 0x0f) << 2) + (input[2] >> 6)]; - target += Base64[input[2] & 0x3f]; - } - - /* Now we worry about padding */ - if (src_pos != src_len) - { - input[0] = input[1] = input[2] = 0; - for (size_t i = 0; i < src_len - src_pos; ++i) - input[i] = src[src_pos + i]; - - target += Base64[input[0] >> 2]; - target += Base64[((input[0] & 0x03) << 4) + (input[1] >> 4)]; - if (src_pos == src_len - 1) - target += Pad64; - else - target += Base64[((input[1] & 0x0f) << 2) + (input[2] >> 6)]; - target += Pad64; - } -} - -static Anope::string Hex(const char *data, size_t l) -{ - const char hextable[] = "0123456789abcdef"; - - std::string rv; - for (size_t i = 0; i < l; ++i) - { - unsigned char c = data[i]; - rv += hextable[c >> 4]; - rv += hextable[c & 0xF]; - } - return rv; -} - -static Anope::string GetLevelName(int level) -{ - switch (level) - { - case 0: - return "INVITE"; - case 1: - return "AKICK"; - case 2: - return "SET"; - case 3: - return "UNBAN"; - case 4: - return "AUTOOP"; - case 5: - return "AUTODEOP"; - case 6: - return "AUTOVOICE"; - case 7: - return "OP"; - case 8: - return "ACCESS_LIST"; - case 9: - return "CLEAR"; - case 10: - return "NOJOIN"; - case 11: - return "ACCESS_CHANGE"; - case 12: - return "MEMO"; - case 13: - return "ASSIGN"; - case 14: - return "BADWORDS"; - case 15: - return "NOKICK"; - case 16: - return "FANTASIA"; - case 17: - return "SAY"; - case 18: - return "GREET"; - case 19: - return "VOICEME"; - case 20: - return "VOICE"; - case 21: - return "GETKEY"; - case 22: - return "AUTOHALFOP"; - case 23: - return "AUTOPROTECT"; - case 24: - return "OPME"; - case 25: - return "HALFOPME"; - case 26: - return "HALFOP"; - case 27: - return "PROTECTME"; - case 28: - return "PROTECT"; - case 29: - return "KICKME"; - case 30: - return "KICK"; - case 31: - return "SIGNKICK"; - case 32: - return "BANME"; - case 33: - return "BAN"; - case 34: - return "TOPIC"; - case 35: - return "INFO"; - default: - return "INVALID"; - } -} - -static char *strscpy(char *d, const char *s, size_t len) -{ - char *d_orig = d; - - if (!len) - return d; - while (--len && (*d++ = *s++)); - *d = '\0'; - return d_orig; -} - -struct dbFILE -{ - int mode; /* 'r' for reading, 'w' for writing */ - FILE *fp; /* The normal file descriptor */ - char filename[1024]; /* Name of the database file */ -}; - -static dbFILE *open_db_read(const char *service, const char *filename, int version) -{ - dbFILE *f; - FILE *fp; - int myversion; - - f = new dbFILE; - strscpy(f->filename, (Anope::DataDir + "/" + filename).c_str(), sizeof(f->filename)); - f->mode = 'r'; - fp = fopen(f->filename, "rb"); - if (!fp) - { - Log() << "Can't read " << service << " database " << f->filename; - delete f; - return NULL; - } - f->fp = fp; - myversion = fgetc(fp) << 24 | fgetc(fp) << 16 | fgetc(fp) << 8 | fgetc(fp); - if (feof(fp)) - { - Log() << "Error reading version number on " << f->filename << ": End of file detected."; - delete f; - return NULL; - } - else if (myversion < version) - { - Log() << "Unsuported database version (" << myversion << ") on " << f->filename << "."; - delete f; - return NULL; - } - return f; -} - -void close_db(dbFILE *f) -{ - fclose(f->fp); - delete f; -} - -static int read_int16(int16_t *ret, dbFILE *f) -{ - int c1, c2; - - *ret = 0; - - c1 = fgetc(f->fp); - c2 = fgetc(f->fp); - if (c1 == EOF || c2 == EOF) - return -1; - *ret = c1 << 8 | c2; - return 0; -} - -static int read_uint16(uint16_t *ret, dbFILE *f) -{ - int c1, c2; - - *ret = 0; - - c1 = fgetc(f->fp); - c2 = fgetc(f->fp); - if (c1 == EOF || c2 == EOF) - return -1; - *ret = c1 << 8 | c2; - return 0; -} - -static int read_string(Anope::string &str, dbFILE *f) -{ - str.clear(); - uint16_t len; - - if (read_uint16(&len, f) < 0) - return -1; - if (len == 0) - return 0; - char *s = new char[len]; - if (len != fread(s, 1, len, f->fp)) - { - delete [] s; - return -1; - } - str = s; - delete [] s; - return 0; -} - -static int read_uint32(uint32_t *ret, dbFILE *f) -{ - int c1, c2, c3, c4; - - *ret = 0; - - c1 = fgetc(f->fp); - c2 = fgetc(f->fp); - c3 = fgetc(f->fp); - c4 = fgetc(f->fp); - if (c1 == EOF || c2 == EOF || c3 == EOF || c4 == EOF) - return -1; - *ret = c1 << 24 | c2 << 16 | c3 << 8 | c4; - return 0; -} - -int read_int32(int32_t *ret, dbFILE *f) -{ - int c1, c2, c3, c4; - - *ret = 0; - - c1 = fgetc(f->fp); - c2 = fgetc(f->fp); - c3 = fgetc(f->fp); - c4 = fgetc(f->fp); - if (c1 == EOF || c2 == EOF || c3 == EOF || c4 == EOF) - return -1; - *ret = c1 << 24 | c2 << 16 | c3 << 8 | c4; - return 0; -} - -static void LoadNicks() -{ - if (!NickServ::service) - return; - dbFILE *f = open_db_read("NickServ", "nick.db", 14); - if (f == NULL) - return; - for (int i = 0; i < 1024; ++i) - for (int c; (c = getc_db(f)) == 1;) - { - Anope::string buffer; - - READ(read_string(buffer, f)); - - NickServ::Account *nc = NickServ::account.Create(); - nc->SetDisplay(buffer); - - const Anope::string settings[] = { "killprotect", "kill_quick", "ns_secure", "ns_private", "hide_email", - "hide_mask", "hide_quit", "memo_signon", "memo_receive", "autoop", "msg", "ns_keepmodes" }; - for (unsigned j = 0; j < sizeof(settings) / sizeof(Anope::string); ++j) - nc->UnsetS(settings[j].upper()); - - char pwbuf[32]; - READ(read_buffer(pwbuf, f)); - if (hashm == "plain") - { - Anope::string p; - my_b64_encode(pwbuf, p); - nc->SetPassword(p); - } - else if (hashm == "md5" || hashm == "oldmd5") - nc->SetPassword(Hex(pwbuf, 16)); - else if (hashm == "sha1") - nc->SetPassword(Hex(pwbuf, 20)); - else - nc->SetPassword(Hex(pwbuf, strlen(pwbuf))); - nc->SetPassword(hashm + ":" + nc->GetPassword()); - - READ(read_string(buffer, f)); - nc->SetEmail(buffer); - - READ(read_string(buffer, f)); - if (!buffer.empty()) - nc->Extend("greet", buffer); - - uint32_t u32; - READ(read_uint32(&u32, f)); - //nc->icq = u32; - - READ(read_string(buffer, f)); - //nc->url = buffer; - - READ(read_uint32(&u32, f)); - if (u32 & OLD_NI_KILLPROTECT) - nc->SetS("KILLPROTECT", true); - if (u32 & OLD_NI_SECURE) - nc->SetS("NS_SECURE", true); - if (u32 & OLD_NI_MSG) - nc->SetS("MSG", true); - if (u32 & OLD_NI_MEMO_HARDMAX) - nc->SetS("MEMO_HARDMAX", true); - if (u32 & OLD_NI_MEMO_SIGNON) - nc->SetS("MEMO_SIGNON", true); - if (u32 & OLD_NI_MEMO_RECEIVE) - nc->SetS("MEMO_RECEIVE", true); - if (u32 & OLD_NI_PRIVATE) - nc->SetS("NS_PRIVATE", true); - if (u32 & OLD_NI_HIDE_EMAIL) - nc->SetS("HIDE_EMAIL", true); - if (u32 & OLD_NI_HIDE_MASK) - nc->SetS("HIDE_MASK", true); - if (u32 & OLD_NI_HIDE_QUIT) - nc->SetS("HIDE_QUIT", true); - if (u32 & OLD_NI_KILL_QUICK) - nc->SetS("KILL_QUICK", true); - if (u32 & OLD_NI_KILL_IMMED) - nc->SetS("KILL_IMMED", true); - if (u32 & OLD_NI_MEMO_MAIL) - nc->SetS("MEMO_MAIL", true); - if (u32 & OLD_NI_HIDE_STATUS) - nc->SetS("HIDE_STATUS", true); - if (u32 & OLD_NI_SUSPENDED) - { - if (nssuspendinfo) - { - NSSuspendInfo *si = nssuspendinfo.Create(); - si->SetAccount(nc); - } - } - if (!(u32 & OLD_NI_AUTOOP)) - nc->SetS("AUTOOP", true); - - uint16_t u16; - READ(read_uint16(&u16, f)); - switch (u16) - { - case LANG_ES: - nc->SetLanguage("es_ES"); - break; - case LANG_PT: - nc->SetLanguage("pt_PT"); - break; - case LANG_FR: - nc->SetLanguage("fr_FR"); - break; - case LANG_TR: - nc->SetLanguage("tr_TR"); - break; - case LANG_IT: - nc->SetLanguage("it_IT"); - break; - case LANG_DE: - nc->SetLanguage("de_DE"); - break; - case LANG_CAT: - nc->SetLanguage("ca_ES"); // yes, iso639 defines catalan as CA - break; - case LANG_GR: - nc->SetLanguage("el_GR"); - break; - case LANG_NL: - nc->SetLanguage("nl_NL"); - break; - case LANG_RU: - nc->SetLanguage("ru_RU"); - break; - case LANG_HUN: - nc->SetLanguage("hu_HU"); - break; - case LANG_PL: - nc->SetLanguage("pl_PL"); - break; - case LANG_EN_US: - case LANG_JA_JIS: - case LANG_JA_EUC: - case LANG_JA_SJIS: // these seem to be unused - default: - nc->SetLanguage("en"); - } - - READ(read_uint16(&u16, f)); - for (uint16_t j = 0; j < u16; ++j) - { - READ(read_string(buffer, f)); - - if (nsaccess) - { - NickAccess *a = nsaccess.Create(); - a->SetAccount(nc); - a->SetMask(buffer); - } - } - - int16_t i16; - READ(read_int16(&i16, f)); - READ(read_int16(&i16, f)); - MemoServ::MemoInfo *mi = nc->GetMemos(); - if (mi) - mi->SetMemoMax(i16); - for (int16_t j = 0; j < i16; ++j) - { - MemoServ::Memo *m = MemoServ::service ? MemoServ::service->CreateMemo() : nullptr; - READ(read_uint32(&u32, f)); - uint16_t flags; - READ(read_uint16(&flags, f)); - int32_t tmp32; - READ(read_int32(&tmp32, f)); - if (m) - m->SetTime(tmp32); - char sbuf[32]; - READ(read_buffer(sbuf, f)); - if (m) - m->SetSender(sbuf); - Anope::string text; - READ(read_string(text, f)); - if (m) - m->SetText(text); - } - READ(read_uint16(&u16, f)); - READ(read_int16(&i16, f)); - - Log(LOG_DEBUG) << "Loaded NickServ::Account " << nc->GetDisplay(); - } - - for (int i = 0; i < 1024; ++i) - for (int c; (c = getc_db(f)) == 1;) - { - Anope::string nick, last_usermask, last_realname, last_quit; - time_t time_registered, last_seen; - - READ(read_string(nick, f)); - READ(read_string(last_usermask, f)); - READ(read_string(last_realname, f)); - READ(read_string(last_quit, f)); - - int32_t tmp32; - READ(read_int32(&tmp32, f)); - time_registered = tmp32; - READ(read_int32(&tmp32, f)); - last_seen = tmp32; - - uint16_t tmpu16; - READ(read_uint16(&tmpu16, f)); - - Anope::string core; - READ(read_string(core, f)); - NickServ::Account *nc = NickServ::FindAccount(core); - if (nc == NULL) - { - Log() << "Skipping coreless nick " << nick << " with core " << core; - continue; - } - - if (tmpu16 & OLD_NS_VERBOTEN) - { - if (!forbiddata) - { - delete nc; - continue; - } - - if (nc->GetDisplay().find_first_of("?*") != Anope::string::npos) - { - delete nc; - continue; - } - - ForbidData *d = forbiddata.Create(); - d->SetMask(nc->GetDisplay()); - d->SetCreator(last_usermask); - d->SetReason(last_realname); - d->SetType(FT_NICK); - delete nc; - continue; - } - - NickServ::Nick *na = NickServ::nick.Create(); - na->SetNick(nick); - na->SetAccount(nc); - na->SetLastUsermask(last_usermask); - na->SetLastRealname(last_realname); - na->SetLastQuit(last_quit); - na->SetTimeRegistered(time_registered); - na->SetLastSeen(last_seen); - - if (tmpu16 & OLD_NS_NO_EXPIRE) - na->SetS("NS_NO_EXPIRE", true); - - Log(LOG_DEBUG) << "Loaded NickServ::Nick " << na->GetNick(); - } - - close_db(f); /* End of section Ia */ -} - -static void LoadVHosts() -{ - dbFILE *f = open_db_read("HostServ", "hosts.db", 3); - if (f == NULL) - return; - - for (int c; (c = getc_db(f)) == 1;) - { - Anope::string nick, ident, host, creator; - int32_t vtime; - - READ(read_string(nick, f)); - READ(read_string(ident, f)); - READ(read_string(host, f)); - READ(read_string(creator, f)); - READ(read_int32(&vtime, f)); - - NickServ::Nick *na = NickServ::FindNick(nick); - if (na == NULL) - { - Log() << "Removing vhost for non-existent nick " << nick; - continue; - } - - na->SetVhost(ident, host, creator, vtime); - - Log() << "Loaded vhost for " << na->GetNick(); - } - - close_db(f); -} - -static void LoadBots() -{ - dbFILE *f = open_db_read("Botserv", "bot.db", 10); - if (f == NULL) - return; - - for (int c; (c = getc_db(f)) == 1;) - { - Anope::string nick, user, host, real; - int16_t flags, chancount; - int32_t created; - - READ(read_string(nick, f)); - READ(read_string(user, f)); - READ(read_string(host, f)); - READ(read_string(real, f)); - READ(read_int16(&flags, f)); - READ(read_int32(&created, f)); - READ(read_int16(&chancount, f)); - - ServiceBot *bi = ServiceBot::Find(nick, true); - //XXX - // if (!bi) - // bi = new ServiceBot(nick, user, host, real); - bi->bi->SetCreated(created); - - if (flags & OLD_BI_PRIVATE) - bi->bi->SetOperOnly(true); - - Log(LOG_DEBUG) << "Loaded bot " << bi->nick; - } - - close_db(f); -} - -static void LoadChannels() -{ - if (!ChanServ::service) - return; - - ServiceReference forbid("ForbidService", "forbid"); - dbFILE *f = open_db_read("ChanServ", "chan.db", 16); - if (f == NULL) - return; - - for (int i = 0; i < 256; ++i) - for (int c; (c = getc_db(f)) == 1;) - { - Anope::string buffer; - char namebuf[64]; - READ(read_buffer(namebuf, f)); - ChanServ::Channel *ci = ChanServ::channel.Create(); - ci->SetName(namebuf); - - const Anope::string settings[] = { "keeptopic", "peace", "cs_private", "restricted", "cs_secure", "secureops", "securefounder", - "signkick", "signkick_level", "topiclock", "persist", "noautoop", "cs_keepmodes" }; - for (unsigned j = 0; j < sizeof(settings) / sizeof(Anope::string); ++j) - ci->UnsetS(settings[j].upper()); - - READ(read_string(buffer, f)); - ci->SetFounder(NickServ::FindAccount(buffer)); - - READ(read_string(buffer, f)); - ci->SetSuccessor(NickServ::FindAccount(buffer)); - - char pwbuf[32]; - READ(read_buffer(pwbuf, f)); - - Anope::string desc; - READ(read_string(desc, f)); - ci->SetDesc(desc); - READ(read_string(buffer, f)); - READ(read_string(buffer, f)); - - int32_t tmp32; - READ(read_int32(&tmp32, f)); - ci->SetTimeRegistered(tmp32); - - READ(read_int32(&tmp32, f)); - ci->SetLastUsed(tmp32); - - Anope::string last_topic; - READ(read_string(last_topic, f)); - ci->SetLastTopic(last_topic); - - READ(read_buffer(pwbuf, f)); - ci->SetLastTopicSetter(pwbuf); - - READ(read_int32(&tmp32, f)); - ci->SetLastTopicTime(tmp32); - - uint32_t tmpu32; - READ(read_uint32(&tmpu32, f)); - // Temporary flags cleanup - tmpu32 &= ~0x80000000; - if (tmpu32 & OLD_CI_KEEPTOPIC) - ci->SetS("KEEPTOPIC", true); - if (tmpu32 & OLD_CI_SECUREOPS) - ci->SetS("SECUREOPS", true); - if (tmpu32 & OLD_CI_PRIVATE) - ci->SetS("CS_PRIVATE", true); - if (tmpu32 & OLD_CI_TOPICLOCK) - ci->SetS("TOPICLOCK", true); - if (tmpu32 & OLD_CI_RESTRICTED) - ci->SetS("RESTRICTED", true); - if (tmpu32 & OLD_CI_PEACE) - ci->SetS("PEACE", true); - if (tmpu32 & OLD_CI_SECURE) - ci->SetS("CS_SECURE", true); - if (tmpu32 & OLD_CI_NO_EXPIRE) - ci->SetS("CS_NO_EXPIRE", true); - if (tmpu32 & OLD_CI_MEMO_HARDMAX) - ci->SetS("MEMO_HARDMAX", true); - if (tmpu32 & OLD_CI_SECUREFOUNDER) - ci->SetS("SECUREFOUNDER", true); - if (tmpu32 & OLD_CI_SIGNKICK) - ci->SetS("SIGNKICK", true); - if (tmpu32 & OLD_CI_SIGNKICK_LEVEL) - ci->SetS("SIGNKICK_LEVEL", true); - - Anope::string forbidby, forbidreason; - READ(read_string(forbidby, f)); - READ(read_string(forbidreason, f)); - if (tmpu32 & OLD_CI_SUSPENDED) - { - if (cssuspendinfo) - { - CSSuspendInfo *si = cssuspendinfo.Create(); - si->SetChannel(ci); - si->SetBy(forbidby); - } - } - bool forbid_chan = tmpu32 & OLD_CI_VERBOTEN; - - int16_t tmp16; - READ(read_int16(&tmp16, f)); - ci->SetBanType(tmp16); - - READ(read_int16(&tmp16, f)); - if (tmp16 > 36) - tmp16 = 36; - for (int16_t j = 0; j < tmp16; ++j) - { - int16_t level; - READ(read_int16(&level, f)); - - if (level == ChanServ::ACCESS_INVALID) - level = ChanServ::ACCESS_FOUNDER; - - if (j == 10 && level < 0) // NOJOIN - ci->UnsetS("RESTRICTED"); // If CSDefRestricted was enabled this can happen - - ci->SetLevel(GetLevelName(j), level); - } - - bool xop = tmpu32 & OLD_CI_XOP; - uint16_t tmpu16; - READ(read_uint16(&tmpu16, f)); - for (uint16_t j = 0; j < tmpu16; ++j) - { - uint16_t in_use; - READ(read_uint16(&in_use, f)); - if (in_use) - { - ChanServ::ChanAccess *access = NULL; - - if (xop) - { - if (xopchanaccess) - access = xopchanaccess.Create(); - } - else - { - if (accesschanaccess) - access = accesschanaccess.Create(); - } - - if (access) - access->SetChannel(ci); - - int16_t level; - READ(read_int16(&level, f)); - if (access) - { - if (xop) - { - switch (level) - { - case 3: - access->AccessUnserialize("VOP"); - break; - case 4: - access->AccessUnserialize("HOP"); - break; - case 5: - access->AccessUnserialize("AOP"); - break; - case 10: - access->AccessUnserialize("SOP"); - break; - } - } - else - access->AccessUnserialize(stringify(level)); - } - - Anope::string mask; - READ(read_string(mask, f)); - if (access) - { - access->SetMask(mask); - NickServ::Nick *na = NickServ::FindNick(mask); - if (na) - na->SetAccount(na->GetAccount()); - } - - READ(read_int32(&tmp32, f)); - if (access) - { - access->SetLastSeen(tmp32); - access->SetCreator("Unknown"); - access->SetCreated(Anope::CurTime); - } - } - } - - READ(read_uint16(&tmpu16, f)); - for (uint16_t j = 0; j < tmpu16; ++j) - { - uint16_t flags; - READ(read_uint16(&flags, f)); - if (flags & 0x0001) - { - Anope::string mask, reason, creator; - READ(read_string(mask, f)); - READ(read_string(reason, f)); - READ(read_string(creator, f)); - READ(read_int32(&tmp32, f)); - - ci->AddAkick(creator, mask, reason, tmp32); - } - } - - READ(read_uint32(&tmpu32, f)); // mlock on - ci->Extend("mlock_on", tmpu32); - READ(read_uint32(&tmpu32, f)); // mlock off - ci->Extend("mlock_off", tmpu32); - READ(read_uint32(&tmpu32, f)); // mlock limit - ci->Extend("mlock_limit", tmpu32); - READ(read_string(buffer, f)); // key - ci->Extend("mlock_key", buffer); - READ(read_string(buffer, f)); // +f - READ(read_string(buffer, f)); // +L - - READ(read_int16(&tmp16, f)); - READ(read_int16(&tmp16, f)); - MemoServ::MemoInfo *mi = ci->GetMemos(); - if (mi) - mi->SetMemoMax(tmp16); - for (int16_t j = 0; j < tmp16; ++j) - { - READ(read_uint32(&tmpu32, f)); - READ(read_uint16(&tmpu16, f)); - MemoServ::Memo *m = MemoServ::service ? MemoServ::service->CreateMemo() : nullptr; - READ(read_int32(&tmp32, f)); - if (m) - m->SetTime(tmp32); - char sbuf[32]; - READ(read_buffer(sbuf, f)); - if (m) - m->SetSender(sbuf); - Anope::string text; - READ(read_string(text, f)); - if (m) - m->SetText(text); - } - - READ(read_string(buffer, f)); - if (!buffer.empty()) - { - if (entrymsg) - { - EntryMsg *e = entrymsg.Create(); - e->SetChannel(ci); - e->SetCreator("Unknown"); - e->SetMessage(buffer); - e->SetWhen(Anope::CurTime); - } - } - - READ(read_string(buffer, f)); - ci->SetBot(ServiceBot::Find(buffer, true)); - - READ(read_int32(&tmp32, f)); - if (tmp32 & OLD_BS_DONTKICKOPS) - ci->SetS("BS_DONTKICKOPS", true); - if (tmp32 & OLD_BS_DONTKICKVOICES) - ci->SetS("BS_DONTKICKVOICES", true); - if (tmp32 & OLD_BS_FANTASY) - ci->SetS("BS_FANTASY", true); - if (tmp32 & OLD_BS_GREET) - ci->SetS("BS_GREET", true); - if (tmp32 & OLD_BS_NOBOT) - ci->SetS("BS_NOBOT", true); - - KickerData *kd = GetKickerData(ci); - if (kd) - { - kd->SetBolds(tmp32 & OLD_BS_KICK_BOLDS); - kd->SetColors(tmp32 & OLD_BS_KICK_COLORS); - kd->SetReverses(tmp32 & OLD_BS_KICK_REVERSES); - kd->SetUnderlines(tmp32 & OLD_BS_KICK_UNDERLINES); - kd->SetBadwords(tmp32 & OLD_BS_KICK_BADWORDS); - kd->SetCaps(tmp32 & OLD_BS_KICK_CAPS); - kd->SetFlood(tmp32 & OLD_BS_KICK_FLOOD); - kd->SetRepeat(tmp32 & OLD_BS_KICK_REPEAT); - } - - READ(read_int16(&tmp16, f)); - for (int16_t j = 0; j < tmp16; ++j) - { - int16_t ttb; - READ(read_int16(&ttb, f)); - switch (j) - { - case TTB_BOLDS: - kd->SetTTBBolds(ttb); - break; - case TTB_COLORS: - kd->SetTTBColors(ttb); - break; - case TTB_REVERSES: - kd->SetTTBReverses(ttb); - break; - case TTB_UNDERLINES: - kd->SetTTBUnderlines(ttb); - break; - case TTB_BADWORDS: - kd->SetTTBBadwords(ttb); - break; - case TTB_CAPS: - kd->SetTTBCaps(ttb); - break; - case TTB_FLOOD: - kd->SetTTBFlood(ttb); - break; - case TTB_REPEAT: - kd->SetTTBRepeat(ttb); - break; - } - } - - READ(read_int16(&tmp16, f)); - if (kd) - kd->SetCapsMin(tmp16); - READ(read_int16(&tmp16, f)); - if (kd) - kd->SetCapsPercent(tmp16); - READ(read_int16(&tmp16, f)); - if (kd) - kd->SetFloodLines(tmp16); - READ(read_int16(&tmp16, f)); - if (kd) - kd->SetFloodSecs(tmp16); - READ(read_int16(&tmp16, f)); - if (kd) - kd->SetRepeatTimes(tmp16); - - READ(read_uint16(&tmpu16, f)); - for (uint16_t j = 0; j < tmpu16; ++j) - { - uint16_t in_use; - READ(read_uint16(&in_use, f)); - if (in_use) - { - READ(read_string(buffer, f)); - uint16_t type; - READ(read_uint16(&type, f)); - - BadWordType bwtype = BW_ANY; - if (type == 1) - bwtype = BW_SINGLE; - else if (type == 2) - bwtype = BW_START; - else if (type == 3) - bwtype = BW_END; - - if (badwords) - badwords->AddBadWord(ci, buffer, bwtype); - } - } - - if (forbid_chan) - { - if (!forbiddata) - { - delete ci; - continue; - } - - if (ci->GetName().find_first_of("?*") != Anope::string::npos) - { - delete ci; - continue; - } - - ForbidData *d = forbiddata.Create(); - d->SetMask(ci->GetName()); - d->SetCreator(forbidby); - d->SetReason(forbidreason); - d->SetType(FT_CHAN); - delete ci; - continue; - } - - Log(LOG_DEBUG) << "Loaded channel " << ci->GetName(); - } - - close_db(f); -} - -static void LoadOper() -{ - dbFILE *f = open_db_read("OperServ", "oper.db", 13); - if (f == NULL) - return; - - XLineManager *akill, *sqline, *snline, *szline; - akill = sqline = snline = szline = NULL; - - for (XLineManager *xl : XLineManager::XLineManagers) - { - if (xl->Type() == 'G') - akill = xl; - else if (xl->Type() == 'Q') - sqline = xl; - else if (xl->Type() == 'N') - snline = xl; - else if (xl->Type() == 'Z') - szline = xl; - } - - int32_t tmp32; - READ(read_int32(&tmp32, f)); - READ(read_int32(&tmp32, f)); - - int16_t capacity; - read_int16(&capacity, f); // AKill count - for (int16_t i = 0; i < capacity; ++i) - { - Anope::string user, host, by, reason; - int32_t seton, expires; - - READ(read_string(user, f)); - READ(read_string(host, f)); - READ(read_string(by, f)); - READ(read_string(reason, f)); - READ(read_int32(&seton, f)); - READ(read_int32(&expires, f)); - - if (!akill) - continue; - - XLine *x = new XLine(user + "@" + host, by, expires, reason, XLineManager::GenerateUID()); - x->SetCreated(seton); - akill->AddXLine(x); - } - - read_int16(&capacity, f); // SNLines - for (int16_t i = 0; i < capacity; ++i) - { - Anope::string mask, by, reason; - int32_t seton, expires; - - READ(read_string(mask, f)); - READ(read_string(by, f)); - READ(read_string(reason, f)); - READ(read_int32(&seton, f)); - READ(read_int32(&expires, f)); - - if (!snline) - continue; - - XLine *x = new XLine(mask, by, expires, reason, XLineManager::GenerateUID()); - x->SetCreated(seton); - snline->AddXLine(x); - } - - read_int16(&capacity, f); // SQLines - for (int16_t i = 0; i < capacity; ++i) - { - Anope::string mask, by, reason; - int32_t seton, expires; - - READ(read_string(mask, f)); - READ(read_string(by, f)); - READ(read_string(reason, f)); - READ(read_int32(&seton, f)); - READ(read_int32(&expires, f)); - - if (!sqline) - continue; - - XLine *x = new XLine(mask, by, expires, reason, XLineManager::GenerateUID()); - x->SetCreated(seton); - sqline->AddXLine(x); - } - - read_int16(&capacity, f); // SZLines - for (int16_t i = 0; i < capacity; ++i) - { - Anope::string mask, by, reason; - int32_t seton, expires; - - READ(read_string(mask, f)); - READ(read_string(by, f)); - READ(read_string(reason, f)); - READ(read_int32(&seton, f)); - READ(read_int32(&expires, f)); - - if (!szline) - continue; - - XLine *x = new XLine(mask, by, expires, reason, XLineManager::GenerateUID()); - x->SetCreated(seton); - szline->AddXLine(x); - } - - close_db(f); -} - -static void LoadExceptions() -{ - if (!session_service) - return; - - dbFILE *f = open_db_read("OperServ", "exception.db", 9); - if (f == NULL) - return; - - int16_t num; - READ(read_int16(&num, f)); - for (int i = 0; i < num; ++i) - { - Anope::string mask, reason; - int16_t limit; - char who[32]; - int32_t time, expires; - - READ(read_string(mask, f)); - READ(read_int16(&limit, f)); - READ(read_buffer(who, f)); - READ(read_string(reason, f)); - READ(read_int32(&time, f)); - READ(read_int32(&expires, f)); - - if (exception && session_service) - { - Exception *e = exception.Create(); - e->SetMask(mask); - e->SetLimit(limit); - e->SetWho(who); - e->SetTime(time); - e->SetExpires(expires); - e->SetReason(reason); - } - } - - close_db(f); -} - -static void LoadNews() -{ - if (!newsitem) - return; - - dbFILE *f = open_db_read("OperServ", "news.db", 9); - - if (f == NULL) - return; - - int16_t n; - READ(read_int16(&n, f)); - - for (int16_t i = 0; i < n; i++) - { - int16_t type; - NewsItem *ni = newsitem.Create(); - - READ(read_int16(&type, f)); - - switch (type) - { - case OLD_NEWS_LOGON: - ni->SetNewsType(NEWS_LOGON); - break; - case OLD_NEWS_OPER: - ni->SetNewsType(NEWS_OPER); - break; - case OLD_NEWS_RANDOM: - ni->SetNewsType(NEWS_RANDOM); - break; - } - - int32_t unused; - READ(read_int32(&unused, f)); - - Anope::string text; - READ(read_string(text, f)); - ni->SetText(text); - - char who[32]; - READ(read_buffer(who, f)); - ni->SetWho(who); - - int32_t tmp; - READ(read_int32(&tmp, f)); - ni->SetTime(tmp); - } - - close_db(f); -} - -class DBOld : public Module - , public EventHook - , public EventHook -{ - ExtensibleItem mlock_on, mlock_off, mlock_limit; - ExtensibleItem mlock_key; - - public: - DBOld(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, DATABASE | VENDOR) - , mlock_on(this, "mlock_on") - , mlock_off(this, "mlock_off") - , mlock_limit(this, "mlock_limit") - , mlock_key(this, "mlock_key") - { - hashm = Config->GetModule(this)->Get("hash"); - - if (hashm != "md5" && hashm != "oldmd5" && hashm != "sha1" && hashm != "plain" && hashm != "sha256") - throw ModuleException("Invalid hash method"); - } - - EventReturn OnLoadDatabase() override - { - LoadNicks(); - LoadVHosts(); - LoadBots(); - LoadChannels(); - LoadOper(); - LoadExceptions(); - LoadNews(); - - return EVENT_STOP; - } - - void OnUplinkSync(Server *s) override - { - if (!ChanServ::service) - return; - for (auto& it : ChanServ::service->GetChannels()) - { - ChanServ::Channel *ci = it.second; - uint32_t *limit = mlock_limit.Get(ci); - Anope::string *key = mlock_key.Get(ci); - - uint32_t *u = mlock_on.Get(ci); - if (u) - { - process_mlock(ci, *u, true, limit, key); - mlock_on.Unset(ci); - } - - u = mlock_off.Get(ci); - if (u) - { - process_mlock(ci, *u, false, limit, key); - mlock_off.Unset(ci); - } - - mlock_limit.Unset(ci); - mlock_key.Unset(ci); - - if (ci->c) - ci->c->CheckModes(); - } - } -}; - -MODULE_INIT(DBOld) - diff --git a/modules/database/db_redis.cpp b/modules/database/db_redis.cpp deleted file mode 100644 index 72f25a16e..000000000 --- a/modules/database/db_redis.cpp +++ /dev/null @@ -1,434 +0,0 @@ -/* - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * - */ - -#include "module.h" -#include "modules/redis.h" - -using namespace Redis; - -class DatabaseRedis; -static DatabaseRedis *me; - -class TypeLoader : public Interface -{ - Serialize::TypeBase *type; - - public: - TypeLoader(Module *creator, Serialize::TypeBase *t) : Interface(creator), type(t) { } - - void OnResult(const Reply &r) override; -}; - -class ObjectLoader : public Interface -{ - Serialize::Object *obj; - - public: - ObjectLoader(Module *creator, Serialize::Object *s) : Interface(creator), obj(s) { } - - void OnResult(const Reply &r) override; -}; - -class FieldLoader : public Interface -{ - Serialize::Object *obj; - Serialize::FieldBase *field; - - public: - FieldLoader(Module *creator, Serialize::Object *o, Serialize::FieldBase *f) : Interface(creator), obj(o), field(f) { } - - void OnResult(const Reply &) override; -}; - -class SubscriptionListener : public Interface -{ - public: - SubscriptionListener(Module *creator) : Interface(creator) { } - - void OnResult(const Reply &r) override; -}; - -class DatabaseRedis : public Module - , public EventHook - , public EventHook -{ - SubscriptionListener sl; - - public: - ServiceReference redis; - - DatabaseRedis(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, DATABASE | VENDOR) - , sl(this) - { - me = this; - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - this->redis = ServiceReference("Redis::Provider", block->Get("engine", "redis/main")); - } - - EventReturn OnLoadDatabase() override - { - if (!redis) - return EVENT_STOP; - - const std::map &types = Serialize::TypeBase::GetTypes(); - for (const std::pair &p : types) - this->OnSerializeTypeCreate(p.second); - - while (redis->BlockAndProcess()); - - redis->Subscribe(&this->sl, "anope"); - - return EVENT_STOP; - } - - void OnSerializeTypeCreate(Serialize::TypeBase *sb) - { - std::vector args = { "SMEMBERS", "ids:" + sb->GetName() }; - - redis->SendCommand(new TypeLoader(this, sb), args); - } - - EventReturn OnSerializeList(Serialize::TypeBase *type, std::vector &ids) override - { - return EVENT_CONTINUE; - } - - EventReturn OnSerializeFind(Serialize::TypeBase *type, Serialize::FieldBase *field, const Anope::string &value, Serialize::ID &id) override - { - return EVENT_CONTINUE; - } - - EventReturn OnSerializeGet(Serialize::Object *object, Serialize::FieldBase *field, Anope::string &value) override - { - return EVENT_CONTINUE; - } - - EventReturn OnSerializeGetRefs(Serialize::Object *object, Serialize::TypeBase *type, std::vector &) override - { - return EVENT_CONTINUE; - } - - EventReturn OnSerializeDeref(Serialize::ID id, Serialize::TypeBase *type) override - { - return EVENT_CONTINUE; - } - - EventReturn OnSerializeGetSerializable(Serialize::Object *object, Serialize::FieldBase *field, Anope::string &type, Serialize::ID &value) override - { - return EVENT_CONTINUE; - } - - EventReturn OnSerializeSet(Serialize::Object *object, Serialize::FieldBase *field, const Anope::string &value) override - { - std::vector args; - - redis->StartTransaction(); - - const Anope::string &old = field->SerializeToString(object); - args = { "SREM", "lookup:" + object->GetSerializableType()->GetName() + ":" + field->GetName() + ":" + old, stringify(object->id) }; - redis->SendCommand(nullptr, args); - - // add object to type set - args = { "SADD", "ids:" + object->GetSerializableType()->GetName(), stringify(object->id) }; - redis->SendCommand(nullptr, args); - - // add key to key set - args = { "SADD", "keys:" + stringify(object->id), field->GetName() }; - redis->SendCommand(nullptr, args); - - // set value - args = { "SET", "values:" + stringify(object->id) + ":" + field->GetName(), value }; - redis->SendCommand(nullptr, args); - - // lookup - args = { "SADD", "lookup:" + object->GetSerializableType()->GetName() + ":" + field->GetName() + ":" + value, stringify(object->id) }; - redis->SendCommand(nullptr, args); - - redis->CommitTransaction(); - - return EVENT_CONTINUE; - } - - EventReturn OnSerializeSetSerializable(Serialize::Object *object, Serialize::FieldBase *field, Serialize::Object *value) override - { - return OnSerializeSet(object, field, stringify(value->id)); - } - - EventReturn OnSerializeUnset(Serialize::Object *object, Serialize::FieldBase *field) override - { - std::vector args; - - redis->StartTransaction(); - - const Anope::string &old = field->SerializeToString(object); - args = { "SREM", "lookup:" + object->GetSerializableType()->GetName() + ":" + field->GetName() + ":" + old, stringify(object->id) }; - redis->SendCommand(nullptr, args); - - // remove field from set - args = { "SREM", "keys:" + stringify(object->id), field->GetName() }; - redis->SendCommand(nullptr, args); - - redis->CommitTransaction(); - - return EVENT_CONTINUE; - } - - EventReturn OnSerializeUnsetSerializable(Serialize::Object *object, Serialize::FieldBase *field) override - { - return OnSerializeUnset(object, field); - } - - EventReturn OnSerializeHasField(Serialize::Object *object, Serialize::FieldBase *field) override - { - return EVENT_CONTINUE; - } - - EventReturn OnSerializableGetId(Serialize::ID &id) override - { - std::vector args = { "INCR", "id" }; - - auto f = [&](const Reply &r) - { - id = r.i; - }; - - FInterface inter(this, f); - redis->SendCommand(&inter, args); - while (redis->BlockAndProcess()); - return EVENT_ALLOW; - } - - void OnSerializableCreate(Serialize::Object *) override - { - } - - void OnSerializableDelete(Serialize::Object *obj) override - { - std::vector args; - - redis->StartTransaction(); - - for (Serialize::FieldBase *field : obj->GetSerializableType()->fields) - { - Anope::string value = field->SerializeToString(obj); - - args = { "SREM", "lookup:" + obj->GetSerializableType()->GetName() + ":" + field->GetName() + ":" + value, stringify(obj->id) }; - redis->SendCommand(nullptr, args); - - args = { "DEL", "values:" + stringify(obj->id) + ":" + field->GetName() }; - redis->SendCommand(nullptr, args); - - args = { "SREM", "keys:" + stringify(obj->id), field->GetName() }; - redis->SendCommand(nullptr, args); - } - - args = { "SREM", "ids:" + obj->GetSerializableType()->GetName(), stringify(obj->id) }; - redis->SendCommand(nullptr, args); - - redis->CommitTransaction(); - } -}; - -void TypeLoader::OnResult(const Reply &r) -{ - if (r.type != Reply::MULTI_BULK || !me->redis) - { - delete this; - return; - } - - for (unsigned i = 0; i < r.multi_bulk.size(); ++i) - { - const Reply *reply = r.multi_bulk[i]; - - if (reply->type != Reply::BULK) - continue; - - int64_t id; - try - { - id = convertTo(reply->bulk); - } - catch (const ConvertException &) - { - continue; - } - - Serialize::Object *obj = type->Require(id); - if (obj == nullptr) - { - Log(LOG_DEBUG) << "redis: Unable to require object #" << id << " of type " << type->GetName(); - continue; - } - - std::vector args = { "SMEMBERS", "keys:" + stringify(id) }; - - me->redis->SendCommand(new ObjectLoader(me, obj), args); - } - - delete this; -} - -void ObjectLoader::OnResult(const Reply &r) -{ - if (r.type != Reply::MULTI_BULK || r.multi_bulk.empty() || !me->redis) - { - delete this; - return; - } - - Serialize::TypeBase *type = obj->GetSerializableType(); - - for (Reply *reply : r.multi_bulk) - { - const Anope::string &key = reply->bulk; - Serialize::FieldBase *field = type->GetField(key); - - if (field == nullptr) - continue; - - std::vector args = { "GET", "values:" + stringify(obj->id) + ":" + key }; - - me->redis->SendCommand(new FieldLoader(me, obj, field), args); - } - - delete this; -} - -void FieldLoader::OnResult(const Reply &r) -{ - Log(LOG_DEBUG_2) << "redis: Setting field " << field->GetName() << " of object #" << obj->id << " of type " << obj->GetSerializableType()->GetName() << " to " << r.bulk; - field->UnserializeFromString(obj, r.bulk); - - delete this; -} - -void SubscriptionListener::OnResult(const Reply &r) -{ - /* - * message - * anope - * message - * - * set 4 email adam@anope.org - * unset 4 email - * create 4 NickCore - * delete 4 - */ - - const Anope::string &message = r.multi_bulk[2]->bulk; - Anope::string command; - spacesepstream sep(message); - - sep.GetToken(command); - - if (command == "set" || command == "unset") - { - Anope::string sid, key, value; - - sep.GetToken(sid); - sep.GetToken(key); - value = sep.GetRemaining(); - - Serialize::ID id; - try - { - id = convertTo(sid); - } - catch (const ConvertException &ex) - { - Log(LOG_DEBUG) << "redis: unable to get id for SL update key " << sid; - return; - } - - Serialize::Object *obj = Serialize::GetID(id); - if (obj == nullptr) - { - Log(LOG_DEBUG) << "redis: pmessage for unknown object #" << id; - return; - } - - Serialize::FieldBase *field = obj->GetSerializableType()->GetField(key); - if (field == nullptr) - { - Log(LOG_DEBUG) << "redis: pmessage for unknown field of object #" << id << ": " << key; - return; - } - - Log(LOG_DEBUG_2) << "redis: Setting field " << field->GetName() << " of object #" << obj->id << " of type " << obj->GetSerializableType()->GetName() << " to " << value; - field->UnserializeFromString(obj, value); - } - else if (command == "create") - { - Anope::string sid, stype; - - sep.GetToken(sid); - sep.GetToken(stype); - - Serialize::ID id; - try - { - id = convertTo(sid); - } - catch (const ConvertException &ex) - { - Log(LOG_DEBUG) << "redis: unable to get id for SL update key " << sid; - return; - } - - Serialize::TypeBase *type = Serialize::TypeBase::Find(stype); - if (type == nullptr) - { - Log(LOG_DEBUG) << "redis: pmessage create for nonexistant type " << stype; - return; - } - - Serialize::Object *obj = type->Require(id); - if (obj == nullptr) - { - Log(LOG_DEBUG) << "redis: require for pmessage create type " << type->GetName() << " id #" << id << " returned nullptr"; - return; - } - } - else if (command == "delete") - { - Anope::string sid; - - sep.GetToken(sid); - - Serialize::ID id; - try - { - id = convertTo(sid); - } - catch (const ConvertException &ex) - { - Log(LOG_DEBUG) << "redis: unable to get id for SL update key " << sid; - return; - } - - Serialize::Object *obj = Serialize::GetID(id); - if (obj == nullptr) - { - Log(LOG_DEBUG) << "redis: message for unknown object #" << id; - return; - } - - obj->Delete(); - } - else - Log(LOG_DEBUG) << "redis: unknown message: " << message; -} - -MODULE_INIT(DatabaseRedis) diff --git a/modules/database/db_sql.cpp b/modules/database/db_sql.cpp deleted file mode 100644 index fed0050e4..000000000 --- a/modules/database/db_sql.cpp +++ /dev/null @@ -1,372 +0,0 @@ -#include "module.h" -#include "modules/sql.h" - -using namespace SQL; - -class DBMySQL : public Module, public Pipe - , public EventHook -{ - private: - bool transaction = false; - bool inited = false; - Anope::string prefix; - ServiceReference SQL; - - Result Run(const Query &query) - { - if (!SQL) - ;//XXX - - if (!inited) - { - inited = true; - for (const Query &q : SQL->InitSchema(prefix)) - SQL->RunQuery(q); - } - - return SQL->RunQuery(query); - } - - void StartTransaction() - { - if (!SQL || transaction) - return; - - Run(SQL->BeginTransaction()); - - transaction = true; - Notify(); - } - - void Commit() - { - if (!SQL || !transaction) - return; - - Run(SQL->Commit()); - - transaction = false; - } - - public: - DBMySQL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, DATABASE | VENDOR) - { - } - - void OnNotify() override - { - Commit(); - Serialize::Clear(); - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - this->SQL = ServiceReference("SQL::Provider", block->Get("engine")); - this->prefix = block->Get("prefix", "anope_db_"); - inited = false; - } - - EventReturn OnSerializeList(Serialize::TypeBase *type, std::vector &ids) override - { - StartTransaction(); - - ids.clear(); - - Query query = "SELECT `id` FROM `" + prefix + type->GetName() + "`"; - Result res = Run(query); - for (int i = 0; i < res.Rows(); ++i) - { - Serialize::ID id = convertTo(res.Get(i, "id")); - ids.push_back(id); - } - - return EVENT_ALLOW; - } - - EventReturn OnSerializeFind(Serialize::TypeBase *type, Serialize::FieldBase *field, const Anope::string &value, Serialize::ID &id) override - { - if (!SQL) - return EVENT_CONTINUE; - - StartTransaction(); - - for (Query &q : SQL->CreateTable(prefix, type->GetName())) - Run(q); - - for (Query &q : SQL->AlterTable(prefix, type->GetName(), field->GetName(), false)) - Run(q); - - for (const Query &q : SQL->CreateIndex(prefix + type->GetName(), field->GetName())) - Run(q); - - Query query("SELECT `id` FROM `" + prefix + type->GetName() + "` WHERE `" + field->GetName() + "` = @value@"); - query.SetValue("value", value); - Result res = Run(query); - if (res.Rows()) - try - { - id = convertTo(res.Get(0, "id")); - return EVENT_ALLOW; - } - catch (const ConvertException &) - { - } - return EVENT_CONTINUE; - } - - private: - bool GetValue(Serialize::Object *object, Serialize::FieldBase *field, SQL::Result::Value &v) - { - StartTransaction(); - - Query query = "SELECT `" + field->GetName() + "` FROM `" + prefix + object->GetSerializableType()->GetName() + "` WHERE `id` = @id@"; - query.SetValue("id", object->id); - Result res = Run(query); - - if (res.Rows() == 0) - return false; - - v = res.GetValue(0, field->GetName()); - return true; - } - - public: - EventReturn OnSerializeGet(Serialize::Object *object, Serialize::FieldBase *field, Anope::string &value) override - { - SQL::Result::Value v; - - if (!GetValue(object, field, v)) - { - field->CacheMiss(object); - return EVENT_CONTINUE; - } - - value = v.value; - return EVENT_ALLOW; - } - - EventReturn OnSerializeGetRefs(Serialize::Object *object, Serialize::TypeBase *type, std::vector &edges) override - { - StartTransaction(); - - edges.clear(); - - Query query; - if (type) - query = "SELECT field," + prefix + "edges.id,other_id,j1.type,j2.type AS other_type FROM `" + prefix + "edges` " - "JOIN `" + prefix + "objects` AS j1 ON " + prefix + "edges.id = j1.id " - "JOIN `" + prefix + "objects` AS j2 ON " + prefix + "edges.other_id = j2.id " - "WHERE " - " (" + prefix + "edges.id = @id@ AND j2.type = @other_type@) " - "OR" - " (other_id = @id@ AND j1.type = @other_type@)"; - else - query = "SELECT field," + prefix + "edges.id,other_id,j1.type,j2.type AS other_type FROM `" + prefix + "edges` " - "JOIN `" + prefix + "objects` AS j1 ON " + prefix + "edges.id = j1.id " - "JOIN `" + prefix + "objects` AS j2 ON " + prefix + "edges.other_id = j2.id " - "WHERE " + prefix + "edges.id = @id@ OR other_id = @id@"; - - query.SetValue("type", object->GetSerializableType()->GetName()); - query.SetValue("id", object->id); - if (type) - query.SetValue("other_type", type->GetName()); - - Result res = Run(query); - for (int i = 0; i < res.Rows(); ++i) - { - Serialize::ID id = convertTo(res.Get(i, "id")); - - if (id == object->id) - { - // we want other type, this is my edge - Anope::string t = res.Get(i, "other_type"); - Anope::string f = res.Get(i, "field"); - id = convertTo(res.Get(i, "other_id")); - - //XXX sanity checks - Serialize::FieldBase *obj_field = object->GetSerializableType()->GetField(f); - - Serialize::TypeBase *obj_type = Serialize::TypeBase::Find(t); - Serialize::Object *other = obj_type->Require(id); - - edges.emplace_back(other, obj_field, true); - } - else - { - // edge to me - Anope::string t = res.Get(i, "type"); - Anope::string f = res.Get(i, "field"); - - //XXX sanity checks - Serialize::TypeBase *obj_type = Serialize::TypeBase::Find(t); - Serialize::FieldBase *obj_field = obj_type->GetField(f); - Serialize::Object *other = obj_type->Require(id); - - // other type, other field, - edges.emplace_back(other, obj_field, false); - } - } - - return EVENT_ALLOW; - } - - EventReturn OnSerializeDeref(Serialize::ID id, Serialize::TypeBase *type) override - { - StartTransaction(); - - Query query = "SELECT `id` FROM `" + prefix + type->GetName() + "` WHERE `id` = @id@"; - query.SetValue("id", id); - Result res = Run(query); - if (res.Rows() == 0) - return EVENT_CONTINUE; - return EVENT_ALLOW; - } - - EventReturn OnSerializeGetSerializable(Serialize::Object *object, Serialize::FieldBase *field, Anope::string &type, Serialize::ID &value) override - { - StartTransaction(); - - Query query = "SELECT `" + field->GetName() + "`,j1.type AS " + field->GetName() + "_type FROM `" + prefix + object->GetSerializableType()->GetName() + "` " - "JOIN `" + prefix + "objects` AS j1 ON " + prefix + object->GetSerializableType()->GetName() + "." + field->GetName() + " = j1.id " - "WHERE " + prefix + object->GetSerializableType()->GetName() + ".id = @id@"; - query.SetValue("id", object->id); - Result res = Run(query); - - if (res.Rows() == 0) - return EVENT_CONTINUE; - - type = res.Get(0, field->GetName() + "_type"); - try - { - value = convertTo(res.Get(0, field->GetName())); - } - catch (const ConvertException &ex) - { - return EVENT_STOP; - } - - return EVENT_ALLOW; - } - - private: - void DoSet(Serialize::Object *object, Serialize::FieldBase *field, bool is_object, const Anope::string *value) - { - if (!SQL) - return; - - StartTransaction(); - - for (Query &q : SQL->CreateTable(prefix, object->GetSerializableType()->GetName())) - Run(q); - - for (Query &q : SQL->AlterTable(prefix, object->GetSerializableType()->GetName(), field->GetName(), is_object)) - Run(q); - - Query q; - q.SetValue("id", object->id); - if (value) - q.SetValue(field->GetName(), *value); - else - q.SetNull(field->GetName()); - - for (Query &q2 : SQL->Replace(prefix + object->GetSerializableType()->GetName(), q, { "id" })) - Run(q2); - } - - public: - EventReturn OnSerializeSet(Serialize::Object *object, Serialize::FieldBase *field, const Anope::string &value) override - { - DoSet(object, field, false, &value); - return EVENT_STOP; - } - - EventReturn OnSerializeSetSerializable(Serialize::Object *object, Serialize::FieldBase *field, Serialize::Object *value) override - { - if (!SQL) - return EVENT_CONTINUE; - - StartTransaction(); - - if (value) - { - Anope::string v = stringify(value->id); - DoSet(object, field, true, &v); - - Query query; - query.SetValue("field", field->GetName()); - query.SetValue("id", object->id); - query.SetValue("other_id", value->id); - - for (Query &q : SQL->Replace(prefix + "edges", query, { "id", "field" })) - Run(q); - } - else - { - DoSet(object, field, true, nullptr); - - Query query("DELETE FROM `" + prefix + "edges` WHERE `id` = @id@ AND `field` = @field@"); - query.SetValue("id", object->id); - query.SetValue("field", field->GetName()); - Run(query); - } - - return EVENT_STOP; - } - - EventReturn OnSerializeUnset(Serialize::Object *object, Serialize::FieldBase *field) override - { - DoSet(object, field, false, nullptr); - field->CacheMiss(object); - return EVENT_STOP; - } - - EventReturn OnSerializeUnsetSerializable(Serialize::Object *object, Serialize::FieldBase *field) override - { - DoSet(object, field, true, nullptr); - field->CacheMiss(object); - - Query query("DELETE FROM `" + prefix + "edges` WHERE `id` = @id@ AND `field` = @field@"); - query.SetValue("id", object->id); - query.SetValue("field", field->GetName()); - Run(query); - - return EVENT_STOP; - } - - EventReturn OnSerializeHasField(Serialize::Object *object, Serialize::FieldBase *field) override - { - SQL::Result::Value v; - - return GetValue(object, field, v) && !v.null ? EVENT_STOP : EVENT_CONTINUE; - } - - EventReturn OnSerializableGetId(Serialize::ID &id) override - { - StartTransaction(); - - id = SQL->GetID(prefix); - return EVENT_ALLOW; - } - - void OnSerializableCreate(Serialize::Object *object) override - { - StartTransaction(); - - Query q = Query("INSERT INTO `" + prefix + "objects` (`id`,`type`) VALUES (@id@, @type@)"); - q.SetValue("id", object->id); - q.SetValue("type", object->GetSerializableType()->GetName()); - Run(q); - } - - void OnSerializableDelete(Serialize::Object *object) override - { - StartTransaction(); - - Query query("DELETE FROM `" + prefix + object->GetSerializableType()->GetName() + "` WHERE `id` = " + stringify(object->id)); - Run(query); - } -}; - -MODULE_INIT(DBMySQL) - diff --git a/modules/database/flatfile.cpp b/modules/database/flatfile.cpp new file mode 100644 index 000000000..122134960 --- /dev/null +++ b/modules/database/flatfile.cpp @@ -0,0 +1,206 @@ +/* + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class DBFlatFile : public Module + , public EventHook + , public EventHook +{ + /* Day the last backup was on */ + int last_day; + /* Backup file names */ + std::map > backups; + bool loaded; + + void BackupDatabase() + { + tm *tm = localtime(&Anope::CurTime); + + if (tm->tm_mday != last_day) + { + last_day = tm->tm_mday; + + const std::map &types = Serialize::TypeBase::GetTypes(); + + std::set dbs; + dbs.insert(Config->GetModule(this)->Get("database", "anope.db")); + + for (const std::pair &p : types) + { + Serialize::TypeBase *stype = p.second; + + if (stype->GetOwner()) + dbs.insert("module_" + stype->GetOwner()->name + ".db"); + } + + + for (std::set::const_iterator it = dbs.begin(), it_end = dbs.end(); it != it_end; ++it) + { + const Anope::string &oldname = Anope::DataDir + "/" + *it; + Anope::string newname = Anope::DataDir + "/backups/" + *it + "-" + stringify(tm->tm_year + 1900) + Anope::printf("-%02i-", tm->tm_mon + 1) + Anope::printf("%02i", tm->tm_mday); + + /* Backup already exists or no database to backup */ + if (Anope::IsFile(newname) || !Anope::IsFile(oldname)) + continue; + + Log(LOG_DEBUG) << "db_flatfile: Attempting to rename " << *it << " to " << newname; + if (rename(oldname.c_str(), newname.c_str())) + { + Anope::string err = Anope::LastError(); + Log(this) << "Unable to back up database " << *it << " (" << err << ")!"; + + if (!Config->GetModule(this)->Get("nobackupokay")) + { + Anope::Quitting = true; + Anope::QuitReason = "Unable to back up database " + *it + " (" + err + ")"; + } + + continue; + } + + backups[*it].push_back(newname); + + unsigned keepbackups = Config->GetModule(this)->Get("keepbackups"); + if (keepbackups > 0 && backups[*it].size() > keepbackups) + { + unlink(backups[*it].front().c_str()); + backups[*it].pop_front(); + } + } + } + } + + public: + DBFlatFile(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, DATABASE | VENDOR) + , last_day(0) + , loaded(false) + { + + } + + EventReturn OnLoadDatabase() override + { + const Anope::string &db_name = Anope::DataDir + "/" + Config->GetModule(this)->Get("database", "anope.db"); + + std::fstream fd(db_name.c_str(), std::ios_base::in | std::ios_base::binary); + if (!fd.is_open()) + { + Log(this) << "Unable to open " << db_name << " for reading!"; + return EVENT_STOP; + } + + Serialize::TypeBase *type = nullptr; + Serialize::Object *obj = nullptr; + for (Anope::string buf; std::getline(fd, buf.str());) + { + if (buf.find("OBJECT ") == 0) + { + Anope::string t = buf.substr(7); + if (obj) + Log(LOG_DEBUG) << "obj != null but got OBJECT"; + if (type) + Log(LOG_DEBUG) << "type != null but got OBJECT"; + type = Serialize::TypeBase::Find(t); + obj = nullptr; + } + else if (buf.find("ID ") == 0) + { + if (!type || obj) + continue; + + try + { + Serialize::ID id = convertTo(buf.substr(3)); + obj = type->Require(id); + } + catch (const ConvertException &) + { + Log(LOG_DEBUG) << "Unable to parse object id " << buf.substr(3); + } + } + else if (buf.find("DATA ") == 0) + { + if (!type) + continue; + + if (!obj) + obj = type->Create(); + + size_t sp = buf.find(' ', 5); // Skip DATA + if (sp == Anope::string::npos) + continue; + + Anope::string key = buf.substr(5, sp - 5), value = buf.substr(sp + 1); + + Serialize::FieldBase *field = type->GetField(key); + if (field) + field->UnserializeFromString(obj, value); + } + else if (buf.find("END") == 0) + { + type = nullptr; + obj = nullptr; + } + } + + fd.close(); + + loaded = true; + return EVENT_STOP; + } + + + void OnSaveDatabase() override + { + BackupDatabase(); + + Anope::string db_name = Anope::DataDir + "/" + Config->GetModule(this)->Get("database", "anope.db"); + + if (Anope::IsFile(db_name)) + rename(db_name.c_str(), (db_name + ".tmp").c_str()); + + std::fstream f(db_name.c_str(), std::ios_base::out | std::ios_base::trunc | std::ios_base::binary); + + if (!f.is_open()) + { + Log(this) << "Unable to open " << db_name << " for writing"; + } + else + { + for (std::pair p : Serialize::objects) + { + Serialize::Object *object = p.second; + Serialize::TypeBase *s_type = object->GetSerializableType(); + + f << "OBJECT " << s_type->GetName() << "\n"; + f << "ID " << object->id << "\n"; + for (Serialize::FieldBase *field : s_type->fields) + f << "DATA " << field->GetName() << " " << field->SerializeToString(object) << "\n"; + f << "END\n"; + } + } + + if (!f.is_open() || !f.good()) + { + f.close(); + rename((db_name + ".tmp").c_str(), db_name.c_str()); + } + else + { + f.close(); + unlink((db_name + ".tmp").c_str()); + } + } +}; + +MODULE_INIT(DBFlatFile) + + diff --git a/modules/database/old.cpp b/modules/database/old.cpp new file mode 100644 index 000000000..3d9b92922 --- /dev/null +++ b/modules/database/old.cpp @@ -0,0 +1,1428 @@ +/* + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/os_session.h" +#include "modules/bs_kick.h" +#include "modules/cs_mode.h" +#include "modules/bs_badwords.h" +#include "modules/os_news.h" +#include "modules/os_forbid.h" +#include "modules/cs_entrymsg.h" +#include "modules/ns_suspend.h" +#include "modules/cs_suspend.h" +#include "modules/cs_access.h" +#include "modules/ns_access.h" + +#define READ(x) \ +if (true) \ +{ \ + if ((x) < 0) \ + printf("Error, the database is broken, line %d, trying to continue... no guarantee.\n", __LINE__); \ +} \ +else \ + static_cast(0) + +#define getc_db(f) (fgetc((f)->fp)) +#define read_db(f, buf, len) (fread((buf), 1, (len), (f)->fp)) +#define read_buffer(buf, f) (read_db((f), (buf), sizeof(buf)) == sizeof(buf)) + +#define OLD_BI_PRIVATE 0x0001 + +#define OLD_NI_KILLPROTECT 0x00000001 /* Kill others who take this nick */ +#define OLD_NI_SECURE 0x00000002 /* Don't recognize unless IDENTIFY'd */ +#define OLD_NI_MSG 0x00000004 /* Use PRIVMSGs instead of NOTICEs */ +#define OLD_NI_MEMO_HARDMAX 0x00000008 /* Don't allow user to change memo limit */ +#define OLD_NI_MEMO_SIGNON 0x00000010 /* Notify of memos at signon and un-away */ +#define OLD_NI_MEMO_RECEIVE 0x00000020 /* Notify of new memos when sent */ +#define OLD_NI_PRIVATE 0x00000040 /* Don't show in LIST to non-servadmins */ +#define OLD_NI_HIDE_EMAIL 0x00000080 /* Don't show E-mail in INFO */ +#define OLD_NI_HIDE_MASK 0x00000100 /* Don't show last seen address in INFO */ +#define OLD_NI_HIDE_QUIT 0x00000200 /* Don't show last quit message in INFO */ +#define OLD_NI_KILL_QUICK 0x00000400 /* Kill in 20 seconds instead of 60 */ +#define OLD_NI_KILL_IMMED 0x00000800 /* Kill immediately instead of in 60 sec */ +#define OLD_NI_MEMO_MAIL 0x00010000 /* User gets email on memo */ +#define OLD_NI_HIDE_STATUS 0x00020000 /* Don't show services access status */ +#define OLD_NI_SUSPENDED 0x00040000 /* Nickname is suspended */ +#define OLD_NI_AUTOOP 0x00080000 /* Autoop nickname in channels */ + +#define OLD_NS_NO_EXPIRE 0x0004 /* nick won't expire */ +#define OLD_NS_VERBOTEN 0x0002 + +#define OLD_CI_KEEPTOPIC 0x00000001 +#define OLD_CI_SECUREOPS 0x00000002 +#define OLD_CI_PRIVATE 0x00000004 +#define OLD_CI_TOPICLOCK 0x00000008 +#define OLD_CI_RESTRICTED 0x00000010 +#define OLD_CI_PEACE 0x00000020 +#define OLD_CI_SECURE 0x00000040 +#define OLD_CI_VERBOTEN 0x00000080 +#define OLD_CI_ENCRYPTEDPW 0x00000100 +#define OLD_CI_NO_EXPIRE 0x00000200 +#define OLD_CI_MEMO_HARDMAX 0x00000400 +#define OLD_CI_OPNOTICE 0x00000800 +#define OLD_CI_SECUREFOUNDER 0x00001000 +#define OLD_CI_SIGNKICK 0x00002000 +#define OLD_CI_SIGNKICK_LEVEL 0x00004000 +#define OLD_CI_XOP 0x00008000 +#define OLD_CI_SUSPENDED 0x00010000 + +/* BotServ SET flags */ +#define OLD_BS_DONTKICKOPS 0x00000001 +#define OLD_BS_DONTKICKVOICES 0x00000002 +#define OLD_BS_FANTASY 0x00000004 +#define OLD_BS_SYMBIOSIS 0x00000008 +#define OLD_BS_GREET 0x00000010 +#define OLD_BS_NOBOT 0x00000020 + +/* BotServ Kickers flags */ +#define OLD_BS_KICK_BOLDS 0x80000000 +#define OLD_BS_KICK_COLORS 0x40000000 +#define OLD_BS_KICK_REVERSES 0x20000000 +#define OLD_BS_KICK_UNDERLINES 0x10000000 +#define OLD_BS_KICK_BADWORDS 0x08000000 +#define OLD_BS_KICK_CAPS 0x04000000 +#define OLD_BS_KICK_FLOOD 0x02000000 +#define OLD_BS_KICK_REPEAT 0x01000000 + +#define OLD_NEWS_LOGON 0 +#define OLD_NEWS_OPER 1 +#define OLD_NEWS_RANDOM 2 + +enum +{ + TTB_BOLDS, + TTB_COLORS, + TTB_REVERSES, + TTB_UNDERLINES, + TTB_BADWORDS, + TTB_CAPS, + TTB_FLOOD, + TTB_REPEAT, +}; + +static struct mlock_info +{ + char c; + uint32_t m; +} mlock_infos[] = { + {'i', 0x00000001}, + {'m', 0x00000002}, + {'n', 0x00000004}, + {'p', 0x00000008}, + {'s', 0x00000010}, + {'t', 0x00000020}, + {'R', 0x00000100}, + {'r', 0x00000200}, + {'c', 0x00000400}, + {'A', 0x00000800}, + {'K', 0x00002000}, + {'O', 0x00008000}, + {'Q', 0x00010000}, + {'S', 0x00020000}, + {'G', 0x00100000}, + {'C', 0x00200000}, + {'u', 0x00400000}, + {'z', 0x00800000}, + {'N', 0x01000000}, + {'M', 0x04000000} +}; + +static Anope::string hashm; + +enum +{ + LANG_EN_US, /* United States English */ + LANG_JA_JIS, /* Japanese (JIS encoding) */ + LANG_JA_EUC, /* Japanese (EUC encoding) */ + LANG_JA_SJIS, /* Japanese (SJIS encoding) */ + LANG_ES, /* Spanish */ + LANG_PT, /* Portugese */ + LANG_FR, /* French */ + LANG_TR, /* Turkish */ + LANG_IT, /* Italian */ + LANG_DE, /* German */ + LANG_CAT, /* Catalan */ + LANG_GR, /* Greek */ + LANG_NL, /* Dutch */ + LANG_RU, /* Russian */ + LANG_HUN, /* Hungarian */ + LANG_PL /* Polish */ +}; + +static void process_mlock(ChanServ::Channel *ci, uint32_t lock, bool status, uint32_t *limit, Anope::string *key) +{ + if (!mlocks) + return; + + for (unsigned i = 0; i < (sizeof(mlock_infos) / sizeof(mlock_info)); ++i) + if (lock & mlock_infos[i].m) + { + ChannelMode *cm = ModeManager::FindChannelModeByChar(mlock_infos[i].c); + if (cm) + { + if (limit && mlock_infos[i].c == 'l') + mlocks->SetMLock(ci, cm, status, stringify(*limit)); + else if (key && mlock_infos[i].c == 'k') + mlocks->SetMLock(ci, cm, status, *key); + else + mlocks->SetMLock(ci, cm, status); + } + } +} + +static const char Base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +static void my_b64_encode(const Anope::string &src, Anope::string &target) +{ + size_t src_pos = 0, src_len = src.length(); + unsigned char input[3]; + + target.clear(); + + while (src_len - src_pos > 2) + { + input[0] = src[src_pos++]; + input[1] = src[src_pos++]; + input[2] = src[src_pos++]; + + target += Base64[input[0] >> 2]; + target += Base64[((input[0] & 0x03) << 4) + (input[1] >> 4)]; + target += Base64[((input[1] & 0x0f) << 2) + (input[2] >> 6)]; + target += Base64[input[2] & 0x3f]; + } + + /* Now we worry about padding */ + if (src_pos != src_len) + { + input[0] = input[1] = input[2] = 0; + for (size_t i = 0; i < src_len - src_pos; ++i) + input[i] = src[src_pos + i]; + + target += Base64[input[0] >> 2]; + target += Base64[((input[0] & 0x03) << 4) + (input[1] >> 4)]; + if (src_pos == src_len - 1) + target += Pad64; + else + target += Base64[((input[1] & 0x0f) << 2) + (input[2] >> 6)]; + target += Pad64; + } +} + +static Anope::string Hex(const char *data, size_t l) +{ + const char hextable[] = "0123456789abcdef"; + + std::string rv; + for (size_t i = 0; i < l; ++i) + { + unsigned char c = data[i]; + rv += hextable[c >> 4]; + rv += hextable[c & 0xF]; + } + return rv; +} + +static Anope::string GetLevelName(int level) +{ + switch (level) + { + case 0: + return "INVITE"; + case 1: + return "AKICK"; + case 2: + return "SET"; + case 3: + return "UNBAN"; + case 4: + return "AUTOOP"; + case 5: + return "AUTODEOP"; + case 6: + return "AUTOVOICE"; + case 7: + return "OP"; + case 8: + return "ACCESS_LIST"; + case 9: + return "CLEAR"; + case 10: + return "NOJOIN"; + case 11: + return "ACCESS_CHANGE"; + case 12: + return "MEMO"; + case 13: + return "ASSIGN"; + case 14: + return "BADWORDS"; + case 15: + return "NOKICK"; + case 16: + return "FANTASIA"; + case 17: + return "SAY"; + case 18: + return "GREET"; + case 19: + return "VOICEME"; + case 20: + return "VOICE"; + case 21: + return "GETKEY"; + case 22: + return "AUTOHALFOP"; + case 23: + return "AUTOPROTECT"; + case 24: + return "OPME"; + case 25: + return "HALFOPME"; + case 26: + return "HALFOP"; + case 27: + return "PROTECTME"; + case 28: + return "PROTECT"; + case 29: + return "KICKME"; + case 30: + return "KICK"; + case 31: + return "SIGNKICK"; + case 32: + return "BANME"; + case 33: + return "BAN"; + case 34: + return "TOPIC"; + case 35: + return "INFO"; + default: + return "INVALID"; + } +} + +static char *strscpy(char *d, const char *s, size_t len) +{ + char *d_orig = d; + + if (!len) + return d; + while (--len && (*d++ = *s++)); + *d = '\0'; + return d_orig; +} + +struct dbFILE +{ + int mode; /* 'r' for reading, 'w' for writing */ + FILE *fp; /* The normal file descriptor */ + char filename[1024]; /* Name of the database file */ +}; + +static dbFILE *open_db_read(const char *service, const char *filename, int version) +{ + dbFILE *f; + FILE *fp; + int myversion; + + f = new dbFILE; + strscpy(f->filename, (Anope::DataDir + "/" + filename).c_str(), sizeof(f->filename)); + f->mode = 'r'; + fp = fopen(f->filename, "rb"); + if (!fp) + { + Log() << "Can't read " << service << " database " << f->filename; + delete f; + return NULL; + } + f->fp = fp; + myversion = fgetc(fp) << 24 | fgetc(fp) << 16 | fgetc(fp) << 8 | fgetc(fp); + if (feof(fp)) + { + Log() << "Error reading version number on " << f->filename << ": End of file detected."; + delete f; + return NULL; + } + else if (myversion < version) + { + Log() << "Unsuported database version (" << myversion << ") on " << f->filename << "."; + delete f; + return NULL; + } + return f; +} + +void close_db(dbFILE *f) +{ + fclose(f->fp); + delete f; +} + +static int read_int16(int16_t *ret, dbFILE *f) +{ + int c1, c2; + + *ret = 0; + + c1 = fgetc(f->fp); + c2 = fgetc(f->fp); + if (c1 == EOF || c2 == EOF) + return -1; + *ret = c1 << 8 | c2; + return 0; +} + +static int read_uint16(uint16_t *ret, dbFILE *f) +{ + int c1, c2; + + *ret = 0; + + c1 = fgetc(f->fp); + c2 = fgetc(f->fp); + if (c1 == EOF || c2 == EOF) + return -1; + *ret = c1 << 8 | c2; + return 0; +} + +static int read_string(Anope::string &str, dbFILE *f) +{ + str.clear(); + uint16_t len; + + if (read_uint16(&len, f) < 0) + return -1; + if (len == 0) + return 0; + char *s = new char[len]; + if (len != fread(s, 1, len, f->fp)) + { + delete [] s; + return -1; + } + str = s; + delete [] s; + return 0; +} + +static int read_uint32(uint32_t *ret, dbFILE *f) +{ + int c1, c2, c3, c4; + + *ret = 0; + + c1 = fgetc(f->fp); + c2 = fgetc(f->fp); + c3 = fgetc(f->fp); + c4 = fgetc(f->fp); + if (c1 == EOF || c2 == EOF || c3 == EOF || c4 == EOF) + return -1; + *ret = c1 << 24 | c2 << 16 | c3 << 8 | c4; + return 0; +} + +int read_int32(int32_t *ret, dbFILE *f) +{ + int c1, c2, c3, c4; + + *ret = 0; + + c1 = fgetc(f->fp); + c2 = fgetc(f->fp); + c3 = fgetc(f->fp); + c4 = fgetc(f->fp); + if (c1 == EOF || c2 == EOF || c3 == EOF || c4 == EOF) + return -1; + *ret = c1 << 24 | c2 << 16 | c3 << 8 | c4; + return 0; +} + +static void LoadNicks() +{ + if (!NickServ::service) + return; + dbFILE *f = open_db_read("NickServ", "nick.db", 14); + if (f == NULL) + return; + for (int i = 0; i < 1024; ++i) + for (int c; (c = getc_db(f)) == 1;) + { + Anope::string buffer; + + READ(read_string(buffer, f)); + + NickServ::Account *nc = NickServ::account.Create(); + nc->SetDisplay(buffer); + + const Anope::string settings[] = { "killprotect", "kill_quick", "ns_secure", "ns_private", "hide_email", + "hide_mask", "hide_quit", "memo_signon", "memo_receive", "autoop", "msg", "ns_keepmodes" }; + for (unsigned j = 0; j < sizeof(settings) / sizeof(Anope::string); ++j) + nc->UnsetS(settings[j].upper()); + + char pwbuf[32]; + READ(read_buffer(pwbuf, f)); + if (hashm == "plain") + { + Anope::string p; + my_b64_encode(pwbuf, p); + nc->SetPassword(p); + } + else if (hashm == "md5" || hashm == "oldmd5") + nc->SetPassword(Hex(pwbuf, 16)); + else if (hashm == "sha1") + nc->SetPassword(Hex(pwbuf, 20)); + else + nc->SetPassword(Hex(pwbuf, strlen(pwbuf))); + nc->SetPassword(hashm + ":" + nc->GetPassword()); + + READ(read_string(buffer, f)); + nc->SetEmail(buffer); + + READ(read_string(buffer, f)); + if (!buffer.empty()) + nc->Extend("greet", buffer); + + uint32_t u32; + READ(read_uint32(&u32, f)); + //nc->icq = u32; + + READ(read_string(buffer, f)); + //nc->url = buffer; + + READ(read_uint32(&u32, f)); + if (u32 & OLD_NI_KILLPROTECT) + nc->SetS("KILLPROTECT", true); + if (u32 & OLD_NI_SECURE) + nc->SetS("NS_SECURE", true); + if (u32 & OLD_NI_MSG) + nc->SetS("MSG", true); + if (u32 & OLD_NI_MEMO_HARDMAX) + nc->SetS("MEMO_HARDMAX", true); + if (u32 & OLD_NI_MEMO_SIGNON) + nc->SetS("MEMO_SIGNON", true); + if (u32 & OLD_NI_MEMO_RECEIVE) + nc->SetS("MEMO_RECEIVE", true); + if (u32 & OLD_NI_PRIVATE) + nc->SetS("NS_PRIVATE", true); + if (u32 & OLD_NI_HIDE_EMAIL) + nc->SetS("HIDE_EMAIL", true); + if (u32 & OLD_NI_HIDE_MASK) + nc->SetS("HIDE_MASK", true); + if (u32 & OLD_NI_HIDE_QUIT) + nc->SetS("HIDE_QUIT", true); + if (u32 & OLD_NI_KILL_QUICK) + nc->SetS("KILL_QUICK", true); + if (u32 & OLD_NI_KILL_IMMED) + nc->SetS("KILL_IMMED", true); + if (u32 & OLD_NI_MEMO_MAIL) + nc->SetS("MEMO_MAIL", true); + if (u32 & OLD_NI_HIDE_STATUS) + nc->SetS("HIDE_STATUS", true); + if (u32 & OLD_NI_SUSPENDED) + { + if (nssuspendinfo) + { + NSSuspendInfo *si = nssuspendinfo.Create(); + si->SetAccount(nc); + } + } + if (!(u32 & OLD_NI_AUTOOP)) + nc->SetS("AUTOOP", true); + + uint16_t u16; + READ(read_uint16(&u16, f)); + switch (u16) + { + case LANG_ES: + nc->SetLanguage("es_ES"); + break; + case LANG_PT: + nc->SetLanguage("pt_PT"); + break; + case LANG_FR: + nc->SetLanguage("fr_FR"); + break; + case LANG_TR: + nc->SetLanguage("tr_TR"); + break; + case LANG_IT: + nc->SetLanguage("it_IT"); + break; + case LANG_DE: + nc->SetLanguage("de_DE"); + break; + case LANG_CAT: + nc->SetLanguage("ca_ES"); // yes, iso639 defines catalan as CA + break; + case LANG_GR: + nc->SetLanguage("el_GR"); + break; + case LANG_NL: + nc->SetLanguage("nl_NL"); + break; + case LANG_RU: + nc->SetLanguage("ru_RU"); + break; + case LANG_HUN: + nc->SetLanguage("hu_HU"); + break; + case LANG_PL: + nc->SetLanguage("pl_PL"); + break; + case LANG_EN_US: + case LANG_JA_JIS: + case LANG_JA_EUC: + case LANG_JA_SJIS: // these seem to be unused + default: + nc->SetLanguage("en"); + } + + READ(read_uint16(&u16, f)); + for (uint16_t j = 0; j < u16; ++j) + { + READ(read_string(buffer, f)); + + if (nsaccess) + { + NickAccess *a = nsaccess.Create(); + a->SetAccount(nc); + a->SetMask(buffer); + } + } + + int16_t i16; + READ(read_int16(&i16, f)); + READ(read_int16(&i16, f)); + MemoServ::MemoInfo *mi = nc->GetMemos(); + if (mi) + mi->SetMemoMax(i16); + for (int16_t j = 0; j < i16; ++j) + { + MemoServ::Memo *m = MemoServ::service ? MemoServ::service->CreateMemo() : nullptr; + READ(read_uint32(&u32, f)); + uint16_t flags; + READ(read_uint16(&flags, f)); + int32_t tmp32; + READ(read_int32(&tmp32, f)); + if (m) + m->SetTime(tmp32); + char sbuf[32]; + READ(read_buffer(sbuf, f)); + if (m) + m->SetSender(sbuf); + Anope::string text; + READ(read_string(text, f)); + if (m) + m->SetText(text); + } + READ(read_uint16(&u16, f)); + READ(read_int16(&i16, f)); + + Log(LOG_DEBUG) << "Loaded NickServ::Account " << nc->GetDisplay(); + } + + for (int i = 0; i < 1024; ++i) + for (int c; (c = getc_db(f)) == 1;) + { + Anope::string nick, last_usermask, last_realname, last_quit; + time_t time_registered, last_seen; + + READ(read_string(nick, f)); + READ(read_string(last_usermask, f)); + READ(read_string(last_realname, f)); + READ(read_string(last_quit, f)); + + int32_t tmp32; + READ(read_int32(&tmp32, f)); + time_registered = tmp32; + READ(read_int32(&tmp32, f)); + last_seen = tmp32; + + uint16_t tmpu16; + READ(read_uint16(&tmpu16, f)); + + Anope::string core; + READ(read_string(core, f)); + NickServ::Account *nc = NickServ::FindAccount(core); + if (nc == NULL) + { + Log() << "Skipping coreless nick " << nick << " with core " << core; + continue; + } + + if (tmpu16 & OLD_NS_VERBOTEN) + { + if (!forbiddata) + { + delete nc; + continue; + } + + if (nc->GetDisplay().find_first_of("?*") != Anope::string::npos) + { + delete nc; + continue; + } + + ForbidData *d = forbiddata.Create(); + d->SetMask(nc->GetDisplay()); + d->SetCreator(last_usermask); + d->SetReason(last_realname); + d->SetType(FT_NICK); + delete nc; + continue; + } + + NickServ::Nick *na = NickServ::nick.Create(); + na->SetNick(nick); + na->SetAccount(nc); + na->SetLastUsermask(last_usermask); + na->SetLastRealname(last_realname); + na->SetLastQuit(last_quit); + na->SetTimeRegistered(time_registered); + na->SetLastSeen(last_seen); + + if (tmpu16 & OLD_NS_NO_EXPIRE) + na->SetS("NS_NO_EXPIRE", true); + + Log(LOG_DEBUG) << "Loaded NickServ::Nick " << na->GetNick(); + } + + close_db(f); /* End of section Ia */ +} + +static void LoadVHosts() +{ + dbFILE *f = open_db_read("HostServ", "hosts.db", 3); + if (f == NULL) + return; + + for (int c; (c = getc_db(f)) == 1;) + { + Anope::string nick, ident, host, creator; + int32_t vtime; + + READ(read_string(nick, f)); + READ(read_string(ident, f)); + READ(read_string(host, f)); + READ(read_string(creator, f)); + READ(read_int32(&vtime, f)); + + NickServ::Nick *na = NickServ::FindNick(nick); + if (na == NULL) + { + Log() << "Removing vhost for non-existent nick " << nick; + continue; + } + + na->SetVhost(ident, host, creator, vtime); + + Log() << "Loaded vhost for " << na->GetNick(); + } + + close_db(f); +} + +static void LoadBots() +{ + dbFILE *f = open_db_read("Botserv", "bot.db", 10); + if (f == NULL) + return; + + for (int c; (c = getc_db(f)) == 1;) + { + Anope::string nick, user, host, real; + int16_t flags, chancount; + int32_t created; + + READ(read_string(nick, f)); + READ(read_string(user, f)); + READ(read_string(host, f)); + READ(read_string(real, f)); + READ(read_int16(&flags, f)); + READ(read_int32(&created, f)); + READ(read_int16(&chancount, f)); + + ServiceBot *bi = ServiceBot::Find(nick, true); + //XXX + // if (!bi) + // bi = new ServiceBot(nick, user, host, real); + bi->bi->SetCreated(created); + + if (flags & OLD_BI_PRIVATE) + bi->bi->SetOperOnly(true); + + Log(LOG_DEBUG) << "Loaded bot " << bi->nick; + } + + close_db(f); +} + +static void LoadChannels() +{ + if (!ChanServ::service) + return; + + ServiceReference forbid("ForbidService", "forbid"); + dbFILE *f = open_db_read("ChanServ", "chan.db", 16); + if (f == NULL) + return; + + for (int i = 0; i < 256; ++i) + for (int c; (c = getc_db(f)) == 1;) + { + Anope::string buffer; + char namebuf[64]; + READ(read_buffer(namebuf, f)); + ChanServ::Channel *ci = ChanServ::channel.Create(); + ci->SetName(namebuf); + + const Anope::string settings[] = { "keeptopic", "peace", "cs_private", "restricted", "cs_secure", "secureops", "securefounder", + "signkick", "signkick_level", "topiclock", "persist", "noautoop", "cs_keepmodes" }; + for (unsigned j = 0; j < sizeof(settings) / sizeof(Anope::string); ++j) + ci->UnsetS(settings[j].upper()); + + READ(read_string(buffer, f)); + ci->SetFounder(NickServ::FindAccount(buffer)); + + READ(read_string(buffer, f)); + ci->SetSuccessor(NickServ::FindAccount(buffer)); + + char pwbuf[32]; + READ(read_buffer(pwbuf, f)); + + Anope::string desc; + READ(read_string(desc, f)); + ci->SetDesc(desc); + READ(read_string(buffer, f)); + READ(read_string(buffer, f)); + + int32_t tmp32; + READ(read_int32(&tmp32, f)); + ci->SetTimeRegistered(tmp32); + + READ(read_int32(&tmp32, f)); + ci->SetLastUsed(tmp32); + + Anope::string last_topic; + READ(read_string(last_topic, f)); + ci->SetLastTopic(last_topic); + + READ(read_buffer(pwbuf, f)); + ci->SetLastTopicSetter(pwbuf); + + READ(read_int32(&tmp32, f)); + ci->SetLastTopicTime(tmp32); + + uint32_t tmpu32; + READ(read_uint32(&tmpu32, f)); + // Temporary flags cleanup + tmpu32 &= ~0x80000000; + if (tmpu32 & OLD_CI_KEEPTOPIC) + ci->SetS("KEEPTOPIC", true); + if (tmpu32 & OLD_CI_SECUREOPS) + ci->SetS("SECUREOPS", true); + if (tmpu32 & OLD_CI_PRIVATE) + ci->SetS("CS_PRIVATE", true); + if (tmpu32 & OLD_CI_TOPICLOCK) + ci->SetS("TOPICLOCK", true); + if (tmpu32 & OLD_CI_RESTRICTED) + ci->SetS("RESTRICTED", true); + if (tmpu32 & OLD_CI_PEACE) + ci->SetS("PEACE", true); + if (tmpu32 & OLD_CI_SECURE) + ci->SetS("CS_SECURE", true); + if (tmpu32 & OLD_CI_NO_EXPIRE) + ci->SetS("CS_NO_EXPIRE", true); + if (tmpu32 & OLD_CI_MEMO_HARDMAX) + ci->SetS("MEMO_HARDMAX", true); + if (tmpu32 & OLD_CI_SECUREFOUNDER) + ci->SetS("SECUREFOUNDER", true); + if (tmpu32 & OLD_CI_SIGNKICK) + ci->SetS("SIGNKICK", true); + if (tmpu32 & OLD_CI_SIGNKICK_LEVEL) + ci->SetS("SIGNKICK_LEVEL", true); + + Anope::string forbidby, forbidreason; + READ(read_string(forbidby, f)); + READ(read_string(forbidreason, f)); + if (tmpu32 & OLD_CI_SUSPENDED) + { + if (cssuspendinfo) + { + CSSuspendInfo *si = cssuspendinfo.Create(); + si->SetChannel(ci); + si->SetBy(forbidby); + } + } + bool forbid_chan = tmpu32 & OLD_CI_VERBOTEN; + + int16_t tmp16; + READ(read_int16(&tmp16, f)); + ci->SetBanType(tmp16); + + READ(read_int16(&tmp16, f)); + if (tmp16 > 36) + tmp16 = 36; + for (int16_t j = 0; j < tmp16; ++j) + { + int16_t level; + READ(read_int16(&level, f)); + + if (level == ChanServ::ACCESS_INVALID) + level = ChanServ::ACCESS_FOUNDER; + + if (j == 10 && level < 0) // NOJOIN + ci->UnsetS("RESTRICTED"); // If CSDefRestricted was enabled this can happen + + ci->SetLevel(GetLevelName(j), level); + } + + bool xop = tmpu32 & OLD_CI_XOP; + uint16_t tmpu16; + READ(read_uint16(&tmpu16, f)); + for (uint16_t j = 0; j < tmpu16; ++j) + { + uint16_t in_use; + READ(read_uint16(&in_use, f)); + if (in_use) + { + ChanServ::ChanAccess *access = NULL; + + if (xop) + { + if (xopchanaccess) + access = xopchanaccess.Create(); + } + else + { + if (accesschanaccess) + access = accesschanaccess.Create(); + } + + if (access) + access->SetChannel(ci); + + int16_t level; + READ(read_int16(&level, f)); + if (access) + { + if (xop) + { + switch (level) + { + case 3: + access->AccessUnserialize("VOP"); + break; + case 4: + access->AccessUnserialize("HOP"); + break; + case 5: + access->AccessUnserialize("AOP"); + break; + case 10: + access->AccessUnserialize("SOP"); + break; + } + } + else + access->AccessUnserialize(stringify(level)); + } + + Anope::string mask; + READ(read_string(mask, f)); + if (access) + { + access->SetMask(mask); + NickServ::Nick *na = NickServ::FindNick(mask); + if (na) + na->SetAccount(na->GetAccount()); + } + + READ(read_int32(&tmp32, f)); + if (access) + { + access->SetLastSeen(tmp32); + access->SetCreator("Unknown"); + access->SetCreated(Anope::CurTime); + } + } + } + + READ(read_uint16(&tmpu16, f)); + for (uint16_t j = 0; j < tmpu16; ++j) + { + uint16_t flags; + READ(read_uint16(&flags, f)); + if (flags & 0x0001) + { + Anope::string mask, reason, creator; + READ(read_string(mask, f)); + READ(read_string(reason, f)); + READ(read_string(creator, f)); + READ(read_int32(&tmp32, f)); + + ci->AddAkick(creator, mask, reason, tmp32); + } + } + + READ(read_uint32(&tmpu32, f)); // mlock on + ci->Extend("mlock_on", tmpu32); + READ(read_uint32(&tmpu32, f)); // mlock off + ci->Extend("mlock_off", tmpu32); + READ(read_uint32(&tmpu32, f)); // mlock limit + ci->Extend("mlock_limit", tmpu32); + READ(read_string(buffer, f)); // key + ci->Extend("mlock_key", buffer); + READ(read_string(buffer, f)); // +f + READ(read_string(buffer, f)); // +L + + READ(read_int16(&tmp16, f)); + READ(read_int16(&tmp16, f)); + MemoServ::MemoInfo *mi = ci->GetMemos(); + if (mi) + mi->SetMemoMax(tmp16); + for (int16_t j = 0; j < tmp16; ++j) + { + READ(read_uint32(&tmpu32, f)); + READ(read_uint16(&tmpu16, f)); + MemoServ::Memo *m = MemoServ::service ? MemoServ::service->CreateMemo() : nullptr; + READ(read_int32(&tmp32, f)); + if (m) + m->SetTime(tmp32); + char sbuf[32]; + READ(read_buffer(sbuf, f)); + if (m) + m->SetSender(sbuf); + Anope::string text; + READ(read_string(text, f)); + if (m) + m->SetText(text); + } + + READ(read_string(buffer, f)); + if (!buffer.empty()) + { + if (entrymsg) + { + EntryMsg *e = entrymsg.Create(); + e->SetChannel(ci); + e->SetCreator("Unknown"); + e->SetMessage(buffer); + e->SetWhen(Anope::CurTime); + } + } + + READ(read_string(buffer, f)); + ci->SetBot(ServiceBot::Find(buffer, true)); + + READ(read_int32(&tmp32, f)); + if (tmp32 & OLD_BS_DONTKICKOPS) + ci->SetS("BS_DONTKICKOPS", true); + if (tmp32 & OLD_BS_DONTKICKVOICES) + ci->SetS("BS_DONTKICKVOICES", true); + if (tmp32 & OLD_BS_FANTASY) + ci->SetS("BS_FANTASY", true); + if (tmp32 & OLD_BS_GREET) + ci->SetS("BS_GREET", true); + if (tmp32 & OLD_BS_NOBOT) + ci->SetS("BS_NOBOT", true); + + KickerData *kd = GetKickerData(ci); + if (kd) + { + kd->SetBolds(tmp32 & OLD_BS_KICK_BOLDS); + kd->SetColors(tmp32 & OLD_BS_KICK_COLORS); + kd->SetReverses(tmp32 & OLD_BS_KICK_REVERSES); + kd->SetUnderlines(tmp32 & OLD_BS_KICK_UNDERLINES); + kd->SetBadwords(tmp32 & OLD_BS_KICK_BADWORDS); + kd->SetCaps(tmp32 & OLD_BS_KICK_CAPS); + kd->SetFlood(tmp32 & OLD_BS_KICK_FLOOD); + kd->SetRepeat(tmp32 & OLD_BS_KICK_REPEAT); + } + + READ(read_int16(&tmp16, f)); + for (int16_t j = 0; j < tmp16; ++j) + { + int16_t ttb; + READ(read_int16(&ttb, f)); + switch (j) + { + case TTB_BOLDS: + kd->SetTTBBolds(ttb); + break; + case TTB_COLORS: + kd->SetTTBColors(ttb); + break; + case TTB_REVERSES: + kd->SetTTBReverses(ttb); + break; + case TTB_UNDERLINES: + kd->SetTTBUnderlines(ttb); + break; + case TTB_BADWORDS: + kd->SetTTBBadwords(ttb); + break; + case TTB_CAPS: + kd->SetTTBCaps(ttb); + break; + case TTB_FLOOD: + kd->SetTTBFlood(ttb); + break; + case TTB_REPEAT: + kd->SetTTBRepeat(ttb); + break; + } + } + + READ(read_int16(&tmp16, f)); + if (kd) + kd->SetCapsMin(tmp16); + READ(read_int16(&tmp16, f)); + if (kd) + kd->SetCapsPercent(tmp16); + READ(read_int16(&tmp16, f)); + if (kd) + kd->SetFloodLines(tmp16); + READ(read_int16(&tmp16, f)); + if (kd) + kd->SetFloodSecs(tmp16); + READ(read_int16(&tmp16, f)); + if (kd) + kd->SetRepeatTimes(tmp16); + + READ(read_uint16(&tmpu16, f)); + for (uint16_t j = 0; j < tmpu16; ++j) + { + uint16_t in_use; + READ(read_uint16(&in_use, f)); + if (in_use) + { + READ(read_string(buffer, f)); + uint16_t type; + READ(read_uint16(&type, f)); + + BadWordType bwtype = BW_ANY; + if (type == 1) + bwtype = BW_SINGLE; + else if (type == 2) + bwtype = BW_START; + else if (type == 3) + bwtype = BW_END; + + if (badwords) + badwords->AddBadWord(ci, buffer, bwtype); + } + } + + if (forbid_chan) + { + if (!forbiddata) + { + delete ci; + continue; + } + + if (ci->GetName().find_first_of("?*") != Anope::string::npos) + { + delete ci; + continue; + } + + ForbidData *d = forbiddata.Create(); + d->SetMask(ci->GetName()); + d->SetCreator(forbidby); + d->SetReason(forbidreason); + d->SetType(FT_CHAN); + delete ci; + continue; + } + + Log(LOG_DEBUG) << "Loaded channel " << ci->GetName(); + } + + close_db(f); +} + +static void LoadOper() +{ + dbFILE *f = open_db_read("OperServ", "oper.db", 13); + if (f == NULL) + return; + + XLineManager *akill, *sqline, *snline, *szline; + akill = sqline = snline = szline = NULL; + + for (XLineManager *xl : XLineManager::XLineManagers) + { + if (xl->Type() == 'G') + akill = xl; + else if (xl->Type() == 'Q') + sqline = xl; + else if (xl->Type() == 'N') + snline = xl; + else if (xl->Type() == 'Z') + szline = xl; + } + + int32_t tmp32; + READ(read_int32(&tmp32, f)); + READ(read_int32(&tmp32, f)); + + int16_t capacity; + read_int16(&capacity, f); // AKill count + for (int16_t i = 0; i < capacity; ++i) + { + Anope::string user, host, by, reason; + int32_t seton, expires; + + READ(read_string(user, f)); + READ(read_string(host, f)); + READ(read_string(by, f)); + READ(read_string(reason, f)); + READ(read_int32(&seton, f)); + READ(read_int32(&expires, f)); + + if (!akill) + continue; + + XLine *x = new XLine(user + "@" + host, by, expires, reason, XLineManager::GenerateUID()); + x->SetCreated(seton); + akill->AddXLine(x); + } + + read_int16(&capacity, f); // SNLines + for (int16_t i = 0; i < capacity; ++i) + { + Anope::string mask, by, reason; + int32_t seton, expires; + + READ(read_string(mask, f)); + READ(read_string(by, f)); + READ(read_string(reason, f)); + READ(read_int32(&seton, f)); + READ(read_int32(&expires, f)); + + if (!snline) + continue; + + XLine *x = new XLine(mask, by, expires, reason, XLineManager::GenerateUID()); + x->SetCreated(seton); + snline->AddXLine(x); + } + + read_int16(&capacity, f); // SQLines + for (int16_t i = 0; i < capacity; ++i) + { + Anope::string mask, by, reason; + int32_t seton, expires; + + READ(read_string(mask, f)); + READ(read_string(by, f)); + READ(read_string(reason, f)); + READ(read_int32(&seton, f)); + READ(read_int32(&expires, f)); + + if (!sqline) + continue; + + XLine *x = new XLine(mask, by, expires, reason, XLineManager::GenerateUID()); + x->SetCreated(seton); + sqline->AddXLine(x); + } + + read_int16(&capacity, f); // SZLines + for (int16_t i = 0; i < capacity; ++i) + { + Anope::string mask, by, reason; + int32_t seton, expires; + + READ(read_string(mask, f)); + READ(read_string(by, f)); + READ(read_string(reason, f)); + READ(read_int32(&seton, f)); + READ(read_int32(&expires, f)); + + if (!szline) + continue; + + XLine *x = new XLine(mask, by, expires, reason, XLineManager::GenerateUID()); + x->SetCreated(seton); + szline->AddXLine(x); + } + + close_db(f); +} + +static void LoadExceptions() +{ + if (!session_service) + return; + + dbFILE *f = open_db_read("OperServ", "exception.db", 9); + if (f == NULL) + return; + + int16_t num; + READ(read_int16(&num, f)); + for (int i = 0; i < num; ++i) + { + Anope::string mask, reason; + int16_t limit; + char who[32]; + int32_t time, expires; + + READ(read_string(mask, f)); + READ(read_int16(&limit, f)); + READ(read_buffer(who, f)); + READ(read_string(reason, f)); + READ(read_int32(&time, f)); + READ(read_int32(&expires, f)); + + if (exception && session_service) + { + Exception *e = exception.Create(); + e->SetMask(mask); + e->SetLimit(limit); + e->SetWho(who); + e->SetTime(time); + e->SetExpires(expires); + e->SetReason(reason); + } + } + + close_db(f); +} + +static void LoadNews() +{ + if (!newsitem) + return; + + dbFILE *f = open_db_read("OperServ", "news.db", 9); + + if (f == NULL) + return; + + int16_t n; + READ(read_int16(&n, f)); + + for (int16_t i = 0; i < n; i++) + { + int16_t type; + NewsItem *ni = newsitem.Create(); + + READ(read_int16(&type, f)); + + switch (type) + { + case OLD_NEWS_LOGON: + ni->SetNewsType(NEWS_LOGON); + break; + case OLD_NEWS_OPER: + ni->SetNewsType(NEWS_OPER); + break; + case OLD_NEWS_RANDOM: + ni->SetNewsType(NEWS_RANDOM); + break; + } + + int32_t unused; + READ(read_int32(&unused, f)); + + Anope::string text; + READ(read_string(text, f)); + ni->SetText(text); + + char who[32]; + READ(read_buffer(who, f)); + ni->SetWho(who); + + int32_t tmp; + READ(read_int32(&tmp, f)); + ni->SetTime(tmp); + } + + close_db(f); +} + +class DBOld : public Module + , public EventHook + , public EventHook +{ + ExtensibleItem mlock_on, mlock_off, mlock_limit; + ExtensibleItem mlock_key; + + public: + DBOld(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, DATABASE | VENDOR) + , mlock_on(this, "mlock_on") + , mlock_off(this, "mlock_off") + , mlock_limit(this, "mlock_limit") + , mlock_key(this, "mlock_key") + { + hashm = Config->GetModule(this)->Get("hash"); + + if (hashm != "md5" && hashm != "oldmd5" && hashm != "sha1" && hashm != "plain" && hashm != "sha256") + throw ModuleException("Invalid hash method"); + } + + EventReturn OnLoadDatabase() override + { + LoadNicks(); + LoadVHosts(); + LoadBots(); + LoadChannels(); + LoadOper(); + LoadExceptions(); + LoadNews(); + + return EVENT_STOP; + } + + void OnUplinkSync(Server *s) override + { + if (!ChanServ::service) + return; + for (auto& it : ChanServ::service->GetChannels()) + { + ChanServ::Channel *ci = it.second; + uint32_t *limit = mlock_limit.Get(ci); + Anope::string *key = mlock_key.Get(ci); + + uint32_t *u = mlock_on.Get(ci); + if (u) + { + process_mlock(ci, *u, true, limit, key); + mlock_on.Unset(ci); + } + + u = mlock_off.Get(ci); + if (u) + { + process_mlock(ci, *u, false, limit, key); + mlock_off.Unset(ci); + } + + mlock_limit.Unset(ci); + mlock_key.Unset(ci); + + if (ci->c) + ci->c->CheckModes(); + } + } +}; + +MODULE_INIT(DBOld) + diff --git a/modules/database/redis.cpp b/modules/database/redis.cpp new file mode 100644 index 000000000..72f25a16e --- /dev/null +++ b/modules/database/redis.cpp @@ -0,0 +1,434 @@ +/* + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * + */ + +#include "module.h" +#include "modules/redis.h" + +using namespace Redis; + +class DatabaseRedis; +static DatabaseRedis *me; + +class TypeLoader : public Interface +{ + Serialize::TypeBase *type; + + public: + TypeLoader(Module *creator, Serialize::TypeBase *t) : Interface(creator), type(t) { } + + void OnResult(const Reply &r) override; +}; + +class ObjectLoader : public Interface +{ + Serialize::Object *obj; + + public: + ObjectLoader(Module *creator, Serialize::Object *s) : Interface(creator), obj(s) { } + + void OnResult(const Reply &r) override; +}; + +class FieldLoader : public Interface +{ + Serialize::Object *obj; + Serialize::FieldBase *field; + + public: + FieldLoader(Module *creator, Serialize::Object *o, Serialize::FieldBase *f) : Interface(creator), obj(o), field(f) { } + + void OnResult(const Reply &) override; +}; + +class SubscriptionListener : public Interface +{ + public: + SubscriptionListener(Module *creator) : Interface(creator) { } + + void OnResult(const Reply &r) override; +}; + +class DatabaseRedis : public Module + , public EventHook + , public EventHook +{ + SubscriptionListener sl; + + public: + ServiceReference redis; + + DatabaseRedis(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, DATABASE | VENDOR) + , sl(this) + { + me = this; + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + this->redis = ServiceReference("Redis::Provider", block->Get("engine", "redis/main")); + } + + EventReturn OnLoadDatabase() override + { + if (!redis) + return EVENT_STOP; + + const std::map &types = Serialize::TypeBase::GetTypes(); + for (const std::pair &p : types) + this->OnSerializeTypeCreate(p.second); + + while (redis->BlockAndProcess()); + + redis->Subscribe(&this->sl, "anope"); + + return EVENT_STOP; + } + + void OnSerializeTypeCreate(Serialize::TypeBase *sb) + { + std::vector args = { "SMEMBERS", "ids:" + sb->GetName() }; + + redis->SendCommand(new TypeLoader(this, sb), args); + } + + EventReturn OnSerializeList(Serialize::TypeBase *type, std::vector &ids) override + { + return EVENT_CONTINUE; + } + + EventReturn OnSerializeFind(Serialize::TypeBase *type, Serialize::FieldBase *field, const Anope::string &value, Serialize::ID &id) override + { + return EVENT_CONTINUE; + } + + EventReturn OnSerializeGet(Serialize::Object *object, Serialize::FieldBase *field, Anope::string &value) override + { + return EVENT_CONTINUE; + } + + EventReturn OnSerializeGetRefs(Serialize::Object *object, Serialize::TypeBase *type, std::vector &) override + { + return EVENT_CONTINUE; + } + + EventReturn OnSerializeDeref(Serialize::ID id, Serialize::TypeBase *type) override + { + return EVENT_CONTINUE; + } + + EventReturn OnSerializeGetSerializable(Serialize::Object *object, Serialize::FieldBase *field, Anope::string &type, Serialize::ID &value) override + { + return EVENT_CONTINUE; + } + + EventReturn OnSerializeSet(Serialize::Object *object, Serialize::FieldBase *field, const Anope::string &value) override + { + std::vector args; + + redis->StartTransaction(); + + const Anope::string &old = field->SerializeToString(object); + args = { "SREM", "lookup:" + object->GetSerializableType()->GetName() + ":" + field->GetName() + ":" + old, stringify(object->id) }; + redis->SendCommand(nullptr, args); + + // add object to type set + args = { "SADD", "ids:" + object->GetSerializableType()->GetName(), stringify(object->id) }; + redis->SendCommand(nullptr, args); + + // add key to key set + args = { "SADD", "keys:" + stringify(object->id), field->GetName() }; + redis->SendCommand(nullptr, args); + + // set value + args = { "SET", "values:" + stringify(object->id) + ":" + field->GetName(), value }; + redis->SendCommand(nullptr, args); + + // lookup + args = { "SADD", "lookup:" + object->GetSerializableType()->GetName() + ":" + field->GetName() + ":" + value, stringify(object->id) }; + redis->SendCommand(nullptr, args); + + redis->CommitTransaction(); + + return EVENT_CONTINUE; + } + + EventReturn OnSerializeSetSerializable(Serialize::Object *object, Serialize::FieldBase *field, Serialize::Object *value) override + { + return OnSerializeSet(object, field, stringify(value->id)); + } + + EventReturn OnSerializeUnset(Serialize::Object *object, Serialize::FieldBase *field) override + { + std::vector args; + + redis->StartTransaction(); + + const Anope::string &old = field->SerializeToString(object); + args = { "SREM", "lookup:" + object->GetSerializableType()->GetName() + ":" + field->GetName() + ":" + old, stringify(object->id) }; + redis->SendCommand(nullptr, args); + + // remove field from set + args = { "SREM", "keys:" + stringify(object->id), field->GetName() }; + redis->SendCommand(nullptr, args); + + redis->CommitTransaction(); + + return EVENT_CONTINUE; + } + + EventReturn OnSerializeUnsetSerializable(Serialize::Object *object, Serialize::FieldBase *field) override + { + return OnSerializeUnset(object, field); + } + + EventReturn OnSerializeHasField(Serialize::Object *object, Serialize::FieldBase *field) override + { + return EVENT_CONTINUE; + } + + EventReturn OnSerializableGetId(Serialize::ID &id) override + { + std::vector args = { "INCR", "id" }; + + auto f = [&](const Reply &r) + { + id = r.i; + }; + + FInterface inter(this, f); + redis->SendCommand(&inter, args); + while (redis->BlockAndProcess()); + return EVENT_ALLOW; + } + + void OnSerializableCreate(Serialize::Object *) override + { + } + + void OnSerializableDelete(Serialize::Object *obj) override + { + std::vector args; + + redis->StartTransaction(); + + for (Serialize::FieldBase *field : obj->GetSerializableType()->fields) + { + Anope::string value = field->SerializeToString(obj); + + args = { "SREM", "lookup:" + obj->GetSerializableType()->GetName() + ":" + field->GetName() + ":" + value, stringify(obj->id) }; + redis->SendCommand(nullptr, args); + + args = { "DEL", "values:" + stringify(obj->id) + ":" + field->GetName() }; + redis->SendCommand(nullptr, args); + + args = { "SREM", "keys:" + stringify(obj->id), field->GetName() }; + redis->SendCommand(nullptr, args); + } + + args = { "SREM", "ids:" + obj->GetSerializableType()->GetName(), stringify(obj->id) }; + redis->SendCommand(nullptr, args); + + redis->CommitTransaction(); + } +}; + +void TypeLoader::OnResult(const Reply &r) +{ + if (r.type != Reply::MULTI_BULK || !me->redis) + { + delete this; + return; + } + + for (unsigned i = 0; i < r.multi_bulk.size(); ++i) + { + const Reply *reply = r.multi_bulk[i]; + + if (reply->type != Reply::BULK) + continue; + + int64_t id; + try + { + id = convertTo(reply->bulk); + } + catch (const ConvertException &) + { + continue; + } + + Serialize::Object *obj = type->Require(id); + if (obj == nullptr) + { + Log(LOG_DEBUG) << "redis: Unable to require object #" << id << " of type " << type->GetName(); + continue; + } + + std::vector args = { "SMEMBERS", "keys:" + stringify(id) }; + + me->redis->SendCommand(new ObjectLoader(me, obj), args); + } + + delete this; +} + +void ObjectLoader::OnResult(const Reply &r) +{ + if (r.type != Reply::MULTI_BULK || r.multi_bulk.empty() || !me->redis) + { + delete this; + return; + } + + Serialize::TypeBase *type = obj->GetSerializableType(); + + for (Reply *reply : r.multi_bulk) + { + const Anope::string &key = reply->bulk; + Serialize::FieldBase *field = type->GetField(key); + + if (field == nullptr) + continue; + + std::vector args = { "GET", "values:" + stringify(obj->id) + ":" + key }; + + me->redis->SendCommand(new FieldLoader(me, obj, field), args); + } + + delete this; +} + +void FieldLoader::OnResult(const Reply &r) +{ + Log(LOG_DEBUG_2) << "redis: Setting field " << field->GetName() << " of object #" << obj->id << " of type " << obj->GetSerializableType()->GetName() << " to " << r.bulk; + field->UnserializeFromString(obj, r.bulk); + + delete this; +} + +void SubscriptionListener::OnResult(const Reply &r) +{ + /* + * message + * anope + * message + * + * set 4 email adam@anope.org + * unset 4 email + * create 4 NickCore + * delete 4 + */ + + const Anope::string &message = r.multi_bulk[2]->bulk; + Anope::string command; + spacesepstream sep(message); + + sep.GetToken(command); + + if (command == "set" || command == "unset") + { + Anope::string sid, key, value; + + sep.GetToken(sid); + sep.GetToken(key); + value = sep.GetRemaining(); + + Serialize::ID id; + try + { + id = convertTo(sid); + } + catch (const ConvertException &ex) + { + Log(LOG_DEBUG) << "redis: unable to get id for SL update key " << sid; + return; + } + + Serialize::Object *obj = Serialize::GetID(id); + if (obj == nullptr) + { + Log(LOG_DEBUG) << "redis: pmessage for unknown object #" << id; + return; + } + + Serialize::FieldBase *field = obj->GetSerializableType()->GetField(key); + if (field == nullptr) + { + Log(LOG_DEBUG) << "redis: pmessage for unknown field of object #" << id << ": " << key; + return; + } + + Log(LOG_DEBUG_2) << "redis: Setting field " << field->GetName() << " of object #" << obj->id << " of type " << obj->GetSerializableType()->GetName() << " to " << value; + field->UnserializeFromString(obj, value); + } + else if (command == "create") + { + Anope::string sid, stype; + + sep.GetToken(sid); + sep.GetToken(stype); + + Serialize::ID id; + try + { + id = convertTo(sid); + } + catch (const ConvertException &ex) + { + Log(LOG_DEBUG) << "redis: unable to get id for SL update key " << sid; + return; + } + + Serialize::TypeBase *type = Serialize::TypeBase::Find(stype); + if (type == nullptr) + { + Log(LOG_DEBUG) << "redis: pmessage create for nonexistant type " << stype; + return; + } + + Serialize::Object *obj = type->Require(id); + if (obj == nullptr) + { + Log(LOG_DEBUG) << "redis: require for pmessage create type " << type->GetName() << " id #" << id << " returned nullptr"; + return; + } + } + else if (command == "delete") + { + Anope::string sid; + + sep.GetToken(sid); + + Serialize::ID id; + try + { + id = convertTo(sid); + } + catch (const ConvertException &ex) + { + Log(LOG_DEBUG) << "redis: unable to get id for SL update key " << sid; + return; + } + + Serialize::Object *obj = Serialize::GetID(id); + if (obj == nullptr) + { + Log(LOG_DEBUG) << "redis: message for unknown object #" << id; + return; + } + + obj->Delete(); + } + else + Log(LOG_DEBUG) << "redis: unknown message: " << message; +} + +MODULE_INIT(DatabaseRedis) diff --git a/modules/database/sql.cpp b/modules/database/sql.cpp new file mode 100644 index 000000000..fed0050e4 --- /dev/null +++ b/modules/database/sql.cpp @@ -0,0 +1,372 @@ +#include "module.h" +#include "modules/sql.h" + +using namespace SQL; + +class DBMySQL : public Module, public Pipe + , public EventHook +{ + private: + bool transaction = false; + bool inited = false; + Anope::string prefix; + ServiceReference SQL; + + Result Run(const Query &query) + { + if (!SQL) + ;//XXX + + if (!inited) + { + inited = true; + for (const Query &q : SQL->InitSchema(prefix)) + SQL->RunQuery(q); + } + + return SQL->RunQuery(query); + } + + void StartTransaction() + { + if (!SQL || transaction) + return; + + Run(SQL->BeginTransaction()); + + transaction = true; + Notify(); + } + + void Commit() + { + if (!SQL || !transaction) + return; + + Run(SQL->Commit()); + + transaction = false; + } + + public: + DBMySQL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, DATABASE | VENDOR) + { + } + + void OnNotify() override + { + Commit(); + Serialize::Clear(); + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + this->SQL = ServiceReference("SQL::Provider", block->Get("engine")); + this->prefix = block->Get("prefix", "anope_db_"); + inited = false; + } + + EventReturn OnSerializeList(Serialize::TypeBase *type, std::vector &ids) override + { + StartTransaction(); + + ids.clear(); + + Query query = "SELECT `id` FROM `" + prefix + type->GetName() + "`"; + Result res = Run(query); + for (int i = 0; i < res.Rows(); ++i) + { + Serialize::ID id = convertTo(res.Get(i, "id")); + ids.push_back(id); + } + + return EVENT_ALLOW; + } + + EventReturn OnSerializeFind(Serialize::TypeBase *type, Serialize::FieldBase *field, const Anope::string &value, Serialize::ID &id) override + { + if (!SQL) + return EVENT_CONTINUE; + + StartTransaction(); + + for (Query &q : SQL->CreateTable(prefix, type->GetName())) + Run(q); + + for (Query &q : SQL->AlterTable(prefix, type->GetName(), field->GetName(), false)) + Run(q); + + for (const Query &q : SQL->CreateIndex(prefix + type->GetName(), field->GetName())) + Run(q); + + Query query("SELECT `id` FROM `" + prefix + type->GetName() + "` WHERE `" + field->GetName() + "` = @value@"); + query.SetValue("value", value); + Result res = Run(query); + if (res.Rows()) + try + { + id = convertTo(res.Get(0, "id")); + return EVENT_ALLOW; + } + catch (const ConvertException &) + { + } + return EVENT_CONTINUE; + } + + private: + bool GetValue(Serialize::Object *object, Serialize::FieldBase *field, SQL::Result::Value &v) + { + StartTransaction(); + + Query query = "SELECT `" + field->GetName() + "` FROM `" + prefix + object->GetSerializableType()->GetName() + "` WHERE `id` = @id@"; + query.SetValue("id", object->id); + Result res = Run(query); + + if (res.Rows() == 0) + return false; + + v = res.GetValue(0, field->GetName()); + return true; + } + + public: + EventReturn OnSerializeGet(Serialize::Object *object, Serialize::FieldBase *field, Anope::string &value) override + { + SQL::Result::Value v; + + if (!GetValue(object, field, v)) + { + field->CacheMiss(object); + return EVENT_CONTINUE; + } + + value = v.value; + return EVENT_ALLOW; + } + + EventReturn OnSerializeGetRefs(Serialize::Object *object, Serialize::TypeBase *type, std::vector &edges) override + { + StartTransaction(); + + edges.clear(); + + Query query; + if (type) + query = "SELECT field," + prefix + "edges.id,other_id,j1.type,j2.type AS other_type FROM `" + prefix + "edges` " + "JOIN `" + prefix + "objects` AS j1 ON " + prefix + "edges.id = j1.id " + "JOIN `" + prefix + "objects` AS j2 ON " + prefix + "edges.other_id = j2.id " + "WHERE " + " (" + prefix + "edges.id = @id@ AND j2.type = @other_type@) " + "OR" + " (other_id = @id@ AND j1.type = @other_type@)"; + else + query = "SELECT field," + prefix + "edges.id,other_id,j1.type,j2.type AS other_type FROM `" + prefix + "edges` " + "JOIN `" + prefix + "objects` AS j1 ON " + prefix + "edges.id = j1.id " + "JOIN `" + prefix + "objects` AS j2 ON " + prefix + "edges.other_id = j2.id " + "WHERE " + prefix + "edges.id = @id@ OR other_id = @id@"; + + query.SetValue("type", object->GetSerializableType()->GetName()); + query.SetValue("id", object->id); + if (type) + query.SetValue("other_type", type->GetName()); + + Result res = Run(query); + for (int i = 0; i < res.Rows(); ++i) + { + Serialize::ID id = convertTo(res.Get(i, "id")); + + if (id == object->id) + { + // we want other type, this is my edge + Anope::string t = res.Get(i, "other_type"); + Anope::string f = res.Get(i, "field"); + id = convertTo(res.Get(i, "other_id")); + + //XXX sanity checks + Serialize::FieldBase *obj_field = object->GetSerializableType()->GetField(f); + + Serialize::TypeBase *obj_type = Serialize::TypeBase::Find(t); + Serialize::Object *other = obj_type->Require(id); + + edges.emplace_back(other, obj_field, true); + } + else + { + // edge to me + Anope::string t = res.Get(i, "type"); + Anope::string f = res.Get(i, "field"); + + //XXX sanity checks + Serialize::TypeBase *obj_type = Serialize::TypeBase::Find(t); + Serialize::FieldBase *obj_field = obj_type->GetField(f); + Serialize::Object *other = obj_type->Require(id); + + // other type, other field, + edges.emplace_back(other, obj_field, false); + } + } + + return EVENT_ALLOW; + } + + EventReturn OnSerializeDeref(Serialize::ID id, Serialize::TypeBase *type) override + { + StartTransaction(); + + Query query = "SELECT `id` FROM `" + prefix + type->GetName() + "` WHERE `id` = @id@"; + query.SetValue("id", id); + Result res = Run(query); + if (res.Rows() == 0) + return EVENT_CONTINUE; + return EVENT_ALLOW; + } + + EventReturn OnSerializeGetSerializable(Serialize::Object *object, Serialize::FieldBase *field, Anope::string &type, Serialize::ID &value) override + { + StartTransaction(); + + Query query = "SELECT `" + field->GetName() + "`,j1.type AS " + field->GetName() + "_type FROM `" + prefix + object->GetSerializableType()->GetName() + "` " + "JOIN `" + prefix + "objects` AS j1 ON " + prefix + object->GetSerializableType()->GetName() + "." + field->GetName() + " = j1.id " + "WHERE " + prefix + object->GetSerializableType()->GetName() + ".id = @id@"; + query.SetValue("id", object->id); + Result res = Run(query); + + if (res.Rows() == 0) + return EVENT_CONTINUE; + + type = res.Get(0, field->GetName() + "_type"); + try + { + value = convertTo(res.Get(0, field->GetName())); + } + catch (const ConvertException &ex) + { + return EVENT_STOP; + } + + return EVENT_ALLOW; + } + + private: + void DoSet(Serialize::Object *object, Serialize::FieldBase *field, bool is_object, const Anope::string *value) + { + if (!SQL) + return; + + StartTransaction(); + + for (Query &q : SQL->CreateTable(prefix, object->GetSerializableType()->GetName())) + Run(q); + + for (Query &q : SQL->AlterTable(prefix, object->GetSerializableType()->GetName(), field->GetName(), is_object)) + Run(q); + + Query q; + q.SetValue("id", object->id); + if (value) + q.SetValue(field->GetName(), *value); + else + q.SetNull(field->GetName()); + + for (Query &q2 : SQL->Replace(prefix + object->GetSerializableType()->GetName(), q, { "id" })) + Run(q2); + } + + public: + EventReturn OnSerializeSet(Serialize::Object *object, Serialize::FieldBase *field, const Anope::string &value) override + { + DoSet(object, field, false, &value); + return EVENT_STOP; + } + + EventReturn OnSerializeSetSerializable(Serialize::Object *object, Serialize::FieldBase *field, Serialize::Object *value) override + { + if (!SQL) + return EVENT_CONTINUE; + + StartTransaction(); + + if (value) + { + Anope::string v = stringify(value->id); + DoSet(object, field, true, &v); + + Query query; + query.SetValue("field", field->GetName()); + query.SetValue("id", object->id); + query.SetValue("other_id", value->id); + + for (Query &q : SQL->Replace(prefix + "edges", query, { "id", "field" })) + Run(q); + } + else + { + DoSet(object, field, true, nullptr); + + Query query("DELETE FROM `" + prefix + "edges` WHERE `id` = @id@ AND `field` = @field@"); + query.SetValue("id", object->id); + query.SetValue("field", field->GetName()); + Run(query); + } + + return EVENT_STOP; + } + + EventReturn OnSerializeUnset(Serialize::Object *object, Serialize::FieldBase *field) override + { + DoSet(object, field, false, nullptr); + field->CacheMiss(object); + return EVENT_STOP; + } + + EventReturn OnSerializeUnsetSerializable(Serialize::Object *object, Serialize::FieldBase *field) override + { + DoSet(object, field, true, nullptr); + field->CacheMiss(object); + + Query query("DELETE FROM `" + prefix + "edges` WHERE `id` = @id@ AND `field` = @field@"); + query.SetValue("id", object->id); + query.SetValue("field", field->GetName()); + Run(query); + + return EVENT_STOP; + } + + EventReturn OnSerializeHasField(Serialize::Object *object, Serialize::FieldBase *field) override + { + SQL::Result::Value v; + + return GetValue(object, field, v) && !v.null ? EVENT_STOP : EVENT_CONTINUE; + } + + EventReturn OnSerializableGetId(Serialize::ID &id) override + { + StartTransaction(); + + id = SQL->GetID(prefix); + return EVENT_ALLOW; + } + + void OnSerializableCreate(Serialize::Object *object) override + { + StartTransaction(); + + Query q = Query("INSERT INTO `" + prefix + "objects` (`id`,`type`) VALUES (@id@, @type@)"); + q.SetValue("id", object->id); + q.SetValue("type", object->GetSerializableType()->GetName()); + Run(q); + } + + void OnSerializableDelete(Serialize::Object *object) override + { + StartTransaction(); + + Query query("DELETE FROM `" + prefix + object->GetSerializableType()->GetName() + "` WHERE `id` = " + stringify(object->id)); + Run(query); + } +}; + +MODULE_INIT(DBMySQL) + diff --git a/modules/dns.cpp b/modules/dns.cpp new file mode 100644 index 000000000..1ea70a6ed --- /dev/null +++ b/modules/dns.cpp @@ -0,0 +1,1136 @@ +/* + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + * + */ + +#include "module.h" +#include "modules/dns.h" + +using namespace DNS; + +namespace +{ + Anope::string admin, nameservers; + int refresh; + time_t timeout; +} + +/** A full packet sent or received to/from the nameserver + */ +class Packet : public Query +{ + static bool IsValidName(const Anope::string &name) + { + return name.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-") == Anope::string::npos; + } + + void PackName(unsigned char *output, unsigned short output_size, unsigned short &pos, const Anope::string &name) + { + if (pos + name.length() + 2 > output_size) + throw SocketException("Unable to pack name"); + + Log(LOG_DEBUG_2) << "Resolver: PackName packing " << name; + + sepstream sep(name, '.'); + Anope::string token; + + while (sep.GetToken(token)) + { + output[pos++] = token.length(); + memcpy(&output[pos], token.c_str(), token.length()); + pos += token.length(); + } + + output[pos++] = 0; + } + + Anope::string UnpackName(const unsigned char *input, unsigned short input_size, unsigned short &pos) + { + Anope::string name; + unsigned short pos_ptr = pos, lowest_ptr = input_size; + bool compressed = false; + + if (pos_ptr >= input_size) + throw SocketException("Unable to unpack name - no input"); + + while (input[pos_ptr] > 0) + { + unsigned short offset = input[pos_ptr]; + + if (offset & POINTER) + { + if ((offset & POINTER) != POINTER) + throw SocketException("Unable to unpack name - bogus compression header"); + if (pos_ptr + 1 >= input_size) + throw SocketException("Unable to unpack name - bogus compression header"); + + /* Place pos at the second byte of the first (farthest) compression pointer */ + if (compressed == false) + { + ++pos; + compressed = true; + } + + pos_ptr = (offset & LABEL) << 8 | input[pos_ptr + 1]; + + /* Pointers can only go back */ + if (pos_ptr >= lowest_ptr) + throw SocketException("Unable to unpack name - bogus compression pointer"); + lowest_ptr = pos_ptr; + } + else + { + if (pos_ptr + offset + 1 >= input_size) + throw SocketException("Unable to unpack name - offset too large"); + if (!name.empty()) + name += "."; + for (unsigned i = 1; i <= offset; ++i) + name += input[pos_ptr + i]; + + pos_ptr += offset + 1; + if (compressed == false) + /* Move up pos */ + pos = pos_ptr; + } + } + + /* +1 pos either to one byte after the compression pointer or one byte after the ending \0 */ + ++pos; + + if (name.empty()) + throw SocketException("Unable to unpack name - no name"); + + Log(LOG_DEBUG_2) << "Resolver: UnpackName successfully unpacked " << name; + + return name; + } + + Question UnpackQuestion(const unsigned char *input, unsigned short input_size, unsigned short &pos) + { + Question question; + + question.name = this->UnpackName(input, input_size, pos); + + if (pos + 4 > input_size) + throw SocketException("Unable to unpack question"); + + if (!IsValidName(question.name)) + throw SocketException("Invalid question name"); + + question.type = static_cast(input[pos] << 8 | input[pos + 1]); + pos += 2; + + question.qclass = input[pos] << 8 | input[pos + 1]; + pos += 2; + + return question; + } + + ResourceRecord UnpackResourceRecord(const unsigned char *input, unsigned short input_size, unsigned short &pos) + { + ResourceRecord record = static_cast(this->UnpackQuestion(input, input_size, pos)); + + if (pos + 6 > input_size) + throw SocketException("Unable to unpack resource record"); + + record.ttl = (input[pos] << 24) | (input[pos + 1] << 16) | (input[pos + 2] << 8) | input[pos + 3]; + pos += 4; + + //record.rdlength = input[pos] << 8 | input[pos + 1]; + pos += 2; + + switch (record.type) + { + case QUERY_A: + { + if (pos + 4 > input_size) + throw SocketException("Unable to unpack resource record"); + + in_addr a; + a.s_addr = input[pos] | (input[pos + 1] << 8) | (input[pos + 2] << 16) | (input[pos + 3] << 24); + pos += 4; + + sockaddrs addrs; + addrs.ntop(AF_INET, &a); + if (!addrs.valid()) + throw SocketException("Invalid IP"); + + record.rdata = addrs.addr(); + break; + } + case QUERY_AAAA: + { + if (pos + 16 > input_size) + throw SocketException("Unable to unpack resource record"); + + in6_addr a; + for (int j = 0; j < 16; ++j) + a.s6_addr[j] = input[pos + j]; + pos += 16; + + sockaddrs addrs; + addrs.ntop(AF_INET6, &a); + if (!addrs.valid()) + throw SocketException("Invalid IP"); + + record.rdata = addrs.addr(); + break; + } + case QUERY_CNAME: + case QUERY_PTR: + { + record.rdata = this->UnpackName(input, input_size, pos); + + if (!IsValidName(record.rdata)) + throw SocketException("Invalid cname/ptr record data"); + + break; + } + default: + break; + } + + Log(LOG_DEBUG_2) << "Resolver: " << record.name << " -> " << record.rdata; + + return record; + } + + public: + static const int POINTER = 0xC0; + static const int LABEL = 0x3F; + static const int HEADER_LENGTH = 12; + + Manager *manager; + /* Source or destination of the packet */ + sockaddrs addr; + /* ID for this packet */ + unsigned short id; + /* Flags on the packet */ + unsigned short flags; + + Packet(Manager *m, sockaddrs *a) : manager(m), id(0), flags(0) + { + if (a) + addr = *a; + } + + void Fill(const unsigned char *input, const unsigned short len) + { + if (len < HEADER_LENGTH) + throw SocketException("Unable to fill packet"); + + unsigned short packet_pos = 0; + + this->id = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + this->flags = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + unsigned short qdcount = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + unsigned short ancount = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + unsigned short nscount = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + unsigned short arcount = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + Log(LOG_DEBUG_2) << "Resolver: qdcount: " << qdcount << " ancount: " << ancount << " nscount: " << nscount << " arcount: " << arcount; + + for (unsigned i = 0; i < qdcount; ++i) + this->questions.push_back(this->UnpackQuestion(input, len, packet_pos)); + + for (unsigned i = 0; i < ancount; ++i) + this->answers.push_back(this->UnpackResourceRecord(input, len, packet_pos)); + + for (unsigned i = 0; i < nscount; ++i) + this->authorities.push_back(this->UnpackResourceRecord(input, len, packet_pos)); + + for (unsigned i = 0; i < arcount; ++i) + this->additional.push_back(this->UnpackResourceRecord(input, len, packet_pos)); + } + + unsigned short Pack(unsigned char *output, unsigned short output_size) + { + if (output_size < HEADER_LENGTH) + throw SocketException("Unable to pack packet"); + + unsigned short pos = 0; + + output[pos++] = this->id >> 8; + output[pos++] = this->id & 0xFF; + output[pos++] = this->flags >> 8; + output[pos++] = this->flags & 0xFF; + output[pos++] = this->questions.size() >> 8; + output[pos++] = this->questions.size() & 0xFF; + output[pos++] = this->answers.size() >> 8; + output[pos++] = this->answers.size() & 0xFF; + output[pos++] = this->authorities.size() >> 8; + output[pos++] = this->authorities.size() & 0xFF; + output[pos++] = this->additional.size() >> 8; + output[pos++] = this->additional.size() & 0xFF; + + for (unsigned i = 0; i < this->questions.size(); ++i) + { + Question &q = this->questions[i]; + + if (q.type == QUERY_PTR) + { + sockaddrs ip(q.name); + if (!ip.valid()) + throw SocketException("Invalid IP"); + + if (q.name.find(':') != Anope::string::npos) + { + const char *const hex = "0123456789abcdef"; + char reverse_ip[128]; + unsigned reverse_ip_count = 0; + for (int j = 15; j >= 0; --j) + { + reverse_ip[reverse_ip_count++] = hex[ip.sa6.sin6_addr.s6_addr[j] & 0xF]; + reverse_ip[reverse_ip_count++] = '.'; + reverse_ip[reverse_ip_count++] = hex[ip.sa6.sin6_addr.s6_addr[j] >> 4]; + reverse_ip[reverse_ip_count++] = '.'; + } + reverse_ip[reverse_ip_count++] = 0; + + q.name = reverse_ip; + q.name += "ip6.arpa"; + } + else + { + unsigned long forward = ip.sa4.sin_addr.s_addr; + in_addr reverse; + reverse.s_addr = forward << 24 | (forward & 0xFF00) << 8 | (forward & 0xFF0000) >> 8 | forward >> 24; + + ip.ntop(AF_INET, &reverse); + + q.name = ip.addr() + ".in-addr.arpa"; + } + } + + this->PackName(output, output_size, pos, q.name); + + if (pos + 4 >= output_size) + throw SocketException("Unable to pack packet"); + + short s = htons(q.type); + memcpy(&output[pos], &s, 2); + pos += 2; + + s = htons(q.qclass); + memcpy(&output[pos], &s, 2); + pos += 2; + } + + std::vector types[] = { this->answers, this->authorities, this->additional }; + for (int i = 0; i < 3; ++i) + for (unsigned j = 0; j < types[i].size(); ++j) + { + ResourceRecord &rr = types[i][j]; + + this->PackName(output, output_size, pos, rr.name); + + if (pos + 8 >= output_size) + throw SocketException("Unable to pack packet"); + + short s = htons(rr.type); + memcpy(&output[pos], &s, 2); + pos += 2; + + s = htons(rr.qclass); + memcpy(&output[pos], &s, 2); + pos += 2; + + long l = htonl(rr.ttl); + memcpy(&output[pos], &l, 4); + pos += 4; + + switch (rr.type) + { + case QUERY_A: + { + if (pos + 6 > output_size) + throw SocketException("Unable to pack packet"); + + sockaddrs a(rr.rdata); + if (!a.valid()) + throw SocketException("Invalid IP"); + + s = htons(4); + memcpy(&output[pos], &s, 2); + pos += 2; + + memcpy(&output[pos], &a.sa4.sin_addr, 4); + pos += 4; + break; + } + case QUERY_AAAA: + { + if (pos + 18 > output_size) + throw SocketException("Unable to pack packet"); + + sockaddrs a(rr.rdata); + if (!a.valid()) + throw SocketException("Invalid IP"); + + s = htons(16); + memcpy(&output[pos], &s, 2); + pos += 2; + + memcpy(&output[pos], &a.sa6.sin6_addr, 16); + pos += 16; + break; + } + case QUERY_NS: + case QUERY_CNAME: + case QUERY_PTR: + { + if (pos + 2 >= output_size) + throw SocketException("Unable to pack packet"); + + unsigned short packet_pos_save = pos; + pos += 2; + + this->PackName(output, output_size, pos, rr.rdata); + + s = htons(pos - packet_pos_save - 2); + memcpy(&output[packet_pos_save], &s, 2); + break; + } + case QUERY_SOA: + { + if (pos + 2 >= output_size) + throw SocketException("Unable to pack packet"); + + unsigned short packet_pos_save = pos; + pos += 2; + + std::vector ns; + spacesepstream(nameservers).GetTokens(ns); + this->PackName(output, output_size, pos, !ns.empty() ? ns[0] : ""); + this->PackName(output, output_size, pos, admin.replace_all_cs('@', '.')); + + if (pos + 20 >= output_size) + throw SocketException("Unable to pack SOA"); + + l = htonl(manager->GetSerial()); + memcpy(&output[pos], &l, 4); + pos += 4; + + l = htonl(refresh); // Refresh + memcpy(&output[pos], &l, 4); + pos += 4; + + l = htonl(refresh); // Retry + memcpy(&output[pos], &l, 4); + pos += 4; + + l = htonl(604800); // Expire + memcpy(&output[pos], &l, 4); + pos += 4; + + l = htonl(0); // Minimum + memcpy(&output[pos], &l, 4); + pos += 4; + + s = htons(pos - packet_pos_save - 2); + memcpy(&output[packet_pos_save], &s, 2); + + break; + } + default: + break; + } + } + + return pos; + } +}; + +namespace DNS +{ + class ReplySocket : public virtual Socket + { + public: + virtual ~ReplySocket() { } + virtual void Reply(Packet *p) = 0; + }; +} + +/* Listens for TCP requests */ +class TCPSocket : public ListenSocket +{ + Manager *manager; + + public: + /* A TCP client */ + class Client : public ClientSocket, public Timer, public ReplySocket + { + Manager *manager; + Packet *packet; + unsigned char packet_buffer[524]; + int length; + + public: + Client(Manager *m, TCPSocket *l, int fd, const sockaddrs &addr) : Socket(fd, l->IsIPv6()), ClientSocket(l, addr), Timer(5), + manager(m), packet(NULL), length(0) + { + Log(LOG_DEBUG_2) << "Resolver: New client from " << addr.addr(); + } + + ~Client() + { + Log(LOG_DEBUG_2) << "Resolver: Exiting client from " << clientaddr.addr(); + delete packet; + } + + /* Times out after a few seconds */ + void Tick(time_t) override { } + + void Reply(Packet *p) override + { + delete packet; + packet = p; + SocketEngine::Change(this, true, SF_WRITABLE); + } + + bool ProcessRead() override + { + Log(LOG_DEBUG_2) << "Resolver: Reading from DNS TCP socket"; + + int i = recv(this->GetFD(), reinterpret_cast(packet_buffer) + length, sizeof(packet_buffer) - length, 0); + if (i <= 0) + return false; + + length += i; + + unsigned short want_len = packet_buffer[0] << 8 | packet_buffer[1]; + if (length >= want_len + 2) + { + int len = length - 2; + length -= want_len + 2; + return this->manager->HandlePacket(this, packet_buffer + 2, len, NULL); + } + return true; + } + + bool ProcessWrite() override + { + Log(LOG_DEBUG_2) << "Resolver: Writing to DNS TCP socket"; + + if (packet != NULL) + { + try + { + unsigned char buffer[65535]; + unsigned short len = packet->Pack(buffer + 2, sizeof(buffer) - 2); + + short s = htons(len); + memcpy(buffer, &s, 2); + len += 2; + + send(this->GetFD(), reinterpret_cast(buffer), len, 0); + } + catch (const SocketException &) { } + + delete packet; + packet = NULL; + } + + SocketEngine::Change(this, false, SF_WRITABLE); + return true; /* Do not return false here, bind is unhappy we close the connection so soon after sending */ + } + }; + + TCPSocket(Manager *m, const Anope::string &ip, int port) : Socket(-1, ip.find(':') != Anope::string::npos), ListenSocket(ip, port, ip.find(':') != Anope::string::npos), manager(m) { } + + ClientSocket *OnAccept(int fd, const sockaddrs &addr) override + { + return new Client(this->manager, this, fd, addr); + } +}; + +/* Listens for UDP requests */ +class UDPSocket : public ReplySocket +{ + Manager *manager; + std::deque packets; + + public: + UDPSocket(Manager *m, const Anope::string &ip, int port) : Socket(-1, ip.find(':') != Anope::string::npos, SOCK_DGRAM), manager(m) { } + + ~UDPSocket() + { + for (unsigned i = 0; i < packets.size(); ++i) + delete packets[i]; + } + + void Reply(Packet *p) override + { + packets.push_back(p); + SocketEngine::Change(this, true, SF_WRITABLE); + } + + std::deque& GetPackets() { return packets; } + + bool ProcessRead() override + { + Log(LOG_DEBUG_2) << "Resolver: Reading from DNS UDP socket"; + + unsigned char packet_buffer[524]; + sockaddrs from_server; + socklen_t x = sizeof(from_server); + int length = recvfrom(this->GetFD(), reinterpret_cast(&packet_buffer), sizeof(packet_buffer), 0, &from_server.sa, &x); + return this->manager->HandlePacket(this, packet_buffer, length, &from_server); + } + + bool ProcessWrite() override + { + Log(LOG_DEBUG_2) << "Resolver: Writing to DNS UDP socket"; + + Packet *r = !packets.empty() ? packets.front() : NULL; + if (r != NULL) + { + try + { + unsigned char buffer[524]; + unsigned short len = r->Pack(buffer, sizeof(buffer)); + + sendto(this->GetFD(), reinterpret_cast(buffer), len, 0, &r->addr.sa, r->addr.size()); + } + catch (const SocketException &) { } + + delete r; + packets.pop_front(); + } + + if (packets.empty()) + SocketEngine::Change(this, false, SF_WRITABLE); + + return true; + } +}; + +class NotifySocket : public Socket +{ + Packet *packet; + public: + NotifySocket(bool v6, Packet *p) : Socket(-1, v6, SOCK_DGRAM), packet(p) + { + SocketEngine::Change(this, false, SF_READABLE); + SocketEngine::Change(this, true, SF_WRITABLE); + } + + bool ProcessWrite() override + { + if (!packet) + return false; + + Log(LOG_DEBUG_2) << "Resolver: Notifying slave " << packet->addr.addr(); + + try + { + unsigned char buffer[524]; + unsigned short len = packet->Pack(buffer, sizeof(buffer)); + + sendto(this->GetFD(), reinterpret_cast(buffer), len, 0, &packet->addr.sa, packet->addr.size()); + } + catch (const SocketException &) { } + + delete packet; + packet = NULL; + + return false; + } +}; + +class MyManager : public Manager, public Timer +{ + uint32_t serial; + + typedef std::unordered_map cache_map; + cache_map cache; + + TCPSocket *tcpsock; + UDPSocket *udpsock; + + bool listen; + sockaddrs addrs; + + std::vector > notify; + public: + std::map requests; + + MyManager(Module *creator) : Manager(creator), Timer(300, Anope::CurTime, true), serial(Anope::CurTime), tcpsock(NULL), udpsock(NULL), + listen(false), cur_id(rand()) + { + } + + ~MyManager() + { + delete udpsock; + delete tcpsock; + + for (std::map::iterator it = this->requests.begin(), it_end = this->requests.end(); it != it_end;) + { + Request *request = it->second; + ++it; + + Query rr(*request); + rr.error = ERROR_UNKNOWN; + request->OnError(&rr); + + delete request; + } + this->requests.clear(); + + this->cache.clear(); + } + + void SetIPPort(const Anope::string &nameserver, const Anope::string &ip, unsigned short port, std::vector > n) + { + delete udpsock; + delete tcpsock; + + udpsock = NULL; + tcpsock = NULL; + + try + { + this->addrs.pton(nameserver.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, nameserver, 53); + + udpsock = new UDPSocket(this, ip, port); + + if (!ip.empty()) + { + udpsock->Bind(ip, port); + tcpsock = new TCPSocket(this, ip, port); + listen = true; + } + } + catch (const SocketException &ex) + { + Log() << "Unable to bind dns to " << ip << ":" << port << ": " << ex.GetReason(); + } + + notify = n; + } + + private: + unsigned short cur_id; + + unsigned short GetID() + { + if (this->udpsock->GetPackets().size() == 65535) + throw SocketException("DNS queue full"); + + do + cur_id = (cur_id + 1) & 0xFFFF; + while (!cur_id || this->requests.count(cur_id)); + + return cur_id; + } + + public: + void Process(Request *req) override + { + Log(LOG_DEBUG_2) << "Resolver: Processing request to lookup " << req->name << ", of type " << req->type; + + if (req->use_cache && this->CheckCache(req)) + { + Log(LOG_DEBUG_2) << "Resolver: Using cached result"; + delete req; + return; + } + + if (!this->udpsock) + throw SocketException("No dns socket"); + + req->id = GetID(); + this->requests[req->id] = req; + + req->SetSecs(timeout); + + Packet *p = new Packet(this, &this->addrs); + p->flags = QUERYFLAGS_RD; + p->id = req->id; + p->questions.push_back(*req); + + this->udpsock->Reply(p); + } + + void RemoveRequest(Request *req) override + { + this->requests.erase(req->id); + } + + bool HandlePacket(ReplySocket *s, const unsigned char *const packet_buffer, int length, sockaddrs *from) override + { + if (length < Packet::HEADER_LENGTH) + return true; + + Packet recv_packet(this, from); + + try + { + recv_packet.Fill(packet_buffer, length); + } + catch (const SocketException &ex) + { + Log(LOG_DEBUG_2) << ex.GetReason(); + return true; + } + + if (!(recv_packet.flags & QUERYFLAGS_QR)) + { + if (!listen) + return true; + else if (recv_packet.questions.empty()) + { + Log(LOG_DEBUG_2) << "Resolver: Received a question with no questions?"; + return true; + } + + Packet *packet = new Packet(recv_packet); + packet->flags |= QUERYFLAGS_QR; /* This is a response */ + packet->flags |= QUERYFLAGS_AA; /* And we are authoritative */ + + packet->answers.clear(); + packet->authorities.clear(); + packet->additional.clear(); + + for (unsigned i = 0; i < recv_packet.questions.size(); ++i) + { + const Question& q = recv_packet.questions[i]; + + if (q.type == QUERY_AXFR || q.type == QUERY_SOA) + { + ResourceRecord rr(q.name, QUERY_SOA); + packet->answers.push_back(rr); + + if (q.type == QUERY_AXFR) + { + Anope::string token; + spacesepstream sep(nameservers); + while (sep.GetToken(token)) + { + ResourceRecord rr2(q.name, QUERY_NS); + rr2.rdata = token; + packet->answers.push_back(rr2); + } + } + break; + } + } + + Event::OnDnsRequest(&Event::DnsRequest::OnDnsRequest, recv_packet, packet); + + for (unsigned i = 0; i < recv_packet.questions.size(); ++i) + { + const Question& q = recv_packet.questions[i]; + + if (q.type == QUERY_AXFR) + { + ResourceRecord rr(q.name, QUERY_SOA); + packet->answers.push_back(rr); + break; + } + } + + s->Reply(packet); + return true; + } + + if (from == NULL) + { + Log(LOG_DEBUG_2) << "Resolver: Received an answer over TCP. This is not supported."; + return true; + } + else if (this->addrs != *from) + { + Log(LOG_DEBUG_2) << "Resolver: Received an answer from the wrong nameserver, Bad NAT or DNS forging attempt? '" << this->addrs.addr() << "' != '" << from->addr() << "'"; + return true; + } + + std::map::iterator it = this->requests.find(recv_packet.id); + if (it == this->requests.end()) + { + Log(LOG_DEBUG_2) << "Resolver: Received an answer for something we didn't request"; + return true; + } + Request *request = it->second; + + if (recv_packet.flags & QUERYFLAGS_OPCODE) + { + Log(LOG_DEBUG_2) << "Resolver: Received a nonstandard query"; + recv_packet.error = ERROR_NONSTANDARD_QUERY; + request->OnError(&recv_packet); + } + else if (recv_packet.flags & QUERYFLAGS_RCODE) + { + Error error = ERROR_UNKNOWN; + + switch (recv_packet.flags & QUERYFLAGS_RCODE) + { + case 1: + Log(LOG_DEBUG_2) << "Resolver: format error"; + error = ERROR_FORMAT_ERROR; + break; + case 2: + Log(LOG_DEBUG_2) << "Resolver: server error"; + error = ERROR_SERVER_FAILURE; + break; + case 3: + Log(LOG_DEBUG_2) << "Resolver: domain not found"; + error = ERROR_DOMAIN_NOT_FOUND; + break; + case 4: + Log(LOG_DEBUG_2) << "Resolver: not implemented"; + error = ERROR_NOT_IMPLEMENTED; + break; + case 5: + Log(LOG_DEBUG_2) << "Resolver: refused"; + error = ERROR_REFUSED; + break; + default: + break; + } + + recv_packet.error = error; + request->OnError(&recv_packet); + } + else if (recv_packet.questions.empty() || recv_packet.answers.empty()) + { + Log(LOG_DEBUG_2) << "Resolver: No resource records returned"; + recv_packet.error = ERROR_NO_RECORDS; + request->OnError(&recv_packet); + } + else + { + Log(LOG_DEBUG_2) << "Resolver: Lookup complete for " << request->name; + request->OnLookupComplete(&recv_packet); + this->AddCache(recv_packet); + } + + delete request; + return true; + } + + void UpdateSerial() override + { + serial = Anope::CurTime; + } + + void Notify(const Anope::string &zone) override + { + /* notify slaves of the update */ + for (unsigned i = 0; i < notify.size(); ++i) + { + const Anope::string &ip = notify[i].first; + short port = notify[i].second; + + sockaddrs addr; + addr.pton(ip.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, ip, port); + if (!addr.valid()) + return; + + Packet *packet = new Packet(this, &addr); + packet->flags = QUERYFLAGS_AA | QUERYFLAGS_OPCODE_NOTIFY; + try + { + packet->id = GetID(); + } + catch (const SocketException &) + { + delete packet; + continue; + } + + packet->questions.push_back(Question(zone, QUERY_SOA)); + + new NotifySocket(ip.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, packet); + } + } + + uint32_t GetSerial() const override + { + return serial; + } + + void Tick(time_t now) override + { + Log(LOG_DEBUG_2) << "Resolver: Purging DNS cache"; + + for (cache_map::iterator it = this->cache.begin(), it_next; it != this->cache.end(); it = it_next) + { + const Query &q = it->second; + const ResourceRecord &req = q.answers[0]; + it_next = it; + ++it_next; + + if (req.created + static_cast(req.ttl) < now) + this->cache.erase(it); + } + } + + private: + /** Add a record to the dns cache + * @param r The record + */ + void AddCache(Query &r) + { + const ResourceRecord &rr = r.answers[0]; + Log(LOG_DEBUG_3) << "Resolver cache: added cache for " << rr.name << " -> " << rr.rdata << ", ttl: " << rr.ttl; + this->cache[r.questions[0]] = r; + } + + /** Check the DNS cache to see if request can be handled by a cached result + * @return true if a cached result was found. + */ + bool CheckCache(Request *request) + { + cache_map::iterator it = this->cache.find(*request); + if (it != this->cache.end()) + { + Query &record = it->second; + Log(LOG_DEBUG_3) << "Resolver: Using cached result for " << request->name; + request->OnLookupComplete(&record); + return true; + } + + return false; + } + +}; + +class ModuleDNS : public Module + , public EventHook +{ + MyManager manager; + + Anope::string nameserver; + Anope::string ip; + int port; + + std::vector > notify; + + public: + ModuleDNS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + , manager(this) + { + + } + + ~ModuleDNS() + { + for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) + { + Socket *s = it->second; + ++it; + + if (dynamic_cast(s) || dynamic_cast(s)) + delete s; + } + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + + nameserver = block->Get("nameserver", "127.0.0.1"); + timeout = block->Get("timeout", "5"); + ip = block->Get("ip", "0.0.0.0"); + port = block->Get("port", "53"); + admin = block->Get("admin", "admin@example.com"); + nameservers = block->Get("nameservers", "ns1.example.com"); + refresh = block->Get("refresh", "3600"); + + for (int i = 0; i < block->CountBlock("notify"); ++i) + { + Configuration::Block *n = block->GetBlock("notify", i); + Anope::string nip = n->Get("ip"); + short nport = n->Get("port"); + + notify.push_back(std::make_pair(nip, nport)); + } + + if (Anope::IsFile(nameserver)) + { + std::ifstream f(nameserver.c_str()); + bool success = false; + + if (f.is_open()) + { + for (Anope::string server; std::getline(f, server.str());) + { + if (server.find("nameserver") == 0) + { + size_t i = server.find_first_of("123456789"); + if (i != Anope::string::npos) + { + if (server.substr(i).is_pos_number_only()) + { + nameserver = server.substr(i); + Log(LOG_DEBUG) << "Nameserver set to " << nameserver; + success = true; + break; + } + } + } + } + + f.close(); + } + + if (!success) + { + Log() << "Unable to find nameserver, defaulting to 127.0.0.1"; + nameserver = "127.0.0.1"; + } + } + + try + { + this->manager.SetIPPort(nameserver, ip, port, notify); + } + catch (const SocketException &ex) + { + throw ModuleException(ex.GetReason()); + } + } + + void OnModuleUnload(User *u, Module *m) override + { + for (std::map::iterator it = this->manager.requests.begin(), it_end = this->manager.requests.end(); it != it_end;) + { + unsigned short id = it->first; + Request *req = it->second; + ++it; + + if (req->creator == m) + { + Query rr(*req); + rr.error = ERROR_UNLOADED; + req->OnError(&rr); + + delete req; + this->manager.requests.erase(id); + } + } + } +}; + +MODULE_INIT(ModuleDNS) + diff --git a/modules/dnsbl.cpp b/modules/dnsbl.cpp new file mode 100644 index 000000000..49739a80c --- /dev/null +++ b/modules/dnsbl.cpp @@ -0,0 +1,202 @@ +/* + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" +#include "modules/dns.h" + +using namespace DNS; + +static ServiceReference akills("XLineManager", "xlinemanager/sgline"); + +struct Blacklist +{ + struct Reply + { + int code; + Anope::string reason; + bool allow_account; + + Reply() : code(0), allow_account(false) { } + }; + + Anope::string name; + time_t bantime; + Anope::string reason; + std::vector replies; + + Blacklist() : bantime(0) { } + + Reply *Find(int code) + { + for (unsigned int i = 0; i < replies.size(); ++i) + if (replies[i].code == code) + return &replies[i]; + return NULL; + } +}; + +class DNSBLResolver : public Request +{ + Reference user; + Blacklist blacklist; + bool add_to_akill; + + public: + DNSBLResolver(Module *c, User *u, const Blacklist &b, const Anope::string &host, bool add_akill) : Request(DNS::manager, c, host, QUERY_A, true), user(u), blacklist(b), add_to_akill(add_akill) { } + + void OnLookupComplete(const Query *record) override + { + if (!user || user->Quitting()) + return; + + const ResourceRecord &ans_record = record->answers[0]; + // Replies should be in 127.0.0.0/24 + if (ans_record.rdata.find("127.0.0.") != 0) + return; + + sockaddrs sresult; + sresult.pton(AF_INET, ans_record.rdata); + int result = sresult.sa4.sin_addr.s_addr >> 24; + + Blacklist::Reply *reply = blacklist.Find(result); + if (!blacklist.replies.empty() && !reply) + return; + + if (reply && reply->allow_account && user->Account()) + return; + + Anope::string reason = this->blacklist.reason, addr = user->ip.addr(); + reason = reason.replace_all_cs("%n", user->nick); + reason = reason.replace_all_cs("%u", user->GetIdent()); + reason = reason.replace_all_cs("%g", user->realname); + reason = reason.replace_all_cs("%h", user->host); + reason = reason.replace_all_cs("%i", addr); + reason = reason.replace_all_cs("%r", reply ? reply->reason : ""); + reason = reason.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); + + ServiceBot *OperServ = Config->GetClient("OperServ"); + Log(creator, "dnsbl", OperServ) << user->GetMask() << " (" << addr << ") appears in " << this->blacklist.name; + + XLine *x = new XLine(xline); + x->SetMask("*@" + addr); + x->SetBy(OperServ ? OperServ->nick : "m_dnsbl"); + x->SetCreated(Anope::CurTime); + x->SetExpires(Anope::CurTime + this->blacklist.bantime); + x->SetReason(reason); + x->SetID(XLineManager::GenerateUID()); + + if (this->add_to_akill && akills) + { + akills->AddXLine(x); + akills->Send(NULL, x); + } + else + { + IRCD->SendAkill(NULL, x); + delete x; + } + } +}; + +class ModuleDNSBL : public Module + , public EventHook +{ + std::vector blacklists; + std::set exempts; + bool check_on_connect; + bool check_on_netburst; + bool add_to_akill; + + public: + ModuleDNSBL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) + { + + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + this->check_on_connect = block->Get("check_on_connect"); + this->check_on_netburst = block->Get("check_on_netburst"); + this->add_to_akill = block->Get("add_to_akill", "yes"); + + this->blacklists.clear(); + for (int i = 0; i < block->CountBlock("blacklist"); ++i) + { + Configuration::Block *bl = block->GetBlock("blacklist", i); + Blacklist blacklist; + + blacklist.name = bl->Get("name"); + if (blacklist.name.empty()) + continue; + blacklist.bantime = bl->Get("time", "4h"); + blacklist.reason = bl->Get("reason"); + + for (int j = 0; j < bl->CountBlock("reply"); ++j) + { + Configuration::Block *reply = bl->GetBlock("reply", j); + Blacklist::Reply r; + + r.code = reply->Get("code"); + r.reason = reply->Get("reason"); + r.allow_account = reply->Get("allow_account"); + + blacklist.replies.push_back(r); + } + + this->blacklists.push_back(blacklist); + } + + this->exempts.clear(); + for (int i = 0; i < block->CountBlock("exempt"); ++i) + this->exempts.insert(block->Get("ip")); + } + + void OnUserConnect(User *user, bool &exempt) override + { + if (exempt || user->Quitting() || (!this->check_on_connect && !Me->IsSynced()) || !DNS::manager) + return; + + if (!this->check_on_netburst && !user->server->IsSynced()) + return; + + /* At this time we only support IPv4 */ + if (!user->ip.valid() || user->ip.sa.sa_family != AF_INET) + /* User doesn't have a valid IPv4 IP (ipv6/spoof/etc) */ + return; + + if (this->exempts.count(user->ip.addr())) + return; + + const unsigned long &ip = user->ip.sa4.sin_addr.s_addr; + unsigned long reverse_ip = (ip << 24) | ((ip & 0xFF00) << 8) | ((ip & 0xFF0000) >> 8) | (ip >> 24); + + sockaddrs reverse = user->ip; + reverse.sa4.sin_addr.s_addr = reverse_ip; + + for (unsigned i = 0; i < this->blacklists.size(); ++i) + { + const Blacklist &b = this->blacklists[i]; + + Anope::string dnsbl_host = reverse.addr() + "." + b.name; + DNSBLResolver *res = NULL; + try + { + res = new DNSBLResolver(this, user, b, dnsbl_host, this->add_to_akill); + DNS::manager->Process(res); + } + catch (const SocketException &ex) + { + delete res; + Log(this) << ex.GetReason(); + } + } + } +}; + +MODULE_INIT(ModuleDNSBL) + diff --git a/modules/encryption/CMakeLists.txt b/modules/encryption/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/encryption/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/encryption/bcrypt.cpp b/modules/encryption/bcrypt.cpp new file mode 100644 index 000000000..74d729491 --- /dev/null +++ b/modules/encryption/bcrypt.cpp @@ -0,0 +1,969 @@ +/* Module for providing bcrypt hashing + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * This program is free but copyrighted software; see the file COPYING for + * details. + * + * Most of the code in this file is taken from + * http://openwall.com/crypt/crypt_blowfish-1.2.tar.gz +*/ + +/* + * The crypt_blowfish homepage is: + * + * http://www.openwall.com/crypt/ + * + * This code comes from John the Ripper password cracker, with reentrant + * and crypt(3) interfaces added, but optimizations specific to password + * cracking removed. + * + * Written by Solar Designer in 1998-2011. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 1998-2011 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * It is my intent that you should be able to use this on your system, + * as part of a software package, or anywhere else to improve security, + * ensure compatibility, or for any other purpose. I would appreciate + * it if you give credit where it is due and keep your modifications in + * the public domain as well, but I don't require that in order to let + * you place this code and any modifications you make under a license + * of your choice. + * + * This implementation is mostly compatible with OpenBSD's bcrypt.c (prefix + * "$2a$") by Niels Provos , and uses some of his + * ideas. The password hashing algorithm was designed by David Mazieres + * . For more information on the level of compatibility, + * prefer refer to the comments in BF_set_key() below and to the included + * crypt(3) man page. + * + * There's a paper on the algorithm that explains its design decisions: + * + * http://www.usenix.org/events/usenix99/provos.html + * + * Some of the tricks in BF_ROUND might be inspired by Eric Young's + * Blowfish library (I can't be sure if I would think of something if I + * hadn't seen his code). + */ + +#include + +#ifdef __i386__ +#define BF_SCALE 1 +#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) +#define BF_SCALE 1 +#else +#define BF_SCALE 0 +#endif + +typedef unsigned int BF_word; +typedef signed int BF_word_signed; + +/* Number of Blowfish rounds, this is also hardcoded into a few places */ +#define BF_N 16 + +typedef BF_word BF_key[BF_N + 2]; + +typedef struct { + BF_word S[4][0x100]; + BF_key P; +} BF_ctx; + +/* + * Magic IV for 64 Blowfish encryptions that we do at the end. + * The string is "OrpheanBeholderScryDoubt" on big-endian. + */ +static BF_word BF_magic_w[6] = { + 0x4F727068, 0x65616E42, 0x65686F6C, + 0x64657253, 0x63727944, 0x6F756274 +}; + +/* + * P-box and S-box tables initialized with digits of Pi. + */ +static BF_ctx BF_init_state = { + { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + }, { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + }, { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + }, { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + } + }, { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } +}; + +static unsigned char BF_itoa64[64 + 1] = + "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +static unsigned char BF_atoi64[0x60] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, + 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, + 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 +}; + +#define BF_safe_atoi64(dst, src) \ +{ \ + tmp = (unsigned char)(src); \ + if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ + tmp = BF_atoi64[tmp]; \ + if (tmp > 63) return -1; \ + (dst) = tmp; \ +} + +static int BF_decode(BF_word *dst, const char *src, int size) +{ + unsigned char *dptr = (unsigned char *)dst; + unsigned char *end = dptr + size; + const unsigned char *sptr = (const unsigned char *)src; + unsigned int tmp, c1, c2, c3, c4; + + do { + BF_safe_atoi64(c1, *sptr++); + BF_safe_atoi64(c2, *sptr++); + *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); + if (dptr >= end) break; + + BF_safe_atoi64(c3, *sptr++); + *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); + if (dptr >= end) break; + + BF_safe_atoi64(c4, *sptr++); + *dptr++ = ((c3 & 0x03) << 6) | c4; + } while (dptr < end); + + return 0; +} + +static void BF_encode(char *dst, const BF_word *src, int size) +{ + const unsigned char *sptr = (const unsigned char *)src; + const unsigned char *end = sptr + size; + unsigned char *dptr = (unsigned char *)dst; + unsigned int c1, c2; + + do { + c1 = *sptr++; + *dptr++ = BF_itoa64[c1 >> 2]; + c1 = (c1 & 0x03) << 4; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 4; + *dptr++ = BF_itoa64[c1]; + c1 = (c2 & 0x0f) << 2; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 6; + *dptr++ = BF_itoa64[c1]; + *dptr++ = BF_itoa64[c2 & 0x3f]; + } while (sptr < end); +} + +static void BF_swap(BF_word *x, int count) +{ + static int endianness_check = 1; + char *is_little_endian = (char *)&endianness_check; + BF_word tmp; + + if (*is_little_endian) + do { + tmp = *x; + tmp = (tmp << 16) | (tmp >> 16); + *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); + } while (--count); +} + +#if BF_SCALE +/* Architectures which can shift addresses left by 2 bits with no extra cost */ +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp2 = L >> 8; \ + tmp2 &= 0xFF; \ + tmp3 = L >> 16; \ + tmp3 &= 0xFF; \ + tmp4 = L >> 24; \ + tmp1 = data.ctx.S[3][tmp1]; \ + tmp2 = data.ctx.S[2][tmp2]; \ + tmp3 = data.ctx.S[1][tmp3]; \ + tmp3 += data.ctx.S[0][tmp4]; \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#else +/* Architectures with no complicated addressing modes supported */ +#define BF_INDEX(S, i) \ + (*((BF_word *)(((unsigned char *)S) + (i)))) +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp1 <<= 2; \ + tmp2 = L >> 6; \ + tmp2 &= 0x3FC; \ + tmp3 = L >> 14; \ + tmp3 &= 0x3FC; \ + tmp4 = L >> 22; \ + tmp4 &= 0x3FC; \ + tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ + tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ + tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ + tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#endif + +/* + * Encrypt one block, BF_N is hardcoded here. + */ +#define BF_ENCRYPT \ + L ^= data.ctx.P[0]; \ + BF_ROUND(L, R, 0); \ + BF_ROUND(R, L, 1); \ + BF_ROUND(L, R, 2); \ + BF_ROUND(R, L, 3); \ + BF_ROUND(L, R, 4); \ + BF_ROUND(R, L, 5); \ + BF_ROUND(L, R, 6); \ + BF_ROUND(R, L, 7); \ + BF_ROUND(L, R, 8); \ + BF_ROUND(R, L, 9); \ + BF_ROUND(L, R, 10); \ + BF_ROUND(R, L, 11); \ + BF_ROUND(L, R, 12); \ + BF_ROUND(R, L, 13); \ + BF_ROUND(L, R, 14); \ + BF_ROUND(R, L, 15); \ + tmp4 = R; \ + R = L; \ + L = tmp4 ^ data.ctx.P[BF_N + 1]; + +#define BF_body() \ + L = R = 0; \ + ptr = data.ctx.P; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.P[BF_N + 2]); \ +\ + ptr = data.ctx.S[0]; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.S[3][0xFF]); + +static void BF_set_key(const char *key, BF_key expanded, BF_key initial, + unsigned char flags) +{ + const char *ptr = key; + unsigned int bug, i, j; + BF_word safety, sign, diff, tmp[2]; + +/* + * There was a sign extension bug in older revisions of this function. While + * we would have liked to simply fix the bug and move on, we have to provide + * a backwards compatibility feature (essentially the bug) for some systems and + * a safety measure for some others. The latter is needed because for certain + * multiple inputs to the buggy algorithm there exist easily found inputs to + * the correct algorithm that produce the same hash. Thus, we optionally + * deviate from the correct algorithm just enough to avoid such collisions. + * While the bug itself affected the majority of passwords containing + * characters with the 8th bit set (although only a percentage of those in a + * collision-producing way), the anti-collision safety measure affects + * only a subset of passwords containing the '\xff' character (not even all of + * those passwords, just some of them). This character is not found in valid + * UTF-8 sequences and is rarely used in popular 8-bit character encodings. + * Thus, the safety measure is unlikely to cause much annoyance, and is a + * reasonable tradeoff to use when authenticating against existing hashes that + * are not reliably known to have been computed with the correct algorithm. + * + * We use an approach that tries to minimize side-channel leaks of password + * information - that is, we mostly use fixed-cost bitwise operations instead + * of branches or table lookups. (One conditional branch based on password + * length remains. It is not part of the bug aftermath, though, and is + * difficult and possibly unreasonable to avoid given the use of C strings by + * the caller, which results in similar timing leaks anyway.) + * + * For actual implementation, we set an array index in the variable "bug" + * (0 means no bug, 1 means sign extension bug emulation) and a flag in the + * variable "safety" (bit 16 is set when the safety measure is requested). + * Valid combinations of settings are: + * + * Prefix "$2a$": bug = 0, safety = 0x10000 + * Prefix "$2x$": bug = 1, safety = 0 + * Prefix "$2y$": bug = 0, safety = 0 + */ + bug = (unsigned int)flags & 1; + safety = ((BF_word)flags & 2) << 15; + + sign = diff = 0; + + for (i = 0; i < BF_N + 2; i++) { + tmp[0] = tmp[1] = 0; + for (j = 0; j < 4; j++) { + tmp[0] <<= 8; + tmp[0] |= (unsigned char)*ptr; /* correct */ + tmp[1] <<= 8; + tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ +/* + * Sign extension in the first char has no effect - nothing to overwrite yet, + * and those extra 24 bits will be fully shifted out of the 32-bit word. For + * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign + * extension in tmp[1] occurs. Once this flag is set, it remains set. + */ + if (j) + sign |= tmp[1] & 0x80; + if (!*ptr) + ptr = key; + else + ptr++; + } + diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ + + expanded[i] = tmp[bug]; + initial[i] = BF_init_state.P[i] ^ tmp[bug]; + } + +/* + * At this point, "diff" is zero iff the correct and buggy algorithms produced + * exactly the same result. If so and if "sign" is non-zero, which indicates + * that there was a non-benign sign extension, this means that we have a + * collision between the correctly computed hash for this password and a set of + * passwords that could be supplied to the buggy algorithm. Our safety measure + * is meant to protect from such many-buggy to one-correct collisions, by + * deviating from the correct algorithm in such cases. Let's check for this. + */ + diff |= diff >> 16; /* still zero iff exact match */ + diff &= 0xffff; /* ditto */ + diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ + sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ + sign &= ~diff & safety; /* action needed? */ + +/* + * If we have determined that we need to deviate from the correct algorithm, + * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but + * let's stick to it now. It came out of the approach we used above, and it's + * not any worse than any other choice we could make.) + * + * It is crucial that we don't do the same to the expanded key used in the main + * Eksblowfish loop. By doing it to only one of these two, we deviate from a + * state that could be directly specified by a password to the buggy algorithm + * (and to the fully correct one as well, but that's a side-effect). + */ + initial[0] ^= sign; +} + +static char *BF_crypt(const char *key, const char *setting, + char *output, int size, + BF_word min) +{ + static const unsigned char flags_by_subtype[26] = + {2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; + struct { + BF_ctx ctx; + BF_key expanded_key; + union { + BF_word salt[4]; + BF_word output[6]; + } binary; + } data; + BF_word L, R; + BF_word tmp1, tmp2, tmp3, tmp4; + BF_word *ptr; + BF_word count; + int i; + + if (size < 7 + 22 + 31 + 1) { + return NULL; + } + + if (setting[0] != '$' || + setting[1] != '2' || + setting[2] < 'a' || setting[2] > 'z' || + !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || + setting[3] != '$' || + setting[4] < '0' || setting[4] > '3' || + setting[5] < '0' || setting[5] > '9' || + (setting[4] == '3' && setting[5] > '1') || + setting[6] != '$') { + return NULL; + } + + count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); + if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { + return NULL; + } + BF_swap(data.binary.salt, 4); + + BF_set_key(key, data.expanded_key, data.ctx.P, + flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); + + memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); + + L = R = 0; + for (i = 0; i < BF_N + 2; i += 2) { + L ^= data.binary.salt[i & 2]; + R ^= data.binary.salt[(i & 2) + 1]; + BF_ENCRYPT; + data.ctx.P[i] = L; + data.ctx.P[i + 1] = R; + } + + ptr = data.ctx.S[0]; + do { + ptr += 4; + L ^= data.binary.salt[(BF_N + 2) & 3]; + R ^= data.binary.salt[(BF_N + 3) & 3]; + BF_ENCRYPT; + *(ptr - 4) = L; + *(ptr - 3) = R; + + L ^= data.binary.salt[(BF_N + 4) & 3]; + R ^= data.binary.salt[(BF_N + 5) & 3]; + BF_ENCRYPT; + *(ptr - 2) = L; + *(ptr - 1) = R; + } while (ptr < &data.ctx.S[3][0xFF]); + + do { + int done; + + for (i = 0; i < BF_N + 2; i += 2) { + data.ctx.P[i] ^= data.expanded_key[i]; + data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; + } + + done = 0; + do { + BF_body(); + if (done) + break; + done = 1; + + tmp1 = data.binary.salt[0]; + tmp2 = data.binary.salt[1]; + tmp3 = data.binary.salt[2]; + tmp4 = data.binary.salt[3]; + for (i = 0; i < BF_N; i += 4) { + data.ctx.P[i] ^= tmp1; + data.ctx.P[i + 1] ^= tmp2; + data.ctx.P[i + 2] ^= tmp3; + data.ctx.P[i + 3] ^= tmp4; + } + data.ctx.P[16] ^= tmp1; + data.ctx.P[17] ^= tmp2; + } while (1); + } while (--count); + + for (i = 0; i < 6; i += 2) { + L = BF_magic_w[i]; + R = BF_magic_w[i + 1]; + + count = 64; + do { + BF_ENCRYPT; + } while (--count); + + data.binary.output[i] = L; + data.binary.output[i + 1] = R; + } + + memcpy(output, setting, 7 + 22 - 1); + output[7 + 22 - 1] = BF_itoa64[(int) + BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; + +/* This has to be bug-compatible with the original implementation, so + * only encode 23 of the 24 bytes. :-) */ + BF_swap(data.binary.output, 6); + BF_encode(&output[7 + 22], data.binary.output, 23); + output[7 + 22 + 31] = '\0'; + + return output; +} + +int _crypt_output_magic(const char *setting, char *output, int size) +{ + if (size < 3) + return -1; + + output[0] = '*'; + output[1] = '0'; + output[2] = '\0'; + + if (setting[0] == '*' && setting[1] == '0') + output[1] = '1'; + + return 0; +} + +/* + * Please preserve the runtime self-test. It serves two purposes at once: + * + * 1. We really can't afford the risk of producing incompatible hashes e.g. + * when there's something like gcc bug 26587 again, whereas an application or + * library integrating this code might not also integrate our external tests or + * it might not run them after every build. Even if it does, the miscompile + * might only occur on the production build, but not on a testing build (such + * as because of different optimization settings). It is painful to recover + * from incorrectly-computed hashes - merely fixing whatever broke is not + * enough. Thus, a proactive measure like this self-test is needed. + * + * 2. We don't want to leave sensitive data from our actual password hash + * computation on the stack or in registers. Previous revisions of the code + * would do explicit cleanups, but simply running the self-test after hash + * computation is more reliable. + * + * The performance cost of this quick self-test is around 0.6% at the "$2a$08" + * setting. + */ +char *_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size) +{ + _crypt_output_magic(setting, output, size); + return BF_crypt(key, setting, output, size, 16); +} + +char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + if (size < 16 || output_size < 7 + 22 + 1 || + (count && (count < 4 || count > 31)) || + prefix[0] != '$' || prefix[1] != '2' || + (prefix[2] != 'a' && prefix[2] != 'y')) { + if (output_size > 0) output[0] = '\0'; + return NULL; + } + + if (!count) count = 5; + + output[0] = '$'; + output[1] = '2'; + output[2] = prefix[2]; + output[3] = '$'; + output[4] = '0' + count / 10; + output[5] = '0' + count % 10; + output[6] = '$'; + + BF_encode(&output[7], (const BF_word *)input, 16); + output[7 + 22] = '\0'; + + return output; +} + +// Start Anope-specific code + +#include "module.h" +#include "modules/encryption.h" +#include "modules/nickserv.h" + +class EBCRYPT : public Module + , public EventHook + , public EventHook +{ + unsigned int rounds; + + Anope::string Salt() + { + char entropy[16]; + for (unsigned int i = 0; i < sizeof(entropy); i++) + entropy[i] = static_cast(rand() % 0xFF); + + char salt[32]; + if (!_crypt_gensalt_blowfish_rn("$2a$", rounds, entropy, sizeof(entropy), salt, sizeof(salt))) + return ""; + return salt; + } + + Anope::string Generate(const Anope::string& data, const Anope::string& salt) + { + char hash[64]; + _crypt_blowfish_rn(data.c_str(), salt.c_str(), hash, sizeof(hash)); + return hash; + } + + bool Compare(const Anope::string& string, const Anope::string& hash) + { + Anope::string ret = Generate(string, hash); + if (ret.empty()) + return false; + + return (ret == hash); + } + + public: + EBCRYPT(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR) + , rounds(10) + { + // Test a pre-calculated hash + bool test = Compare("Test!", "$2a$10$x9AQFAQScY0v9KF2suqkEOepsHFrG.CXHbIXI.1F28SfSUb56A/7K"); + + Anope::string salt; + Anope::string hash; + // Make sure it's working + if (!test || (salt = Salt()).empty() || (hash = Generate("Test!", salt)).empty() || !Compare("Test!", hash)) + throw ModuleException("BCrypt could not load!"); + } + + EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override + { + dest = "bcrypt:" + Generate(src, Salt()); + Log(LOG_DEBUG_2) << "(enc_bcrypt) hashed password from [" << src << "] to [" << dest << "]"; + return EVENT_ALLOW; + } + + void OnCheckAuthentication(User *, NickServ::IdentifyRequest *req) override + { + NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); + if (na == NULL) + return; + NickServ::Account *nc = na->GetAccount(); + + size_t pos = nc->GetPassword().find(':'); + if (pos == Anope::string::npos) + return; + Anope::string hash_method(nc->GetPassword().begin(), nc->GetPassword().begin() + pos); + if (hash_method != "bcrypt") + return; + + if (Compare(req->GetPassword(), nc->GetPassword().substr(7))) + { + /* if we are NOT the first module in the list, + * we want to re-encrypt the pass with the new encryption + */ + + unsigned int hashrounds = 0; + try + { + size_t roundspos = nc->GetPassword().find('$', 11); + if (roundspos == Anope::string::npos) + throw ConvertException("Could not find hashrounds"); + + hashrounds = convertTo(nc->GetPassword().substr(11, roundspos - 11)); + } + catch (const ConvertException &) + { + Log(this) << "Could not get the round size of a hash. This is probably a bug. Hash: " << nc->GetPassword(); + } + + if (ModuleManager::FindFirstOf(ENCRYPTION) != this || (hashrounds && hashrounds != rounds)) + { + Anope::string p; + Anope::Encrypt(req->GetPassword(), p); + nc->SetPassword(p); + } + req->Success(this); + } + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + rounds = block->Get("rounds", "10"); + + if (rounds == 0) + { + rounds = 10; + Log(this) << "Rounds can't be 0! Setting ignored."; + } + else if (rounds < 10) + { + Log(this) << "10 to 12 rounds is recommended."; + } + else if (rounds >= 32) + { + rounds = 10; + Log(this) << "The maximum number of rounds supported is 31. Ignoring setting and using 10."; + } + else if (rounds >= 14) + { + Log(this) << "Are you sure you want to use " << stringify(rounds) << " in your bcrypt settings? This is very CPU intensive! Recommended rounds is 10-12."; + } + } +}; + +MODULE_INIT(EBCRYPT) diff --git a/modules/encryption/enc_bcrypt.cpp b/modules/encryption/enc_bcrypt.cpp deleted file mode 100644 index 74d729491..000000000 --- a/modules/encryption/enc_bcrypt.cpp +++ /dev/null @@ -1,969 +0,0 @@ -/* Module for providing bcrypt hashing - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * This program is free but copyrighted software; see the file COPYING for - * details. - * - * Most of the code in this file is taken from - * http://openwall.com/crypt/crypt_blowfish-1.2.tar.gz -*/ - -/* - * The crypt_blowfish homepage is: - * - * http://www.openwall.com/crypt/ - * - * This code comes from John the Ripper password cracker, with reentrant - * and crypt(3) interfaces added, but optimizations specific to password - * cracking removed. - * - * Written by Solar Designer in 1998-2011. - * No copyright is claimed, and the software is hereby placed in the public - * domain. In case this attempt to disclaim copyright and place the software - * in the public domain is deemed null and void, then the software is - * Copyright (c) 1998-2011 Solar Designer and it is hereby released to the - * general public under the following terms: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted. - * - * There's ABSOLUTELY NO WARRANTY, express or implied. - * - * It is my intent that you should be able to use this on your system, - * as part of a software package, or anywhere else to improve security, - * ensure compatibility, or for any other purpose. I would appreciate - * it if you give credit where it is due and keep your modifications in - * the public domain as well, but I don't require that in order to let - * you place this code and any modifications you make under a license - * of your choice. - * - * This implementation is mostly compatible with OpenBSD's bcrypt.c (prefix - * "$2a$") by Niels Provos , and uses some of his - * ideas. The password hashing algorithm was designed by David Mazieres - * . For more information on the level of compatibility, - * prefer refer to the comments in BF_set_key() below and to the included - * crypt(3) man page. - * - * There's a paper on the algorithm that explains its design decisions: - * - * http://www.usenix.org/events/usenix99/provos.html - * - * Some of the tricks in BF_ROUND might be inspired by Eric Young's - * Blowfish library (I can't be sure if I would think of something if I - * hadn't seen his code). - */ - -#include - -#ifdef __i386__ -#define BF_SCALE 1 -#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) -#define BF_SCALE 1 -#else -#define BF_SCALE 0 -#endif - -typedef unsigned int BF_word; -typedef signed int BF_word_signed; - -/* Number of Blowfish rounds, this is also hardcoded into a few places */ -#define BF_N 16 - -typedef BF_word BF_key[BF_N + 2]; - -typedef struct { - BF_word S[4][0x100]; - BF_key P; -} BF_ctx; - -/* - * Magic IV for 64 Blowfish encryptions that we do at the end. - * The string is "OrpheanBeholderScryDoubt" on big-endian. - */ -static BF_word BF_magic_w[6] = { - 0x4F727068, 0x65616E42, 0x65686F6C, - 0x64657253, 0x63727944, 0x6F756274 -}; - -/* - * P-box and S-box tables initialized with digits of Pi. - */ -static BF_ctx BF_init_state = { - { - { - 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, - 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, - 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, - 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, - 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, - 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, - 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, - 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, - 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, - 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, - 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, - 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, - 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, - 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, - 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, - 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, - 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, - 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, - 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, - 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, - 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, - 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, - 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, - 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, - 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, - 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, - 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, - 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, - 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, - 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, - 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, - 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, - 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, - 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, - 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, - 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, - 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, - 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, - 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, - 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, - 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, - 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, - 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, - 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, - 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, - 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, - 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, - 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, - 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, - 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, - 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, - 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, - 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, - 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, - 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, - 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, - 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, - 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, - 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, - 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, - 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, - 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, - 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, - 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a - }, { - 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, - 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, - 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, - 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, - 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, - 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, - 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, - 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, - 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, - 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, - 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, - 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, - 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, - 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, - 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, - 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, - 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, - 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, - 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, - 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, - 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, - 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, - 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, - 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, - 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, - 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, - 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, - 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, - 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, - 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, - 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, - 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, - 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, - 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, - 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, - 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, - 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, - 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, - 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, - 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, - 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, - 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, - 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, - 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, - 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, - 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, - 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, - 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, - 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, - 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, - 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, - 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, - 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, - 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, - 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, - 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, - 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, - 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, - 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, - 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, - 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, - 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, - 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, - 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 - }, { - 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, - 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, - 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, - 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, - 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, - 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, - 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, - 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, - 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, - 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, - 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, - 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, - 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, - 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, - 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, - 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, - 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, - 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, - 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, - 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, - 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, - 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, - 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, - 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, - 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, - 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, - 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, - 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, - 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, - 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, - 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, - 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, - 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, - 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, - 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, - 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, - 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, - 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, - 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, - 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, - 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, - 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, - 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, - 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, - 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, - 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, - 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, - 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, - 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, - 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, - 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, - 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, - 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, - 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, - 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, - 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, - 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, - 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, - 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, - 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, - 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, - 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, - 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, - 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 - }, { - 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, - 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, - 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, - 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, - 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, - 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, - 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, - 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, - 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, - 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, - 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, - 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, - 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, - 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, - 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, - 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, - 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, - 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, - 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, - 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, - 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, - 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, - 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, - 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, - 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, - 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, - 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, - 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, - 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, - 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, - 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, - 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, - 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, - 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, - 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, - 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, - 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, - 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, - 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, - 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, - 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, - 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, - 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, - 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, - 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, - 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, - 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, - 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, - 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, - 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, - 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, - 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, - 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, - 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, - 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, - 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, - 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, - 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, - 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, - 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, - 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, - 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, - 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, - 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 - } - }, { - 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, - 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, - 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, - 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, - 0x9216d5d9, 0x8979fb1b - } -}; - -static unsigned char BF_itoa64[64 + 1] = - "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - -static unsigned char BF_atoi64[0x60] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, - 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, - 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, - 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, - 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 -}; - -#define BF_safe_atoi64(dst, src) \ -{ \ - tmp = (unsigned char)(src); \ - if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ - tmp = BF_atoi64[tmp]; \ - if (tmp > 63) return -1; \ - (dst) = tmp; \ -} - -static int BF_decode(BF_word *dst, const char *src, int size) -{ - unsigned char *dptr = (unsigned char *)dst; - unsigned char *end = dptr + size; - const unsigned char *sptr = (const unsigned char *)src; - unsigned int tmp, c1, c2, c3, c4; - - do { - BF_safe_atoi64(c1, *sptr++); - BF_safe_atoi64(c2, *sptr++); - *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); - if (dptr >= end) break; - - BF_safe_atoi64(c3, *sptr++); - *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); - if (dptr >= end) break; - - BF_safe_atoi64(c4, *sptr++); - *dptr++ = ((c3 & 0x03) << 6) | c4; - } while (dptr < end); - - return 0; -} - -static void BF_encode(char *dst, const BF_word *src, int size) -{ - const unsigned char *sptr = (const unsigned char *)src; - const unsigned char *end = sptr + size; - unsigned char *dptr = (unsigned char *)dst; - unsigned int c1, c2; - - do { - c1 = *sptr++; - *dptr++ = BF_itoa64[c1 >> 2]; - c1 = (c1 & 0x03) << 4; - if (sptr >= end) { - *dptr++ = BF_itoa64[c1]; - break; - } - - c2 = *sptr++; - c1 |= c2 >> 4; - *dptr++ = BF_itoa64[c1]; - c1 = (c2 & 0x0f) << 2; - if (sptr >= end) { - *dptr++ = BF_itoa64[c1]; - break; - } - - c2 = *sptr++; - c1 |= c2 >> 6; - *dptr++ = BF_itoa64[c1]; - *dptr++ = BF_itoa64[c2 & 0x3f]; - } while (sptr < end); -} - -static void BF_swap(BF_word *x, int count) -{ - static int endianness_check = 1; - char *is_little_endian = (char *)&endianness_check; - BF_word tmp; - - if (*is_little_endian) - do { - tmp = *x; - tmp = (tmp << 16) | (tmp >> 16); - *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); - } while (--count); -} - -#if BF_SCALE -/* Architectures which can shift addresses left by 2 bits with no extra cost */ -#define BF_ROUND(L, R, N) \ - tmp1 = L & 0xFF; \ - tmp2 = L >> 8; \ - tmp2 &= 0xFF; \ - tmp3 = L >> 16; \ - tmp3 &= 0xFF; \ - tmp4 = L >> 24; \ - tmp1 = data.ctx.S[3][tmp1]; \ - tmp2 = data.ctx.S[2][tmp2]; \ - tmp3 = data.ctx.S[1][tmp3]; \ - tmp3 += data.ctx.S[0][tmp4]; \ - tmp3 ^= tmp2; \ - R ^= data.ctx.P[N + 1]; \ - tmp3 += tmp1; \ - R ^= tmp3; -#else -/* Architectures with no complicated addressing modes supported */ -#define BF_INDEX(S, i) \ - (*((BF_word *)(((unsigned char *)S) + (i)))) -#define BF_ROUND(L, R, N) \ - tmp1 = L & 0xFF; \ - tmp1 <<= 2; \ - tmp2 = L >> 6; \ - tmp2 &= 0x3FC; \ - tmp3 = L >> 14; \ - tmp3 &= 0x3FC; \ - tmp4 = L >> 22; \ - tmp4 &= 0x3FC; \ - tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ - tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ - tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ - tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ - tmp3 ^= tmp2; \ - R ^= data.ctx.P[N + 1]; \ - tmp3 += tmp1; \ - R ^= tmp3; -#endif - -/* - * Encrypt one block, BF_N is hardcoded here. - */ -#define BF_ENCRYPT \ - L ^= data.ctx.P[0]; \ - BF_ROUND(L, R, 0); \ - BF_ROUND(R, L, 1); \ - BF_ROUND(L, R, 2); \ - BF_ROUND(R, L, 3); \ - BF_ROUND(L, R, 4); \ - BF_ROUND(R, L, 5); \ - BF_ROUND(L, R, 6); \ - BF_ROUND(R, L, 7); \ - BF_ROUND(L, R, 8); \ - BF_ROUND(R, L, 9); \ - BF_ROUND(L, R, 10); \ - BF_ROUND(R, L, 11); \ - BF_ROUND(L, R, 12); \ - BF_ROUND(R, L, 13); \ - BF_ROUND(L, R, 14); \ - BF_ROUND(R, L, 15); \ - tmp4 = R; \ - R = L; \ - L = tmp4 ^ data.ctx.P[BF_N + 1]; - -#define BF_body() \ - L = R = 0; \ - ptr = data.ctx.P; \ - do { \ - ptr += 2; \ - BF_ENCRYPT; \ - *(ptr - 2) = L; \ - *(ptr - 1) = R; \ - } while (ptr < &data.ctx.P[BF_N + 2]); \ -\ - ptr = data.ctx.S[0]; \ - do { \ - ptr += 2; \ - BF_ENCRYPT; \ - *(ptr - 2) = L; \ - *(ptr - 1) = R; \ - } while (ptr < &data.ctx.S[3][0xFF]); - -static void BF_set_key(const char *key, BF_key expanded, BF_key initial, - unsigned char flags) -{ - const char *ptr = key; - unsigned int bug, i, j; - BF_word safety, sign, diff, tmp[2]; - -/* - * There was a sign extension bug in older revisions of this function. While - * we would have liked to simply fix the bug and move on, we have to provide - * a backwards compatibility feature (essentially the bug) for some systems and - * a safety measure for some others. The latter is needed because for certain - * multiple inputs to the buggy algorithm there exist easily found inputs to - * the correct algorithm that produce the same hash. Thus, we optionally - * deviate from the correct algorithm just enough to avoid such collisions. - * While the bug itself affected the majority of passwords containing - * characters with the 8th bit set (although only a percentage of those in a - * collision-producing way), the anti-collision safety measure affects - * only a subset of passwords containing the '\xff' character (not even all of - * those passwords, just some of them). This character is not found in valid - * UTF-8 sequences and is rarely used in popular 8-bit character encodings. - * Thus, the safety measure is unlikely to cause much annoyance, and is a - * reasonable tradeoff to use when authenticating against existing hashes that - * are not reliably known to have been computed with the correct algorithm. - * - * We use an approach that tries to minimize side-channel leaks of password - * information - that is, we mostly use fixed-cost bitwise operations instead - * of branches or table lookups. (One conditional branch based on password - * length remains. It is not part of the bug aftermath, though, and is - * difficult and possibly unreasonable to avoid given the use of C strings by - * the caller, which results in similar timing leaks anyway.) - * - * For actual implementation, we set an array index in the variable "bug" - * (0 means no bug, 1 means sign extension bug emulation) and a flag in the - * variable "safety" (bit 16 is set when the safety measure is requested). - * Valid combinations of settings are: - * - * Prefix "$2a$": bug = 0, safety = 0x10000 - * Prefix "$2x$": bug = 1, safety = 0 - * Prefix "$2y$": bug = 0, safety = 0 - */ - bug = (unsigned int)flags & 1; - safety = ((BF_word)flags & 2) << 15; - - sign = diff = 0; - - for (i = 0; i < BF_N + 2; i++) { - tmp[0] = tmp[1] = 0; - for (j = 0; j < 4; j++) { - tmp[0] <<= 8; - tmp[0] |= (unsigned char)*ptr; /* correct */ - tmp[1] <<= 8; - tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ -/* - * Sign extension in the first char has no effect - nothing to overwrite yet, - * and those extra 24 bits will be fully shifted out of the 32-bit word. For - * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign - * extension in tmp[1] occurs. Once this flag is set, it remains set. - */ - if (j) - sign |= tmp[1] & 0x80; - if (!*ptr) - ptr = key; - else - ptr++; - } - diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ - - expanded[i] = tmp[bug]; - initial[i] = BF_init_state.P[i] ^ tmp[bug]; - } - -/* - * At this point, "diff" is zero iff the correct and buggy algorithms produced - * exactly the same result. If so and if "sign" is non-zero, which indicates - * that there was a non-benign sign extension, this means that we have a - * collision between the correctly computed hash for this password and a set of - * passwords that could be supplied to the buggy algorithm. Our safety measure - * is meant to protect from such many-buggy to one-correct collisions, by - * deviating from the correct algorithm in such cases. Let's check for this. - */ - diff |= diff >> 16; /* still zero iff exact match */ - diff &= 0xffff; /* ditto */ - diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ - sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ - sign &= ~diff & safety; /* action needed? */ - -/* - * If we have determined that we need to deviate from the correct algorithm, - * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but - * let's stick to it now. It came out of the approach we used above, and it's - * not any worse than any other choice we could make.) - * - * It is crucial that we don't do the same to the expanded key used in the main - * Eksblowfish loop. By doing it to only one of these two, we deviate from a - * state that could be directly specified by a password to the buggy algorithm - * (and to the fully correct one as well, but that's a side-effect). - */ - initial[0] ^= sign; -} - -static char *BF_crypt(const char *key, const char *setting, - char *output, int size, - BF_word min) -{ - static const unsigned char flags_by_subtype[26] = - {2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; - struct { - BF_ctx ctx; - BF_key expanded_key; - union { - BF_word salt[4]; - BF_word output[6]; - } binary; - } data; - BF_word L, R; - BF_word tmp1, tmp2, tmp3, tmp4; - BF_word *ptr; - BF_word count; - int i; - - if (size < 7 + 22 + 31 + 1) { - return NULL; - } - - if (setting[0] != '$' || - setting[1] != '2' || - setting[2] < 'a' || setting[2] > 'z' || - !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || - setting[3] != '$' || - setting[4] < '0' || setting[4] > '3' || - setting[5] < '0' || setting[5] > '9' || - (setting[4] == '3' && setting[5] > '1') || - setting[6] != '$') { - return NULL; - } - - count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); - if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { - return NULL; - } - BF_swap(data.binary.salt, 4); - - BF_set_key(key, data.expanded_key, data.ctx.P, - flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); - - memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); - - L = R = 0; - for (i = 0; i < BF_N + 2; i += 2) { - L ^= data.binary.salt[i & 2]; - R ^= data.binary.salt[(i & 2) + 1]; - BF_ENCRYPT; - data.ctx.P[i] = L; - data.ctx.P[i + 1] = R; - } - - ptr = data.ctx.S[0]; - do { - ptr += 4; - L ^= data.binary.salt[(BF_N + 2) & 3]; - R ^= data.binary.salt[(BF_N + 3) & 3]; - BF_ENCRYPT; - *(ptr - 4) = L; - *(ptr - 3) = R; - - L ^= data.binary.salt[(BF_N + 4) & 3]; - R ^= data.binary.salt[(BF_N + 5) & 3]; - BF_ENCRYPT; - *(ptr - 2) = L; - *(ptr - 1) = R; - } while (ptr < &data.ctx.S[3][0xFF]); - - do { - int done; - - for (i = 0; i < BF_N + 2; i += 2) { - data.ctx.P[i] ^= data.expanded_key[i]; - data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; - } - - done = 0; - do { - BF_body(); - if (done) - break; - done = 1; - - tmp1 = data.binary.salt[0]; - tmp2 = data.binary.salt[1]; - tmp3 = data.binary.salt[2]; - tmp4 = data.binary.salt[3]; - for (i = 0; i < BF_N; i += 4) { - data.ctx.P[i] ^= tmp1; - data.ctx.P[i + 1] ^= tmp2; - data.ctx.P[i + 2] ^= tmp3; - data.ctx.P[i + 3] ^= tmp4; - } - data.ctx.P[16] ^= tmp1; - data.ctx.P[17] ^= tmp2; - } while (1); - } while (--count); - - for (i = 0; i < 6; i += 2) { - L = BF_magic_w[i]; - R = BF_magic_w[i + 1]; - - count = 64; - do { - BF_ENCRYPT; - } while (--count); - - data.binary.output[i] = L; - data.binary.output[i + 1] = R; - } - - memcpy(output, setting, 7 + 22 - 1); - output[7 + 22 - 1] = BF_itoa64[(int) - BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; - -/* This has to be bug-compatible with the original implementation, so - * only encode 23 of the 24 bytes. :-) */ - BF_swap(data.binary.output, 6); - BF_encode(&output[7 + 22], data.binary.output, 23); - output[7 + 22 + 31] = '\0'; - - return output; -} - -int _crypt_output_magic(const char *setting, char *output, int size) -{ - if (size < 3) - return -1; - - output[0] = '*'; - output[1] = '0'; - output[2] = '\0'; - - if (setting[0] == '*' && setting[1] == '0') - output[1] = '1'; - - return 0; -} - -/* - * Please preserve the runtime self-test. It serves two purposes at once: - * - * 1. We really can't afford the risk of producing incompatible hashes e.g. - * when there's something like gcc bug 26587 again, whereas an application or - * library integrating this code might not also integrate our external tests or - * it might not run them after every build. Even if it does, the miscompile - * might only occur on the production build, but not on a testing build (such - * as because of different optimization settings). It is painful to recover - * from incorrectly-computed hashes - merely fixing whatever broke is not - * enough. Thus, a proactive measure like this self-test is needed. - * - * 2. We don't want to leave sensitive data from our actual password hash - * computation on the stack or in registers. Previous revisions of the code - * would do explicit cleanups, but simply running the self-test after hash - * computation is more reliable. - * - * The performance cost of this quick self-test is around 0.6% at the "$2a$08" - * setting. - */ -char *_crypt_blowfish_rn(const char *key, const char *setting, - char *output, int size) -{ - _crypt_output_magic(setting, output, size); - return BF_crypt(key, setting, output, size, 16); -} - -char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size) -{ - if (size < 16 || output_size < 7 + 22 + 1 || - (count && (count < 4 || count > 31)) || - prefix[0] != '$' || prefix[1] != '2' || - (prefix[2] != 'a' && prefix[2] != 'y')) { - if (output_size > 0) output[0] = '\0'; - return NULL; - } - - if (!count) count = 5; - - output[0] = '$'; - output[1] = '2'; - output[2] = prefix[2]; - output[3] = '$'; - output[4] = '0' + count / 10; - output[5] = '0' + count % 10; - output[6] = '$'; - - BF_encode(&output[7], (const BF_word *)input, 16); - output[7 + 22] = '\0'; - - return output; -} - -// Start Anope-specific code - -#include "module.h" -#include "modules/encryption.h" -#include "modules/nickserv.h" - -class EBCRYPT : public Module - , public EventHook - , public EventHook -{ - unsigned int rounds; - - Anope::string Salt() - { - char entropy[16]; - for (unsigned int i = 0; i < sizeof(entropy); i++) - entropy[i] = static_cast(rand() % 0xFF); - - char salt[32]; - if (!_crypt_gensalt_blowfish_rn("$2a$", rounds, entropy, sizeof(entropy), salt, sizeof(salt))) - return ""; - return salt; - } - - Anope::string Generate(const Anope::string& data, const Anope::string& salt) - { - char hash[64]; - _crypt_blowfish_rn(data.c_str(), salt.c_str(), hash, sizeof(hash)); - return hash; - } - - bool Compare(const Anope::string& string, const Anope::string& hash) - { - Anope::string ret = Generate(string, hash); - if (ret.empty()) - return false; - - return (ret == hash); - } - - public: - EBCRYPT(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR) - , rounds(10) - { - // Test a pre-calculated hash - bool test = Compare("Test!", "$2a$10$x9AQFAQScY0v9KF2suqkEOepsHFrG.CXHbIXI.1F28SfSUb56A/7K"); - - Anope::string salt; - Anope::string hash; - // Make sure it's working - if (!test || (salt = Salt()).empty() || (hash = Generate("Test!", salt)).empty() || !Compare("Test!", hash)) - throw ModuleException("BCrypt could not load!"); - } - - EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override - { - dest = "bcrypt:" + Generate(src, Salt()); - Log(LOG_DEBUG_2) << "(enc_bcrypt) hashed password from [" << src << "] to [" << dest << "]"; - return EVENT_ALLOW; - } - - void OnCheckAuthentication(User *, NickServ::IdentifyRequest *req) override - { - NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); - if (na == NULL) - return; - NickServ::Account *nc = na->GetAccount(); - - size_t pos = nc->GetPassword().find(':'); - if (pos == Anope::string::npos) - return; - Anope::string hash_method(nc->GetPassword().begin(), nc->GetPassword().begin() + pos); - if (hash_method != "bcrypt") - return; - - if (Compare(req->GetPassword(), nc->GetPassword().substr(7))) - { - /* if we are NOT the first module in the list, - * we want to re-encrypt the pass with the new encryption - */ - - unsigned int hashrounds = 0; - try - { - size_t roundspos = nc->GetPassword().find('$', 11); - if (roundspos == Anope::string::npos) - throw ConvertException("Could not find hashrounds"); - - hashrounds = convertTo(nc->GetPassword().substr(11, roundspos - 11)); - } - catch (const ConvertException &) - { - Log(this) << "Could not get the round size of a hash. This is probably a bug. Hash: " << nc->GetPassword(); - } - - if (ModuleManager::FindFirstOf(ENCRYPTION) != this || (hashrounds && hashrounds != rounds)) - { - Anope::string p; - Anope::Encrypt(req->GetPassword(), p); - nc->SetPassword(p); - } - req->Success(this); - } - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - rounds = block->Get("rounds", "10"); - - if (rounds == 0) - { - rounds = 10; - Log(this) << "Rounds can't be 0! Setting ignored."; - } - else if (rounds < 10) - { - Log(this) << "10 to 12 rounds is recommended."; - } - else if (rounds >= 32) - { - rounds = 10; - Log(this) << "The maximum number of rounds supported is 31. Ignoring setting and using 10."; - } - else if (rounds >= 14) - { - Log(this) << "Are you sure you want to use " << stringify(rounds) << " in your bcrypt settings? This is very CPU intensive! Recommended rounds is 10-12."; - } - } -}; - -MODULE_INIT(EBCRYPT) diff --git a/modules/encryption/enc_md5.cpp b/modules/encryption/enc_md5.cpp deleted file mode 100644 index a92f5a4f9..000000000 --- a/modules/encryption/enc_md5.cpp +++ /dev/null @@ -1,402 +0,0 @@ -/* Module for encryption using MD5. - * - * Modified for Anope. - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Taken from IRC Services and is copyright (c) 1996-2002 Andrew Church. - * E-mail: - * Parts written by Andrew Kempe and others. - * This program is free but copyrighted software; see the file COPYING for - * details. - */ - -#include "module.h" -#include "modules/encryption.h" - -/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All -rights reserved. - -License to copy and use this software is granted provided that it -is identified as the "RSA Data Security, Inc. MD5 Message-Digest -Algorithm" in all material mentioning or referencing this software -or this function. - -License is also granted to make and use derivative works provided -that such works are identified as "derived from the RSA Data -Security, Inc. MD5 Message-Digest Algorithm" in all material -mentioning or referencing the derived work. - -RSA Data Security, Inc. makes no representations concerning either -the merchantability of this software or the suitability of this -software for any particular purpose. It is provided "as is" -without express or implied warranty of any kind. - -These notices must be retained in any copies of any part of this -documentation and/or software. - */ - -static unsigned char PADDING[64] = { - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -/* F, G, H and I are basic MD5 functions. - */ -inline static unsigned F(unsigned x, unsigned y, unsigned z) { return (x & y) | (~x & z); } -inline static unsigned G(unsigned x, unsigned y, unsigned z) { return (x & z) | (y & ~z); } -inline static unsigned H(unsigned x, unsigned y, unsigned z) { return x ^ y ^ z; } -inline static unsigned I(unsigned x, unsigned y, unsigned z) { return y ^ (x | ~z); } - -/* ROTATE_LEFT rotates x left n bits. - */ -inline static unsigned ROTATE_LEFT(unsigned x, unsigned n) { return (x << n) | (x >> (32 - n)); } - -/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. - * Rotation is separate from addition to prevent recomputation. - */ -inline static void FF(unsigned &a, unsigned b, unsigned c, unsigned d, unsigned x, unsigned s, unsigned ac) -{ - a += F(b, c, d) + x + ac; - a = ROTATE_LEFT(a, s); - a += b; -} -inline static void GG(unsigned &a, unsigned b, unsigned c, unsigned d, unsigned x, unsigned s, unsigned ac) -{ - a += G(b, c, d) + x + ac; - a = ROTATE_LEFT(a, s); - a += b; -} -inline static void HH(unsigned &a, unsigned b, unsigned c, unsigned d, unsigned x, unsigned s, unsigned ac) -{ - a += H(b, c, d) + x + ac; - a = ROTATE_LEFT(a, s); - a += b; -} -inline static void II(unsigned &a, unsigned b, unsigned c, unsigned d, unsigned x, unsigned s, unsigned ac) -{ - a += I(b, c, d) + x + ac; - a = ROTATE_LEFT(a, s); - a += b; -} - -static const uint32_t md5_iv[4] = -{ - 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 -}; - -class MD5Context : public Encryption::Context -{ - unsigned state[4]; /* state (ABCD) */ - unsigned count[2]; /* number of bits, modulo 2^64 (lsb first) */ - unsigned char buffer[64]; /* input buffer */ - unsigned char digest[16]; /* final digest */ - - /* Constants for MD5Transform routine. - */ - enum - { - S11 = 7, - S12 = 12, - S13 = 17, - S14 = 22, - S21 = 5, - S22 = 9, - S23 = 14, - S24 = 20, - S31 = 4, - S32 = 11, - S33 = 16, - S34 = 23, - S41 = 6, - S42 = 10, - S43 = 15, - S44 = 21 - }; - - /* MD5 basic transformation. Transforms state based on block. - */ - void Transform(const unsigned char block[64]) - { - unsigned a = state[0], b = state[1], c = state[2], d = state[3], x[16]; - - Decode(x, block, 64); - - /* Round 1 */ - FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */ - FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */ - FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */ - FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */ - FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */ - FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */ - FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */ - FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */ - FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */ - FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */ - FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ - FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ - FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ - FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ - FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ - FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ - - /* Round 2 */ - GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */ - GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */ - GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ - GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */ - GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */ - GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */ - GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ - GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */ - GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */ - GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ - GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */ - GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */ - GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ - GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */ - GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */ - GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ - - /* Round 3 */ - HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */ - HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */ - HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ - HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ - HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */ - HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */ - HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */ - HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ - HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ - HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */ - HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */ - HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */ - HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */ - HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ - HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ - HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */ - - /* Round 4 */ - II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */ - II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */ - II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ - II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */ - II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ - II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */ - II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ - II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */ - II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */ - II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ - II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */ - II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ - II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */ - II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ - II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */ - II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */ - - state[0] += a; - state[1] += b; - state[2] += c; - state[3] += d; - - /* Zeroize sensitive information. */ - memset(x, 0, sizeof(x)); - } - - /* Encodes input (unsigned) into output (unsigned char). Assumes len is - * a multiple of 4. - */ - void Encode(unsigned char *output, unsigned *input, unsigned len) - { - for (unsigned i = 0, j = 0; j < len; ++i, j += 4) - { - output[j] = static_cast(input[i] & 0xff); - output[j + 1] = static_cast((input[i] >> 8) & 0xff); - output[j + 2] = static_cast((input[i] >> 16) & 0xff); - output[j + 3] = static_cast((input[i] >> 24) & 0xff); - } - } - - /* Decodes input (unsigned char) into output (unsigned). Assumes len is - * a multiple of 4. - */ - void Decode(unsigned *output, const unsigned char *input, unsigned len) - { - for (unsigned i = 0, j = 0; j < len; ++i, j += 4) - output[i] = static_cast(input[j]) | (static_cast(input[j + 1]) << 8) | (static_cast(input[j + 2]) << 16) | (static_cast(input[j + 3]) << 24); - } - - public: - MD5Context(Encryption::IV *iv = NULL) - { - if (iv != NULL) - { - if (iv->second != 4) - throw CoreException("Invalid IV size"); - /* Load magic initialization constants. */ - for (int i = 0; i < 4; ++i) - this->state[i] = iv->first[i]; - } - else - for (int i = 0; i < 4; ++i) - this->state[i] = md5_iv[i]; - - this->count[0] = this->count[1] = 0; - memset(this->buffer, 0, sizeof(this->buffer)); - } - - /* MD5 block update operation. Continues an MD5 message-digest - * operation, processing another message block, and updating the - * context. - */ - void Update(const unsigned char *input, size_t len) override - { - unsigned i, index, partLen; - - /* Compute number of bytes mod 64 */ - index = (this->count[0] >> 3) & 0x3F; - - /* Update number of bits */ - if ((this->count[0] += len << 3) < (len << 3)) - ++this->count[1]; - this->count[1] += len >> 29; - - partLen = 64 - index; - - /* Transform as many times as possible. */ - if (len >= partLen) - { - memcpy(&this->buffer[index], input, partLen); - this->Transform(this->buffer); - - for (i = partLen; i + 63 < len; i += 64) - this->Transform(&input[i]); - - index = 0; - } - else - i = 0; - - /* Buffer remaining input */ - memcpy(&this->buffer[index], &input[i], len - i); - } - - /* MD5 finalization. Ends an MD5 message-digest opera - * the message digest and zeroizing the context. - */ - void Finalize() override - { - unsigned char bits[8]; - unsigned index, padLen; - - /* Save number of bits */ - this->Encode(bits, this->count, 8); - - /* Pad out to 56 mod 64. */ - index = (this->count[0] >> 3) & 0x3f; - padLen = index < 56 ? 56 - index : 120 - index; - this->Update(PADDING, padLen); - - /* Append length (before padding) */ - this->Update(bits, 8); - /* Store state in digest */ - this->Encode(digest, this->state, 16); - - /* Zeroize sensitive information. */ - memset(this->state, 0, sizeof(this->state)); - memset(this->count, 0, sizeof(this->count)); - memset(this->buffer, 0, sizeof(this->buffer)); - } - - Encryption::Hash GetFinalizedHash() override - { - Encryption::Hash hash; - hash.first = this->digest; - hash.second = sizeof(this->digest); - return hash; - } -}; - -class MD5Provider : public Encryption::Provider -{ - public: - MD5Provider(Module *creator) : Encryption::Provider(creator, "md5") { } - - Encryption::Context *CreateContext(Encryption::IV *iv) override - { - return new MD5Context(iv); - } - - Encryption::IV GetDefaultIV() override - { - Encryption::IV iv; - iv.first = md5_iv; - iv.second = sizeof(md5_iv) / sizeof(uint32_t); - return iv; - } -}; - -class EMD5 : public Module - , public EventHook - , public EventHook -{ - MD5Provider md5provider; - - public: - EMD5(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR) - , md5provider(this) - { - if (ModuleManager::FindFirstOf(ENCRYPTION) == this) - throw ModuleException("enc_md5 is deprecated and can not be used as a primary encryption method"); - } - - EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override - { - MD5Context context; - - context.Update(reinterpret_cast(src.c_str()), src.length()); - context.Finalize(); - - Encryption::Hash hash = context.GetFinalizedHash(); - - Anope::string buf = "md5:" + Anope::Hex(reinterpret_cast(hash.first), hash.second); - - Log(LOG_DEBUG_2) << "(enc_md5) hashed password from [" << src << "] to [" << buf << "]"; - dest = buf; - return EVENT_ALLOW; - } - - void OnCheckAuthentication(User *, NickServ::IdentifyRequest *req) override - { - NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); - if (na == NULL) - return; - NickServ::Account *nc = na->GetAccount(); - - size_t pos = nc->GetPassword().find(':'); - if (pos == Anope::string::npos) - return; - Anope::string hash_method(nc->GetPassword().begin(), nc->GetPassword().begin() + pos); - if (!hash_method.equals_cs("md5")) - return; - - Anope::string buf; - this->OnEncrypt(req->GetPassword(), buf); - if (nc->GetPassword().equals_cs(buf)) - { - /* if we are NOT the first module in the list, - * we want to re-encrypt the pass with the new encryption - */ - if (ModuleManager::FindFirstOf(ENCRYPTION) != this) - { - Anope::string p; - Anope::Encrypt(req->GetPassword(), p); - nc->SetPassword(p); - } - req->Success(this); - } - } -}; - -MODULE_INIT(EMD5) diff --git a/modules/encryption/enc_none.cpp b/modules/encryption/enc_none.cpp deleted file mode 100644 index 8164534eb..000000000 --- a/modules/encryption/enc_none.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* Module for plain text encryption. - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * This program is free but copyrighted software; see the file COPYING for - * details. - */ - -#include "module.h" - -class ENone : public Module - , public EventHook - , public EventHook - , public EventHook -{ - public: - ENone(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR) - { - - } - - EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override - { - Anope::string buf = "plain:"; - Anope::string cpass; - Anope::B64Encode(src, cpass); - buf += cpass; - Log(LOG_DEBUG_2) << "(enc_none) hashed password from [" << src << "] to [" << buf << "]"; - dest = buf; - return EVENT_ALLOW; - } - - EventReturn OnDecrypt(const Anope::string &hashm, const Anope::string &src, Anope::string &dest) override - { - if (!hashm.equals_cs("plain")) - return EVENT_CONTINUE; - size_t pos = src.find(':'); - Anope::string buf = src.substr(pos + 1); - Anope::B64Decode(buf, dest); - return EVENT_ALLOW; - } - - void OnCheckAuthentication(User *, NickServ::IdentifyRequest *req) override - { - NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); - if (na == NULL) - return; - NickServ::Account *nc = na->GetAccount(); - - size_t pos = nc->GetPassword().find(':'); - if (pos == Anope::string::npos) - return; - Anope::string hash_method(nc->GetPassword().begin(), nc->GetPassword().begin() + pos); - if (!hash_method.equals_cs("plain")) - return; - - Anope::string buf; - this->OnEncrypt(req->GetPassword(), buf); - if (nc->GetPassword().equals_cs(buf)) - { - /* if we are NOT the first module in the list, - * we want to re-encrypt the pass with the new encryption - */ - if (ModuleManager::FindFirstOf(ENCRYPTION) != this) - { - Anope::string p; - Anope::Encrypt(req->GetPassword(), p); - nc->SetPassword(p); - } - req->Success(this); - } - } -}; - -MODULE_INIT(ENone) diff --git a/modules/encryption/enc_old.cpp b/modules/encryption/enc_old.cpp deleted file mode 100644 index 4e3e70f7d..000000000 --- a/modules/encryption/enc_old.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* Include file for high-level encryption routines. - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/encryption.h" - -static ServiceReference md5("Encryption::Provider", "md5"); - -class OldMD5Provider : public Encryption::Provider -{ - public: - OldMD5Provider(Module *creator) : Encryption::Provider(creator, "oldmd5") { } - - Encryption::Context *CreateContext(Encryption::IV *iv) override - { - if (md5) - return md5->CreateContext(iv); - return NULL; - } - - Encryption::IV GetDefaultIV() override - { - if (md5) - return md5->GetDefaultIV(); - return Encryption::IV(static_cast(NULL), 0); - } -}; - -class EOld : public Module - , public EventHook - , public EventHook -{ - OldMD5Provider oldmd5provider; - - inline static char XTOI(char c) { return c > 9 ? c - 'A' + 10 : c - '0'; } - - public: - EOld(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR) - , oldmd5provider(this) - { - if (ModuleManager::FindFirstOf(ENCRYPTION) == this) - throw ModuleException("enc_old is deprecated and can not be used as a primary encryption method"); - - ModuleManager::LoadModule("enc_md5", User::Find(creator)); - if (!md5) - throw ModuleException("Unable to find md5 reference"); - - } - - EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override - { - if (!md5) - return EVENT_CONTINUE; - - Encryption::Context *context = md5->CreateContext(); - context->Update(reinterpret_cast(src.c_str()), src.length()); - context->Finalize(); - - Encryption::Hash hash = context->GetFinalizedHash(); - - char digest[32], digest2[16]; - memset(digest, 0, sizeof(digest)); - if (hash.second > sizeof(digest)) - throw CoreException("Hash too large"); - memcpy(digest, hash.first, hash.second); - - for (int i = 0; i < 32; i += 2) - digest2[i / 2] = XTOI(digest[i]) << 4 | XTOI(digest[i + 1]); - - Anope::string buf = "oldmd5:" + Anope::Hex(digest2, sizeof(digest2)); - - Log(LOG_DEBUG_2) << "(enc_old) hashed password from [" << src << "] to [" << buf << "]"; - dest = buf; - delete context; - return EVENT_ALLOW; - } - - void OnCheckAuthentication(User *, NickServ::IdentifyRequest *req) override - { - NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); - if (na == NULL) - return; - NickServ::Account *nc = na->GetAccount(); - - size_t pos = nc->GetPassword().find(':'); - if (pos == Anope::string::npos) - return; - Anope::string hash_method(nc->GetPassword().begin(), nc->GetPassword().begin() + pos); - if (!hash_method.equals_cs("oldmd5")) - return; - - Anope::string buf; - this->OnEncrypt(req->GetPassword(), buf); - if (nc->GetPassword().equals_cs(buf)) - { - /* if we are NOT the first module in the list, - * we want to re-encrypt the pass with the new encryption - */ - if (ModuleManager::FindFirstOf(ENCRYPTION) != this) - { - Anope::string p; - Anope::Encrypt(req->GetPassword(), p); - nc->SetPassword(p); - } - req->Success(this); - } - } -}; - -MODULE_INIT(EOld) diff --git a/modules/encryption/enc_sha1.cpp b/modules/encryption/enc_sha1.cpp deleted file mode 100644 index a99bc834e..000000000 --- a/modules/encryption/enc_sha1.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/* -SHA-1 in C -By Steve Reid -100% Public Domain - -Test Vectors (from FIPS PUB 180-1) -"abc" - A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D -"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" - 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 -A million repetitions of "a" - 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F -*/ - -/* #define LITTLE_ENDIAN * This should be #define'd if true. */ - -#include "module.h" -#include "modules/encryption.h" - -union CHAR64LONG16 -{ - unsigned char c[64]; - uint32_t l[16]; -}; - -inline static uint32_t rol(uint32_t value, uint32_t bits) { return (value << bits) | (value >> (32 - bits)); } - -/* blk0() and blk() perform the initial expand. */ -/* I got the idea of expanding during the round function from SSLeay */ -inline static uint32_t blk0(CHAR64LONG16 &block, uint32_t i) -{ -#ifdef LITTLE_ENDIAN - return block.l[i] = (rol(block.l[i], 24) & 0xFF00FF00) | (rol(block.l[i], 8) & 0x00FF00FF); -#else - return block.l[i]; -#endif -} -inline static uint32_t blk(CHAR64LONG16 &block, uint32_t i) { return block.l[i & 15] = rol(block.l[(i + 13) & 15] ^ block.l[(i + 8) & 15] ^ block.l[(i + 2) & 15] ^ block.l[i & 15],1); } - -/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ -inline static void R0(CHAR64LONG16 &block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); w = rol(w, 30); } -inline static void R1(CHAR64LONG16 &block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += ((w & (x ^ y)) ^ y) + blk(block, i) + 0x5A827999 + rol(v, 5); w = rol(w, 30); } -inline static void R2(CHAR64LONG16 &block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += (w ^ x ^ y) + blk(block, i) + 0x6ED9EBA1 + rol(v, 5); w = rol(w, 30); } -inline static void R3(CHAR64LONG16 &block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += (((w | x) & y) | (w & x)) + blk(block, i) + 0x8F1BBCDC + rol(v, 5); w = rol(w, 30); } -inline static void R4(CHAR64LONG16 &block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += (w ^ x ^ y) + blk(block, i) + 0xCA62C1D6 + rol(v, 5); w = rol(w, 30); } - -static const uint32_t sha1_iv[5] = -{ - 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 -}; - -class SHA1Context : public Encryption::Context -{ - uint32_t state[5]; - uint32_t count[2]; - unsigned char buffer[64]; - unsigned char digest[20]; - - void Transform(const unsigned char buf[64]) - { - uint32_t a, b, c, d, e; - - CHAR64LONG16 block; - memcpy(block.c, buf, 64); - - /* Copy context->state[] to working vars */ - a = this->state[0]; - b = this->state[1]; - c = this->state[2]; - d = this->state[3]; - e = this->state[4]; - - /* 4 rounds of 20 operations each. Loop unrolled. */ - R0(block, a, b, c, d, e, 0); R0(block, e, a, b, c, d, 1); R0(block, d, e, a, b, c, 2); R0(block, c, d, e, a, b, 3); - R0(block, b, c, d, e, a, 4); R0(block, a, b, c, d, e, 5); R0(block, e, a, b, c, d, 6); R0(block, d, e, a, b, c, 7); - R0(block, c, d, e, a, b, 8); R0(block, b, c, d, e, a, 9); R0(block, a, b, c, d, e, 10); R0(block, e, a, b, c, d, 11); - R0(block, d, e, a, b, c, 12); R0(block, c, d, e, a, b, 13); R0(block, b, c, d, e, a, 14); R0(block, a, b, c, d, e, 15); - R1(block, e, a, b, c, d, 16); R1(block, d, e, a, b, c, 17); R1(block, c, d, e, a, b, 18); R1(block, b, c, d, e, a, 19); - R2(block, a, b, c, d, e, 20); R2(block, e, a, b, c, d, 21); R2(block, d, e, a, b, c, 22); R2(block, c, d, e, a, b, 23); - R2(block, b, c, d, e, a, 24); R2(block, a, b, c, d, e, 25); R2(block, e, a, b, c, d, 26); R2(block, d, e, a, b, c, 27); - R2(block, c, d, e, a, b, 28); R2(block, b, c, d, e, a, 29); R2(block, a, b, c, d, e, 30); R2(block, e, a, b, c, d, 31); - R2(block, d, e, a, b, c, 32); R2(block, c, d, e, a, b, 33); R2(block, b, c, d, e, a, 34); R2(block, a, b, c, d, e, 35); - R2(block, e, a, b, c, d, 36); R2(block, d, e, a, b, c, 37); R2(block, c, d, e, a, b, 38); R2(block, b, c, d, e, a, 39); - R3(block, a, b, c, d, e, 40); R3(block, e, a, b, c, d, 41); R3(block, d, e, a, b, c, 42); R3(block, c, d, e, a, b, 43); - R3(block, b, c, d, e, a, 44); R3(block, a, b, c, d, e, 45); R3(block, e, a, b, c, d, 46); R3(block, d, e, a, b, c, 47); - R3(block, c, d, e, a, b, 48); R3(block, b, c, d, e, a, 49); R3(block, a, b, c, d, e, 50); R3(block, e, a, b, c, d, 51); - R3(block, d, e, a, b, c, 52); R3(block, c, d, e, a, b, 53); R3(block, b, c, d, e, a, 54); R3(block, a, b, c, d, e, 55); - R3(block, e, a, b, c, d, 56); R3(block, d, e, a, b, c, 57); R3(block, c, d, e, a, b, 58); R3(block, b, c, d, e, a, 59); - R4(block, a, b, c, d, e, 60); R4(block, e, a, b, c, d, 61); R4(block, d, e, a, b, c, 62); R4(block, c, d, e, a, b, 63); - R4(block, b, c, d, e, a, 64); R4(block, a, b, c, d, e, 65); R4(block, e, a, b, c, d, 66); R4(block, d, e, a, b, c, 67); - R4(block, c, d, e, a, b, 68); R4(block, b, c, d, e, a, 69); R4(block, a, b, c, d, e, 70); R4(block, e, a, b, c, d, 71); - R4(block, d, e, a, b, c, 72); R4(block, c, d, e, a, b, 73); R4(block, b, c, d, e, a, 74); R4(block, a, b, c, d, e, 75); - R4(block, e, a, b, c, d, 76); R4(block, d, e, a, b, c, 77); R4(block, c, d, e, a, b, 78); R4(block, b, c, d, e, a, 79); - /* Add the working vars back into context.state[] */ - this->state[0] += a; - this->state[1] += b; - this->state[2] += c; - this->state[3] += d; - this->state[4] += e; - /* Wipe variables */ - a = b = c = d = e = 0; - } - - public: - SHA1Context(Encryption::IV *iv = NULL) - { - if (iv != NULL) - { - if (iv->second != 5) - throw CoreException("Invalid IV size"); - for (int i = 0; i < 5; ++i) - this->state[i] = iv->first[i]; - } - else - for (int i = 0; i < 5; ++i) - this->state[i] = sha1_iv[i]; - - this->count[0] = this->count[1] = 0; - memset(this->buffer, 0, sizeof(this->buffer)); - memset(this->digest, 0, sizeof(this->digest)); - } - - void Update(const unsigned char *data, size_t len) override - { - uint32_t i, j; - - j = (this->count[0] >> 3) & 63; - if ((this->count[0] += len << 3) < (len << 3)) - ++this->count[1]; - this->count[1] += len >> 29; - if (j + len > 63) - { - memcpy(&this->buffer[j], data, (i = 64 - j)); - this->Transform(this->buffer); - for (; i + 63 < len; i += 64) - this->Transform(&data[i]); - j = 0; - } - else - i = 0; - memcpy(&this->buffer[j], &data[i], len - i); - } - - void Finalize() override - { - uint32_t i; - unsigned char finalcount[8]; - - for (i = 0; i < 8; ++i) - finalcount[i] = static_cast((this->count[i >= 4 ? 0 : 1] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ - this->Update(reinterpret_cast("\200"), 1); - while ((this->count[0] & 504) != 448) - this->Update(reinterpret_cast("\0"), 1); - this->Update(finalcount, 8); /* Should cause a SHA1Transform() */ - for (i = 0; i < 20; ++i) - this->digest[i] = static_cast((this->state[i>>2] >> ((3 - (i & 3)) * 8)) & 255); - - /* Wipe variables */ - memset(this->buffer, 0, sizeof(this->buffer)); - memset(this->state, 0, sizeof(this->state)); - memset(this->count, 0, sizeof(this->count)); - memset(&finalcount, 0, sizeof(finalcount)); - - this->Transform(this->buffer); - } - - Encryption::Hash GetFinalizedHash() override - { - Encryption::Hash hash; - hash.first = this->digest; - hash.second = sizeof(this->digest); - return hash; - } -}; - -class SHA1Provider : public Encryption::Provider -{ - public: - SHA1Provider(Module *creator) : Encryption::Provider(creator, "sha1") { } - - Encryption::Context *CreateContext(Encryption::IV *iv) override - { - return new SHA1Context(iv); - } - - Encryption::IV GetDefaultIV() override - { - Encryption::IV iv; - iv.first = sha1_iv; - iv.second = sizeof(sha1_iv) / sizeof(uint32_t); - return iv; - } -}; - -class ESHA1 : public Module - , public EventHook - , public EventHook -{ - SHA1Provider sha1provider; - - public: - ESHA1(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR) - , sha1provider(this) - { - if (ModuleManager::FindFirstOf(ENCRYPTION) == this) - throw ModuleException("enc_sha1 is deprecated and can not be used as a primary encryption method"); - } - - EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override - { - SHA1Context context; - - context.Update(reinterpret_cast(src.c_str()), src.length()); - context.Finalize(); - - Encryption::Hash hash = context.GetFinalizedHash(); - - Anope::string buf = "sha1:" + Anope::Hex(reinterpret_cast(hash.first), hash.second); - - Log(LOG_DEBUG_2) << "(enc_sha1) hashed password from [" << src << "] to [" << buf << "]"; - dest = buf; - return EVENT_ALLOW; - } - - void OnCheckAuthentication(User *, NickServ::IdentifyRequest *req) override - { - NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); - if (na == NULL) - return; - NickServ::Account *nc = na->GetAccount(); - - size_t pos = nc->GetPassword().find(':'); - if (pos == Anope::string::npos) - return; - Anope::string hash_method(nc->GetPassword().begin(), nc->GetPassword().begin() + pos); - if (!hash_method.equals_cs("sha1")) - return; - - Anope::string buf; - this->OnEncrypt(req->GetPassword(), buf); - if (nc->GetPassword().equals_cs(buf)) - { - if (ModuleManager::FindFirstOf(ENCRYPTION) != this) - { - Anope::string p; - Anope::Encrypt(req->GetPassword(), p); - nc->SetPassword(p); - } - req->Success(this); - } - } -}; - -MODULE_INIT(ESHA1) diff --git a/modules/encryption/enc_sha256.cpp b/modules/encryption/enc_sha256.cpp deleted file mode 100644 index a138b3756..000000000 --- a/modules/encryption/enc_sha256.cpp +++ /dev/null @@ -1,340 +0,0 @@ -/* This module generates and compares password hashes using SHA256 algorithms. - * - * If an intruder gets access to your system or uses a brute force attack, - * salt will not provide much value. - * IMPORTANT: DATA HASHES CANNOT BE "DECRYPTED" BACK TO PLAIN TEXT. - * - * Modified for Anope. - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Taken from InspIRCd ( www.inspircd.org ) - * see http://wiki.inspircd.org/Credits - * - * This program is free but copyrighted software; see - * the file COPYING for details. - */ - -/* FIPS 180-2 SHA-224/256/384/512 implementation - * Last update: 05/23/2005 - * Issue date: 04/30/2005 - * - * Copyright (C) 2005 Olivier Gay - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the project nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include "module.h" -#include "modules/encryption.h" - -static const unsigned SHA256_DIGEST_SIZE = 256 / 8; -static const unsigned SHA256_BLOCK_SIZE = 512 / 8; - -inline static uint32_t SHFR(uint32_t x, uint32_t n) { return x >> n; } -inline static uint32_t ROTR(uint32_t x, uint32_t n) { return (x >> n) | (x << ((sizeof(x) << 3) - n)); } -inline static uint32_t CH(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (~x & z); } -inline static uint32_t MAJ(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (x & z) ^ (y & z); } - -inline static uint32_t SHA256_F1(uint32_t x) { return ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22); } -inline static uint32_t SHA256_F2(uint32_t x) { return ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25); } -inline static uint32_t SHA256_F3(uint32_t x) { return ROTR(x, 7) ^ ROTR(x, 18) ^ SHFR(x, 3); } -inline static uint32_t SHA256_F4(uint32_t x) { return ROTR(x, 17) ^ ROTR(x, 19) ^ SHFR(x, 10); } - -inline static void UNPACK32(unsigned x, unsigned char *str) -{ - str[3] = static_cast(x); - str[2] = static_cast(x >> 8); - str[1] = static_cast(x >> 16); - str[0] = static_cast(x >> 24); -} - -inline static void PACK32(unsigned char *str, uint32_t &x) -{ - x = static_cast(str[3]) | static_cast(str[2]) << 8 | static_cast(str[1]) << 16 | static_cast(str[0]) << 24; -} - -/* Macros used for loops unrolling */ - -inline static void SHA256_SCR(uint32_t w[64], int i) -{ - w[i] = SHA256_F4(w[i - 2]) + w[i - 7] + SHA256_F3(w[i - 15]) + w[i - 16]; -} - -static const uint32_t sha256_h0[8] = -{ - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, - 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 -}; - -static const uint32_t sha256_k[64] = -{ - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, - 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, - 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, - 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, - 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, - 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, - 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, - 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, - 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 -}; - -/** An sha256 context - */ -class SHA256Context : public Encryption::Context -{ - void Transform(unsigned char *message, unsigned block_nb) - { - uint32_t w[64], wv[8]; - unsigned char *sub_block; - for (unsigned i = 1; i <= block_nb; ++i) - { - int j; - sub_block = message + ((i - 1) << 6); - - for (j = 0; j < 16; ++j) - PACK32(&sub_block[j << 2], w[j]); - for (j = 16; j < 64; ++j) - SHA256_SCR(w, j); - for (j = 0; j < 8; ++j) - wv[j] = this->h[j]; - for (j = 0; j < 64; ++j) - { - uint32_t t1 = wv[7] + SHA256_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha256_k[j] + w[j]; - uint32_t t2 = SHA256_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]); - wv[7] = wv[6]; - wv[6] = wv[5]; - wv[5] = wv[4]; - wv[4] = wv[3] + t1; - wv[3] = wv[2]; - wv[2] = wv[1]; - wv[1] = wv[0]; - wv[0] = t1 + t2; - } - for (j = 0; j < 8; ++j) - this->h[j] += wv[j]; - } - } - - unsigned tot_len; - unsigned len; - unsigned char block[2 * SHA256_BLOCK_SIZE]; - uint32_t h[8]; - unsigned char digest[SHA256_DIGEST_SIZE]; - - public: - SHA256Context(Encryption::IV *iv) - { - if (iv != NULL) - { - if (iv->second != 8) - throw CoreException("Invalid IV size"); - for (int i = 0; i < 8; ++i) - this->h[i] = iv->first[i]; - } - else - for (int i = 0; i < 8; ++i) - this->h[i] = sha256_h0[i]; - - this->tot_len = 0; - this->len = 0; - memset(this->block, 0, sizeof(this->block)); - memset(this->digest, 0, sizeof(this->digest)); - } - - void Update(const unsigned char *message, size_t mlen) override - { - unsigned tmp_len = SHA256_BLOCK_SIZE - this->len, rem_len = mlen < tmp_len ? mlen : tmp_len; - - memcpy(&this->block[this->len], message, rem_len); - if (this->len + mlen < SHA256_BLOCK_SIZE) - { - this->len += mlen; - return; - } - unsigned new_len = mlen - rem_len, block_nb = new_len / SHA256_BLOCK_SIZE; - unsigned char *shifted_message = new unsigned char[mlen - rem_len]; - memcpy(shifted_message, message + rem_len, mlen - rem_len); - this->Transform(this->block, 1); - this->Transform(shifted_message, block_nb); - rem_len = new_len % SHA256_BLOCK_SIZE; - memcpy(this->block, &shifted_message[block_nb << 6], rem_len); - delete [] shifted_message; - this->len = rem_len; - this->tot_len += (block_nb + 1) << 6; - } - - void Finalize() override - { - unsigned block_nb = 1 + ((SHA256_BLOCK_SIZE - 9) < (this->len % SHA256_BLOCK_SIZE)); - unsigned len_b = (this->tot_len + this->len) << 3; - unsigned pm_len = block_nb << 6; - memset(this->block + this->len, 0, pm_len - this->len); - this->block[this->len] = 0x80; - UNPACK32(len_b, this->block + pm_len - 4); - this->Transform(this->block, block_nb); - for (int i = 0 ; i < 8; ++i) - UNPACK32(this->h[i], &this->digest[i << 2]); - } - - Encryption::Hash GetFinalizedHash() override - { - Encryption::Hash hash; - hash.first = this->digest; - hash.second = SHA256_DIGEST_SIZE; - return hash; - } -}; - -class SHA256Provider : public Encryption::Provider -{ - public: - SHA256Provider(Module *creator) : Encryption::Provider(creator, "sha256") { } - - Encryption::Context *CreateContext(Encryption::IV *iv) override - { - return new SHA256Context(iv); - } - - Encryption::IV GetDefaultIV() override - { - Encryption::IV iv; - iv.first = sha256_h0; - iv.second = sizeof(sha256_h0) / sizeof(uint32_t); - return iv; - } -}; - -class ESHA256 : public Module - , public EventHook - , public EventHook -{ - SHA256Provider sha256provider; - - unsigned iv[8]; - bool use_iv; - - /* initializes the IV with a new random value */ - void NewRandomIV() - { - for (int i = 0; i < 8; ++i) - iv[i] = static_cast(rand()); - } - - /* returns the IV as base64-encrypted string */ - Anope::string GetIVString() - { - char buf[33]; - for (int i = 0; i < 8; ++i) - UNPACK32(iv[i], reinterpret_cast(&buf[i << 2])); - buf[32] = '\0'; - return Anope::Hex(buf, 32); - } - - /* splits the appended IV from the password string so it can be used for the next encryption */ - /* password format: :: */ - void GetIVFromPass(const Anope::string &password) - { - size_t pos = password.find(':'); - Anope::string buf = password.substr(password.find(':', pos + 1) + 1, password.length()); - char buf2[33]; - Anope::Unhex(buf, buf2, sizeof(buf2)); - for (int i = 0 ; i < 8; ++i) - PACK32(reinterpret_cast(&buf2[i << 2]), iv[i]); - } - - public: - ESHA256(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR) - , sha256provider(this) - { - - - use_iv = false; - } - - EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override - { - if (!use_iv) - NewRandomIV(); - else - use_iv = false; - - Encryption::IV initilization(this->iv, 8); - SHA256Context ctx(&initilization); - ctx.Update(reinterpret_cast(src.c_str()), src.length()); - ctx.Finalize(); - - Encryption::Hash hash = ctx.GetFinalizedHash(); - - std::stringstream buf; - buf << "sha256:" << Anope::Hex(reinterpret_cast(hash.first), hash.second) << ":" << GetIVString(); - Log(LOG_DEBUG_2) << "(enc_sha256) hashed password from [" << src << "] to [" << buf.str() << " ]"; - dest = buf.str(); - return EVENT_ALLOW; - } - - void OnCheckAuthentication(User *, NickServ::IdentifyRequest *req) override - { - NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); - if (na == NULL) - return; - NickServ::Account *nc = na->GetAccount(); - - size_t pos = nc->GetPassword().find(':'); - if (pos == Anope::string::npos) - return; - Anope::string hash_method(nc->GetPassword().substr(0, pos)); - if (!hash_method.equals_cs("sha256")) - return; - - GetIVFromPass(nc->GetPassword()); - use_iv = true; - Anope::string buf; - this->OnEncrypt(req->GetPassword(), buf); - if (nc->GetPassword().equals_cs(buf)) - { - /* if we are NOT the first module in the list, - * we want to re-encrypt the pass with the new encryption - */ - if (ModuleManager::FindFirstOf(ENCRYPTION) != this) - { - Anope::string p; - Anope::Encrypt(req->GetPassword(), p); - nc->SetPassword(p); - } - req->Success(this); - } - } -}; - -MODULE_INIT(ESHA256) diff --git a/modules/encryption/md5.cpp b/modules/encryption/md5.cpp new file mode 100644 index 000000000..a92f5a4f9 --- /dev/null +++ b/modules/encryption/md5.cpp @@ -0,0 +1,402 @@ +/* Module for encryption using MD5. + * + * Modified for Anope. + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Taken from IRC Services and is copyright (c) 1996-2002 Andrew Church. + * E-mail: + * Parts written by Andrew Kempe and others. + * This program is free but copyrighted software; see the file COPYING for + * details. + */ + +#include "module.h" +#include "modules/encryption.h" + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +inline static unsigned F(unsigned x, unsigned y, unsigned z) { return (x & y) | (~x & z); } +inline static unsigned G(unsigned x, unsigned y, unsigned z) { return (x & z) | (y & ~z); } +inline static unsigned H(unsigned x, unsigned y, unsigned z) { return x ^ y ^ z; } +inline static unsigned I(unsigned x, unsigned y, unsigned z) { return y ^ (x | ~z); } + +/* ROTATE_LEFT rotates x left n bits. + */ +inline static unsigned ROTATE_LEFT(unsigned x, unsigned n) { return (x << n) | (x >> (32 - n)); } + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. + * Rotation is separate from addition to prevent recomputation. + */ +inline static void FF(unsigned &a, unsigned b, unsigned c, unsigned d, unsigned x, unsigned s, unsigned ac) +{ + a += F(b, c, d) + x + ac; + a = ROTATE_LEFT(a, s); + a += b; +} +inline static void GG(unsigned &a, unsigned b, unsigned c, unsigned d, unsigned x, unsigned s, unsigned ac) +{ + a += G(b, c, d) + x + ac; + a = ROTATE_LEFT(a, s); + a += b; +} +inline static void HH(unsigned &a, unsigned b, unsigned c, unsigned d, unsigned x, unsigned s, unsigned ac) +{ + a += H(b, c, d) + x + ac; + a = ROTATE_LEFT(a, s); + a += b; +} +inline static void II(unsigned &a, unsigned b, unsigned c, unsigned d, unsigned x, unsigned s, unsigned ac) +{ + a += I(b, c, d) + x + ac; + a = ROTATE_LEFT(a, s); + a += b; +} + +static const uint32_t md5_iv[4] = +{ + 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 +}; + +class MD5Context : public Encryption::Context +{ + unsigned state[4]; /* state (ABCD) */ + unsigned count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ + unsigned char digest[16]; /* final digest */ + + /* Constants for MD5Transform routine. + */ + enum + { + S11 = 7, + S12 = 12, + S13 = 17, + S14 = 22, + S21 = 5, + S22 = 9, + S23 = 14, + S24 = 20, + S31 = 4, + S32 = 11, + S33 = 16, + S34 = 23, + S41 = 6, + S42 = 10, + S43 = 15, + S44 = 21 + }; + + /* MD5 basic transformation. Transforms state based on block. + */ + void Transform(const unsigned char block[64]) + { + unsigned a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode(x, block, 64); + + /* Round 1 */ + FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */ + FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */ + FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */ + FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */ + FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */ + FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */ + FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */ + FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */ + FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */ + FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */ + FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */ + GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */ + GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */ + GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */ + GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */ + GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */ + GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */ + GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */ + GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */ + GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */ + GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */ + HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */ + HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */ + HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */ + HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */ + HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */ + HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */ + HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */ + HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */ + HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */ + II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */ + II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */ + II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */ + II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */ + II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */ + II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */ + II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */ + II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */ + II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. */ + memset(x, 0, sizeof(x)); + } + + /* Encodes input (unsigned) into output (unsigned char). Assumes len is + * a multiple of 4. + */ + void Encode(unsigned char *output, unsigned *input, unsigned len) + { + for (unsigned i = 0, j = 0; j < len; ++i, j += 4) + { + output[j] = static_cast(input[i] & 0xff); + output[j + 1] = static_cast((input[i] >> 8) & 0xff); + output[j + 2] = static_cast((input[i] >> 16) & 0xff); + output[j + 3] = static_cast((input[i] >> 24) & 0xff); + } + } + + /* Decodes input (unsigned char) into output (unsigned). Assumes len is + * a multiple of 4. + */ + void Decode(unsigned *output, const unsigned char *input, unsigned len) + { + for (unsigned i = 0, j = 0; j < len; ++i, j += 4) + output[i] = static_cast(input[j]) | (static_cast(input[j + 1]) << 8) | (static_cast(input[j + 2]) << 16) | (static_cast(input[j + 3]) << 24); + } + + public: + MD5Context(Encryption::IV *iv = NULL) + { + if (iv != NULL) + { + if (iv->second != 4) + throw CoreException("Invalid IV size"); + /* Load magic initialization constants. */ + for (int i = 0; i < 4; ++i) + this->state[i] = iv->first[i]; + } + else + for (int i = 0; i < 4; ++i) + this->state[i] = md5_iv[i]; + + this->count[0] = this->count[1] = 0; + memset(this->buffer, 0, sizeof(this->buffer)); + } + + /* MD5 block update operation. Continues an MD5 message-digest + * operation, processing another message block, and updating the + * context. + */ + void Update(const unsigned char *input, size_t len) override + { + unsigned i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (this->count[0] >> 3) & 0x3F; + + /* Update number of bits */ + if ((this->count[0] += len << 3) < (len << 3)) + ++this->count[1]; + this->count[1] += len >> 29; + + partLen = 64 - index; + + /* Transform as many times as possible. */ + if (len >= partLen) + { + memcpy(&this->buffer[index], input, partLen); + this->Transform(this->buffer); + + for (i = partLen; i + 63 < len; i += 64) + this->Transform(&input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + memcpy(&this->buffer[index], &input[i], len - i); + } + + /* MD5 finalization. Ends an MD5 message-digest opera + * the message digest and zeroizing the context. + */ + void Finalize() override + { + unsigned char bits[8]; + unsigned index, padLen; + + /* Save number of bits */ + this->Encode(bits, this->count, 8); + + /* Pad out to 56 mod 64. */ + index = (this->count[0] >> 3) & 0x3f; + padLen = index < 56 ? 56 - index : 120 - index; + this->Update(PADDING, padLen); + + /* Append length (before padding) */ + this->Update(bits, 8); + /* Store state in digest */ + this->Encode(digest, this->state, 16); + + /* Zeroize sensitive information. */ + memset(this->state, 0, sizeof(this->state)); + memset(this->count, 0, sizeof(this->count)); + memset(this->buffer, 0, sizeof(this->buffer)); + } + + Encryption::Hash GetFinalizedHash() override + { + Encryption::Hash hash; + hash.first = this->digest; + hash.second = sizeof(this->digest); + return hash; + } +}; + +class MD5Provider : public Encryption::Provider +{ + public: + MD5Provider(Module *creator) : Encryption::Provider(creator, "md5") { } + + Encryption::Context *CreateContext(Encryption::IV *iv) override + { + return new MD5Context(iv); + } + + Encryption::IV GetDefaultIV() override + { + Encryption::IV iv; + iv.first = md5_iv; + iv.second = sizeof(md5_iv) / sizeof(uint32_t); + return iv; + } +}; + +class EMD5 : public Module + , public EventHook + , public EventHook +{ + MD5Provider md5provider; + + public: + EMD5(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR) + , md5provider(this) + { + if (ModuleManager::FindFirstOf(ENCRYPTION) == this) + throw ModuleException("enc_md5 is deprecated and can not be used as a primary encryption method"); + } + + EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override + { + MD5Context context; + + context.Update(reinterpret_cast(src.c_str()), src.length()); + context.Finalize(); + + Encryption::Hash hash = context.GetFinalizedHash(); + + Anope::string buf = "md5:" + Anope::Hex(reinterpret_cast(hash.first), hash.second); + + Log(LOG_DEBUG_2) << "(enc_md5) hashed password from [" << src << "] to [" << buf << "]"; + dest = buf; + return EVENT_ALLOW; + } + + void OnCheckAuthentication(User *, NickServ::IdentifyRequest *req) override + { + NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); + if (na == NULL) + return; + NickServ::Account *nc = na->GetAccount(); + + size_t pos = nc->GetPassword().find(':'); + if (pos == Anope::string::npos) + return; + Anope::string hash_method(nc->GetPassword().begin(), nc->GetPassword().begin() + pos); + if (!hash_method.equals_cs("md5")) + return; + + Anope::string buf; + this->OnEncrypt(req->GetPassword(), buf); + if (nc->GetPassword().equals_cs(buf)) + { + /* if we are NOT the first module in the list, + * we want to re-encrypt the pass with the new encryption + */ + if (ModuleManager::FindFirstOf(ENCRYPTION) != this) + { + Anope::string p; + Anope::Encrypt(req->GetPassword(), p); + nc->SetPassword(p); + } + req->Success(this); + } + } +}; + +MODULE_INIT(EMD5) diff --git a/modules/encryption/none.cpp b/modules/encryption/none.cpp new file mode 100644 index 000000000..8164534eb --- /dev/null +++ b/modules/encryption/none.cpp @@ -0,0 +1,76 @@ +/* Module for plain text encryption. + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * This program is free but copyrighted software; see the file COPYING for + * details. + */ + +#include "module.h" + +class ENone : public Module + , public EventHook + , public EventHook + , public EventHook +{ + public: + ENone(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR) + { + + } + + EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override + { + Anope::string buf = "plain:"; + Anope::string cpass; + Anope::B64Encode(src, cpass); + buf += cpass; + Log(LOG_DEBUG_2) << "(enc_none) hashed password from [" << src << "] to [" << buf << "]"; + dest = buf; + return EVENT_ALLOW; + } + + EventReturn OnDecrypt(const Anope::string &hashm, const Anope::string &src, Anope::string &dest) override + { + if (!hashm.equals_cs("plain")) + return EVENT_CONTINUE; + size_t pos = src.find(':'); + Anope::string buf = src.substr(pos + 1); + Anope::B64Decode(buf, dest); + return EVENT_ALLOW; + } + + void OnCheckAuthentication(User *, NickServ::IdentifyRequest *req) override + { + NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); + if (na == NULL) + return; + NickServ::Account *nc = na->GetAccount(); + + size_t pos = nc->GetPassword().find(':'); + if (pos == Anope::string::npos) + return; + Anope::string hash_method(nc->GetPassword().begin(), nc->GetPassword().begin() + pos); + if (!hash_method.equals_cs("plain")) + return; + + Anope::string buf; + this->OnEncrypt(req->GetPassword(), buf); + if (nc->GetPassword().equals_cs(buf)) + { + /* if we are NOT the first module in the list, + * we want to re-encrypt the pass with the new encryption + */ + if (ModuleManager::FindFirstOf(ENCRYPTION) != this) + { + Anope::string p; + Anope::Encrypt(req->GetPassword(), p); + nc->SetPassword(p); + } + req->Success(this); + } + } +}; + +MODULE_INIT(ENone) diff --git a/modules/encryption/old.cpp b/modules/encryption/old.cpp new file mode 100644 index 000000000..4e3e70f7d --- /dev/null +++ b/modules/encryption/old.cpp @@ -0,0 +1,118 @@ +/* Include file for high-level encryption routines. + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/encryption.h" + +static ServiceReference md5("Encryption::Provider", "md5"); + +class OldMD5Provider : public Encryption::Provider +{ + public: + OldMD5Provider(Module *creator) : Encryption::Provider(creator, "oldmd5") { } + + Encryption::Context *CreateContext(Encryption::IV *iv) override + { + if (md5) + return md5->CreateContext(iv); + return NULL; + } + + Encryption::IV GetDefaultIV() override + { + if (md5) + return md5->GetDefaultIV(); + return Encryption::IV(static_cast(NULL), 0); + } +}; + +class EOld : public Module + , public EventHook + , public EventHook +{ + OldMD5Provider oldmd5provider; + + inline static char XTOI(char c) { return c > 9 ? c - 'A' + 10 : c - '0'; } + + public: + EOld(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR) + , oldmd5provider(this) + { + if (ModuleManager::FindFirstOf(ENCRYPTION) == this) + throw ModuleException("enc_old is deprecated and can not be used as a primary encryption method"); + + ModuleManager::LoadModule("enc_md5", User::Find(creator)); + if (!md5) + throw ModuleException("Unable to find md5 reference"); + + } + + EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override + { + if (!md5) + return EVENT_CONTINUE; + + Encryption::Context *context = md5->CreateContext(); + context->Update(reinterpret_cast(src.c_str()), src.length()); + context->Finalize(); + + Encryption::Hash hash = context->GetFinalizedHash(); + + char digest[32], digest2[16]; + memset(digest, 0, sizeof(digest)); + if (hash.second > sizeof(digest)) + throw CoreException("Hash too large"); + memcpy(digest, hash.first, hash.second); + + for (int i = 0; i < 32; i += 2) + digest2[i / 2] = XTOI(digest[i]) << 4 | XTOI(digest[i + 1]); + + Anope::string buf = "oldmd5:" + Anope::Hex(digest2, sizeof(digest2)); + + Log(LOG_DEBUG_2) << "(enc_old) hashed password from [" << src << "] to [" << buf << "]"; + dest = buf; + delete context; + return EVENT_ALLOW; + } + + void OnCheckAuthentication(User *, NickServ::IdentifyRequest *req) override + { + NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); + if (na == NULL) + return; + NickServ::Account *nc = na->GetAccount(); + + size_t pos = nc->GetPassword().find(':'); + if (pos == Anope::string::npos) + return; + Anope::string hash_method(nc->GetPassword().begin(), nc->GetPassword().begin() + pos); + if (!hash_method.equals_cs("oldmd5")) + return; + + Anope::string buf; + this->OnEncrypt(req->GetPassword(), buf); + if (nc->GetPassword().equals_cs(buf)) + { + /* if we are NOT the first module in the list, + * we want to re-encrypt the pass with the new encryption + */ + if (ModuleManager::FindFirstOf(ENCRYPTION) != this) + { + Anope::string p; + Anope::Encrypt(req->GetPassword(), p); + nc->SetPassword(p); + } + req->Success(this); + } + } +}; + +MODULE_INIT(EOld) diff --git a/modules/encryption/sha1.cpp b/modules/encryption/sha1.cpp new file mode 100644 index 000000000..a99bc834e --- /dev/null +++ b/modules/encryption/sha1.cpp @@ -0,0 +1,254 @@ +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd if true. */ + +#include "module.h" +#include "modules/encryption.h" + +union CHAR64LONG16 +{ + unsigned char c[64]; + uint32_t l[16]; +}; + +inline static uint32_t rol(uint32_t value, uint32_t bits) { return (value << bits) | (value >> (32 - bits)); } + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +inline static uint32_t blk0(CHAR64LONG16 &block, uint32_t i) +{ +#ifdef LITTLE_ENDIAN + return block.l[i] = (rol(block.l[i], 24) & 0xFF00FF00) | (rol(block.l[i], 8) & 0x00FF00FF); +#else + return block.l[i]; +#endif +} +inline static uint32_t blk(CHAR64LONG16 &block, uint32_t i) { return block.l[i & 15] = rol(block.l[(i + 13) & 15] ^ block.l[(i + 8) & 15] ^ block.l[(i + 2) & 15] ^ block.l[i & 15],1); } + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +inline static void R0(CHAR64LONG16 &block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); w = rol(w, 30); } +inline static void R1(CHAR64LONG16 &block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += ((w & (x ^ y)) ^ y) + blk(block, i) + 0x5A827999 + rol(v, 5); w = rol(w, 30); } +inline static void R2(CHAR64LONG16 &block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += (w ^ x ^ y) + blk(block, i) + 0x6ED9EBA1 + rol(v, 5); w = rol(w, 30); } +inline static void R3(CHAR64LONG16 &block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += (((w | x) & y) | (w & x)) + blk(block, i) + 0x8F1BBCDC + rol(v, 5); w = rol(w, 30); } +inline static void R4(CHAR64LONG16 &block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += (w ^ x ^ y) + blk(block, i) + 0xCA62C1D6 + rol(v, 5); w = rol(w, 30); } + +static const uint32_t sha1_iv[5] = +{ + 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 +}; + +class SHA1Context : public Encryption::Context +{ + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; + unsigned char digest[20]; + + void Transform(const unsigned char buf[64]) + { + uint32_t a, b, c, d, e; + + CHAR64LONG16 block; + memcpy(block.c, buf, 64); + + /* Copy context->state[] to working vars */ + a = this->state[0]; + b = this->state[1]; + c = this->state[2]; + d = this->state[3]; + e = this->state[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(block, a, b, c, d, e, 0); R0(block, e, a, b, c, d, 1); R0(block, d, e, a, b, c, 2); R0(block, c, d, e, a, b, 3); + R0(block, b, c, d, e, a, 4); R0(block, a, b, c, d, e, 5); R0(block, e, a, b, c, d, 6); R0(block, d, e, a, b, c, 7); + R0(block, c, d, e, a, b, 8); R0(block, b, c, d, e, a, 9); R0(block, a, b, c, d, e, 10); R0(block, e, a, b, c, d, 11); + R0(block, d, e, a, b, c, 12); R0(block, c, d, e, a, b, 13); R0(block, b, c, d, e, a, 14); R0(block, a, b, c, d, e, 15); + R1(block, e, a, b, c, d, 16); R1(block, d, e, a, b, c, 17); R1(block, c, d, e, a, b, 18); R1(block, b, c, d, e, a, 19); + R2(block, a, b, c, d, e, 20); R2(block, e, a, b, c, d, 21); R2(block, d, e, a, b, c, 22); R2(block, c, d, e, a, b, 23); + R2(block, b, c, d, e, a, 24); R2(block, a, b, c, d, e, 25); R2(block, e, a, b, c, d, 26); R2(block, d, e, a, b, c, 27); + R2(block, c, d, e, a, b, 28); R2(block, b, c, d, e, a, 29); R2(block, a, b, c, d, e, 30); R2(block, e, a, b, c, d, 31); + R2(block, d, e, a, b, c, 32); R2(block, c, d, e, a, b, 33); R2(block, b, c, d, e, a, 34); R2(block, a, b, c, d, e, 35); + R2(block, e, a, b, c, d, 36); R2(block, d, e, a, b, c, 37); R2(block, c, d, e, a, b, 38); R2(block, b, c, d, e, a, 39); + R3(block, a, b, c, d, e, 40); R3(block, e, a, b, c, d, 41); R3(block, d, e, a, b, c, 42); R3(block, c, d, e, a, b, 43); + R3(block, b, c, d, e, a, 44); R3(block, a, b, c, d, e, 45); R3(block, e, a, b, c, d, 46); R3(block, d, e, a, b, c, 47); + R3(block, c, d, e, a, b, 48); R3(block, b, c, d, e, a, 49); R3(block, a, b, c, d, e, 50); R3(block, e, a, b, c, d, 51); + R3(block, d, e, a, b, c, 52); R3(block, c, d, e, a, b, 53); R3(block, b, c, d, e, a, 54); R3(block, a, b, c, d, e, 55); + R3(block, e, a, b, c, d, 56); R3(block, d, e, a, b, c, 57); R3(block, c, d, e, a, b, 58); R3(block, b, c, d, e, a, 59); + R4(block, a, b, c, d, e, 60); R4(block, e, a, b, c, d, 61); R4(block, d, e, a, b, c, 62); R4(block, c, d, e, a, b, 63); + R4(block, b, c, d, e, a, 64); R4(block, a, b, c, d, e, 65); R4(block, e, a, b, c, d, 66); R4(block, d, e, a, b, c, 67); + R4(block, c, d, e, a, b, 68); R4(block, b, c, d, e, a, 69); R4(block, a, b, c, d, e, 70); R4(block, e, a, b, c, d, 71); + R4(block, d, e, a, b, c, 72); R4(block, c, d, e, a, b, 73); R4(block, b, c, d, e, a, 74); R4(block, a, b, c, d, e, 75); + R4(block, e, a, b, c, d, 76); R4(block, d, e, a, b, c, 77); R4(block, c, d, e, a, b, 78); R4(block, b, c, d, e, a, 79); + /* Add the working vars back into context.state[] */ + this->state[0] += a; + this->state[1] += b; + this->state[2] += c; + this->state[3] += d; + this->state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; + } + + public: + SHA1Context(Encryption::IV *iv = NULL) + { + if (iv != NULL) + { + if (iv->second != 5) + throw CoreException("Invalid IV size"); + for (int i = 0; i < 5; ++i) + this->state[i] = iv->first[i]; + } + else + for (int i = 0; i < 5; ++i) + this->state[i] = sha1_iv[i]; + + this->count[0] = this->count[1] = 0; + memset(this->buffer, 0, sizeof(this->buffer)); + memset(this->digest, 0, sizeof(this->digest)); + } + + void Update(const unsigned char *data, size_t len) override + { + uint32_t i, j; + + j = (this->count[0] >> 3) & 63; + if ((this->count[0] += len << 3) < (len << 3)) + ++this->count[1]; + this->count[1] += len >> 29; + if (j + len > 63) + { + memcpy(&this->buffer[j], data, (i = 64 - j)); + this->Transform(this->buffer); + for (; i + 63 < len; i += 64) + this->Transform(&data[i]); + j = 0; + } + else + i = 0; + memcpy(&this->buffer[j], &data[i], len - i); + } + + void Finalize() override + { + uint32_t i; + unsigned char finalcount[8]; + + for (i = 0; i < 8; ++i) + finalcount[i] = static_cast((this->count[i >= 4 ? 0 : 1] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ + this->Update(reinterpret_cast("\200"), 1); + while ((this->count[0] & 504) != 448) + this->Update(reinterpret_cast("\0"), 1); + this->Update(finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; ++i) + this->digest[i] = static_cast((this->state[i>>2] >> ((3 - (i & 3)) * 8)) & 255); + + /* Wipe variables */ + memset(this->buffer, 0, sizeof(this->buffer)); + memset(this->state, 0, sizeof(this->state)); + memset(this->count, 0, sizeof(this->count)); + memset(&finalcount, 0, sizeof(finalcount)); + + this->Transform(this->buffer); + } + + Encryption::Hash GetFinalizedHash() override + { + Encryption::Hash hash; + hash.first = this->digest; + hash.second = sizeof(this->digest); + return hash; + } +}; + +class SHA1Provider : public Encryption::Provider +{ + public: + SHA1Provider(Module *creator) : Encryption::Provider(creator, "sha1") { } + + Encryption::Context *CreateContext(Encryption::IV *iv) override + { + return new SHA1Context(iv); + } + + Encryption::IV GetDefaultIV() override + { + Encryption::IV iv; + iv.first = sha1_iv; + iv.second = sizeof(sha1_iv) / sizeof(uint32_t); + return iv; + } +}; + +class ESHA1 : public Module + , public EventHook + , public EventHook +{ + SHA1Provider sha1provider; + + public: + ESHA1(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR) + , sha1provider(this) + { + if (ModuleManager::FindFirstOf(ENCRYPTION) == this) + throw ModuleException("enc_sha1 is deprecated and can not be used as a primary encryption method"); + } + + EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override + { + SHA1Context context; + + context.Update(reinterpret_cast(src.c_str()), src.length()); + context.Finalize(); + + Encryption::Hash hash = context.GetFinalizedHash(); + + Anope::string buf = "sha1:" + Anope::Hex(reinterpret_cast(hash.first), hash.second); + + Log(LOG_DEBUG_2) << "(enc_sha1) hashed password from [" << src << "] to [" << buf << "]"; + dest = buf; + return EVENT_ALLOW; + } + + void OnCheckAuthentication(User *, NickServ::IdentifyRequest *req) override + { + NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); + if (na == NULL) + return; + NickServ::Account *nc = na->GetAccount(); + + size_t pos = nc->GetPassword().find(':'); + if (pos == Anope::string::npos) + return; + Anope::string hash_method(nc->GetPassword().begin(), nc->GetPassword().begin() + pos); + if (!hash_method.equals_cs("sha1")) + return; + + Anope::string buf; + this->OnEncrypt(req->GetPassword(), buf); + if (nc->GetPassword().equals_cs(buf)) + { + if (ModuleManager::FindFirstOf(ENCRYPTION) != this) + { + Anope::string p; + Anope::Encrypt(req->GetPassword(), p); + nc->SetPassword(p); + } + req->Success(this); + } + } +}; + +MODULE_INIT(ESHA1) diff --git a/modules/encryption/sha256.cpp b/modules/encryption/sha256.cpp new file mode 100644 index 000000000..a138b3756 --- /dev/null +++ b/modules/encryption/sha256.cpp @@ -0,0 +1,340 @@ +/* This module generates and compares password hashes using SHA256 algorithms. + * + * If an intruder gets access to your system or uses a brute force attack, + * salt will not provide much value. + * IMPORTANT: DATA HASHES CANNOT BE "DECRYPTED" BACK TO PLAIN TEXT. + * + * Modified for Anope. + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Taken from InspIRCd ( www.inspircd.org ) + * see http://wiki.inspircd.org/Credits + * + * This program is free but copyrighted software; see + * the file COPYING for details. + */ + +/* FIPS 180-2 SHA-224/256/384/512 implementation + * Last update: 05/23/2005 + * Issue date: 04/30/2005 + * + * Copyright (C) 2005 Olivier Gay + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "module.h" +#include "modules/encryption.h" + +static const unsigned SHA256_DIGEST_SIZE = 256 / 8; +static const unsigned SHA256_BLOCK_SIZE = 512 / 8; + +inline static uint32_t SHFR(uint32_t x, uint32_t n) { return x >> n; } +inline static uint32_t ROTR(uint32_t x, uint32_t n) { return (x >> n) | (x << ((sizeof(x) << 3) - n)); } +inline static uint32_t CH(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (~x & z); } +inline static uint32_t MAJ(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (x & z) ^ (y & z); } + +inline static uint32_t SHA256_F1(uint32_t x) { return ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22); } +inline static uint32_t SHA256_F2(uint32_t x) { return ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25); } +inline static uint32_t SHA256_F3(uint32_t x) { return ROTR(x, 7) ^ ROTR(x, 18) ^ SHFR(x, 3); } +inline static uint32_t SHA256_F4(uint32_t x) { return ROTR(x, 17) ^ ROTR(x, 19) ^ SHFR(x, 10); } + +inline static void UNPACK32(unsigned x, unsigned char *str) +{ + str[3] = static_cast(x); + str[2] = static_cast(x >> 8); + str[1] = static_cast(x >> 16); + str[0] = static_cast(x >> 24); +} + +inline static void PACK32(unsigned char *str, uint32_t &x) +{ + x = static_cast(str[3]) | static_cast(str[2]) << 8 | static_cast(str[1]) << 16 | static_cast(str[0]) << 24; +} + +/* Macros used for loops unrolling */ + +inline static void SHA256_SCR(uint32_t w[64], int i) +{ + w[i] = SHA256_F4(w[i - 2]) + w[i - 7] + SHA256_F3(w[i - 15]) + w[i - 16]; +} + +static const uint32_t sha256_h0[8] = +{ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 +}; + +static const uint32_t sha256_k[64] = +{ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +/** An sha256 context + */ +class SHA256Context : public Encryption::Context +{ + void Transform(unsigned char *message, unsigned block_nb) + { + uint32_t w[64], wv[8]; + unsigned char *sub_block; + for (unsigned i = 1; i <= block_nb; ++i) + { + int j; + sub_block = message + ((i - 1) << 6); + + for (j = 0; j < 16; ++j) + PACK32(&sub_block[j << 2], w[j]); + for (j = 16; j < 64; ++j) + SHA256_SCR(w, j); + for (j = 0; j < 8; ++j) + wv[j] = this->h[j]; + for (j = 0; j < 64; ++j) + { + uint32_t t1 = wv[7] + SHA256_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha256_k[j] + w[j]; + uint32_t t2 = SHA256_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]); + wv[7] = wv[6]; + wv[6] = wv[5]; + wv[5] = wv[4]; + wv[4] = wv[3] + t1; + wv[3] = wv[2]; + wv[2] = wv[1]; + wv[1] = wv[0]; + wv[0] = t1 + t2; + } + for (j = 0; j < 8; ++j) + this->h[j] += wv[j]; + } + } + + unsigned tot_len; + unsigned len; + unsigned char block[2 * SHA256_BLOCK_SIZE]; + uint32_t h[8]; + unsigned char digest[SHA256_DIGEST_SIZE]; + + public: + SHA256Context(Encryption::IV *iv) + { + if (iv != NULL) + { + if (iv->second != 8) + throw CoreException("Invalid IV size"); + for (int i = 0; i < 8; ++i) + this->h[i] = iv->first[i]; + } + else + for (int i = 0; i < 8; ++i) + this->h[i] = sha256_h0[i]; + + this->tot_len = 0; + this->len = 0; + memset(this->block, 0, sizeof(this->block)); + memset(this->digest, 0, sizeof(this->digest)); + } + + void Update(const unsigned char *message, size_t mlen) override + { + unsigned tmp_len = SHA256_BLOCK_SIZE - this->len, rem_len = mlen < tmp_len ? mlen : tmp_len; + + memcpy(&this->block[this->len], message, rem_len); + if (this->len + mlen < SHA256_BLOCK_SIZE) + { + this->len += mlen; + return; + } + unsigned new_len = mlen - rem_len, block_nb = new_len / SHA256_BLOCK_SIZE; + unsigned char *shifted_message = new unsigned char[mlen - rem_len]; + memcpy(shifted_message, message + rem_len, mlen - rem_len); + this->Transform(this->block, 1); + this->Transform(shifted_message, block_nb); + rem_len = new_len % SHA256_BLOCK_SIZE; + memcpy(this->block, &shifted_message[block_nb << 6], rem_len); + delete [] shifted_message; + this->len = rem_len; + this->tot_len += (block_nb + 1) << 6; + } + + void Finalize() override + { + unsigned block_nb = 1 + ((SHA256_BLOCK_SIZE - 9) < (this->len % SHA256_BLOCK_SIZE)); + unsigned len_b = (this->tot_len + this->len) << 3; + unsigned pm_len = block_nb << 6; + memset(this->block + this->len, 0, pm_len - this->len); + this->block[this->len] = 0x80; + UNPACK32(len_b, this->block + pm_len - 4); + this->Transform(this->block, block_nb); + for (int i = 0 ; i < 8; ++i) + UNPACK32(this->h[i], &this->digest[i << 2]); + } + + Encryption::Hash GetFinalizedHash() override + { + Encryption::Hash hash; + hash.first = this->digest; + hash.second = SHA256_DIGEST_SIZE; + return hash; + } +}; + +class SHA256Provider : public Encryption::Provider +{ + public: + SHA256Provider(Module *creator) : Encryption::Provider(creator, "sha256") { } + + Encryption::Context *CreateContext(Encryption::IV *iv) override + { + return new SHA256Context(iv); + } + + Encryption::IV GetDefaultIV() override + { + Encryption::IV iv; + iv.first = sha256_h0; + iv.second = sizeof(sha256_h0) / sizeof(uint32_t); + return iv; + } +}; + +class ESHA256 : public Module + , public EventHook + , public EventHook +{ + SHA256Provider sha256provider; + + unsigned iv[8]; + bool use_iv; + + /* initializes the IV with a new random value */ + void NewRandomIV() + { + for (int i = 0; i < 8; ++i) + iv[i] = static_cast(rand()); + } + + /* returns the IV as base64-encrypted string */ + Anope::string GetIVString() + { + char buf[33]; + for (int i = 0; i < 8; ++i) + UNPACK32(iv[i], reinterpret_cast(&buf[i << 2])); + buf[32] = '\0'; + return Anope::Hex(buf, 32); + } + + /* splits the appended IV from the password string so it can be used for the next encryption */ + /* password format: :: */ + void GetIVFromPass(const Anope::string &password) + { + size_t pos = password.find(':'); + Anope::string buf = password.substr(password.find(':', pos + 1) + 1, password.length()); + char buf2[33]; + Anope::Unhex(buf, buf2, sizeof(buf2)); + for (int i = 0 ; i < 8; ++i) + PACK32(reinterpret_cast(&buf2[i << 2]), iv[i]); + } + + public: + ESHA256(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR) + , sha256provider(this) + { + + + use_iv = false; + } + + EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override + { + if (!use_iv) + NewRandomIV(); + else + use_iv = false; + + Encryption::IV initilization(this->iv, 8); + SHA256Context ctx(&initilization); + ctx.Update(reinterpret_cast(src.c_str()), src.length()); + ctx.Finalize(); + + Encryption::Hash hash = ctx.GetFinalizedHash(); + + std::stringstream buf; + buf << "sha256:" << Anope::Hex(reinterpret_cast(hash.first), hash.second) << ":" << GetIVString(); + Log(LOG_DEBUG_2) << "(enc_sha256) hashed password from [" << src << "] to [" << buf.str() << " ]"; + dest = buf.str(); + return EVENT_ALLOW; + } + + void OnCheckAuthentication(User *, NickServ::IdentifyRequest *req) override + { + NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); + if (na == NULL) + return; + NickServ::Account *nc = na->GetAccount(); + + size_t pos = nc->GetPassword().find(':'); + if (pos == Anope::string::npos) + return; + Anope::string hash_method(nc->GetPassword().substr(0, pos)); + if (!hash_method.equals_cs("sha256")) + return; + + GetIVFromPass(nc->GetPassword()); + use_iv = true; + Anope::string buf; + this->OnEncrypt(req->GetPassword(), buf); + if (nc->GetPassword().equals_cs(buf)) + { + /* if we are NOT the first module in the list, + * we want to re-encrypt the pass with the new encryption + */ + if (ModuleManager::FindFirstOf(ENCRYPTION) != this) + { + Anope::string p; + Anope::Encrypt(req->GetPassword(), p); + nc->SetPassword(p); + } + req->Success(this); + } + } +}; + +MODULE_INIT(ESHA256) diff --git a/modules/extra/ldap.cpp b/modules/extra/ldap.cpp new file mode 100644 index 000000000..71c31b9ac --- /dev/null +++ b/modules/extra/ldap.cpp @@ -0,0 +1,623 @@ +/* + * + * (C) 2011-2015 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +/* RequiredLibraries: ldap_r,lber */ +/* RequiredWindowsLibraries: libldap_r,liblber */ + +#include "module.h" +#include "modules/ldap.h" +#include + +class LDAPService; +static Pipe *me; + +class LDAPRequest +{ + public: + LDAPService *service; + LDAPInterface *inter; + LDAPMessage *message; /* message returned by ldap_ */ + LDAPResult *result; /* final result */ + struct timeval tv; + QueryType type; + + LDAPRequest(LDAPService *s, LDAPInterface *i) + : service(s) + , inter(i) + , message(NULL) + , result(NULL) + { + type = QUERY_UNKNOWN; + tv.tv_sec = 0; + tv.tv_usec = 100000; + } + + virtual ~LDAPRequest() + { + delete result; + if (inter != NULL) + inter->OnDelete(); + if (message != NULL) + ldap_msgfree(message); + } + + virtual int run() = 0; +}; + +class LDAPBind : public LDAPRequest +{ + Anope::string who, pass; + + public: + LDAPBind(LDAPService *s, LDAPInterface *i, const Anope::string &w, const Anope::string &p) + : LDAPRequest(s, i) + , who(w) + , pass(p) + { + type = QUERY_BIND; + } + + int run() anope_override; +}; + +class LDAPSearch : public LDAPRequest +{ + Anope::string base; + Anope::string filter; + + public: + LDAPSearch(LDAPService *s, LDAPInterface *i, const Anope::string &b, const Anope::string &f) + : LDAPRequest(s, i) + , base(b) + , filter(f) + { + type = QUERY_SEARCH; + } + + int run() anope_override; +}; + +class LDAPAdd : public LDAPRequest +{ + Anope::string dn; + LDAPMods attributes; + + public: + LDAPAdd(LDAPService *s, LDAPInterface *i, const Anope::string &d, const LDAPMods &attr) + : LDAPRequest(s, i) + , dn(d) + , attributes(attr) + { + type = QUERY_ADD; + } + + int run() anope_override; +}; + +class LDAPDel : public LDAPRequest +{ + Anope::string dn; + + public: + LDAPDel(LDAPService *s, LDAPInterface *i, const Anope::string &d) + : LDAPRequest(s, i) + , dn(d) + { + type = QUERY_DELETE; + } + + int run() anope_override; +}; + +class LDAPModify : public LDAPRequest +{ + Anope::string base; + LDAPMods attributes; + + public: + LDAPModify(LDAPService *s, LDAPInterface *i, const Anope::string &b, const LDAPMods &attr) + : LDAPRequest(s, i) + , base(b) + , attributes(attr) + { + type = QUERY_MODIFY; + } + + int run() anope_override; +}; + +class LDAPService : public LDAPProvider, public Thread, public Condition +{ + Anope::string server; + Anope::string admin_binddn; + Anope::string admin_pass; + + LDAP *con; + + time_t last_connect; + + public: + static LDAPMod **BuildMods(const LDAPMods &attributes) + { + LDAPMod **mods = new LDAPMod*[attributes.size() + 1]; + memset(mods, 0, sizeof(LDAPMod*) * (attributes.size() + 1)); + for (unsigned x = 0; x < attributes.size(); ++x) + { + const LDAPModification &l = attributes[x]; + mods[x] = new LDAPMod(); + + if (l.op == LDAPModification::LDAP_ADD) + mods[x]->mod_op = LDAP_MOD_ADD; + else if (l.op == LDAPModification::LDAP_DEL) + mods[x]->mod_op = LDAP_MOD_DELETE; + else if (l.op == LDAPModification::LDAP_REPLACE) + mods[x]->mod_op = LDAP_MOD_REPLACE; + else if (l.op != 0) + throw LDAPException("Unknown LDAP operation"); + mods[x]->mod_type = strdup(l.name.c_str()); + mods[x]->mod_values = new char*[l.values.size() + 1]; + memset(mods[x]->mod_values, 0, sizeof(char *) * (l.values.size() + 1)); + for (unsigned j = 0, c = 0; j < l.values.size(); ++j) + if (!l.values[j].empty()) + mods[x]->mod_values[c++] = strdup(l.values[j].c_str()); + } + return mods; + } + + static void FreeMods(LDAPMod **mods) + { + for (int i = 0; mods[i] != NULL; ++i) + { + free(mods[i]->mod_type); + for (int j = 0; mods[i]->mod_values[j] != NULL; ++j) + free(mods[i]->mod_values[j]); + delete [] mods[i]->mod_values; + } + delete [] mods; + } + + private: + void Connect() + { + int i = ldap_initialize(&this->con, this->server.c_str()); + if (i != LDAP_SUCCESS) + throw LDAPException("Unable to connect to LDAP service " + this->name + ": " + ldap_err2string(i)); + + const int version = LDAP_VERSION3; + i = ldap_set_option(this->con, LDAP_OPT_PROTOCOL_VERSION, &version); + if (i != LDAP_OPT_SUCCESS) + throw LDAPException("Unable to set protocol version for " + this->name + ": " + ldap_err2string(i)); + + const struct timeval tv = { 0, 0 }; + i = ldap_set_option(this->con, LDAP_OPT_NETWORK_TIMEOUT, &tv); + if (i != LDAP_OPT_SUCCESS) + throw LDAPException("Unable to set timeout for " + this->name + ": " + ldap_err2string(i)); + } + + void Reconnect() + { + /* Only try one connect a minute. It is an expensive blocking operation */ + if (last_connect > Anope::CurTime - 60) + throw LDAPException("Unable to connect to LDAP service " + this->name + ": reconnecting too fast"); + last_connect = Anope::CurTime; + + ldap_unbind_ext(this->con, NULL, NULL); + + Connect(); + } + + void QueueRequest(LDAPRequest *r) + { + this->Lock(); + this->queries.push_back(r); + this->Wakeup(); + this->Unlock(); + } + + public: + typedef std::vector query_queue; + query_queue queries, results; + Mutex process_mutex; /* held when processing requests not in either queue */ + + LDAPService(Module *o, const Anope::string &n, const Anope::string &s, const Anope::string &b, const Anope::string &p) : LDAPProvider(o, n), server(s), admin_binddn(b), admin_pass(p), last_connect(0) + { + Connect(); + } + + ~LDAPService() + { + /* At this point the thread has stopped so we don't need to hold process_mutex */ + + this->Lock(); + + for (unsigned int i = 0; i < this->queries.size(); ++i) + { + LDAPRequest *req = this->queries[i]; + + /* queries have no results yet */ + req->result = new LDAPResult(); + req->result->type = req->type; + req->result->error = "LDAP Interface is going away"; + if (req->inter) + req->inter->OnError(*req->result); + + delete req; + } + this->queries.clear(); + + for (unsigned int i = 0; i < this->results.size(); ++i) + { + LDAPRequest *req = this->results[i]; + + /* even though this may have already finished successfully we return that it didn't */ + req->result->error = "LDAP Interface is going away"; + if (req->inter) + req->inter->OnError(*req->result); + + delete req; + } + + this->Unlock(); + + ldap_unbind_ext(this->con, NULL, NULL); + } + + void BindAsAdmin(LDAPInterface *i) override + { + this->Bind(i, this->admin_binddn, this->admin_pass); + } + + void Bind(LDAPInterface *i, const Anope::string &who, const Anope::string &pass) override + { + LDAPBind *b = new LDAPBind(this, i, who, pass); + QueueRequest(b); + } + + void Search(LDAPInterface *i, const Anope::string &base, const Anope::string &filter) override + { + if (i == NULL) + throw LDAPException("No interface"); + + LDAPSearch *s = new LDAPSearch(this, i, base, filter); + QueueRequest(s); + } + + void Add(LDAPInterface *i, const Anope::string &dn, LDAPMods &attributes) override + { + LDAPAdd *add = new LDAPAdd(this, i, dn, attributes); + QueueRequest(add); + } + + void Del(LDAPInterface *i, const Anope::string &dn) anope_override + { + LDAPDel *del = new LDAPDel(this, i, dn); + QueueRequest(del); + } + + void Modify(LDAPInterface *i, const Anope::string &base, LDAPMods &attributes) anope_override + { + LDAPModify *mod = new LDAPModify(this, i, base, attributes); + QueueRequest(mod); + } + + private: + void BuildReply(int res, LDAPRequest *req) + { + LDAPResult *ldap_result = req->result = new LDAPResult(); + req->result->type = req->type; + + if (res != LDAP_SUCCESS) + { + ldap_result->error = ldap_err2string(res); + return; + } + + if (req->message == NULL) + { + return; + } + + /* a search result */ + + for (LDAPMessage *cur = ldap_first_message(this->con, req->message); cur; cur = ldap_next_message(this->con, cur)) + { + LDAPAttributes attributes; + + char *dn = ldap_get_dn(this->con, cur); + if (dn != NULL) + { + attributes["dn"].push_back(dn); + ldap_memfree(dn); + dn = NULL; + } + + BerElement *ber = NULL; + + for (char *attr = ldap_first_attribute(this->con, cur, &ber); attr; attr = ldap_next_attribute(this->con, cur, ber)) + { + berval **vals = ldap_get_values_len(this->con, cur, attr); + int count = ldap_count_values_len(vals); + + std::vector attrs; + for (int j = 0; j < count; ++j) + attrs.push_back(vals[j]->bv_val); + attributes[attr] = attrs; + + ldap_value_free_len(vals); + ldap_memfree(attr); + } + + if (ber != NULL) + ber_free(ber, 0); + + ldap_result->messages.push_back(attributes); + } + } + + void SendRequests() + { + process_mutex.Lock(); + + query_queue q; + this->Lock(); + queries.swap(q); + this->Unlock(); + + if (q.empty()) + { + process_mutex.Unlock(); + return; + } + + for (unsigned int i = 0; i < q.size(); ++i) + { + LDAPRequest *req = q[i]; + int ret = req->run(); + + if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT) + { + /* try again */ + try + { + Reconnect(); + } + catch (const LDAPException &) + { + } + + ret = req->run(); + } + + BuildReply(ret, req); + + this->Lock(); + results.push_back(req); + this->Unlock(); + } + + me->Notify(); + + process_mutex.Unlock(); + } + + public: + void Run() anope_override + { + while (!this->GetExitState()) + { + this->Lock(); + /* Queries can be non empty if one is pushed during SendRequests() */ + if (queries.empty()) + this->Wait(); + this->Unlock(); + + SendRequests(); + } + } + + LDAP* GetConnection() + { + return con; + } +}; + +class ModuleLDAP : public Module, public Pipe + , public EventHook +{ + std::map LDAPServices; + + public: + + ModuleLDAP(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + { + me = this; + } + + ~ModuleLDAP() + { + for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) + { + it->second->SetExitState(); + it->second->Wakeup(); + it->second->Join(); + delete it->second; + } + LDAPServices.clear(); + } + + void OnReload(Configuration::Conf *config) override + { + Configuration::Block *conf = config->GetModule(this); + + for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end();) + { + const Anope::string &cname = it->first; + LDAPService *s = it->second; + int i; + + ++it; + + for (i = 0; i < conf->CountBlock("ldap"); ++i) + if (conf->GetBlock("ldap", i)->Get("name", "ldap/main") == cname) + break; + + if (i == conf->CountBlock("ldap")) + { + Log(LOG_NORMAL, "ldap") << "LDAP: Removing server connection " << cname; + + s->SetExitState(); + s->Wakeup(); + s->Join(); + delete s; + this->LDAPServices.erase(cname); + } + } + + for (int i = 0; i < conf->CountBlock("ldap"); ++i) + { + Configuration::Block *ldap = conf->GetBlock("ldap", i); + + const Anope::string &connname = ldap->Get("name", "ldap/main"); + + if (this->LDAPServices.find(connname) == this->LDAPServices.end()) + { + const Anope::string &server = ldap->Get("server", "127.0.0.1"); + const Anope::string &admin_binddn = ldap->Get("admin_binddn"); + const Anope::string &admin_password = ldap->GetAnope::string>("admin_password"); + + try + { + LDAPService *ss = new LDAPService(this, connname, server, admin_binddn, admin_password); + ss->Start(); + this->LDAPServices.insert(std::make_pair(connname, ss)); + + Log(LOG_NORMAL, "ldap") << "LDAP: Successfully initialized server " << connname << " (" << server << ")"; + } + catch (const LDAPException &ex) + { + Log(LOG_NORMAL, "ldap") << "LDAP: " << ex.GetReason(); + } + } + } + } + + void OnModuleUnload(User *, Module *m) override + { + for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) + { + LDAPService *s = it->second; + + s->process_mutex.Lock(); + s->Lock(); + + for (unsigned int i = s->queries.size(); i > 0; --i) + { + LDAPRequest *req = s->queries[i - 1]; + LDAPInterface *li = req->inter; + + if (li && li->owner == m) + { + s->queries.erase(s->queries.begin() + i - 1); + delete req; + } + } + for (unsigned int i = s->results.size(); i > 0; --i) + { + LDAPRequest *req = s->results[i - 1]; + LDAPInterface *li = req->inter; + + if (li && li->owner == m) + { + s->results.erase(s->results.begin() + i - 1); + delete req; + } + } + + s->Unlock(); + s->process_mutex.Unlock(); + } + } + + void OnNotify() override + { + for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) + { + LDAPService *s = it->second; + + LDAPService::query_queue results; + s->Lock(); + results.swap(s->results); + s->Unlock(); + + for (unsigned int i = 0; i < results.size(); ++i) + { + LDAPRequest *req = results[i]; + LDAPInterface *li = req->inter; + LDAPResult *r = req->result; + + if (li != NULL) + { + if (!r->getError().empty()) + { + Log(this) << "Error running LDAP query: " << r->getError(); + li->OnError(*r); + } + else + li->OnResult(*r); + } + + delete req; + } + } + } +}; + +int LDAPBind::run() +{ + berval cred; + cred.bv_val = strdup(pass.c_str()); + cred.bv_len = pass.length(); + + int i = ldap_sasl_bind_s(service->GetConnection(), who.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); + + free(cred.bv_val); + + return i; +} + +int LDAPSearch::run() +{ + return ldap_search_ext_s(service->GetConnection(), base.c_str(), LDAP_SCOPE_SUBTREE, filter.c_str(), NULL, 0, NULL, NULL, &tv, 0, &message); +} + +int LDAPAdd::run() +{ + LDAPMod **mods = LDAPService::BuildMods(attributes); + int i = ldap_add_ext_s(service->GetConnection(), dn.c_str(), mods, NULL, NULL); + LDAPService::FreeMods(mods); + return i; +} + +int LDAPDel::run() +{ + return ldap_delete_ext_s(service->GetConnection(), dn.c_str(), NULL, NULL); +} + +int LDAPModify::run() +{ + LDAPMod **mods = LDAPService::BuildMods(attributes); + int i = ldap_modify_ext_s(service->GetConnection(), base.c_str(), mods, NULL, NULL); + LDAPService::FreeMods(mods); + return i; +} + +MODULE_INIT(ModuleLDAP) + diff --git a/modules/extra/ldap_authentication.cpp b/modules/extra/ldap_authentication.cpp new file mode 100644 index 000000000..dd63fd3fb --- /dev/null +++ b/modules/extra/ldap_authentication.cpp @@ -0,0 +1,302 @@ +#include "module.h" +#include "modules/ldap.h" +#include "modules/nickserv.h" + +static Module *me; + +static Anope::string basedn; +static Anope::string search_filter; +static Anope::string object_class; +static Anope::string email_attribute; +static Anope::string username_attribute; + +struct IdentifyInfo +{ + Reference user; + IdentifyRequest *req; + ServiceReference lprov; + bool admin_bind; + Anope::string dn; + + IdentifyInfo(User *u, IdentifyRequest *r, ServiceReference &lp) : user(u), req(r), lprov(lp), admin_bind(true) + { + req->Hold(me); + } + + ~IdentifyInfo() + { + req->Release(me); + } +}; + +class IdentifyInterface : public LDAPInterface +{ + IdentifyInfo *ii; + + public: + IdentifyInterface(Module *m, IdentifyInfo *i) : LDAPInterface(m), ii(i) { } + + ~IdentifyInterface() + { + delete ii; + } + + void OnDelete() anope_override + { + delete this; + } + + void OnResult(const LDAPResult &r) override + { + if (!ii->lprov) + return; + + switch (r.type) + { + case QUERY_SEARCH: + { + if (!r.empty()) + { + try + { + const LDAPAttributes &attr = r.get(0); + ii->dn = attr.get("dn"); + Log(LOG_DEBUG) << "m_ldap_authenticationn: binding as " << ii->dn; + + ii->lprov->Bind(new IdentifyInterface(this->owner, ii), ii->dn, ii->req->GetPassword()); + ii = NULL; + } + catch (const LDAPException &ex) + { + Log(this->owner) << "Error binding after search: " << ex.GetReason(); + } + } + break; + } + case QUERY_BIND: + { + if (ii->admin_bind) + { + Anope::string sf = search_filter.replace_all_cs("%account", ii->req->GetAccount()).replace_all_cs("%object_class", object_class); + try + { + Log(LOG_DEBUG) << "m_ldap_authentication: searching for " << sf; + ii->lprov->Search(new IdentifyInterface(this->owner, ii), basedn, sf); + ii->admin_bind = false; + ii = NULL; + } + catch (const LDAPException &ex) + { + Log(this->owner) << "Unable to search for " << sf << ": " << ex.GetReason(); + } + } + else + { + NickServ::Nick *na = NickServ::FindNick(ii->req->GetAccount()); + if (na == NULL) + { + na = new NickServ::Nick(ii->req->GetAccount(), new NickServ::Account(ii->req->GetAccount())); + na->SetLastRealname(ii->user ? ii->user->realname : ii->req->GetAccount()); + NickServ::Event::OnNickRegister(&NickServ::Event::NickRegister::OnNickRegister, ii->user, na, ii->req->GetPassword());; + ServiceBot *NickServ = Config->GetClient("NickServ"); + if (ii->user && NickServ) + ii->user->SendMessage(NickServ, _("Your account \002%s\002 has been successfully created."), na->GetNick().c_str()); + } + // encrypt and store the password in the nickcore + Anope::Encrypt(ii->req->GetPassword(), na->GetAccount()->pass); + + na->GetAccount()->Extend("m_ldap_authentication_dn", ii->dn); + ii->req->Success(me); + } + break; + } + default: + break; + } + } + + void OnError(const LDAPResult &r) override + { + } +}; + +class OnIdentifyInterface : public LDAPInterface +{ + Anope::string uid; + + public: + OnIdentifyInterface(Module *m, const Anope::string &i) : LDAPInterface(m), uid(i) { } + + void OnDelete() anope_override + { + delete this; + } + + void OnResult(const LDAPResult &r) override + { + User *u = User::Find(uid); + + if (!u || !u->Account() || r.empty()) + return; + + try + { + const LDAPAttributes &attr = r.get(0); + Anope::string email = attr.get(email_attribute); + + if (!email.equals_ci(u->Account()->GetEmail())) + { + u->Account()->GetEmail() = email; + ServiceBot *NickServ = Config->GetClient("NickServ"); + if (NickServ) + u->SendMessage(NickServ, _("Your email has been updated to \002%s\002"), email.c_str()); + Log(this->owner) << "Updated email address for " << u->nick << " (" << u->Account()->GetDisplay() << ") to " << email; + } + } + catch (const LDAPException &ex) + { + Log(this->owner) << ex.GetReason(); + } + } + + void OnError(const LDAPResult &r) override + { + Log(this->owner) << r.error; + } +}; + +class OnRegisterInterface : public LDAPInterface +{ + public: + OnRegisterInterface(Module *m) : LDAPInterface(m) { } + + void OnResult(const LDAPResult &r) override + { + Log(this->owner) << "Successfully added newly created account to LDAP"; + } + + void OnError(const LDAPResult &r) override + { + Log(this->owner) << "Error adding newly created account to LDAP: " << r.getError(); + } +}; + +class ModuleLDAPAuthentication : public Module + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + ServiceReference ldap; + OnRegisterInterface orinterface; + + PrimitiveExtensibleItem dn; + + Anope::string password_attribute; + Anope::string disable_register_reason; + Anope::string disable_email_reason; + + public: + ModuleLDAPAuthentication(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + , EventHook("OnPreCommand", EventHook::Priority::FIRST) + , EventHook("OnCheckAuthentication", EventHook::Priority::FIRST) + , EventHook("OnNickIdentify", EventHook::Priority::FIRST) + , EventHook("OnNickRegister", EventHook::Priority::FIRST) + , ldap("LDAPProvider", "ldap/main") + , orinterface(this) + , dn(this, "m_ldap_authentication_dn") + { + me = this; + } + + void OnReload(Configuration::Conf *config) override + { + Configuration::Block *conf = Config->GetModule(this); + + basedn = conf->Get("basedn"); + search_filter = conf->Get("search_filter"); + object_class = conf->Get("object_class"); + username_attribute = conf->Get("username_attribute"); + this->password_attribute = conf->Get("password_attribute"); + email_attribute = conf->Get("email_attribute"); + this->disable_register_reason = conf->Get("disable_register_reason"); + this->disable_email_reason = conf->Get("disable_email_reason"); + + if (!email_attribute.empty()) + /* Don't complain to users about how they need to update their email, we will do it for them */ + config->GetModule("nickserv")->Set("forceemail", "false"); + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override + { + if (!this->disable_register_reason.empty()) + { + if (command->name == "nickserv/register" || command->name == "nickserv/group") + { + source.Reply(this->disable_register_reason); + return EVENT_STOP; + } + } + + if (!email_attribute.empty() && !this->disable_email_reason.empty() && command->name == "nickserv/set/email") + { + source.Reply(this->disable_email_reason); + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + void OnCheckAuthentication(User *u, IdentifyRequest *req) override + { + if (!this->ldap) + return; + + IdentifyInfo *ii = new IdentifyInfo(u, req, this->ldap); + this->ldap->BindAsAdmin(new IdentifyInterface(this, ii)); + } + + void OnNickIdentify(User *u) override + { + if (email_attribute.empty() || !this->ldap) + return; + + Anope::string *d = dn.Get(u->Account()); + if (!d || d->empty()) + return; + + this->ldap->Search(new OnIdentifyInterface(this, u->GetUID()), *d, "(" + email_attribute + "=*)"); + } + + void OnNickRegister(User *, NickServ::Nick *na, const Anope::string &pass) override + { + if (!this->disable_register_reason.empty() || !this->ldap) + return; + + this->ldap->BindAsAdmin(NULL); + + LDAPMods attributes; + attributes.resize(4); + + attributes[0].name = "objectClass"; + attributes[0].values.push_back("top"); + attributes[0].values.push_back(object_class); + + attributes[1].name = username_attribute; + attributes[1].values.push_back(na->GetNick()); + + if (!na->GetAccount()->GetEmail().empty()) + { + attributes[2].name = email_attribute; + attributes[2].values.push_back(na->GetAccount()->GetEmail()); + } + + attributes[3].name = this->password_attribute; + attributes[3].values.push_back(pass); + + Anope::string new_dn = username_attribute + "=" + na->GetNick() + "," + basedn; + this->ldap->Add(&this->orinterface, new_dn, attributes); + } +}; + +MODULE_INIT(ModuleLDAPAuthentication) diff --git a/modules/extra/ldap_oper.cpp b/modules/extra/ldap_oper.cpp new file mode 100644 index 000000000..31653544e --- /dev/null +++ b/modules/extra/ldap_oper.cpp @@ -0,0 +1,134 @@ +#include "module.h" +#include "modules/ldap.h" + +static std::set my_opers; +static Anope::string opertype_attribute; + +class IdentifyInterface : public LDAPInterface +{ + Reference u; + + public: + IdentifyInterface(Module *m, User *user) : LDAPInterface(m), u(user) + { + } + + void OnResult(const LDAPResult &r) override + { + if (!u || !u->Account()) + return; + + NickServ::Account *nc = u->Account(); + + try + { + const LDAPAttributes &attr = r.get(0); + + const Anope::string &opertype = attr.get(opertype_attribute); + + OperType *ot = OperType::Find(opertype); + if (ot != NULL && (nc->o == NULL || ot != nc->o->ot)) + { + Oper *o = nc->o; + if (o != NULL && my_opers.count(o) > 0) + { + my_opers.erase(o); + delete o; + } + o = new Oper(u->nick, ot); + my_opers.insert(o); + nc->o = o; + + Log(this->owner) << "Tied " << u->nick << " (" << nc->GetDisplay() << ") to opertype " << ot->GetName(); + } + } + catch (const LDAPException &ex) + { + if (nc->o != NULL) + { + if (my_opers.count(nc->o) > 0) + { + my_opers.erase(nc->o); + delete nc->o; + } + nc->o = NULL; + + Log(this->owner) << "Removed services operator from " << u->nick << " (" << nc->GetDisplay() << ")"; + } + } + } + + void OnError(const LDAPResult &r) override + { + } + + void OnDelete() anope_override + { + delete this; + } +}; + +class LDAPOper : public Module + , public EventHook + , public EventHook +{ + ServiceReference ldap; + + Anope::string binddn; + Anope::string password; + Anope::string basedn; + Anope::string filter; + public: + LDAPOper(const Anope::string &modname, const Anope::string &creator) + : Module(modname, creator, EXTRA | VENDOR) + , ldap("LDAPProvider", "ldap/main") + { + + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = Config->GetModule(this); + + this->binddn = config->Get("binddn"); + this->password = config->Get("password"); + this->basedn = config->Get("basedn"); + this->filter = config->Get("filter"); + opertype_attribute = config->Get("opertype_attribute"); + + for (std::set::iterator it = my_opers.begin(), it_end = my_opers.end(); it != it_end; ++it) + delete *it; + my_opers.clear(); + } + + void OnNickIdentify(User *u) override + { + try + { + if (!this->ldap) + throw LDAPException("No LDAP interface. Is m_ldap loaded and configured correctly?"); + else if (this->basedn.empty() || this->filter.empty() || opertype_attribute.empty()) + throw LDAPException("Could not search LDAP for opertype settings, invalid configuration."); + + if (!this->binddn.empty()) + this->ldap->Bind(NULL, this->binddn.replace_all_cs("%a", u->Account()->GetDisplay()), this->password.c_str()); + this->ldap->Search(new IdentifyInterface(this, u), this->basedn, this->filter.replace_all_cs("%a", u->Account()->GetDisplay())); + } + catch (const LDAPException &ex) + { + Log() << ex.GetReason(); + } + } + + void OnDelCore(NickServ::Account *nc) override + { + if (nc->o != NULL && my_opers.count(nc->o) > 0) + { + my_opers.erase(nc->o); + delete nc->o; + nc->o = NULL; + } + } +}; + +MODULE_INIT(LDAPOper) diff --git a/modules/extra/m_ldap.cpp b/modules/extra/m_ldap.cpp deleted file mode 100644 index 71c31b9ac..000000000 --- a/modules/extra/m_ldap.cpp +++ /dev/null @@ -1,623 +0,0 @@ -/* - * - * (C) 2011-2015 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -/* RequiredLibraries: ldap_r,lber */ -/* RequiredWindowsLibraries: libldap_r,liblber */ - -#include "module.h" -#include "modules/ldap.h" -#include - -class LDAPService; -static Pipe *me; - -class LDAPRequest -{ - public: - LDAPService *service; - LDAPInterface *inter; - LDAPMessage *message; /* message returned by ldap_ */ - LDAPResult *result; /* final result */ - struct timeval tv; - QueryType type; - - LDAPRequest(LDAPService *s, LDAPInterface *i) - : service(s) - , inter(i) - , message(NULL) - , result(NULL) - { - type = QUERY_UNKNOWN; - tv.tv_sec = 0; - tv.tv_usec = 100000; - } - - virtual ~LDAPRequest() - { - delete result; - if (inter != NULL) - inter->OnDelete(); - if (message != NULL) - ldap_msgfree(message); - } - - virtual int run() = 0; -}; - -class LDAPBind : public LDAPRequest -{ - Anope::string who, pass; - - public: - LDAPBind(LDAPService *s, LDAPInterface *i, const Anope::string &w, const Anope::string &p) - : LDAPRequest(s, i) - , who(w) - , pass(p) - { - type = QUERY_BIND; - } - - int run() anope_override; -}; - -class LDAPSearch : public LDAPRequest -{ - Anope::string base; - Anope::string filter; - - public: - LDAPSearch(LDAPService *s, LDAPInterface *i, const Anope::string &b, const Anope::string &f) - : LDAPRequest(s, i) - , base(b) - , filter(f) - { - type = QUERY_SEARCH; - } - - int run() anope_override; -}; - -class LDAPAdd : public LDAPRequest -{ - Anope::string dn; - LDAPMods attributes; - - public: - LDAPAdd(LDAPService *s, LDAPInterface *i, const Anope::string &d, const LDAPMods &attr) - : LDAPRequest(s, i) - , dn(d) - , attributes(attr) - { - type = QUERY_ADD; - } - - int run() anope_override; -}; - -class LDAPDel : public LDAPRequest -{ - Anope::string dn; - - public: - LDAPDel(LDAPService *s, LDAPInterface *i, const Anope::string &d) - : LDAPRequest(s, i) - , dn(d) - { - type = QUERY_DELETE; - } - - int run() anope_override; -}; - -class LDAPModify : public LDAPRequest -{ - Anope::string base; - LDAPMods attributes; - - public: - LDAPModify(LDAPService *s, LDAPInterface *i, const Anope::string &b, const LDAPMods &attr) - : LDAPRequest(s, i) - , base(b) - , attributes(attr) - { - type = QUERY_MODIFY; - } - - int run() anope_override; -}; - -class LDAPService : public LDAPProvider, public Thread, public Condition -{ - Anope::string server; - Anope::string admin_binddn; - Anope::string admin_pass; - - LDAP *con; - - time_t last_connect; - - public: - static LDAPMod **BuildMods(const LDAPMods &attributes) - { - LDAPMod **mods = new LDAPMod*[attributes.size() + 1]; - memset(mods, 0, sizeof(LDAPMod*) * (attributes.size() + 1)); - for (unsigned x = 0; x < attributes.size(); ++x) - { - const LDAPModification &l = attributes[x]; - mods[x] = new LDAPMod(); - - if (l.op == LDAPModification::LDAP_ADD) - mods[x]->mod_op = LDAP_MOD_ADD; - else if (l.op == LDAPModification::LDAP_DEL) - mods[x]->mod_op = LDAP_MOD_DELETE; - else if (l.op == LDAPModification::LDAP_REPLACE) - mods[x]->mod_op = LDAP_MOD_REPLACE; - else if (l.op != 0) - throw LDAPException("Unknown LDAP operation"); - mods[x]->mod_type = strdup(l.name.c_str()); - mods[x]->mod_values = new char*[l.values.size() + 1]; - memset(mods[x]->mod_values, 0, sizeof(char *) * (l.values.size() + 1)); - for (unsigned j = 0, c = 0; j < l.values.size(); ++j) - if (!l.values[j].empty()) - mods[x]->mod_values[c++] = strdup(l.values[j].c_str()); - } - return mods; - } - - static void FreeMods(LDAPMod **mods) - { - for (int i = 0; mods[i] != NULL; ++i) - { - free(mods[i]->mod_type); - for (int j = 0; mods[i]->mod_values[j] != NULL; ++j) - free(mods[i]->mod_values[j]); - delete [] mods[i]->mod_values; - } - delete [] mods; - } - - private: - void Connect() - { - int i = ldap_initialize(&this->con, this->server.c_str()); - if (i != LDAP_SUCCESS) - throw LDAPException("Unable to connect to LDAP service " + this->name + ": " + ldap_err2string(i)); - - const int version = LDAP_VERSION3; - i = ldap_set_option(this->con, LDAP_OPT_PROTOCOL_VERSION, &version); - if (i != LDAP_OPT_SUCCESS) - throw LDAPException("Unable to set protocol version for " + this->name + ": " + ldap_err2string(i)); - - const struct timeval tv = { 0, 0 }; - i = ldap_set_option(this->con, LDAP_OPT_NETWORK_TIMEOUT, &tv); - if (i != LDAP_OPT_SUCCESS) - throw LDAPException("Unable to set timeout for " + this->name + ": " + ldap_err2string(i)); - } - - void Reconnect() - { - /* Only try one connect a minute. It is an expensive blocking operation */ - if (last_connect > Anope::CurTime - 60) - throw LDAPException("Unable to connect to LDAP service " + this->name + ": reconnecting too fast"); - last_connect = Anope::CurTime; - - ldap_unbind_ext(this->con, NULL, NULL); - - Connect(); - } - - void QueueRequest(LDAPRequest *r) - { - this->Lock(); - this->queries.push_back(r); - this->Wakeup(); - this->Unlock(); - } - - public: - typedef std::vector query_queue; - query_queue queries, results; - Mutex process_mutex; /* held when processing requests not in either queue */ - - LDAPService(Module *o, const Anope::string &n, const Anope::string &s, const Anope::string &b, const Anope::string &p) : LDAPProvider(o, n), server(s), admin_binddn(b), admin_pass(p), last_connect(0) - { - Connect(); - } - - ~LDAPService() - { - /* At this point the thread has stopped so we don't need to hold process_mutex */ - - this->Lock(); - - for (unsigned int i = 0; i < this->queries.size(); ++i) - { - LDAPRequest *req = this->queries[i]; - - /* queries have no results yet */ - req->result = new LDAPResult(); - req->result->type = req->type; - req->result->error = "LDAP Interface is going away"; - if (req->inter) - req->inter->OnError(*req->result); - - delete req; - } - this->queries.clear(); - - for (unsigned int i = 0; i < this->results.size(); ++i) - { - LDAPRequest *req = this->results[i]; - - /* even though this may have already finished successfully we return that it didn't */ - req->result->error = "LDAP Interface is going away"; - if (req->inter) - req->inter->OnError(*req->result); - - delete req; - } - - this->Unlock(); - - ldap_unbind_ext(this->con, NULL, NULL); - } - - void BindAsAdmin(LDAPInterface *i) override - { - this->Bind(i, this->admin_binddn, this->admin_pass); - } - - void Bind(LDAPInterface *i, const Anope::string &who, const Anope::string &pass) override - { - LDAPBind *b = new LDAPBind(this, i, who, pass); - QueueRequest(b); - } - - void Search(LDAPInterface *i, const Anope::string &base, const Anope::string &filter) override - { - if (i == NULL) - throw LDAPException("No interface"); - - LDAPSearch *s = new LDAPSearch(this, i, base, filter); - QueueRequest(s); - } - - void Add(LDAPInterface *i, const Anope::string &dn, LDAPMods &attributes) override - { - LDAPAdd *add = new LDAPAdd(this, i, dn, attributes); - QueueRequest(add); - } - - void Del(LDAPInterface *i, const Anope::string &dn) anope_override - { - LDAPDel *del = new LDAPDel(this, i, dn); - QueueRequest(del); - } - - void Modify(LDAPInterface *i, const Anope::string &base, LDAPMods &attributes) anope_override - { - LDAPModify *mod = new LDAPModify(this, i, base, attributes); - QueueRequest(mod); - } - - private: - void BuildReply(int res, LDAPRequest *req) - { - LDAPResult *ldap_result = req->result = new LDAPResult(); - req->result->type = req->type; - - if (res != LDAP_SUCCESS) - { - ldap_result->error = ldap_err2string(res); - return; - } - - if (req->message == NULL) - { - return; - } - - /* a search result */ - - for (LDAPMessage *cur = ldap_first_message(this->con, req->message); cur; cur = ldap_next_message(this->con, cur)) - { - LDAPAttributes attributes; - - char *dn = ldap_get_dn(this->con, cur); - if (dn != NULL) - { - attributes["dn"].push_back(dn); - ldap_memfree(dn); - dn = NULL; - } - - BerElement *ber = NULL; - - for (char *attr = ldap_first_attribute(this->con, cur, &ber); attr; attr = ldap_next_attribute(this->con, cur, ber)) - { - berval **vals = ldap_get_values_len(this->con, cur, attr); - int count = ldap_count_values_len(vals); - - std::vector attrs; - for (int j = 0; j < count; ++j) - attrs.push_back(vals[j]->bv_val); - attributes[attr] = attrs; - - ldap_value_free_len(vals); - ldap_memfree(attr); - } - - if (ber != NULL) - ber_free(ber, 0); - - ldap_result->messages.push_back(attributes); - } - } - - void SendRequests() - { - process_mutex.Lock(); - - query_queue q; - this->Lock(); - queries.swap(q); - this->Unlock(); - - if (q.empty()) - { - process_mutex.Unlock(); - return; - } - - for (unsigned int i = 0; i < q.size(); ++i) - { - LDAPRequest *req = q[i]; - int ret = req->run(); - - if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT) - { - /* try again */ - try - { - Reconnect(); - } - catch (const LDAPException &) - { - } - - ret = req->run(); - } - - BuildReply(ret, req); - - this->Lock(); - results.push_back(req); - this->Unlock(); - } - - me->Notify(); - - process_mutex.Unlock(); - } - - public: - void Run() anope_override - { - while (!this->GetExitState()) - { - this->Lock(); - /* Queries can be non empty if one is pushed during SendRequests() */ - if (queries.empty()) - this->Wait(); - this->Unlock(); - - SendRequests(); - } - } - - LDAP* GetConnection() - { - return con; - } -}; - -class ModuleLDAP : public Module, public Pipe - , public EventHook -{ - std::map LDAPServices; - - public: - - ModuleLDAP(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - { - me = this; - } - - ~ModuleLDAP() - { - for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) - { - it->second->SetExitState(); - it->second->Wakeup(); - it->second->Join(); - delete it->second; - } - LDAPServices.clear(); - } - - void OnReload(Configuration::Conf *config) override - { - Configuration::Block *conf = config->GetModule(this); - - for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end();) - { - const Anope::string &cname = it->first; - LDAPService *s = it->second; - int i; - - ++it; - - for (i = 0; i < conf->CountBlock("ldap"); ++i) - if (conf->GetBlock("ldap", i)->Get("name", "ldap/main") == cname) - break; - - if (i == conf->CountBlock("ldap")) - { - Log(LOG_NORMAL, "ldap") << "LDAP: Removing server connection " << cname; - - s->SetExitState(); - s->Wakeup(); - s->Join(); - delete s; - this->LDAPServices.erase(cname); - } - } - - for (int i = 0; i < conf->CountBlock("ldap"); ++i) - { - Configuration::Block *ldap = conf->GetBlock("ldap", i); - - const Anope::string &connname = ldap->Get("name", "ldap/main"); - - if (this->LDAPServices.find(connname) == this->LDAPServices.end()) - { - const Anope::string &server = ldap->Get("server", "127.0.0.1"); - const Anope::string &admin_binddn = ldap->Get("admin_binddn"); - const Anope::string &admin_password = ldap->GetAnope::string>("admin_password"); - - try - { - LDAPService *ss = new LDAPService(this, connname, server, admin_binddn, admin_password); - ss->Start(); - this->LDAPServices.insert(std::make_pair(connname, ss)); - - Log(LOG_NORMAL, "ldap") << "LDAP: Successfully initialized server " << connname << " (" << server << ")"; - } - catch (const LDAPException &ex) - { - Log(LOG_NORMAL, "ldap") << "LDAP: " << ex.GetReason(); - } - } - } - } - - void OnModuleUnload(User *, Module *m) override - { - for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) - { - LDAPService *s = it->second; - - s->process_mutex.Lock(); - s->Lock(); - - for (unsigned int i = s->queries.size(); i > 0; --i) - { - LDAPRequest *req = s->queries[i - 1]; - LDAPInterface *li = req->inter; - - if (li && li->owner == m) - { - s->queries.erase(s->queries.begin() + i - 1); - delete req; - } - } - for (unsigned int i = s->results.size(); i > 0; --i) - { - LDAPRequest *req = s->results[i - 1]; - LDAPInterface *li = req->inter; - - if (li && li->owner == m) - { - s->results.erase(s->results.begin() + i - 1); - delete req; - } - } - - s->Unlock(); - s->process_mutex.Unlock(); - } - } - - void OnNotify() override - { - for (std::map::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) - { - LDAPService *s = it->second; - - LDAPService::query_queue results; - s->Lock(); - results.swap(s->results); - s->Unlock(); - - for (unsigned int i = 0; i < results.size(); ++i) - { - LDAPRequest *req = results[i]; - LDAPInterface *li = req->inter; - LDAPResult *r = req->result; - - if (li != NULL) - { - if (!r->getError().empty()) - { - Log(this) << "Error running LDAP query: " << r->getError(); - li->OnError(*r); - } - else - li->OnResult(*r); - } - - delete req; - } - } - } -}; - -int LDAPBind::run() -{ - berval cred; - cred.bv_val = strdup(pass.c_str()); - cred.bv_len = pass.length(); - - int i = ldap_sasl_bind_s(service->GetConnection(), who.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); - - free(cred.bv_val); - - return i; -} - -int LDAPSearch::run() -{ - return ldap_search_ext_s(service->GetConnection(), base.c_str(), LDAP_SCOPE_SUBTREE, filter.c_str(), NULL, 0, NULL, NULL, &tv, 0, &message); -} - -int LDAPAdd::run() -{ - LDAPMod **mods = LDAPService::BuildMods(attributes); - int i = ldap_add_ext_s(service->GetConnection(), dn.c_str(), mods, NULL, NULL); - LDAPService::FreeMods(mods); - return i; -} - -int LDAPDel::run() -{ - return ldap_delete_ext_s(service->GetConnection(), dn.c_str(), NULL, NULL); -} - -int LDAPModify::run() -{ - LDAPMod **mods = LDAPService::BuildMods(attributes); - int i = ldap_modify_ext_s(service->GetConnection(), base.c_str(), mods, NULL, NULL); - LDAPService::FreeMods(mods); - return i; -} - -MODULE_INIT(ModuleLDAP) - diff --git a/modules/extra/m_ldap_authentication.cpp b/modules/extra/m_ldap_authentication.cpp deleted file mode 100644 index dd63fd3fb..000000000 --- a/modules/extra/m_ldap_authentication.cpp +++ /dev/null @@ -1,302 +0,0 @@ -#include "module.h" -#include "modules/ldap.h" -#include "modules/nickserv.h" - -static Module *me; - -static Anope::string basedn; -static Anope::string search_filter; -static Anope::string object_class; -static Anope::string email_attribute; -static Anope::string username_attribute; - -struct IdentifyInfo -{ - Reference user; - IdentifyRequest *req; - ServiceReference lprov; - bool admin_bind; - Anope::string dn; - - IdentifyInfo(User *u, IdentifyRequest *r, ServiceReference &lp) : user(u), req(r), lprov(lp), admin_bind(true) - { - req->Hold(me); - } - - ~IdentifyInfo() - { - req->Release(me); - } -}; - -class IdentifyInterface : public LDAPInterface -{ - IdentifyInfo *ii; - - public: - IdentifyInterface(Module *m, IdentifyInfo *i) : LDAPInterface(m), ii(i) { } - - ~IdentifyInterface() - { - delete ii; - } - - void OnDelete() anope_override - { - delete this; - } - - void OnResult(const LDAPResult &r) override - { - if (!ii->lprov) - return; - - switch (r.type) - { - case QUERY_SEARCH: - { - if (!r.empty()) - { - try - { - const LDAPAttributes &attr = r.get(0); - ii->dn = attr.get("dn"); - Log(LOG_DEBUG) << "m_ldap_authenticationn: binding as " << ii->dn; - - ii->lprov->Bind(new IdentifyInterface(this->owner, ii), ii->dn, ii->req->GetPassword()); - ii = NULL; - } - catch (const LDAPException &ex) - { - Log(this->owner) << "Error binding after search: " << ex.GetReason(); - } - } - break; - } - case QUERY_BIND: - { - if (ii->admin_bind) - { - Anope::string sf = search_filter.replace_all_cs("%account", ii->req->GetAccount()).replace_all_cs("%object_class", object_class); - try - { - Log(LOG_DEBUG) << "m_ldap_authentication: searching for " << sf; - ii->lprov->Search(new IdentifyInterface(this->owner, ii), basedn, sf); - ii->admin_bind = false; - ii = NULL; - } - catch (const LDAPException &ex) - { - Log(this->owner) << "Unable to search for " << sf << ": " << ex.GetReason(); - } - } - else - { - NickServ::Nick *na = NickServ::FindNick(ii->req->GetAccount()); - if (na == NULL) - { - na = new NickServ::Nick(ii->req->GetAccount(), new NickServ::Account(ii->req->GetAccount())); - na->SetLastRealname(ii->user ? ii->user->realname : ii->req->GetAccount()); - NickServ::Event::OnNickRegister(&NickServ::Event::NickRegister::OnNickRegister, ii->user, na, ii->req->GetPassword());; - ServiceBot *NickServ = Config->GetClient("NickServ"); - if (ii->user && NickServ) - ii->user->SendMessage(NickServ, _("Your account \002%s\002 has been successfully created."), na->GetNick().c_str()); - } - // encrypt and store the password in the nickcore - Anope::Encrypt(ii->req->GetPassword(), na->GetAccount()->pass); - - na->GetAccount()->Extend("m_ldap_authentication_dn", ii->dn); - ii->req->Success(me); - } - break; - } - default: - break; - } - } - - void OnError(const LDAPResult &r) override - { - } -}; - -class OnIdentifyInterface : public LDAPInterface -{ - Anope::string uid; - - public: - OnIdentifyInterface(Module *m, const Anope::string &i) : LDAPInterface(m), uid(i) { } - - void OnDelete() anope_override - { - delete this; - } - - void OnResult(const LDAPResult &r) override - { - User *u = User::Find(uid); - - if (!u || !u->Account() || r.empty()) - return; - - try - { - const LDAPAttributes &attr = r.get(0); - Anope::string email = attr.get(email_attribute); - - if (!email.equals_ci(u->Account()->GetEmail())) - { - u->Account()->GetEmail() = email; - ServiceBot *NickServ = Config->GetClient("NickServ"); - if (NickServ) - u->SendMessage(NickServ, _("Your email has been updated to \002%s\002"), email.c_str()); - Log(this->owner) << "Updated email address for " << u->nick << " (" << u->Account()->GetDisplay() << ") to " << email; - } - } - catch (const LDAPException &ex) - { - Log(this->owner) << ex.GetReason(); - } - } - - void OnError(const LDAPResult &r) override - { - Log(this->owner) << r.error; - } -}; - -class OnRegisterInterface : public LDAPInterface -{ - public: - OnRegisterInterface(Module *m) : LDAPInterface(m) { } - - void OnResult(const LDAPResult &r) override - { - Log(this->owner) << "Successfully added newly created account to LDAP"; - } - - void OnError(const LDAPResult &r) override - { - Log(this->owner) << "Error adding newly created account to LDAP: " << r.getError(); - } -}; - -class ModuleLDAPAuthentication : public Module - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - ServiceReference ldap; - OnRegisterInterface orinterface; - - PrimitiveExtensibleItem dn; - - Anope::string password_attribute; - Anope::string disable_register_reason; - Anope::string disable_email_reason; - - public: - ModuleLDAPAuthentication(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - , EventHook("OnPreCommand", EventHook::Priority::FIRST) - , EventHook("OnCheckAuthentication", EventHook::Priority::FIRST) - , EventHook("OnNickIdentify", EventHook::Priority::FIRST) - , EventHook("OnNickRegister", EventHook::Priority::FIRST) - , ldap("LDAPProvider", "ldap/main") - , orinterface(this) - , dn(this, "m_ldap_authentication_dn") - { - me = this; - } - - void OnReload(Configuration::Conf *config) override - { - Configuration::Block *conf = Config->GetModule(this); - - basedn = conf->Get("basedn"); - search_filter = conf->Get("search_filter"); - object_class = conf->Get("object_class"); - username_attribute = conf->Get("username_attribute"); - this->password_attribute = conf->Get("password_attribute"); - email_attribute = conf->Get("email_attribute"); - this->disable_register_reason = conf->Get("disable_register_reason"); - this->disable_email_reason = conf->Get("disable_email_reason"); - - if (!email_attribute.empty()) - /* Don't complain to users about how they need to update their email, we will do it for them */ - config->GetModule("nickserv")->Set("forceemail", "false"); - } - - EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override - { - if (!this->disable_register_reason.empty()) - { - if (command->name == "nickserv/register" || command->name == "nickserv/group") - { - source.Reply(this->disable_register_reason); - return EVENT_STOP; - } - } - - if (!email_attribute.empty() && !this->disable_email_reason.empty() && command->name == "nickserv/set/email") - { - source.Reply(this->disable_email_reason); - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } - - void OnCheckAuthentication(User *u, IdentifyRequest *req) override - { - if (!this->ldap) - return; - - IdentifyInfo *ii = new IdentifyInfo(u, req, this->ldap); - this->ldap->BindAsAdmin(new IdentifyInterface(this, ii)); - } - - void OnNickIdentify(User *u) override - { - if (email_attribute.empty() || !this->ldap) - return; - - Anope::string *d = dn.Get(u->Account()); - if (!d || d->empty()) - return; - - this->ldap->Search(new OnIdentifyInterface(this, u->GetUID()), *d, "(" + email_attribute + "=*)"); - } - - void OnNickRegister(User *, NickServ::Nick *na, const Anope::string &pass) override - { - if (!this->disable_register_reason.empty() || !this->ldap) - return; - - this->ldap->BindAsAdmin(NULL); - - LDAPMods attributes; - attributes.resize(4); - - attributes[0].name = "objectClass"; - attributes[0].values.push_back("top"); - attributes[0].values.push_back(object_class); - - attributes[1].name = username_attribute; - attributes[1].values.push_back(na->GetNick()); - - if (!na->GetAccount()->GetEmail().empty()) - { - attributes[2].name = email_attribute; - attributes[2].values.push_back(na->GetAccount()->GetEmail()); - } - - attributes[3].name = this->password_attribute; - attributes[3].values.push_back(pass); - - Anope::string new_dn = username_attribute + "=" + na->GetNick() + "," + basedn; - this->ldap->Add(&this->orinterface, new_dn, attributes); - } -}; - -MODULE_INIT(ModuleLDAPAuthentication) diff --git a/modules/extra/m_ldap_oper.cpp b/modules/extra/m_ldap_oper.cpp deleted file mode 100644 index 31653544e..000000000 --- a/modules/extra/m_ldap_oper.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include "module.h" -#include "modules/ldap.h" - -static std::set my_opers; -static Anope::string opertype_attribute; - -class IdentifyInterface : public LDAPInterface -{ - Reference u; - - public: - IdentifyInterface(Module *m, User *user) : LDAPInterface(m), u(user) - { - } - - void OnResult(const LDAPResult &r) override - { - if (!u || !u->Account()) - return; - - NickServ::Account *nc = u->Account(); - - try - { - const LDAPAttributes &attr = r.get(0); - - const Anope::string &opertype = attr.get(opertype_attribute); - - OperType *ot = OperType::Find(opertype); - if (ot != NULL && (nc->o == NULL || ot != nc->o->ot)) - { - Oper *o = nc->o; - if (o != NULL && my_opers.count(o) > 0) - { - my_opers.erase(o); - delete o; - } - o = new Oper(u->nick, ot); - my_opers.insert(o); - nc->o = o; - - Log(this->owner) << "Tied " << u->nick << " (" << nc->GetDisplay() << ") to opertype " << ot->GetName(); - } - } - catch (const LDAPException &ex) - { - if (nc->o != NULL) - { - if (my_opers.count(nc->o) > 0) - { - my_opers.erase(nc->o); - delete nc->o; - } - nc->o = NULL; - - Log(this->owner) << "Removed services operator from " << u->nick << " (" << nc->GetDisplay() << ")"; - } - } - } - - void OnError(const LDAPResult &r) override - { - } - - void OnDelete() anope_override - { - delete this; - } -}; - -class LDAPOper : public Module - , public EventHook - , public EventHook -{ - ServiceReference ldap; - - Anope::string binddn; - Anope::string password; - Anope::string basedn; - Anope::string filter; - public: - LDAPOper(const Anope::string &modname, const Anope::string &creator) - : Module(modname, creator, EXTRA | VENDOR) - , ldap("LDAPProvider", "ldap/main") - { - - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = Config->GetModule(this); - - this->binddn = config->Get("binddn"); - this->password = config->Get("password"); - this->basedn = config->Get("basedn"); - this->filter = config->Get("filter"); - opertype_attribute = config->Get("opertype_attribute"); - - for (std::set::iterator it = my_opers.begin(), it_end = my_opers.end(); it != it_end; ++it) - delete *it; - my_opers.clear(); - } - - void OnNickIdentify(User *u) override - { - try - { - if (!this->ldap) - throw LDAPException("No LDAP interface. Is m_ldap loaded and configured correctly?"); - else if (this->basedn.empty() || this->filter.empty() || opertype_attribute.empty()) - throw LDAPException("Could not search LDAP for opertype settings, invalid configuration."); - - if (!this->binddn.empty()) - this->ldap->Bind(NULL, this->binddn.replace_all_cs("%a", u->Account()->GetDisplay()), this->password.c_str()); - this->ldap->Search(new IdentifyInterface(this, u), this->basedn, this->filter.replace_all_cs("%a", u->Account()->GetDisplay())); - } - catch (const LDAPException &ex) - { - Log() << ex.GetReason(); - } - } - - void OnDelCore(NickServ::Account *nc) override - { - if (nc->o != NULL && my_opers.count(nc->o) > 0) - { - my_opers.erase(nc->o); - delete nc->o; - nc->o = NULL; - } - } -}; - -MODULE_INIT(LDAPOper) diff --git a/modules/extra/m_mysql.cpp b/modules/extra/m_mysql.cpp deleted file mode 100644 index 843a2ba0a..000000000 --- a/modules/extra/m_mysql.cpp +++ /dev/null @@ -1,614 +0,0 @@ -/* RequiredLibraries: mysqlclient */ -/* RequiredWindowsLibraries: libmysql */ - -#include "module.h" -#include "modules/sql.h" -#define NO_CLIENT_LONG_LONG -#ifdef WIN32 -# include -#else -# include -#endif - -using namespace SQL; - -/** Non blocking threaded MySQL API, based loosely from InspIRCd's m_mysql.cpp - * - * This module spawns a single thread that is used to execute blocking MySQL queries. - * When a module requests a query to be executed it is added to a list for the thread - * (which never stops looping and sleeing) to pick up and execute, the result of which - * is inserted in to another queue to be picked up by the main thread. The main thread - * uses Pipe to become notified through the socket engine when there are results waiting - * to be sent back to the modules requesting the query - */ - -class MySQLService; - -/** A query request - */ -struct QueryRequest -{ - /* The connection to the database */ - MySQLService *service; - /* The interface to use once we have the result to send the data back */ - Interface *sqlinterface; - /* The actual query */ - Query query; - - QueryRequest(MySQLService *s, Interface *i, const Query &q) : service(s), sqlinterface(i), query(q) { } -}; - -/** A query result */ -struct QueryResult -{ - /* The interface to send the data back on */ - Interface *sqlinterface; - /* The result */ - Result result; - - QueryResult(Interface *i, Result &r) : sqlinterface(i), result(r) { } -}; - -/** A MySQL result - */ -class MySQLResult : public Result -{ - MYSQL_RES *res; - - public: - MySQLResult(unsigned int i, const Query &q, const Anope::string &fq, MYSQL_RES *r) : Result(i, q, fq), res(r) - { - if (!res) - return; - - unsigned num_fields = mysql_num_fields(res); - MYSQL_FIELD *fields = mysql_fetch_fields(res); - - /* It is not thread safe to log anything here using Log(this->owner) now :( */ - - if (!num_fields || !fields) - return; - - for (unsigned field_count = 0; field_count < num_fields; ++field_count) - columns.push_back(fields[field_count].name ? fields[field_count].name : ""); - - for (MYSQL_ROW row; (row = mysql_fetch_row(res));) - { - std::vector values; - - for (unsigned field_count = 0; field_count < num_fields; ++field_count) - { - const char *data = row[field_count]; - - Value v; - v.null = !data; - v.value = data ? data : ""; - values.push_back(v); - } - - this->values.push_back(values); - } - } - - MySQLResult(const Query &q, const Anope::string &fq, const Anope::string &err) : Result(0, q, fq, err), res(NULL) - { - } - - ~MySQLResult() - { - if (this->res) - mysql_free_result(this->res); - } -}; - -/** A MySQL connection, there can be multiple - */ -class MySQLService : public Provider -{ - std::map > active_schema, indexes; - - Anope::string database; - Anope::string server; - Anope::string user; - Anope::string password; - int port; - - MYSQL *sql; - - /** Escape a query. - * Note the mutex must be held! - */ - Anope::string Escape(const Anope::string &query); - - public: - /* Locked by the SQL thread when a query is pending on this database, - * prevents us from deleting a connection while a query is executing - * in the thread - */ - Mutex Lock; - - MySQLService(Module *o, const Anope::string &n, const Anope::string &d, const Anope::string &s, const Anope::string &u, const Anope::string &p, int po); - - ~MySQLService(); - - void Run(Interface *i, const Query &query) override; - - Result RunQuery(const Query &query) override; - - std::vector InitSchema(const Anope::string &prefix) override; - std::vector Replace(const Anope::string &table, const Query &, const std::set &) override; - std::vector CreateTable(const Anope::string &prefix, const Anope::string &table) override; - std::vector AlterTable(const Anope::string &, const Anope::string &table, const Anope::string &field, bool) override; - std::vector CreateIndex(const Anope::string &table, const Anope::string &field) override; - - Query BeginTransaction() override; - Query Commit() override; - - Serialize::ID GetID(const Anope::string &) override; - - Query GetTables(const Anope::string &prefix) override; - - void Connect(); - - bool CheckConnection(); - - Anope::string BuildQuery(const Query &q); -}; - -/** The SQL thread used to execute queries - */ -class DispatcherThread : public Thread, public Condition -{ - public: - DispatcherThread() : Thread() { } - - void Run() override; -}; - -class ModuleSQL; -static ModuleSQL *me; -class ModuleSQL : public Module - , public Pipe - , public EventHook -{ - /* SQL connections */ - std::map MySQLServices; - public: - /* Pending query requests */ - std::deque QueryRequests; - /* Pending finished requests with results */ - std::deque FinishedRequests; - /* The thread used to execute queries */ - DispatcherThread *DThread; - - ModuleSQL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - { - me = this; - - - DThread = new DispatcherThread(); - DThread->Start(); - } - - ~ModuleSQL() - { - for (std::map::iterator it = this->MySQLServices.begin(); it != this->MySQLServices.end(); ++it) - delete it->second; - MySQLServices.clear(); - - DThread->SetExitState(); - DThread->Wakeup(); - DThread->Join(); - delete DThread; - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - - for (std::map::iterator it = this->MySQLServices.begin(); it != this->MySQLServices.end();) - { - const Anope::string &cname = it->first; - MySQLService *s = it->second; - int i; - - ++it; - - for (i = 0; i < config->CountBlock("mysql"); ++i) - if (config->GetBlock("mysql", i)->Get("name", "mysql/main") == cname) - break; - - if (i == config->CountBlock("mysql")) - { - Log(LOG_NORMAL, "mysql") << "MySQL: Removing server connection " << cname; - - delete s; - this->MySQLServices.erase(cname); - } - } - - for (int i = 0; i < config->CountBlock("mysql"); ++i) - { - Configuration::Block *block = config->GetBlock("mysql", i); - const Anope::string &connname = block->Get("name", "mysql/main"); - - if (this->MySQLServices.find(connname) == this->MySQLServices.end()) - { - const Anope::string &database = block->Get("database", "anope"); - const Anope::string &server = block->Get("server", "127.0.0.1"); - const Anope::string &user = block->Get("username", "anope"); - const Anope::string &password = block->Get("password"); - int port = block->Get("port", "3306"); - - try - { - MySQLService *ss = new MySQLService(this, connname, database, server, user, password, port); - this->MySQLServices.insert(std::make_pair(connname, ss)); - - Log(LOG_NORMAL, "mysql") << "MySQL: Successfully connected to server " << connname << " (" << server << ")"; - } - catch (const SQL::Exception &ex) - { - Log(LOG_NORMAL, "mysql") << "MySQL: " << ex.GetReason(); - } - } - } - } - - void OnModuleUnload(User *, Module *m) override - { - this->DThread->Lock(); - - for (unsigned i = this->QueryRequests.size(); i > 0; --i) - { - QueryRequest &r = this->QueryRequests[i - 1]; - - if (r.sqlinterface && r.sqlinterface->owner == m) - { - if (i == 1) - { - r.service->Lock.Lock(); - r.service->Lock.Unlock(); - } - - this->QueryRequests.erase(this->QueryRequests.begin() + i - 1); - } - } - - this->DThread->Unlock(); - - this->OnNotify(); - } - - void OnNotify() override - { - this->DThread->Lock(); - std::deque finishedRequests = this->FinishedRequests; - this->FinishedRequests.clear(); - this->DThread->Unlock(); - - for (std::deque::const_iterator it = finishedRequests.begin(), it_end = finishedRequests.end(); it != it_end; ++it) - { - const QueryResult &qr = *it; - - if (!qr.sqlinterface) - throw SQL::Exception("NULL qr.sqlinterface in MySQLPipe::OnNotify() ?"); - - if (qr.result.GetError().empty()) - qr.sqlinterface->OnResult(qr.result); - else - qr.sqlinterface->OnError(qr.result); - } - } -}; - -MySQLService::MySQLService(Module *o, const Anope::string &n, const Anope::string &d, const Anope::string &s, const Anope::string &u, const Anope::string &p, int po) -: Provider(o, n), database(d), server(s), user(u), password(p), port(po), sql(NULL) -{ - Connect(); -} - -MySQLService::~MySQLService() -{ - me->DThread->Lock(); - this->Lock.Lock(); - mysql_close(this->sql); - this->sql = NULL; - - for (unsigned i = me->QueryRequests.size(); i > 0; --i) - { - QueryRequest &r = me->QueryRequests[i - 1]; - - if (r.service == this) - { - if (r.sqlinterface) - r.sqlinterface->OnError(Result(0, r.query, "SQL Interface is going away")); - me->QueryRequests.erase(me->QueryRequests.begin() + i - 1); - } - } - this->Lock.Unlock(); - me->DThread->Unlock(); -} - -void MySQLService::Run(Interface *i, const Query &query) -{ - me->DThread->Lock(); - me->QueryRequests.push_back(QueryRequest(this, i, query)); - me->DThread->Unlock(); - me->DThread->Wakeup(); -} - -Result MySQLService::RunQuery(const Query &query) -{ - this->Lock.Lock(); - - Anope::string real_query = this->BuildQuery(query); - - if (this->CheckConnection() && !mysql_real_query(this->sql, real_query.c_str(), real_query.length())) - { - MYSQL_RES *res = mysql_store_result(this->sql); - unsigned int id = mysql_insert_id(this->sql); - - /* because we enabled CLIENT_MULTI_RESULTS in our options - * a multiple statement or a procedure call can return - * multiple result sets. - * we must process them all before the next query. - */ - - while (!mysql_next_result(this->sql)) - mysql_free_result(mysql_store_result(this->sql)); - - this->Lock.Unlock(); - return MySQLResult(id, query, real_query, res); - } - else - { - Anope::string error = mysql_error(this->sql); - this->Lock.Unlock(); - return MySQLResult(query, real_query, error); - } -} - -std::vector MySQLService::InitSchema(const Anope::string &prefix) -{ - std::vector queries; - - Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + "id` (" - "`id` bigint(20) NOT NULL" - ") ENGINE=InnoDB"; - queries.push_back(t); - - t = "CREATE TABLE IF NOT EXISTS `" + prefix + "objects` (`id` bigint(20) NOT NULL PRIMARY KEY, `type` varchar(256)) ENGINE=InnoDB"; - queries.push_back(t); - - t = "CREATE TABLE IF NOT EXISTS `" + prefix + "edges` (" - "`id` bigint(20) NOT NULL," - "`field` varchar(64) NOT NULL," - "`other_id` bigint(20) NOT NULL," - "PRIMARY KEY (`id`, `field`)," - "KEY `other` (`other_id`)," - "CONSTRAINT `edges_id_fk` FOREIGN KEY (`id`) REFERENCES `" + prefix + "objects` (`id`)," - "CONSTRAINT `edges_other_id_fk` FOREIGN KEY (`other_id`) REFERENCES `" + prefix + "objects` (`id`)" - ") ENGINE=InnoDB"; - queries.push_back(t); - - return queries; -} - -std::vector MySQLService::Replace(const Anope::string &table, const Query &q, const std::set &keys) -{ - std::vector queries; - - Anope::string query_text = "INSERT INTO `" + table + "` ("; - for (const std::pair &p : q.parameters) - query_text += "`" + p.first + "`,"; - query_text.erase(query_text.length() - 1); - query_text += ") VALUES ("; - for (const std::pair &p : q.parameters) - query_text += "@" + p.first + "@,"; - query_text.erase(query_text.length() - 1); - query_text += ") ON DUPLICATE KEY UPDATE "; - for (const std::pair &p : q.parameters) - if (!keys.count(p.first)) - query_text += "`" + p.first + "` = VALUES(`" + p.first + "`),"; - query_text.erase(query_text.length() - 1); - - Query query(query_text); - query.parameters = q.parameters; - - queries.push_back(query); - - return queries; -} - -std::vector MySQLService::CreateTable(const Anope::string &prefix, const Anope::string &table) -{ - std::vector queries; - - if (active_schema.find(prefix + table) == active_schema.end()) - { - Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + table + "` (`id` bigint(20) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB"; - queries.push_back(t); - - t = "ALTER TABLE `" + prefix + table + "` " - "ADD CONSTRAINT `" + table + "_id_fk` FOREIGN KEY (`id`) REFERENCES `" + prefix + "objects` (`id`)"; - queries.push_back(t); - - active_schema[prefix + table]; - } - - return queries; -} - -std::vector MySQLService::AlterTable(const Anope::string &prefix, const Anope::string &table, const Anope::string &field, bool object) -{ - std::vector queries; - std::set &s = active_schema[prefix + table]; - - if (!s.count(field)) - { - Query column; - if (!object) - column = "ALTER TABLE `" + prefix + table + "` ADD COLUMN `" + field + "` TINYTEXT"; - else - column = "ALTER TABLE `" + prefix + table + "` " - "ADD COLUMN `" + field + "` bigint(20), " - "ADD CONSTRAINT `" + table + "_" + field + "_fk` FOREIGN KEY (`" + field + "`) REFERENCES `" + prefix + "objects` (`id`)"; - queries.push_back(column); - s.insert(field); - } - - return queries; -} - -std::vector MySQLService::CreateIndex(const Anope::string &table, const Anope::string &field) -{ - std::vector queries; - - if (indexes[table].count(field)) - return queries; - - Query t = "ALTER TABLE `" + table + "` ADD KEY `idx_" + field + "` (`" + field + "`(512))"; - queries.push_back(t); - - indexes[table].insert(field); - - return queries; -} - -Query MySQLService::BeginTransaction() -{ - return Query("START TRANSACTION WITH CONSISTENT SNAPSHOT"); -} - -Query MySQLService::Commit() -{ - return Query("COMMIT"); -} - -Serialize::ID MySQLService::GetID(const Anope::string &prefix) -{ - Query query("SELECT `id` FROM `" + prefix + "id` FOR UPDATE"); - Serialize::ID id; - - Result res = RunQuery(query); - if (res.Rows()) - { - id = convertTo(res.Get(0, "id")); - - Query update_query("UPDATE `" + prefix + "id` SET `id` = `id` + 1"); - RunQuery(update_query); - } - else - { - id = 0; - - Query insert_query("INSERT INTO `" + prefix + "id` (id) VALUES(@id@)"); - insert_query.SetValue("id", 1); - RunQuery(insert_query); - } - - return id; -} - -Query MySQLService::GetTables(const Anope::string &prefix) -{ - return Query("SHOW TABLES LIKE '" + prefix + "%';"); -} - -void MySQLService::Connect() -{ - this->sql = mysql_init(this->sql); - - const unsigned int timeout = 1; - mysql_options(this->sql, MYSQL_OPT_CONNECT_TIMEOUT, reinterpret_cast(&timeout)); - - bool connect = mysql_real_connect(this->sql, this->server.c_str(), this->user.c_str(), this->password.c_str(), this->database.c_str(), this->port, NULL, CLIENT_MULTI_RESULTS); - - if (!connect) - throw SQL::Exception("Unable to connect to MySQL service " + this->name + ": " + mysql_error(this->sql)); - - Log(LOG_DEBUG) << "Successfully connected to MySQL service " << this->name << " at " << this->server << ":" << this->port; -} - - -bool MySQLService::CheckConnection() -{ - if (!this->sql || mysql_ping(this->sql)) - { - try - { - this->Connect(); - } - catch (const SQL::Exception &) - { - return false; - } - } - - return true; -} - -Anope::string MySQLService::Escape(const Anope::string &query) -{ - std::vector buffer(query.length() * 2 + 1); - mysql_real_escape_string(this->sql, &buffer[0], query.c_str(), query.length()); - return &buffer[0]; -} - -Anope::string MySQLService::BuildQuery(const Query &q) -{ - Anope::string real_query = q.query; - - for (std::map::const_iterator it = q.parameters.begin(), it_end = q.parameters.end(); it != it_end; ++it) - { - const QueryData& qd = it->second; - Anope::string replacement; - - if (qd.null) - replacement = "NULL"; - else if (!qd.escape) - replacement = qd.data; - else - replacement = "'" + this->Escape(qd.data) + "'"; - - real_query = real_query.replace_all_cs("@" + it->first + "@", replacement); - } - - return real_query; -} - -void DispatcherThread::Run() -{ - this->Lock(); - - while (!this->GetExitState()) - { - if (!me->QueryRequests.empty()) - { - QueryRequest &r = me->QueryRequests.front(); - this->Unlock(); - - Result sresult = r.service->RunQuery(r.query); - - this->Lock(); - if (!me->QueryRequests.empty() && me->QueryRequests.front().query == r.query) - { - if (r.sqlinterface) - me->FinishedRequests.push_back(QueryResult(r.sqlinterface, sresult)); - me->QueryRequests.pop_front(); - } - } - else - { - if (!me->FinishedRequests.empty()) - me->Notify(); - this->Wait(); - } - } - - this->Unlock(); -} - -MODULE_INIT(ModuleSQL) - diff --git a/modules/extra/m_sasl_dh-aes.cpp b/modules/extra/m_sasl_dh-aes.cpp deleted file mode 100644 index dd06d118b..000000000 --- a/modules/extra/m_sasl_dh-aes.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/* RequiredLibraries: ssl,crypto */ -/* RequiredWindowsLibraries: ssleay32,libeay32 */ - -#include "module.h" -#include "modules/sasl.h" - -#include -#include -#include - -using namespace SASL; - -class DHAES : public Mechanism -{ - void Err(Session* sess, BIGNUM* key = NULL) - { - if (key) - BN_free(key); - - sasl->Fail(sess); - delete sess; - } - - public: - struct DHAESSession : SASL::Session - { - DH* dh; - DHAESSession(Mechanism *m, const Anope::string &u, DH* dh_params) : SASL::Session(m, u) - { - if (!(dh = DH_new())) - return; - - dh->g = BN_dup(dh_params->g); - dh->p = BN_dup(dh_params->p); - - if (!DH_generate_key(dh)) - { - DH_free(dh); - dh = NULL; - } - } - - ~DHAESSession() - { - if (dh) - DH_free(dh); - } - }; - - DH* dh_params; - const size_t keysize; - SASL::Session* CreateSession(const Anope::string &uid) override - { - return new DHAESSession(this, uid, dh_params); - } - - DHAES(Module *o) : Mechanism(o, "DH-AES"), keysize(256 / 8) - { - if (!(dh_params = DH_new())) - throw ModuleException("DH_new() failed!"); - - if (!DH_generate_parameters_ex(dh_params, keysize * 8, 5, NULL)) - { - DH_free(dh_params); - throw ModuleException("Could not generate DH-params"); - } - } - - ~DHAES() - { - DH_free(dh_params); - } - - void ProcessMessage(SASL::Session *session, const SASL::Message &m) override - { - DHAESSession *sess = anope_dynamic_static_cast(session); - - if (!sess->dh) - { - sasl->SendMessage(sess, "D", "A"); - delete sess; - return; - } - - if (m.type == "S") - { - // Format: [ss]

[ss][ss] - // Where ss is a unsigned short with the size of the key - const BIGNUM* dhval[] = { sess->dh->p, sess->dh->g, sess->dh->pub_key }; - - // Find the size of our buffer - initialized at 6 because of string size data - size_t size = 6; - for (size_t i = 0; i < 3; i++) - size += BN_num_bytes(dhval[i]); - - // Fill in the DH data - std::vector buffer(size); - for (size_t i = 0, pos = 0; i < 3; i++) - { - *reinterpret_cast(&buffer[pos]) = htons(BN_num_bytes(dhval[i])); - pos += 2; - BN_bn2bin(dhval[i], &buffer[pos]); - pos += BN_num_bytes(dhval[i]); - } - - Anope::string encoded; - Anope::B64Encode(Anope::string(buffer.begin(), buffer.end()), encoded); - sasl->SendMessage(sess, "C", encoded); - } - else if (m.type == "C") - { - // Make sure we have some data - actual size check is done later - if (m.data.length() < 10) - return Err(sess); - - // Format: [ss][ss][ss] - // = \0\0 - - Anope::string decoded; - Anope::B64Decode(m.data, decoded); - - // Make sure we have an IV and at least one encrypted block - if ((decoded.length() < keysize + 2 + (AES_BLOCK_SIZE * 2)) || ((decoded.length() - keysize - 2) % AES_BLOCK_SIZE)) - return Err(sess); - - const unsigned char* data = reinterpret_cast(decoded.data()); - - // Control the size of the key - if (ntohs(*reinterpret_cast(&data[0])) != keysize) - return Err(sess); - - // Convert pubkey from binary - size_t pos = 2; - BIGNUM* pubkey = BN_bin2bn(&data[pos], keysize, NULL); - if (!pubkey) - return Err(sess); - - // Find shared key - std::vector secretkey(keysize); - if (DH_compute_key(&secretkey[0], pubkey, sess->dh) != static_cast(keysize)) - return Err(sess, pubkey); - - // Set decryption key - AES_KEY AESKey; - AES_set_decrypt_key(&secretkey[0], keysize * 8, &AESKey); - - // Fetch IV - pos += keysize; - std::vector IV(data + pos, data + pos + AES_BLOCK_SIZE); - - // Find encrypted blocks, and decrypt - pos += AES_BLOCK_SIZE; - size_t size = decoded.length() - pos; - std::vector decrypted(size + 2, 0); - AES_cbc_encrypt(&data[pos], reinterpret_cast(&decrypted[0]), size, &AESKey, &IV[0], AES_DECRYPT); - - std::string username = &decrypted[0]; - std::string password = &decrypted[username.length() + 1]; - - if (username.empty() || password.empty() || !IRCD->IsNickValid(username) || password.find_first_of("\r\n") != Anope::string::npos) - return Err(sess, pubkey); - - SASL::IdentifyRequest* req = new SASL::IdentifyRequest(this->owner, m.source, username, password); - Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req); - req->Dispatch(); - - BN_free(pubkey); - } - } -}; - - -class ModuleSASLDHAES : public Module -{ - DHAES dhaes; - - public: - ModuleSASLDHAES(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) - , dhaes(this) - { - } -}; - -MODULE_INIT(ModuleSASLDHAES) diff --git a/modules/extra/m_sasl_dh-blowfish.cpp b/modules/extra/m_sasl_dh-blowfish.cpp deleted file mode 100644 index f6e4b27b3..000000000 --- a/modules/extra/m_sasl_dh-blowfish.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* RequiredLibraries: ssl,crypto */ -/* RequiredWindowsLibraries: ssleay32,libeay32 */ - -#include "module.h" -#include "modules/sasl.h" - -#include -#include -#include - -using namespace SASL; - -class DHBS : public Mechanism -{ - void Err(Session* sess, BIGNUM* key = NULL) - { - if (key) - BN_free(key); - - sasl->Fail(sess); - delete sess; - } - - public: - struct DHBSSession : SASL::Session - { - DH* dh; - DHBSSession(Mechanism *m, const Anope::string &u, DH* dh_params) : SASL::Session(m, u) - { - if (!(dh = DH_new())) - return; - - dh->g = BN_dup(dh_params->g); - dh->p = BN_dup(dh_params->p); - - if (!DH_generate_key(dh)) - { - DH_free(dh); - dh = NULL; - } - } - - ~DHBSSession() - { - if (dh) - DH_free(dh); - } - }; - - DH* dh_params; - const size_t keysize; - SASL::Session* CreateSession(const Anope::string &uid) override - { - return new DHBSSession(this, uid, dh_params); - } - - DHBS(Module *o) : Mechanism(o, "DH-BLOWFISH"), keysize(256 / 8) - { - if (!(dh_params = DH_new())) - throw ModuleException("DH_new() failed!"); - - if (!DH_generate_parameters_ex(dh_params, keysize * 8, 5, NULL)) - { - DH_free(dh_params); - throw ModuleException("Could not generate DH-params"); - } - } - - ~DHBS() - { - DH_free(dh_params); - } - - void ProcessMessage(SASL::Session *session, const SASL::Message &m) override - { - DHBSSession *sess = anope_dynamic_static_cast(session); - - if (!sess->dh) - { - sasl->SendMessage(sess, "D", "A"); - delete sess; - return; - } - - if (m.type == "S") - { - // Format: [ss]

[ss][ss] - // Where ss is a unsigned short with the size of the key - const BIGNUM* dhval[] = { sess->dh->p, sess->dh->g, sess->dh->pub_key }; - - // Find the size of our buffer - initialized at 6 because of string size data - size_t size = 6; - for (size_t i = 0; i < 3; i++) - size += BN_num_bytes(dhval[i]); - - // Fill in the DH data - std::vector buffer(size); - for (size_t i = 0, pos = 0; i < 3; i++) - { - *reinterpret_cast(&buffer[pos]) = htons(BN_num_bytes(dhval[i])); - pos += 2; - BN_bn2bin(dhval[i], &buffer[pos]); - pos += BN_num_bytes(dhval[i]); - } - - Anope::string encoded; - Anope::B64Encode(Anope::string(buffer.begin(), buffer.end()), encoded); - sasl->SendMessage(sess, "C", encoded); - } - else if (m.type == "C") - { - // Make sure we have some data - actual size check is done later - if (m.data.length() < 10) - return Err(sess); - - // Format: [ss]<\0> - - Anope::string decoded; - Anope::B64Decode(m.data, decoded); - - // As we rely on the client giving us a null terminator at the right place, - // let's add one extra in case the client tries to crash us - const size_t decodedlen = decoded.length(); - decoded.push_back('\0'); - - // Make sure we have enough data for at least the key, a one letter username, and a block of data - if (decodedlen < keysize + 2 + 2 + 8) - return Err(sess); - - const unsigned char* data = reinterpret_cast(decoded.data()); - - // Control the size of the key - if (ntohs(*reinterpret_cast(&data[0])) != keysize) - return Err(sess); - - // Convert pubkey from binary - size_t pos = 2; - BIGNUM* pubkey = BN_bin2bn(&data[pos], keysize, NULL); - if (!pubkey) - return Err(sess); - - // Find shared key - std::vector secretkey(DH_size(sess->dh) + 1, 0); - if (DH_compute_key(&secretkey[0], pubkey, sess->dh) != static_cast(keysize)) - return Err(sess, pubkey); - - // Set decryption key - BF_KEY BFKey; - BF_set_key(&BFKey, keysize, &secretkey[0]); - - pos += keysize; - const Anope::string username = reinterpret_cast(&data[pos]); - // Check that the username is valid, and that we have at least one block of data - // 2 + 1 + 8 = uint16_t size for keylen, \0 for username, 8 for one block of data - if (username.empty() || username.length() + keysize + 2 + 1 + 8 > decodedlen || !IRCD->IsNickValid(username)) - return Err(sess, pubkey); - - pos += username.length() + 1; - size_t size = decodedlen - pos; - - // Blowfish data blocks are 64 bits wide - valid format? - if (size % 8) - return Err(sess, pubkey); - - std::vector decrypted(size + 1, 0); - for (size_t i = 0; i < size; i += 8) - BF_ecb_encrypt(&data[pos + i], reinterpret_cast(&decrypted[i]), &BFKey, BF_DECRYPT); - - std::string password = &decrypted[0]; - if (password.empty() || password.find_first_of("\r\n") != Anope::string::npos) - return Err(sess, pubkey); - - SASL::IdentifyRequest* req = new SASL::IdentifyRequest(this->owner, m.source, username, password); - Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req); - req->Dispatch(); - - BN_free(pubkey); - } - } -}; - - -class ModuleSASLDHBS : public Module -{ - DHBS dhbs; - - public: - ModuleSASLDHBS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) - , dhbs(this) - { - } -}; - -MODULE_INIT(ModuleSASLDHBS) diff --git a/modules/extra/m_sql_authentication.cpp b/modules/extra/m_sql_authentication.cpp deleted file mode 100644 index 1c2dbe729..000000000 --- a/modules/extra/m_sql_authentication.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "module.h" -#include "modules/sql.h" -#include "modules/nickserv.h" - -static Module *me; - -class SQLAuthenticationResult : public SQL::Interface -{ - Reference user; - IdentifyRequest *req; - - public: - SQLAuthenticationResult(User *u, IdentifyRequest *r) : SQL::Interface(me), user(u), req(r) - { - req->Hold(me); - } - - ~SQLAuthenticationResult() - { - req->Release(me); - } - - void OnResult(const SQL::Result &r) override - { - if (r.Rows() == 0) - { - Log(LOG_DEBUG) << "m_sql_authentication: Unsuccessful authentication for " << req->GetAccount(); - delete this; - return; - } - - Log(LOG_DEBUG) << "m_sql_authentication: Successful authentication for " << req->GetAccount(); - - Anope::string email; - try - { - email = r.Get(0, "email"); - } - catch (const SQL::Exception &) { } - - NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); - ServiceBot *NickServ = Config->GetClient("NickServ"); - if (na == NULL) - { - na = new NickServ::Nick(req->GetAccount(), new NickServ::Account(req->GetAccount())); - NickServ::Event::OnNickRegister(&NickServ::Event::NickRegister::OnNickRegister, user, na, ""); - if (user && NickServ) - user->SendMessage(NickServ, _("Your account \002%s\002 has been successfully created."), na->GetNick().c_str()); - } - - if (!email.empty() && email != na->GetAccount()->GetEmail()) - { - na->GetAccount()->GetEmail() = email; - if (user && NickServ) - user->SendMessage(NickServ, _("Your email has been updated to \002%s\002."), email.c_str()); - } - - req->Success(me); - delete this; - } - - void OnError(const SQL::Result &r) override - { - Log(this->owner) << "m_sql_authentication: Error executing query " << r.GetQuery().query << ": " << r.GetError(); - delete this; - } -}; - -class ModuleSQLAuthentication : public Module - , public EventHook - , public EventHook -{ - Anope::string engine; - Anope::string query; - Anope::string disable_reason, disable_email_reason; - - ServiceReference SQL; - - public: - ModuleSQLAuthentication(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - { - me = this; - - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - this->engine = config->Get("engine"); - this->query = config->Get("query"); - this->disable_reason = config->Get("disable_reason"); - this->disable_email_reason = config->Get("disable_email_reason"); - - this->SQL = ServiceReference("SQL::Provider", this->engine); - } - - EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override - { - if (!this->disable_reason.empty() && (command->name == "nickserv/register" || command->name == "nickserv/group")) - { - source.Reply(this->disable_reason); - return EVENT_STOP; - } - - if (!this->disable_email_reason.empty() && command->name == "nickserv/set/email") - { - source.Reply(this->disable_email_reason); - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } - - void OnCheckAuthentication(User *u, IdentifyRequest *req) override - { - if (!this->SQL) - { - Log(this) << "Unable to find SQL engine"; - return; - } - - SQL::Query q(this->query); - q.SetValue("a", req->GetAccount()); - q.SetValue("p", req->GetPassword()); - if (u) - { - q.SetValue("n", u->nick); - q.SetValue("i", u->ip.addr()); - } - else - { - q.SetValue("n", ""); - q.SetValue("i", ""); - } - - - this->SQL->Run(new SQLAuthenticationResult(u, req), q); - - Log(LOG_DEBUG) << "m_sql_authentication: Checking authentication for " << req->GetAccount(); - } -}; - -MODULE_INIT(ModuleSQLAuthentication) diff --git a/modules/extra/m_sql_log.cpp b/modules/extra/m_sql_log.cpp deleted file mode 100644 index 6922d168e..000000000 --- a/modules/extra/m_sql_log.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - */ - -#include "module.h" -#include "modules/sql.h" - -class SQLLog : public Module - , public EventHook -{ - std::set inited; - Anope::string table; - - public: - SQLLog(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) - { - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - this->table = config->Get("table", "logs"); - } - - void OnLogMessage(LogInfo *li, const Log *l, const Anope::string &msg) override - { - Anope::string ref_name; - ServiceReference SQL; - - for (unsigned i = 0; i < li->targets.size(); ++i) - { - const Anope::string &target = li->targets[i]; - size_t sz = target.find("sql_log:"); - if (!sz) - { - ref_name = target.substr(8); - SQL = ServiceReference("SQL::Provider", ref_name); - break; - } - } - - if (!SQL) - return; - - if (!inited.count(ref_name)) - { - inited.insert(ref_name); - - SQL::Query create("CREATE TABLE IF NOT EXISTS `" + table + "` (" - "`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," - "`type` varchar(64) NOT NULL," - "`user` varchar(64) NOT NULL," - "`acc` varchar(64) NOT NULL," - "`command` varchar(64) NOT NULL," - "`channel` varchar(64) NOT NULL," - "`msg` text NOT NULL" - ")"); - - SQL->Run(NULL, create); - } - - SQL::Query insert("INSERT INTO `" + table + "` (`type`,`user`,`acc`,`command`,`channel`,`msg`)" - "VALUES (@type@, @user@, @acc@, @command@, @channel@, @msg@)"); - - switch (l->type) - { - case LOG_ADMIN: - insert.SetValue("type", "ADMIN"); - break; - case LOG_OVERRIDE: - insert.SetValue("type", "OVERRIDE"); - break; - case LOG_COMMAND: - insert.SetValue("type", "COMMAND"); - break; - case LOG_SERVER: - insert.SetValue("type", "SERVER"); - break; - case LOG_CHANNEL: - insert.SetValue("type", "CHANNEL"); - break; - case LOG_USER: - insert.SetValue("type", "USER"); - break; - case LOG_MODULE: - insert.SetValue("type", "MODULE"); - break; - case LOG_NORMAL: - insert.SetValue("type", "NORMAL"); - break; - default: - return; - } - - insert.SetValue("user", l->u ? l->u->nick : ""); - insert.SetValue("acc", l->nc ? l->nc->GetDisplay() : ""); - insert.SetValue("command", l->c ? l->c->name : ""); - insert.SetValue("channel", l->ci ? l->ci->GetName() : ""); - insert.SetValue("msg", msg); - - SQL->Run(NULL, insert); - } -}; - -MODULE_INIT(SQLLog) diff --git a/modules/extra/m_sql_oper.cpp b/modules/extra/m_sql_oper.cpp deleted file mode 100644 index 6bddbf0eb..000000000 --- a/modules/extra/m_sql_oper.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include "module.h" -#include "modules/sql.h" - -struct SQLOper : Oper -{ - SQLOper(const Anope::string &n, OperType *o) : Oper(n, o) { } -}; - -class SQLOperResult : public SQL::Interface -{ - Reference user; - - struct SQLOperResultDeleter - { - SQLOperResult *res; - SQLOperResultDeleter(SQLOperResult *r) : res(r) { } - ~SQLOperResultDeleter() { delete res; } - }; - - public: - SQLOperResult(Module *m, User *u) : SQL::Interface(m), user(u) { } - - void OnResult(const SQL::Result &r) override - { - SQLOperResultDeleter d(this); - - if (!user || !user->Account() || r.Rows() == 0) - return; - - Anope::string opertype; - try - { - opertype = r.Get(0, "opertype"); - } - catch (const SQL::Exception &) - { - return; - } - - Log(LOG_DEBUG) << "m_sql_oper: Got result for " << user->nick << ", opertype " << opertype; - - Anope::string modes; - try - { - modes = r.Get(0, "modes"); - } - catch (const SQL::Exception &) { } - - ServiceBot *OperServ = Config->GetClient("OperServ"); - if (opertype.empty()) - { - if (user->Account() && user->Account()->o && dynamic_cast(user->Account()->o)) - { - delete user->Account()->o; - user->Account()->o = NULL; - - Log(this->owner) << "m_sql_oper: Removed services operator from " << user->nick << " (" << user->Account()->GetDisplay() << ")"; - user->RemoveMode(OperServ, "OPER"); // Probably not set, just incase - } - return; - } - - OperType *ot = OperType::Find(opertype); - if (ot == NULL) - { - Log(this->owner) << "m_sql_oper: Oper " << user->nick << " has type " << opertype << ", but this opertype does not exist?"; - return; - } - - if (!user->Account()->o || user->Account()->o->ot != ot) - { - Log(this->owner) << "m_sql_oper: Tieing oper " << user->nick << " to type " << opertype; - user->Account()->o = new SQLOper(user->Account()->GetDisplay(), ot); - } - - if (!user->HasMode("OPER")) - { - IRCD->SendOper(user); - - if (!modes.empty()) - user->SetModes(OperServ, "%s", modes.c_str()); - } - } - - void OnError(const SQL::Result &r) override - { - SQLOperResultDeleter d(this); - Log(this->owner) << "m_sql_oper: Error executing query " << r.GetQuery().query << ": " << r.GetError(); - } -}; - -class ModuleSQLOper : public Module - , public EventHook -{ - Anope::string engine; - Anope::string query; - - ServiceReference SQL; - - public: - ModuleSQLOper(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), - EventHook() - { - } - - ~ModuleSQLOper() - { - for (nickcore_map::const_iterator it = NickServ::AccountList->begin(), it_end = NickServ::AccountList->end(); it != it_end; ++it) - { - NickServ::Account *nc = it->second; - - if (nc->o && dynamic_cast(nc->o)) - { - delete nc->o; - nc->o = NULL; - } - } - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - - this->engine = config->Get("engine"); - this->query = config->Get("query"); - - this->SQL = ServiceReference("SQL::Provider", this->engine); - } - - void OnNickIdentify(User *u) override - { - if (!this->SQL) - { - Log() << "Unable to find SQL engine"; - return; - } - - SQL::Query q(this->query); - q.SetValue("a", u->Account()->GetDisplay()); - q.SetValue("i", u->ip.addr()); - - this->SQL->Run(new SQLOperResult(this, u), q); - - Log(LOG_DEBUG) << "m_sql_oper: Checking authentication for " << u->Account()->GetDisplay(); - } -}; - -MODULE_INIT(ModuleSQLOper) diff --git a/modules/extra/m_sqlite.cpp b/modules/extra/m_sqlite.cpp deleted file mode 100644 index 9370a0d77..000000000 --- a/modules/extra/m_sqlite.cpp +++ /dev/null @@ -1,380 +0,0 @@ -/* RequiredLibraries: sqlite3 */ -/* RequiredWindowsLibraries: sqlite3 */ - -#include "module.h" -#include "modules/sql.h" -#include - -using namespace SQL; - -/* SQLite3 API, based from InspiRCd */ - -/** A SQLite result - */ -class SQLiteResult : public Result -{ - public: - SQLiteResult(sqlite3 *sql, unsigned int id, const Query &q, const Anope::string &fq, sqlite3_stmt *stmt) : Result(id, q, fq) - { - int cols = sqlite3_column_count(stmt); - for (int i = 0; i < cols; ++i) - this->columns.push_back(sqlite3_column_name(stmt, i)); - - int err; - while ((err = sqlite3_step(stmt)) == SQLITE_ROW) - { - std::vector values; - - for (int i = 0; i < cols; ++i) - { - const char *data = reinterpret_cast(sqlite3_column_text(stmt, i)); - - Value v; - v.null = !data; - v.value = data ? data : ""; - values.push_back(v); - } - - this->values.push_back(values); - } - - if (err != SQLITE_DONE) - { - error = sqlite3_errmsg(sql); - } - } - - SQLiteResult(const Query &q, const Anope::string &fq, const Anope::string &err) : Result(0, q, fq, err) - { - } -}; - -/** A SQLite database, there can be multiple - */ -class SQLiteService : public Provider -{ - std::map > active_schema, indexes; - - Anope::string database; - - sqlite3 *sql; - - Anope::string Escape(const Anope::string &query); - - public: - SQLiteService(Module *o, const Anope::string &n, const Anope::string &d); - - ~SQLiteService(); - - void Run(Interface *i, const Query &query) override; - - Result RunQuery(const Query &query); - - std::vector InitSchema(const Anope::string &prefix) override; - std::vector Replace(const Anope::string &table, const Query &, const std::set &) override; - std::vector CreateTable(const Anope::string &, const Anope::string &table) override; - std::vector AlterTable(const Anope::string &, const Anope::string &table, const Anope::string &field, bool) override; - std::vector CreateIndex(const Anope::string &table, const Anope::string &field) override; - - Query BeginTransaction() override; - Query Commit() override; - - Serialize::ID GetID(const Anope::string &) override; - - Query GetTables(const Anope::string &prefix); - - Anope::string BuildQuery(const Query &q); -}; - -class ModuleSQLite : public Module -{ - /* SQL connections */ - std::map SQLiteServices; - - public: - ModuleSQLite(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - { - } - - ~ModuleSQLite() - { - for (std::map::iterator it = this->SQLiteServices.begin(); it != this->SQLiteServices.end(); ++it) - delete it->second; - SQLiteServices.clear(); - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - - for (std::map::iterator it = this->SQLiteServices.begin(); it != this->SQLiteServices.end();) - { - const Anope::string &cname = it->first; - SQLiteService *s = it->second; - int i, num; - ++it; - - for (i = 0, num = config->CountBlock("sqlite"); i < num; ++i) - if (config->GetBlock("sqlite", i)->Get("name", "sqlite/main") == cname) - break; - - if (i == num) - { - Log(LOG_NORMAL, "sqlite") << "SQLite: Removing server connection " << cname; - - delete s; - this->SQLiteServices.erase(cname); - } - } - - for (int i = 0; i < config->CountBlock("sqlite"); ++i) - { - Configuration::Block *block = config->GetBlock("sqlite", i); - Anope::string connname = block->Get("name", "sqlite/main"); - - if (this->SQLiteServices.find(connname) == this->SQLiteServices.end()) - { - Anope::string database = Anope::DataDir + "/" + block->Get("database", "anope"); - - try - { - SQLiteService *ss = new SQLiteService(this, connname, database); - this->SQLiteServices[connname] = ss; - - Log(LOG_NORMAL, "sqlite") << "SQLite: Successfully added database " << database; - } - catch (const SQL::Exception &ex) - { - Log(LOG_NORMAL, "sqlite") << "SQLite: " << ex.GetReason(); - } - } - } - } -}; - -SQLiteService::SQLiteService(Module *o, const Anope::string &n, const Anope::string &d) -: Provider(o, n), database(d), sql(NULL) -{ - int db = sqlite3_open_v2(database.c_str(), &this->sql, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); - if (db != SQLITE_OK) - throw SQL::Exception("Unable to open SQLite database " + database + ": " + sqlite3_errmsg(this->sql)); -} - -SQLiteService::~SQLiteService() -{ - sqlite3_interrupt(this->sql); - sqlite3_close(this->sql); -} - -void SQLiteService::Run(Interface *i, const Query &query) -{ - Result res = this->RunQuery(query); - if (!res.GetError().empty()) - i->OnError(res); - else - i->OnResult(res); -} - -Result SQLiteService::RunQuery(const Query &query) -{ - Anope::string real_query = this->BuildQuery(query); - sqlite3_stmt *stmt; - int err = sqlite3_prepare_v2(this->sql, real_query.c_str(), real_query.length(), &stmt, NULL); - if (err != SQLITE_OK) - { - return SQLiteResult(query, real_query, sqlite3_errmsg(this->sql)); - } - - int id = sqlite3_last_insert_rowid(this->sql); - SQLiteResult result(this->sql, id, query, real_query, stmt); - - sqlite3_finalize(stmt); - - return result; -} - -std::vector SQLiteService::InitSchema(const Anope::string &prefix) -{ - std::vector queries; - - Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + "id` (" - "`id`" - ")"; - queries.push_back(t); - - t = "CREATE TABLE IF NOT EXISTS `" + prefix + "objects` (`id` PRIMARY KEY, `type`)"; - queries.push_back(t); - - t = "CREATE TABLE IF NOT EXISTS `" + prefix + "edges` (" - "`id`," - "`field`," - "`other_id`," - "PRIMARY KEY (`id`, `field`)" - ")"; - queries.push_back(t); - - t = "CREATE INDEX IF NOT EXISTS idx_edge ON `" + prefix + "edges` (other_id)"; - queries.push_back(t); - - return queries; -} - -std::vector SQLiteService::Replace(const Anope::string &table, const Query &q, const std::set &keys) -{ - std::vector queries; - - Anope::string query_text = "INSERT OR IGNORE INTO `" + table + "` ("; - for (const std::pair &p : q.parameters) - query_text += "`" + p.first + "`,"; - query_text.erase(query_text.length() - 1); - query_text += ") VALUES ("; - for (const std::pair &p : q.parameters) - query_text += "@" + p.first + "@,"; - query_text.erase(query_text.length() - 1); - query_text += ")"; - - Query query(query_text); - query.parameters = q.parameters; - queries.push_back(query); - - query_text = "UPDATE `" + table + "` SET "; - for (const std::pair &p : q.parameters) - if (!keys.count(p.first)) - query_text += "`" + p.first + "` = @" + p.first + "@,"; - query_text.erase(query_text.length() - 1); - unsigned int i = 0; - for (const Anope::string &key : keys) - { - if (!i++) - query_text += " WHERE "; - else - query_text += " AND "; - query_text += "`" + key + "` = @" + key + "@"; - } - - query = query_text; - query.parameters = q.parameters; - queries.push_back(query); - - return queries; -} - -std::vector SQLiteService::CreateTable(const Anope::string &prefix, const Anope::string &table) -{ - std::vector queries; - - if (active_schema.find(prefix + table) == active_schema.end()) - { - Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + table + "` (`id` bigint(20) NOT NULL, PRIMARY KEY (`id`))"; - queries.push_back(t); - - active_schema[prefix + table]; - } - - return queries; -} - -std::vector SQLiteService::AlterTable(const Anope::string &prefix, const Anope::string &table, const Anope::string &field, bool) -{ - std::vector queries; - std::set &s = active_schema[prefix + table]; - - if (!s.count(field)) - { - Query t = "ALTER TABLE `" + prefix + table + "` ADD `" + field + "` COLLATE NOCASE"; - queries.push_back(t); - s.insert(field); - } - - return queries; -} - -std::vector SQLiteService::CreateIndex(const Anope::string &table, const Anope::string &field) -{ - std::vector queries; - - if (indexes[table].count(field)) - return queries; - - Query t = "CREATE INDEX IF NOT EXISTS idx_" + field + " ON `" + table + "` (" + field + ")"; - queries.push_back(t); - - indexes[table].insert(field); - - return queries; -} - -Query SQLiteService::BeginTransaction() -{ - return Query("BEGIN TRANSACTION"); -} - -Query SQLiteService::Commit() -{ - return Query("COMMIT"); -} - -Serialize::ID SQLiteService::GetID(const Anope::string &prefix) -{ - /* must be in a deferred or reserved transaction here for atomic row update */ - - Query query("SELECT `id` FROM `" + prefix + "id`"); - Serialize::ID id; - - Result res = RunQuery(query); - if (res.Rows()) - { - id = convertTo(res.Get(0, "id")); - - Query update_query("UPDATE `" + prefix + "id` SET `id` = `id` + 1"); - RunQuery(update_query); - } - else - { - id = 0; - - Query insert_query("INSERT INTO `" + prefix + "id` (id) VALUES(@id@)"); - insert_query.SetValue("id", 1); - RunQuery(insert_query); - } - - return id; -} - -Query SQLiteService::GetTables(const Anope::string &prefix) -{ - return Query("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '" + prefix + "%';"); -} - -Anope::string SQLiteService::Escape(const Anope::string &query) -{ - char *e = sqlite3_mprintf("%q", query.c_str()); - Anope::string buffer = e; - sqlite3_free(e); - return buffer; -} - -Anope::string SQLiteService::BuildQuery(const Query &q) -{ - Anope::string real_query = q.query; - - for (std::map::const_iterator it = q.parameters.begin(), it_end = q.parameters.end(); it != it_end; ++it) - { - const QueryData& qd = it->second; - Anope::string replacement; - - if (qd.null) - replacement = "NULL"; - else if (!qd.escape) - replacement = qd.data; - else - replacement = "'" + this->Escape(qd.data) + "'"; - - real_query = real_query.replace_all_cs("@" + it->first + "@", replacement); - } - - return real_query; -} - -MODULE_INIT(ModuleSQLite) - diff --git a/modules/extra/m_ssl_gnutls.cpp b/modules/extra/m_ssl_gnutls.cpp deleted file mode 100644 index 93a56f03a..000000000 --- a/modules/extra/m_ssl_gnutls.cpp +++ /dev/null @@ -1,658 +0,0 @@ -/* - * (C) 2014 Attila Molnar - * (C) 2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - */ - -/* RequiredLibraries: gnutls */ -/* RequiredWindowsLibraries: libgnutls-28 */ - -#include "module.h" -#include "modules/ssl.h" - -#include -#include -#include - -class GnuTLSModule; -static GnuTLSModule *me; - -namespace GnuTLS { class X509CertCredentials; } - -class MySSLService : public SSLService -{ - public: - MySSLService(Module *o, const Anope::string &n); - - /** Initialize a socket to use SSL - * @param s The socket - */ - void Init(Socket *s) override; -}; - -class SSLSocketIO : public SocketIO -{ - public: - gnutls_session_t sess; - GnuTLS::X509CertCredentials* mycreds; - - /** Constructor - */ - SSLSocketIO(); - - /** Really receive something from the buffer - * @param s The socket - * @param buf The buf to read to - * @param sz How much to read - * @return Number of bytes received - */ - int Recv(Socket *s, char *buf, size_t sz) override; - - /** Write something to the socket - * @param s The socket - * @param buf The data to write - * @param size The length of the data - */ - int Send(Socket *s, const char *buf, size_t sz) override; - - /** Accept a connection from a socket - * @param s The socket - * @return The new socket - */ - ClientSocket *Accept(ListenSocket *s) override; - - /** Finished accepting a connection from a socket - * @param s The socket - * @return SF_ACCEPTED if accepted, SF_ACCEPTING if still in process, SF_DEAD on error - */ - SocketFlag FinishAccept(ClientSocket *cs) override; - - /** Connect the socket - * @param s THe socket - * @param target IP to connect to - * @param port to connect to - */ - void Connect(ConnectionSocket *s, const Anope::string &target, int port) override; - - /** Called to potentially finish a pending connection - * @param s The socket - * @return SF_CONNECTED on success, SF_CONNECTING if still pending, and SF_DEAD on error. - */ - SocketFlag FinishConnect(ConnectionSocket *s) override; - - /** Called when the socket is destructing - */ - void Destroy() override; -}; - -namespace GnuTLS -{ - class Init - { - public: - Init() { gnutls_global_init(); } - ~Init() { gnutls_global_deinit(); } - }; - - /** Used to create a gnutls_datum_t* from an Anope::string - */ - class Datum - { - gnutls_datum_t datum; - - public: - Datum(const Anope::string &dat) - { - datum.data = reinterpret_cast(const_cast(dat.data())); - datum.size = static_cast(dat.length()); - } - - const gnutls_datum_t *get() const { return &datum; } - }; - - class DHParams - { - gnutls_dh_params_t dh_params; - - public: - DHParams() : dh_params(NULL) { } - - void Import(const Anope::string &dhstr) - { - if (dh_params != NULL) - { - gnutls_dh_params_deinit(dh_params); - dh_params = NULL; - } - - int ret = gnutls_dh_params_init(&dh_params); - if (ret < 0) - throw ConfigException("Unable to initialize DH parameters"); - - ret = gnutls_dh_params_import_pkcs3(dh_params, Datum(dhstr).get(), GNUTLS_X509_FMT_PEM); - if (ret < 0) - { - gnutls_dh_params_deinit(dh_params); - dh_params = NULL; - throw ConfigException("Unable to import DH parameters"); - } - } - - ~DHParams() - { - if (dh_params) - gnutls_dh_params_deinit(dh_params); - } - - gnutls_dh_params_t get() const { return dh_params; } - }; - - class X509Key - { - /** Ensure that the key is deinited in case the constructor of X509Key throws - */ - class RAIIKey - { - public: - gnutls_x509_privkey_t key; - - RAIIKey() - { - int ret = gnutls_x509_privkey_init(&key); - if (ret < 0) - throw ConfigException("gnutls_x509_privkey_init() failed"); - } - - ~RAIIKey() - { - gnutls_x509_privkey_deinit(key); - } - } key; - - public: - /** Import */ - X509Key(const Anope::string &keystr) - { - int ret = gnutls_x509_privkey_import(key.key, Datum(keystr).get(), GNUTLS_X509_FMT_PEM); - if (ret < 0) - throw ConfigException("Error loading private key: " + Anope::string(gnutls_strerror(ret))); - } - - gnutls_x509_privkey_t& get() { return key.key; } - }; - - class X509CertList - { - std::vector certs; - - public: - /** Import */ - X509CertList(const Anope::string &certstr) - { - unsigned int certcount = 3; - certs.resize(certcount); - Datum datum(certstr); - - int ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); - if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) - { - // the buffer wasn't big enough to hold all certs but gnutls changed certcount to the number of available certs, - // try again with a bigger buffer - certs.resize(certcount); - ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); - } - - if (ret < 0) - throw ConfigException("Unable to load certificates" + Anope::string(gnutls_strerror(ret))); - - // Resize the vector to the actual number of certs because we rely on its size being correct - // when deallocating the certs - certs.resize(certcount); - } - - ~X509CertList() - { - for (std::vector::iterator i = certs.begin(); i != certs.end(); ++i) - gnutls_x509_crt_deinit(*i); - } - - gnutls_x509_crt_t* raw() { return &certs[0]; } - unsigned int size() const { return certs.size(); } - }; - - class X509CertCredentials - { - unsigned int refcount; - gnutls_certificate_credentials_t cred; - DHParams dh; - - static Anope::string LoadFile(const Anope::string &filename) - { - std::ifstream ifs(filename.c_str()); - const Anope::string ret((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); - return ret; - } - - #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12)) - static int cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr_st* st); - #else - static int cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr2_st* st); - #endif - - public: - X509CertList certs; - X509Key key; - - X509CertCredentials(const Anope::string &certfile, const Anope::string &keyfile) - : refcount(0), certs(LoadFile(certfile)), key(LoadFile(keyfile)) - { - if (gnutls_certificate_allocate_credentials(&cred) < 0) - throw ConfigException("Cannot allocate certificate credentials"); - - int ret = gnutls_certificate_set_x509_key(cred, certs.raw(), certs.size(), key.get()); - if (ret < 0) - { - gnutls_certificate_free_credentials(cred); - throw ConfigException("Unable to set cert/key pair"); - } - - #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12)) - gnutls_certificate_client_set_retrieve_function(cred, cert_callback); - #else - gnutls_certificate_set_retrieve_function(cred, cert_callback); - #endif - } - - ~X509CertCredentials() - { - gnutls_certificate_free_credentials(cred); - } - - void SetupSession(gnutls_session_t sess) - { - gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred); - gnutls_set_default_priority(sess); - } - - void SetDH(const Anope::string &dhfile) - { - const Anope::string dhdata = LoadFile(dhfile); - dh.Import(dhdata); - gnutls_certificate_set_dh_params(cred, dh.get()); - } - - bool HasDH() const - { - return (dh.get() != NULL); - } - - void incrref() { refcount++; } - void decrref() { if (!--refcount) delete this; } - }; -} - -class GnuTLSModule : public Module - , public EventHook -{ - GnuTLS::Init libinit; - - public: - GnuTLS::X509CertCredentials *cred; - MySSLService service; - - GnuTLSModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - , cred(NULL) - , service(this, "ssl") - { - me = this; - this->SetPermanent(true); - } - - ~GnuTLSModule() - { - for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) - { - Socket *s = it->second; - ++it; - - if (dynamic_cast(s->io)) - delete s; - } - - if (cred) - cred->decrref(); - } - - static void CheckFile(const Anope::string &filename) - { - if (!Anope::IsFile(filename.c_str())) - { - Log() << "File does not exist: " << filename; - throw ConfigException("Error loading certificate/private key"); - } - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - - const Anope::string certfile = config->Get("cert", "data/anope.crt"); - const Anope::string keyfile = config->Get("key", "data/anope.key"); - const Anope::string dhfile = config->Get("dh", "data/dhparams.pem"); - - CheckFile(certfile); - CheckFile(keyfile); - - GnuTLS::X509CertCredentials *newcred = new GnuTLS::X509CertCredentials(certfile, keyfile); - - // DH params is not mandatory - if (Anope::IsFile(dhfile.c_str())) - { - try - { - newcred->SetDH(dhfile); - } - catch (...) - { - delete newcred; - throw; - } - Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded DH parameters from " << dhfile; - } - - if (cred) - cred->decrref(); - cred = newcred; - cred->incrref(); - - Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded certificate " << certfile << " and private key " << keyfile; - } - - void OnPreServerConnect() override - { - Configuration::Block *config = Config->GetBlock("uplink", Anope::CurrentUplink); - - if (config->Get("ssl")) - { - this->service.Init(UplinkSock); - } - } -}; - -MySSLService::MySSLService(Module *o, const Anope::string &n) : SSLService(o, n) -{ -} - -void MySSLService::Init(Socket *s) -{ - if (s->io != &NormalSocketIO) - throw CoreException("Socket initializing SSL twice"); - - s->io = new SSLSocketIO(); -} - -int SSLSocketIO::Recv(Socket *s, char *buf, size_t sz) -{ - int ret = gnutls_record_recv(this->sess, buf, sz); - - if (ret > 0) - TotalRead += ret; - else if (ret < 0) - { - switch (ret) - { - case GNUTLS_E_AGAIN: - case GNUTLS_E_INTERRUPTED: - SocketEngine::SetLastError(EAGAIN); - break; - default: - if (s == UplinkSock) - { - // Log and fake an errno because this is a fatal error on the uplink socket - Log() << "SSL error: " << gnutls_strerror(ret); - } - SocketEngine::SetLastError(ECONNRESET); - } - } - - return ret; -} - -int SSLSocketIO::Send(Socket *s, const char *buf, size_t sz) -{ - int ret = gnutls_record_send(this->sess, buf, sz); - - if (ret > 0) - TotalWritten += ret; - else - { - switch (ret) - { - case 0: - case GNUTLS_E_AGAIN: - case GNUTLS_E_INTERRUPTED: - SocketEngine::SetLastError(EAGAIN); - break; - default: - if (s == UplinkSock) - { - // Log and fake an errno because this is a fatal error on the uplink socket - Log() << "SSL error: " << gnutls_strerror(ret); - } - SocketEngine::SetLastError(ECONNRESET); - } - } - - return ret; -} - -ClientSocket *SSLSocketIO::Accept(ListenSocket *s) -{ - if (s->io == &NormalSocketIO) - throw SocketException("Attempting to accept on uninitialized socket with SSL"); - - sockaddrs conaddr; - - socklen_t size = sizeof(conaddr); - int newsock = accept(s->GetFD(), &conaddr.sa, &size); - -#ifndef INVALID_SOCKET - const int INVALID_SOCKET = -1; -#endif - - if (newsock < 0 || newsock == INVALID_SOCKET) - throw SocketException("Unable to accept connection: " + Anope::LastError()); - - ClientSocket *newsocket = s->OnAccept(newsock, conaddr); - me->service.Init(newsocket); - SSLSocketIO *io = anope_dynamic_static_cast(newsocket->io); - - if (gnutls_init(&io->sess, GNUTLS_SERVER) != GNUTLS_E_SUCCESS) - throw SocketException("Unable to initialize SSL socket"); - - me->cred->SetupSession(io->sess); - gnutls_transport_set_ptr(io->sess, reinterpret_cast(newsock)); - - newsocket->flags[SF_ACCEPTING] = true; - this->FinishAccept(newsocket); - - return newsocket; -} - -SocketFlag SSLSocketIO::FinishAccept(ClientSocket *cs) -{ - if (cs->io == &NormalSocketIO) - throw SocketException("Attempting to finish connect uninitialized socket with SSL"); - else if (cs->flags[SF_ACCEPTED]) - return SF_ACCEPTED; - else if (!cs->flags[SF_ACCEPTING]) - throw SocketException("SSLSocketIO::FinishAccept called for a socket not accepted nor accepting?"); - - SSLSocketIO *io = anope_dynamic_static_cast(cs->io); - - int ret = gnutls_handshake(io->sess); - if (ret < 0) - { - if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) - { - // gnutls_handshake() wants to read or write again; - // if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write. - if (gnutls_record_get_direction(io->sess) == 0) - { - SocketEngine::Change(cs, false, SF_WRITABLE); - SocketEngine::Change(cs, true, SF_READABLE); - } - else - { - SocketEngine::Change(cs, true, SF_WRITABLE); - SocketEngine::Change(cs, false, SF_READABLE); - } - return SF_ACCEPTING; - } - else - { - cs->OnError(Anope::string(gnutls_strerror(ret))); - cs->flags[SF_DEAD] = true; - cs->flags[SF_ACCEPTING] = false; - return SF_DEAD; - } - } - else - { - cs->flags[SF_ACCEPTED] = true; - cs->flags[SF_ACCEPTING] = false; - SocketEngine::Change(cs, false, SF_WRITABLE); - SocketEngine::Change(cs, true, SF_READABLE); - cs->OnAccept(); - return SF_ACCEPTED; - } -} - -void SSLSocketIO::Connect(ConnectionSocket *s, const Anope::string &target, int port) -{ - if (s->io == &NormalSocketIO) - throw SocketException("Attempting to connect uninitialized socket with SSL"); - - s->flags[SF_CONNECTING] = s->flags[SF_CONNECTED] = false; - - s->conaddr.pton(s->IsIPv6() ? AF_INET6 : AF_INET, target, port); - int c = connect(s->GetFD(), &s->conaddr.sa, s->conaddr.size()); - if (c == -1) - { - if (Anope::LastErrorCode() != EINPROGRESS) - { - s->OnError(Anope::LastError()); - s->flags[SF_DEAD] = true; - return; - } - else - { - SocketEngine::Change(s, true, SF_WRITABLE); - s->flags[SF_CONNECTING] = true; - return; - } - } - else - { - s->flags[SF_CONNECTING] = true; - this->FinishConnect(s); - } -} - -SocketFlag SSLSocketIO::FinishConnect(ConnectionSocket *s) -{ - if (s->io == &NormalSocketIO) - throw SocketException("Attempting to finish connect uninitialized socket with SSL"); - else if (s->flags[SF_CONNECTED]) - return SF_CONNECTED; - else if (!s->flags[SF_CONNECTING]) - throw SocketException("SSLSocketIO::FinishConnect called for a socket not connected nor connecting?"); - - SSLSocketIO *io = anope_dynamic_static_cast(s->io); - - if (io->sess == NULL) - { - if (gnutls_init(&io->sess, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS) - throw SocketException("Unable to initialize SSL socket"); - me->cred->SetupSession(io->sess); - gnutls_transport_set_ptr(io->sess, reinterpret_cast(s->GetFD())); - } - - int ret = gnutls_handshake(io->sess); - if (ret < 0) - { - if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) - { - // gnutls_handshake() wants to read or write again; - // if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write. - if (gnutls_record_get_direction(io->sess) == 0) - { - SocketEngine::Change(s, false, SF_WRITABLE); - SocketEngine::Change(s, true, SF_READABLE); - } - else - { - SocketEngine::Change(s, true, SF_WRITABLE); - SocketEngine::Change(s, false, SF_READABLE); - } - - return SF_CONNECTING; - } - else - { - s->OnError(Anope::string(gnutls_strerror(ret))); - s->flags[SF_CONNECTING] = false; - s->flags[SF_DEAD] = true; - return SF_DEAD; - } - } - else - { - s->flags[SF_CONNECTING] = false; - s->flags[SF_CONNECTED] = true; - SocketEngine::Change(s, false, SF_WRITABLE); - SocketEngine::Change(s, true, SF_READABLE); - s->OnConnect(); - return SF_CONNECTED; - } -} - -void SSLSocketIO::Destroy() -{ - if (this->sess) - { - gnutls_bye(this->sess, GNUTLS_SHUT_WR); - gnutls_deinit(this->sess); - } - - mycreds->decrref(); - - delete this; -} - -SSLSocketIO::SSLSocketIO() : sess(NULL), mycreds(me->cred) -{ - mycreds->incrref(); -} - -#if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12)) -int GnuTLS::X509CertCredentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr_st* st) -{ - st->type = GNUTLS_CRT_X509; -#else -int GnuTLS::X509CertCredentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr2_st* st) -{ - st->cert_type = GNUTLS_CRT_X509; - st->key_type = GNUTLS_PRIVKEY_X509; -#endif - st->ncerts = me->cred->certs.size(); - st->cert.x509 = me->cred->certs.raw(); - st->key.x509 = me->cred->key.get(); - st->deinit_all = 0; - - return 0; -} - -MODULE_INIT(GnuTLSModule) diff --git a/modules/extra/m_ssl_openssl.cpp b/modules/extra/m_ssl_openssl.cpp deleted file mode 100644 index c5e2feefc..000000000 --- a/modules/extra/m_ssl_openssl.cpp +++ /dev/null @@ -1,416 +0,0 @@ -/* RequiredLibraries: ssl,crypto */ -/* RequiredWindowsLibraries: ssleay32,libeay32 */ - -#include "module.h" -#include "modules/ssl.h" - -#define OPENSSL_NO_SHA512 -#include -#include -#include -#include -#include - -static SSL_CTX *server_ctx, *client_ctx; - -class MySSLService : public SSLService -{ - public: - MySSLService(Module *o, const Anope::string &n); - - /** Initialize a socket to use SSL - * @param s The socket - */ - void Init(Socket *s) override; -}; - -class SSLSocketIO : public SocketIO -{ - public: - /* The SSL socket for this socket */ - SSL *sslsock; - - /** Constructor - */ - SSLSocketIO(); - - /** Really receive something from the buffer - * @param s The socket - * @param buf The buf to read to - * @param sz How much to read - * @return Number of bytes received - */ - int Recv(Socket *s, char *buf, size_t sz) override; - - /** Write something to the socket - * @param s The socket - * @param buf The data to write - * @param size The length of the data - */ - int Send(Socket *s, const char *buf, size_t sz) override; - - /** Accept a connection from a socket - * @param s The socket - * @return The new socket - */ - ClientSocket *Accept(ListenSocket *s) override; - - /** Finished accepting a connection from a socket - * @param s The socket - * @return SF_ACCEPTED if accepted, SF_ACCEPTING if still in process, SF_DEAD on error - */ - SocketFlag FinishAccept(ClientSocket *cs) override; - - /** Connect the socket - * @param s THe socket - * @param target IP to connect to - * @param port to connect to - */ - void Connect(ConnectionSocket *s, const Anope::string &target, int port) override; - - /** Called to potentially finish a pending connection - * @param s The socket - * @return SF_CONNECTED on success, SF_CONNECTING if still pending, and SF_DEAD on error. - */ - SocketFlag FinishConnect(ConnectionSocket *s) override; - - /** Called when the socket is destructing - */ - void Destroy() override; -}; - -class SSLModule; -static SSLModule *me; -class SSLModule : public Module - , public EventHook -{ - Anope::string certfile, keyfile; - - public: - MySSLService service; - - SSLModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - , service(this, "ssl") - { - me = this; - - this->SetPermanent(true); - - SSL_library_init(); - SSL_load_error_strings(); - - client_ctx = SSL_CTX_new(SSLv23_client_method()); - server_ctx = SSL_CTX_new(SSLv23_server_method()); - - if (!client_ctx || !server_ctx) - throw ModuleException("Error initializing SSL CTX"); - - long opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_CIPHER_SERVER_PREFERENCE; - SSL_CTX_set_options(client_ctx, opts); - SSL_CTX_set_options(server_ctx, opts); - - SSL_CTX_set_mode(client_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - SSL_CTX_set_mode(server_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - - Anope::string context_name = "Anope"; - SSL_CTX_set_session_id_context(client_ctx, reinterpret_cast(context_name.c_str()), context_name.length()); - SSL_CTX_set_session_id_context(server_ctx, reinterpret_cast(context_name.c_str()), context_name.length()); - } - - ~SSLModule() - { - for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) - { - Socket *s = it->second; - ++it; - - if (dynamic_cast(s->io)) - delete s; - } - - SSL_CTX_free(client_ctx); - SSL_CTX_free(server_ctx); - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = conf->GetModule(this); - - this->certfile = config->Get("cert", "data/anope.crt"); - this->keyfile = config->Get("key", "data/anope.key"); - - if (Anope::IsFile(this->certfile.c_str())) - { - if (!SSL_CTX_use_certificate_file(client_ctx, this->certfile.c_str(), SSL_FILETYPE_PEM) || !SSL_CTX_use_certificate_file(server_ctx, this->certfile.c_str(), SSL_FILETYPE_PEM)) - throw ConfigException("Error loading certificate"); - else - Log(LOG_DEBUG) << "m_ssl_openssl: Successfully loaded certificate " << this->certfile; - } - else - Log() << "Unable to open certificate " << this->certfile; - - if (Anope::IsFile(this->keyfile.c_str())) - { - if (!SSL_CTX_use_PrivateKey_file(client_ctx, this->keyfile.c_str(), SSL_FILETYPE_PEM) || !SSL_CTX_use_PrivateKey_file(server_ctx, this->keyfile.c_str(), SSL_FILETYPE_PEM)) - throw ConfigException("Error loading private key"); - else - Log(LOG_DEBUG) << "m_ssl_openssl: Successfully loaded private key " << this->keyfile; - } - else - { - if (Anope::IsFile(this->certfile.c_str())) - throw ConfigException("Error loading private key " + this->keyfile + " - file not found"); - else - Log() << "Unable to open private key " << this->keyfile; - } - - // Allow disabling SSLv3 - if (!config->Get("sslv3").empty()) - { - if (config->Get("sslv3")) - { - SSL_CTX_clear_options(client_ctx, SSL_OP_NO_SSLv3); - SSL_CTX_clear_options(server_ctx, SSL_OP_NO_SSLv3); - } - else - { - SSL_CTX_set_options(client_ctx, SSL_OP_NO_SSLv3); - SSL_CTX_set_options(server_ctx, SSL_OP_NO_SSLv3); - } - } - } - - void OnPreServerConnect() override - { - Configuration::Block *config = Config->GetBlock("uplink", Anope::CurrentUplink); - - if (config->Get("ssl")) - { - this->service.Init(UplinkSock); - } - } -}; - -MySSLService::MySSLService(Module *o, const Anope::string &n) : SSLService(o, n) -{ -} - -void MySSLService::Init(Socket *s) -{ - if (s->io != &NormalSocketIO) - throw CoreException("Socket initializing SSL twice"); - - s->io = new SSLSocketIO(); -} - -SSLSocketIO::SSLSocketIO() -{ - this->sslsock = NULL; -} - -int SSLSocketIO::Recv(Socket *s, char *buf, size_t sz) -{ - int i = SSL_read(this->sslsock, buf, sz); - if (i > 0) - TotalRead += i; - else if (i < 0) - { - int err = SSL_get_error(this->sslsock, i); - switch (err) - { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - SocketEngine::SetLastError(EAGAIN); - } - } - - return i; -} - -int SSLSocketIO::Send(Socket *s, const char *buf, size_t sz) -{ - int i = SSL_write(this->sslsock, buf, sz); - if (i > 0) - TotalWritten += i; - else if (i < 0) - { - int err = SSL_get_error(this->sslsock, i); - switch (err) - { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - SocketEngine::SetLastError(EAGAIN); - } - } - return i; -} - -ClientSocket *SSLSocketIO::Accept(ListenSocket *s) -{ - if (s->io == &NormalSocketIO) - throw SocketException("Attempting to accept on uninitialized socket with SSL"); - - sockaddrs conaddr; - - socklen_t size = sizeof(conaddr); - int newsock = accept(s->GetFD(), &conaddr.sa, &size); - -#ifndef INVALID_SOCKET - const int INVALID_SOCKET = -1; -#endif - - if (newsock < 0 || newsock == INVALID_SOCKET) - throw SocketException("Unable to accept connection: " + Anope::LastError()); - - ClientSocket *newsocket = s->OnAccept(newsock, conaddr); - me->service.Init(newsocket); - SSLSocketIO *io = anope_dynamic_static_cast(newsocket->io); - - io->sslsock = SSL_new(server_ctx); - if (!io->sslsock) - throw SocketException("Unable to initialize SSL socket"); - - SSL_set_accept_state(io->sslsock); - - if (!SSL_set_fd(io->sslsock, newsocket->GetFD())) - throw SocketException("Unable to set SSL fd"); - - newsocket->flags[SF_ACCEPTING] = true; - this->FinishAccept(newsocket); - - return newsocket; -} - -SocketFlag SSLSocketIO::FinishAccept(ClientSocket *cs) -{ - if (cs->io == &NormalSocketIO) - throw SocketException("Attempting to finish connect uninitialized socket with SSL"); - else if (cs->flags[SF_ACCEPTED]) - return SF_ACCEPTED; - else if (!cs->flags[SF_ACCEPTING]) - throw SocketException("SSLSocketIO::FinishAccept called for a socket not accepted nor accepting?"); - - SSLSocketIO *io = anope_dynamic_static_cast(cs->io); - - int ret = SSL_accept(io->sslsock); - if (ret <= 0) - { - int error = SSL_get_error(io->sslsock, ret); - if (ret == -1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)) - { - SocketEngine::Change(cs, error == SSL_ERROR_WANT_WRITE, SF_WRITABLE); - SocketEngine::Change(cs, error == SSL_ERROR_WANT_READ, SF_READABLE); - return SF_ACCEPTING; - } - else - { - cs->OnError(ERR_error_string(ERR_get_error(), NULL)); - cs->flags[SF_DEAD] = true; - cs->flags[SF_ACCEPTING] = false; - return SF_DEAD; - } - } - else - { - cs->flags[SF_ACCEPTED] = true; - cs->flags[SF_ACCEPTING] = false; - SocketEngine::Change(cs, false, SF_WRITABLE); - SocketEngine::Change(cs, true, SF_READABLE); - cs->OnAccept(); - return SF_ACCEPTED; - } -} - -void SSLSocketIO::Connect(ConnectionSocket *s, const Anope::string &target, int port) -{ - if (s->io == &NormalSocketIO) - throw SocketException("Attempting to connect uninitialized socket with SSL"); - - s->flags[SF_CONNECTING] = s->flags[SF_CONNECTED] = false; - - s->conaddr.pton(s->IsIPv6() ? AF_INET6 : AF_INET, target, port); - int c = connect(s->GetFD(), &s->conaddr.sa, s->conaddr.size()); - if (c == -1) - { - if (Anope::LastErrorCode() != EINPROGRESS) - { - s->OnError(Anope::LastError()); - s->flags[SF_DEAD] = true; - return; - } - else - { - SocketEngine::Change(s, true, SF_WRITABLE); - s->flags[SF_CONNECTING] = true; - return; - } - } - else - { - s->flags[SF_CONNECTING] = true; - this->FinishConnect(s); - } -} - -SocketFlag SSLSocketIO::FinishConnect(ConnectionSocket *s) -{ - if (s->io == &NormalSocketIO) - throw SocketException("Attempting to finish connect uninitialized socket with SSL"); - else if (s->flags[SF_CONNECTED]) - return SF_CONNECTED; - else if (!s->flags[SF_CONNECTING]) - throw SocketException("SSLSocketIO::FinishConnect called for a socket not connected nor connecting?"); - - SSLSocketIO *io = anope_dynamic_static_cast(s->io); - - if (io->sslsock == NULL) - { - io->sslsock = SSL_new(client_ctx); - if (!io->sslsock) - throw SocketException("Unable to initialize SSL socket"); - - if (!SSL_set_fd(io->sslsock, s->GetFD())) - throw SocketException("Unable to set SSL fd"); - } - - int ret = SSL_connect(io->sslsock); - if (ret <= 0) - { - int error = SSL_get_error(io->sslsock, ret); - if (ret == -1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)) - { - SocketEngine::Change(s, error == SSL_ERROR_WANT_WRITE, SF_WRITABLE); - SocketEngine::Change(s, error == SSL_ERROR_WANT_READ, SF_READABLE); - return SF_CONNECTING; - } - else - { - s->OnError(ERR_error_string(ERR_get_error(), NULL)); - s->flags[SF_CONNECTING] = false; - s->flags[SF_DEAD] = true; - return SF_DEAD; - } - } - else - { - s->flags[SF_CONNECTING] = false; - s->flags[SF_CONNECTED] = true; - SocketEngine::Change(s, false, SF_WRITABLE); - SocketEngine::Change(s, true, SF_READABLE); - s->OnConnect(); - return SF_CONNECTED; - } -} - -void SSLSocketIO::Destroy() -{ - if (this->sslsock) - { - SSL_shutdown(this->sslsock); - SSL_free(this->sslsock); - } - - delete this; -} - -MODULE_INIT(SSLModule) diff --git a/modules/extra/mysql.cpp b/modules/extra/mysql.cpp new file mode 100644 index 000000000..843a2ba0a --- /dev/null +++ b/modules/extra/mysql.cpp @@ -0,0 +1,614 @@ +/* RequiredLibraries: mysqlclient */ +/* RequiredWindowsLibraries: libmysql */ + +#include "module.h" +#include "modules/sql.h" +#define NO_CLIENT_LONG_LONG +#ifdef WIN32 +# include +#else +# include +#endif + +using namespace SQL; + +/** Non blocking threaded MySQL API, based loosely from InspIRCd's m_mysql.cpp + * + * This module spawns a single thread that is used to execute blocking MySQL queries. + * When a module requests a query to be executed it is added to a list for the thread + * (which never stops looping and sleeing) to pick up and execute, the result of which + * is inserted in to another queue to be picked up by the main thread. The main thread + * uses Pipe to become notified through the socket engine when there are results waiting + * to be sent back to the modules requesting the query + */ + +class MySQLService; + +/** A query request + */ +struct QueryRequest +{ + /* The connection to the database */ + MySQLService *service; + /* The interface to use once we have the result to send the data back */ + Interface *sqlinterface; + /* The actual query */ + Query query; + + QueryRequest(MySQLService *s, Interface *i, const Query &q) : service(s), sqlinterface(i), query(q) { } +}; + +/** A query result */ +struct QueryResult +{ + /* The interface to send the data back on */ + Interface *sqlinterface; + /* The result */ + Result result; + + QueryResult(Interface *i, Result &r) : sqlinterface(i), result(r) { } +}; + +/** A MySQL result + */ +class MySQLResult : public Result +{ + MYSQL_RES *res; + + public: + MySQLResult(unsigned int i, const Query &q, const Anope::string &fq, MYSQL_RES *r) : Result(i, q, fq), res(r) + { + if (!res) + return; + + unsigned num_fields = mysql_num_fields(res); + MYSQL_FIELD *fields = mysql_fetch_fields(res); + + /* It is not thread safe to log anything here using Log(this->owner) now :( */ + + if (!num_fields || !fields) + return; + + for (unsigned field_count = 0; field_count < num_fields; ++field_count) + columns.push_back(fields[field_count].name ? fields[field_count].name : ""); + + for (MYSQL_ROW row; (row = mysql_fetch_row(res));) + { + std::vector values; + + for (unsigned field_count = 0; field_count < num_fields; ++field_count) + { + const char *data = row[field_count]; + + Value v; + v.null = !data; + v.value = data ? data : ""; + values.push_back(v); + } + + this->values.push_back(values); + } + } + + MySQLResult(const Query &q, const Anope::string &fq, const Anope::string &err) : Result(0, q, fq, err), res(NULL) + { + } + + ~MySQLResult() + { + if (this->res) + mysql_free_result(this->res); + } +}; + +/** A MySQL connection, there can be multiple + */ +class MySQLService : public Provider +{ + std::map > active_schema, indexes; + + Anope::string database; + Anope::string server; + Anope::string user; + Anope::string password; + int port; + + MYSQL *sql; + + /** Escape a query. + * Note the mutex must be held! + */ + Anope::string Escape(const Anope::string &query); + + public: + /* Locked by the SQL thread when a query is pending on this database, + * prevents us from deleting a connection while a query is executing + * in the thread + */ + Mutex Lock; + + MySQLService(Module *o, const Anope::string &n, const Anope::string &d, const Anope::string &s, const Anope::string &u, const Anope::string &p, int po); + + ~MySQLService(); + + void Run(Interface *i, const Query &query) override; + + Result RunQuery(const Query &query) override; + + std::vector InitSchema(const Anope::string &prefix) override; + std::vector Replace(const Anope::string &table, const Query &, const std::set &) override; + std::vector CreateTable(const Anope::string &prefix, const Anope::string &table) override; + std::vector AlterTable(const Anope::string &, const Anope::string &table, const Anope::string &field, bool) override; + std::vector CreateIndex(const Anope::string &table, const Anope::string &field) override; + + Query BeginTransaction() override; + Query Commit() override; + + Serialize::ID GetID(const Anope::string &) override; + + Query GetTables(const Anope::string &prefix) override; + + void Connect(); + + bool CheckConnection(); + + Anope::string BuildQuery(const Query &q); +}; + +/** The SQL thread used to execute queries + */ +class DispatcherThread : public Thread, public Condition +{ + public: + DispatcherThread() : Thread() { } + + void Run() override; +}; + +class ModuleSQL; +static ModuleSQL *me; +class ModuleSQL : public Module + , public Pipe + , public EventHook +{ + /* SQL connections */ + std::map MySQLServices; + public: + /* Pending query requests */ + std::deque QueryRequests; + /* Pending finished requests with results */ + std::deque FinishedRequests; + /* The thread used to execute queries */ + DispatcherThread *DThread; + + ModuleSQL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + { + me = this; + + + DThread = new DispatcherThread(); + DThread->Start(); + } + + ~ModuleSQL() + { + for (std::map::iterator it = this->MySQLServices.begin(); it != this->MySQLServices.end(); ++it) + delete it->second; + MySQLServices.clear(); + + DThread->SetExitState(); + DThread->Wakeup(); + DThread->Join(); + delete DThread; + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + + for (std::map::iterator it = this->MySQLServices.begin(); it != this->MySQLServices.end();) + { + const Anope::string &cname = it->first; + MySQLService *s = it->second; + int i; + + ++it; + + for (i = 0; i < config->CountBlock("mysql"); ++i) + if (config->GetBlock("mysql", i)->Get("name", "mysql/main") == cname) + break; + + if (i == config->CountBlock("mysql")) + { + Log(LOG_NORMAL, "mysql") << "MySQL: Removing server connection " << cname; + + delete s; + this->MySQLServices.erase(cname); + } + } + + for (int i = 0; i < config->CountBlock("mysql"); ++i) + { + Configuration::Block *block = config->GetBlock("mysql", i); + const Anope::string &connname = block->Get("name", "mysql/main"); + + if (this->MySQLServices.find(connname) == this->MySQLServices.end()) + { + const Anope::string &database = block->Get("database", "anope"); + const Anope::string &server = block->Get("server", "127.0.0.1"); + const Anope::string &user = block->Get("username", "anope"); + const Anope::string &password = block->Get("password"); + int port = block->Get("port", "3306"); + + try + { + MySQLService *ss = new MySQLService(this, connname, database, server, user, password, port); + this->MySQLServices.insert(std::make_pair(connname, ss)); + + Log(LOG_NORMAL, "mysql") << "MySQL: Successfully connected to server " << connname << " (" << server << ")"; + } + catch (const SQL::Exception &ex) + { + Log(LOG_NORMAL, "mysql") << "MySQL: " << ex.GetReason(); + } + } + } + } + + void OnModuleUnload(User *, Module *m) override + { + this->DThread->Lock(); + + for (unsigned i = this->QueryRequests.size(); i > 0; --i) + { + QueryRequest &r = this->QueryRequests[i - 1]; + + if (r.sqlinterface && r.sqlinterface->owner == m) + { + if (i == 1) + { + r.service->Lock.Lock(); + r.service->Lock.Unlock(); + } + + this->QueryRequests.erase(this->QueryRequests.begin() + i - 1); + } + } + + this->DThread->Unlock(); + + this->OnNotify(); + } + + void OnNotify() override + { + this->DThread->Lock(); + std::deque finishedRequests = this->FinishedRequests; + this->FinishedRequests.clear(); + this->DThread->Unlock(); + + for (std::deque::const_iterator it = finishedRequests.begin(), it_end = finishedRequests.end(); it != it_end; ++it) + { + const QueryResult &qr = *it; + + if (!qr.sqlinterface) + throw SQL::Exception("NULL qr.sqlinterface in MySQLPipe::OnNotify() ?"); + + if (qr.result.GetError().empty()) + qr.sqlinterface->OnResult(qr.result); + else + qr.sqlinterface->OnError(qr.result); + } + } +}; + +MySQLService::MySQLService(Module *o, const Anope::string &n, const Anope::string &d, const Anope::string &s, const Anope::string &u, const Anope::string &p, int po) +: Provider(o, n), database(d), server(s), user(u), password(p), port(po), sql(NULL) +{ + Connect(); +} + +MySQLService::~MySQLService() +{ + me->DThread->Lock(); + this->Lock.Lock(); + mysql_close(this->sql); + this->sql = NULL; + + for (unsigned i = me->QueryRequests.size(); i > 0; --i) + { + QueryRequest &r = me->QueryRequests[i - 1]; + + if (r.service == this) + { + if (r.sqlinterface) + r.sqlinterface->OnError(Result(0, r.query, "SQL Interface is going away")); + me->QueryRequests.erase(me->QueryRequests.begin() + i - 1); + } + } + this->Lock.Unlock(); + me->DThread->Unlock(); +} + +void MySQLService::Run(Interface *i, const Query &query) +{ + me->DThread->Lock(); + me->QueryRequests.push_back(QueryRequest(this, i, query)); + me->DThread->Unlock(); + me->DThread->Wakeup(); +} + +Result MySQLService::RunQuery(const Query &query) +{ + this->Lock.Lock(); + + Anope::string real_query = this->BuildQuery(query); + + if (this->CheckConnection() && !mysql_real_query(this->sql, real_query.c_str(), real_query.length())) + { + MYSQL_RES *res = mysql_store_result(this->sql); + unsigned int id = mysql_insert_id(this->sql); + + /* because we enabled CLIENT_MULTI_RESULTS in our options + * a multiple statement or a procedure call can return + * multiple result sets. + * we must process them all before the next query. + */ + + while (!mysql_next_result(this->sql)) + mysql_free_result(mysql_store_result(this->sql)); + + this->Lock.Unlock(); + return MySQLResult(id, query, real_query, res); + } + else + { + Anope::string error = mysql_error(this->sql); + this->Lock.Unlock(); + return MySQLResult(query, real_query, error); + } +} + +std::vector MySQLService::InitSchema(const Anope::string &prefix) +{ + std::vector queries; + + Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + "id` (" + "`id` bigint(20) NOT NULL" + ") ENGINE=InnoDB"; + queries.push_back(t); + + t = "CREATE TABLE IF NOT EXISTS `" + prefix + "objects` (`id` bigint(20) NOT NULL PRIMARY KEY, `type` varchar(256)) ENGINE=InnoDB"; + queries.push_back(t); + + t = "CREATE TABLE IF NOT EXISTS `" + prefix + "edges` (" + "`id` bigint(20) NOT NULL," + "`field` varchar(64) NOT NULL," + "`other_id` bigint(20) NOT NULL," + "PRIMARY KEY (`id`, `field`)," + "KEY `other` (`other_id`)," + "CONSTRAINT `edges_id_fk` FOREIGN KEY (`id`) REFERENCES `" + prefix + "objects` (`id`)," + "CONSTRAINT `edges_other_id_fk` FOREIGN KEY (`other_id`) REFERENCES `" + prefix + "objects` (`id`)" + ") ENGINE=InnoDB"; + queries.push_back(t); + + return queries; +} + +std::vector MySQLService::Replace(const Anope::string &table, const Query &q, const std::set &keys) +{ + std::vector queries; + + Anope::string query_text = "INSERT INTO `" + table + "` ("; + for (const std::pair &p : q.parameters) + query_text += "`" + p.first + "`,"; + query_text.erase(query_text.length() - 1); + query_text += ") VALUES ("; + for (const std::pair &p : q.parameters) + query_text += "@" + p.first + "@,"; + query_text.erase(query_text.length() - 1); + query_text += ") ON DUPLICATE KEY UPDATE "; + for (const std::pair &p : q.parameters) + if (!keys.count(p.first)) + query_text += "`" + p.first + "` = VALUES(`" + p.first + "`),"; + query_text.erase(query_text.length() - 1); + + Query query(query_text); + query.parameters = q.parameters; + + queries.push_back(query); + + return queries; +} + +std::vector MySQLService::CreateTable(const Anope::string &prefix, const Anope::string &table) +{ + std::vector queries; + + if (active_schema.find(prefix + table) == active_schema.end()) + { + Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + table + "` (`id` bigint(20) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB"; + queries.push_back(t); + + t = "ALTER TABLE `" + prefix + table + "` " + "ADD CONSTRAINT `" + table + "_id_fk` FOREIGN KEY (`id`) REFERENCES `" + prefix + "objects` (`id`)"; + queries.push_back(t); + + active_schema[prefix + table]; + } + + return queries; +} + +std::vector MySQLService::AlterTable(const Anope::string &prefix, const Anope::string &table, const Anope::string &field, bool object) +{ + std::vector queries; + std::set &s = active_schema[prefix + table]; + + if (!s.count(field)) + { + Query column; + if (!object) + column = "ALTER TABLE `" + prefix + table + "` ADD COLUMN `" + field + "` TINYTEXT"; + else + column = "ALTER TABLE `" + prefix + table + "` " + "ADD COLUMN `" + field + "` bigint(20), " + "ADD CONSTRAINT `" + table + "_" + field + "_fk` FOREIGN KEY (`" + field + "`) REFERENCES `" + prefix + "objects` (`id`)"; + queries.push_back(column); + s.insert(field); + } + + return queries; +} + +std::vector MySQLService::CreateIndex(const Anope::string &table, const Anope::string &field) +{ + std::vector queries; + + if (indexes[table].count(field)) + return queries; + + Query t = "ALTER TABLE `" + table + "` ADD KEY `idx_" + field + "` (`" + field + "`(512))"; + queries.push_back(t); + + indexes[table].insert(field); + + return queries; +} + +Query MySQLService::BeginTransaction() +{ + return Query("START TRANSACTION WITH CONSISTENT SNAPSHOT"); +} + +Query MySQLService::Commit() +{ + return Query("COMMIT"); +} + +Serialize::ID MySQLService::GetID(const Anope::string &prefix) +{ + Query query("SELECT `id` FROM `" + prefix + "id` FOR UPDATE"); + Serialize::ID id; + + Result res = RunQuery(query); + if (res.Rows()) + { + id = convertTo(res.Get(0, "id")); + + Query update_query("UPDATE `" + prefix + "id` SET `id` = `id` + 1"); + RunQuery(update_query); + } + else + { + id = 0; + + Query insert_query("INSERT INTO `" + prefix + "id` (id) VALUES(@id@)"); + insert_query.SetValue("id", 1); + RunQuery(insert_query); + } + + return id; +} + +Query MySQLService::GetTables(const Anope::string &prefix) +{ + return Query("SHOW TABLES LIKE '" + prefix + "%';"); +} + +void MySQLService::Connect() +{ + this->sql = mysql_init(this->sql); + + const unsigned int timeout = 1; + mysql_options(this->sql, MYSQL_OPT_CONNECT_TIMEOUT, reinterpret_cast(&timeout)); + + bool connect = mysql_real_connect(this->sql, this->server.c_str(), this->user.c_str(), this->password.c_str(), this->database.c_str(), this->port, NULL, CLIENT_MULTI_RESULTS); + + if (!connect) + throw SQL::Exception("Unable to connect to MySQL service " + this->name + ": " + mysql_error(this->sql)); + + Log(LOG_DEBUG) << "Successfully connected to MySQL service " << this->name << " at " << this->server << ":" << this->port; +} + + +bool MySQLService::CheckConnection() +{ + if (!this->sql || mysql_ping(this->sql)) + { + try + { + this->Connect(); + } + catch (const SQL::Exception &) + { + return false; + } + } + + return true; +} + +Anope::string MySQLService::Escape(const Anope::string &query) +{ + std::vector buffer(query.length() * 2 + 1); + mysql_real_escape_string(this->sql, &buffer[0], query.c_str(), query.length()); + return &buffer[0]; +} + +Anope::string MySQLService::BuildQuery(const Query &q) +{ + Anope::string real_query = q.query; + + for (std::map::const_iterator it = q.parameters.begin(), it_end = q.parameters.end(); it != it_end; ++it) + { + const QueryData& qd = it->second; + Anope::string replacement; + + if (qd.null) + replacement = "NULL"; + else if (!qd.escape) + replacement = qd.data; + else + replacement = "'" + this->Escape(qd.data) + "'"; + + real_query = real_query.replace_all_cs("@" + it->first + "@", replacement); + } + + return real_query; +} + +void DispatcherThread::Run() +{ + this->Lock(); + + while (!this->GetExitState()) + { + if (!me->QueryRequests.empty()) + { + QueryRequest &r = me->QueryRequests.front(); + this->Unlock(); + + Result sresult = r.service->RunQuery(r.query); + + this->Lock(); + if (!me->QueryRequests.empty() && me->QueryRequests.front().query == r.query) + { + if (r.sqlinterface) + me->FinishedRequests.push_back(QueryResult(r.sqlinterface, sresult)); + me->QueryRequests.pop_front(); + } + } + else + { + if (!me->FinishedRequests.empty()) + me->Notify(); + this->Wait(); + } + } + + this->Unlock(); +} + +MODULE_INIT(ModuleSQL) + diff --git a/modules/extra/sasl_dh-aes.cpp b/modules/extra/sasl_dh-aes.cpp new file mode 100644 index 000000000..dd06d118b --- /dev/null +++ b/modules/extra/sasl_dh-aes.cpp @@ -0,0 +1,184 @@ +/* RequiredLibraries: ssl,crypto */ +/* RequiredWindowsLibraries: ssleay32,libeay32 */ + +#include "module.h" +#include "modules/sasl.h" + +#include +#include +#include + +using namespace SASL; + +class DHAES : public Mechanism +{ + void Err(Session* sess, BIGNUM* key = NULL) + { + if (key) + BN_free(key); + + sasl->Fail(sess); + delete sess; + } + + public: + struct DHAESSession : SASL::Session + { + DH* dh; + DHAESSession(Mechanism *m, const Anope::string &u, DH* dh_params) : SASL::Session(m, u) + { + if (!(dh = DH_new())) + return; + + dh->g = BN_dup(dh_params->g); + dh->p = BN_dup(dh_params->p); + + if (!DH_generate_key(dh)) + { + DH_free(dh); + dh = NULL; + } + } + + ~DHAESSession() + { + if (dh) + DH_free(dh); + } + }; + + DH* dh_params; + const size_t keysize; + SASL::Session* CreateSession(const Anope::string &uid) override + { + return new DHAESSession(this, uid, dh_params); + } + + DHAES(Module *o) : Mechanism(o, "DH-AES"), keysize(256 / 8) + { + if (!(dh_params = DH_new())) + throw ModuleException("DH_new() failed!"); + + if (!DH_generate_parameters_ex(dh_params, keysize * 8, 5, NULL)) + { + DH_free(dh_params); + throw ModuleException("Could not generate DH-params"); + } + } + + ~DHAES() + { + DH_free(dh_params); + } + + void ProcessMessage(SASL::Session *session, const SASL::Message &m) override + { + DHAESSession *sess = anope_dynamic_static_cast(session); + + if (!sess->dh) + { + sasl->SendMessage(sess, "D", "A"); + delete sess; + return; + } + + if (m.type == "S") + { + // Format: [ss]

[ss][ss] + // Where ss is a unsigned short with the size of the key + const BIGNUM* dhval[] = { sess->dh->p, sess->dh->g, sess->dh->pub_key }; + + // Find the size of our buffer - initialized at 6 because of string size data + size_t size = 6; + for (size_t i = 0; i < 3; i++) + size += BN_num_bytes(dhval[i]); + + // Fill in the DH data + std::vector buffer(size); + for (size_t i = 0, pos = 0; i < 3; i++) + { + *reinterpret_cast(&buffer[pos]) = htons(BN_num_bytes(dhval[i])); + pos += 2; + BN_bn2bin(dhval[i], &buffer[pos]); + pos += BN_num_bytes(dhval[i]); + } + + Anope::string encoded; + Anope::B64Encode(Anope::string(buffer.begin(), buffer.end()), encoded); + sasl->SendMessage(sess, "C", encoded); + } + else if (m.type == "C") + { + // Make sure we have some data - actual size check is done later + if (m.data.length() < 10) + return Err(sess); + + // Format: [ss][ss][ss] + // = \0\0 + + Anope::string decoded; + Anope::B64Decode(m.data, decoded); + + // Make sure we have an IV and at least one encrypted block + if ((decoded.length() < keysize + 2 + (AES_BLOCK_SIZE * 2)) || ((decoded.length() - keysize - 2) % AES_BLOCK_SIZE)) + return Err(sess); + + const unsigned char* data = reinterpret_cast(decoded.data()); + + // Control the size of the key + if (ntohs(*reinterpret_cast(&data[0])) != keysize) + return Err(sess); + + // Convert pubkey from binary + size_t pos = 2; + BIGNUM* pubkey = BN_bin2bn(&data[pos], keysize, NULL); + if (!pubkey) + return Err(sess); + + // Find shared key + std::vector secretkey(keysize); + if (DH_compute_key(&secretkey[0], pubkey, sess->dh) != static_cast(keysize)) + return Err(sess, pubkey); + + // Set decryption key + AES_KEY AESKey; + AES_set_decrypt_key(&secretkey[0], keysize * 8, &AESKey); + + // Fetch IV + pos += keysize; + std::vector IV(data + pos, data + pos + AES_BLOCK_SIZE); + + // Find encrypted blocks, and decrypt + pos += AES_BLOCK_SIZE; + size_t size = decoded.length() - pos; + std::vector decrypted(size + 2, 0); + AES_cbc_encrypt(&data[pos], reinterpret_cast(&decrypted[0]), size, &AESKey, &IV[0], AES_DECRYPT); + + std::string username = &decrypted[0]; + std::string password = &decrypted[username.length() + 1]; + + if (username.empty() || password.empty() || !IRCD->IsNickValid(username) || password.find_first_of("\r\n") != Anope::string::npos) + return Err(sess, pubkey); + + SASL::IdentifyRequest* req = new SASL::IdentifyRequest(this->owner, m.source, username, password); + Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req); + req->Dispatch(); + + BN_free(pubkey); + } + } +}; + + +class ModuleSASLDHAES : public Module +{ + DHAES dhaes; + + public: + ModuleSASLDHAES(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) + , dhaes(this) + { + } +}; + +MODULE_INIT(ModuleSASLDHAES) diff --git a/modules/extra/sasl_dh-blowfish.cpp b/modules/extra/sasl_dh-blowfish.cpp new file mode 100644 index 000000000..f6e4b27b3 --- /dev/null +++ b/modules/extra/sasl_dh-blowfish.cpp @@ -0,0 +1,194 @@ +/* RequiredLibraries: ssl,crypto */ +/* RequiredWindowsLibraries: ssleay32,libeay32 */ + +#include "module.h" +#include "modules/sasl.h" + +#include +#include +#include + +using namespace SASL; + +class DHBS : public Mechanism +{ + void Err(Session* sess, BIGNUM* key = NULL) + { + if (key) + BN_free(key); + + sasl->Fail(sess); + delete sess; + } + + public: + struct DHBSSession : SASL::Session + { + DH* dh; + DHBSSession(Mechanism *m, const Anope::string &u, DH* dh_params) : SASL::Session(m, u) + { + if (!(dh = DH_new())) + return; + + dh->g = BN_dup(dh_params->g); + dh->p = BN_dup(dh_params->p); + + if (!DH_generate_key(dh)) + { + DH_free(dh); + dh = NULL; + } + } + + ~DHBSSession() + { + if (dh) + DH_free(dh); + } + }; + + DH* dh_params; + const size_t keysize; + SASL::Session* CreateSession(const Anope::string &uid) override + { + return new DHBSSession(this, uid, dh_params); + } + + DHBS(Module *o) : Mechanism(o, "DH-BLOWFISH"), keysize(256 / 8) + { + if (!(dh_params = DH_new())) + throw ModuleException("DH_new() failed!"); + + if (!DH_generate_parameters_ex(dh_params, keysize * 8, 5, NULL)) + { + DH_free(dh_params); + throw ModuleException("Could not generate DH-params"); + } + } + + ~DHBS() + { + DH_free(dh_params); + } + + void ProcessMessage(SASL::Session *session, const SASL::Message &m) override + { + DHBSSession *sess = anope_dynamic_static_cast(session); + + if (!sess->dh) + { + sasl->SendMessage(sess, "D", "A"); + delete sess; + return; + } + + if (m.type == "S") + { + // Format: [ss]

[ss][ss] + // Where ss is a unsigned short with the size of the key + const BIGNUM* dhval[] = { sess->dh->p, sess->dh->g, sess->dh->pub_key }; + + // Find the size of our buffer - initialized at 6 because of string size data + size_t size = 6; + for (size_t i = 0; i < 3; i++) + size += BN_num_bytes(dhval[i]); + + // Fill in the DH data + std::vector buffer(size); + for (size_t i = 0, pos = 0; i < 3; i++) + { + *reinterpret_cast(&buffer[pos]) = htons(BN_num_bytes(dhval[i])); + pos += 2; + BN_bn2bin(dhval[i], &buffer[pos]); + pos += BN_num_bytes(dhval[i]); + } + + Anope::string encoded; + Anope::B64Encode(Anope::string(buffer.begin(), buffer.end()), encoded); + sasl->SendMessage(sess, "C", encoded); + } + else if (m.type == "C") + { + // Make sure we have some data - actual size check is done later + if (m.data.length() < 10) + return Err(sess); + + // Format: [ss]<\0> + + Anope::string decoded; + Anope::B64Decode(m.data, decoded); + + // As we rely on the client giving us a null terminator at the right place, + // let's add one extra in case the client tries to crash us + const size_t decodedlen = decoded.length(); + decoded.push_back('\0'); + + // Make sure we have enough data for at least the key, a one letter username, and a block of data + if (decodedlen < keysize + 2 + 2 + 8) + return Err(sess); + + const unsigned char* data = reinterpret_cast(decoded.data()); + + // Control the size of the key + if (ntohs(*reinterpret_cast(&data[0])) != keysize) + return Err(sess); + + // Convert pubkey from binary + size_t pos = 2; + BIGNUM* pubkey = BN_bin2bn(&data[pos], keysize, NULL); + if (!pubkey) + return Err(sess); + + // Find shared key + std::vector secretkey(DH_size(sess->dh) + 1, 0); + if (DH_compute_key(&secretkey[0], pubkey, sess->dh) != static_cast(keysize)) + return Err(sess, pubkey); + + // Set decryption key + BF_KEY BFKey; + BF_set_key(&BFKey, keysize, &secretkey[0]); + + pos += keysize; + const Anope::string username = reinterpret_cast(&data[pos]); + // Check that the username is valid, and that we have at least one block of data + // 2 + 1 + 8 = uint16_t size for keylen, \0 for username, 8 for one block of data + if (username.empty() || username.length() + keysize + 2 + 1 + 8 > decodedlen || !IRCD->IsNickValid(username)) + return Err(sess, pubkey); + + pos += username.length() + 1; + size_t size = decodedlen - pos; + + // Blowfish data blocks are 64 bits wide - valid format? + if (size % 8) + return Err(sess, pubkey); + + std::vector decrypted(size + 1, 0); + for (size_t i = 0; i < size; i += 8) + BF_ecb_encrypt(&data[pos + i], reinterpret_cast(&decrypted[i]), &BFKey, BF_DECRYPT); + + std::string password = &decrypted[0]; + if (password.empty() || password.find_first_of("\r\n") != Anope::string::npos) + return Err(sess, pubkey); + + SASL::IdentifyRequest* req = new SASL::IdentifyRequest(this->owner, m.source, username, password); + Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req); + req->Dispatch(); + + BN_free(pubkey); + } + } +}; + + +class ModuleSASLDHBS : public Module +{ + DHBS dhbs; + + public: + ModuleSASLDHBS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) + , dhbs(this) + { + } +}; + +MODULE_INIT(ModuleSASLDHBS) diff --git a/modules/extra/sql_authentication.cpp b/modules/extra/sql_authentication.cpp new file mode 100644 index 000000000..1c2dbe729 --- /dev/null +++ b/modules/extra/sql_authentication.cpp @@ -0,0 +1,143 @@ +#include "module.h" +#include "modules/sql.h" +#include "modules/nickserv.h" + +static Module *me; + +class SQLAuthenticationResult : public SQL::Interface +{ + Reference user; + IdentifyRequest *req; + + public: + SQLAuthenticationResult(User *u, IdentifyRequest *r) : SQL::Interface(me), user(u), req(r) + { + req->Hold(me); + } + + ~SQLAuthenticationResult() + { + req->Release(me); + } + + void OnResult(const SQL::Result &r) override + { + if (r.Rows() == 0) + { + Log(LOG_DEBUG) << "m_sql_authentication: Unsuccessful authentication for " << req->GetAccount(); + delete this; + return; + } + + Log(LOG_DEBUG) << "m_sql_authentication: Successful authentication for " << req->GetAccount(); + + Anope::string email; + try + { + email = r.Get(0, "email"); + } + catch (const SQL::Exception &) { } + + NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); + ServiceBot *NickServ = Config->GetClient("NickServ"); + if (na == NULL) + { + na = new NickServ::Nick(req->GetAccount(), new NickServ::Account(req->GetAccount())); + NickServ::Event::OnNickRegister(&NickServ::Event::NickRegister::OnNickRegister, user, na, ""); + if (user && NickServ) + user->SendMessage(NickServ, _("Your account \002%s\002 has been successfully created."), na->GetNick().c_str()); + } + + if (!email.empty() && email != na->GetAccount()->GetEmail()) + { + na->GetAccount()->GetEmail() = email; + if (user && NickServ) + user->SendMessage(NickServ, _("Your email has been updated to \002%s\002."), email.c_str()); + } + + req->Success(me); + delete this; + } + + void OnError(const SQL::Result &r) override + { + Log(this->owner) << "m_sql_authentication: Error executing query " << r.GetQuery().query << ": " << r.GetError(); + delete this; + } +}; + +class ModuleSQLAuthentication : public Module + , public EventHook + , public EventHook +{ + Anope::string engine; + Anope::string query; + Anope::string disable_reason, disable_email_reason; + + ServiceReference SQL; + + public: + ModuleSQLAuthentication(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + { + me = this; + + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + this->engine = config->Get("engine"); + this->query = config->Get("query"); + this->disable_reason = config->Get("disable_reason"); + this->disable_email_reason = config->Get("disable_email_reason"); + + this->SQL = ServiceReference("SQL::Provider", this->engine); + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override + { + if (!this->disable_reason.empty() && (command->name == "nickserv/register" || command->name == "nickserv/group")) + { + source.Reply(this->disable_reason); + return EVENT_STOP; + } + + if (!this->disable_email_reason.empty() && command->name == "nickserv/set/email") + { + source.Reply(this->disable_email_reason); + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + void OnCheckAuthentication(User *u, IdentifyRequest *req) override + { + if (!this->SQL) + { + Log(this) << "Unable to find SQL engine"; + return; + } + + SQL::Query q(this->query); + q.SetValue("a", req->GetAccount()); + q.SetValue("p", req->GetPassword()); + if (u) + { + q.SetValue("n", u->nick); + q.SetValue("i", u->ip.addr()); + } + else + { + q.SetValue("n", ""); + q.SetValue("i", ""); + } + + + this->SQL->Run(new SQLAuthenticationResult(u, req), q); + + Log(LOG_DEBUG) << "m_sql_authentication: Checking authentication for " << req->GetAccount(); + } +}; + +MODULE_INIT(ModuleSQLAuthentication) diff --git a/modules/extra/sql_log.cpp b/modules/extra/sql_log.cpp new file mode 100644 index 000000000..6922d168e --- /dev/null +++ b/modules/extra/sql_log.cpp @@ -0,0 +1,110 @@ +/* + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + */ + +#include "module.h" +#include "modules/sql.h" + +class SQLLog : public Module + , public EventHook +{ + std::set inited; + Anope::string table; + + public: + SQLLog(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) + { + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + this->table = config->Get("table", "logs"); + } + + void OnLogMessage(LogInfo *li, const Log *l, const Anope::string &msg) override + { + Anope::string ref_name; + ServiceReference SQL; + + for (unsigned i = 0; i < li->targets.size(); ++i) + { + const Anope::string &target = li->targets[i]; + size_t sz = target.find("sql_log:"); + if (!sz) + { + ref_name = target.substr(8); + SQL = ServiceReference("SQL::Provider", ref_name); + break; + } + } + + if (!SQL) + return; + + if (!inited.count(ref_name)) + { + inited.insert(ref_name); + + SQL::Query create("CREATE TABLE IF NOT EXISTS `" + table + "` (" + "`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," + "`type` varchar(64) NOT NULL," + "`user` varchar(64) NOT NULL," + "`acc` varchar(64) NOT NULL," + "`command` varchar(64) NOT NULL," + "`channel` varchar(64) NOT NULL," + "`msg` text NOT NULL" + ")"); + + SQL->Run(NULL, create); + } + + SQL::Query insert("INSERT INTO `" + table + "` (`type`,`user`,`acc`,`command`,`channel`,`msg`)" + "VALUES (@type@, @user@, @acc@, @command@, @channel@, @msg@)"); + + switch (l->type) + { + case LOG_ADMIN: + insert.SetValue("type", "ADMIN"); + break; + case LOG_OVERRIDE: + insert.SetValue("type", "OVERRIDE"); + break; + case LOG_COMMAND: + insert.SetValue("type", "COMMAND"); + break; + case LOG_SERVER: + insert.SetValue("type", "SERVER"); + break; + case LOG_CHANNEL: + insert.SetValue("type", "CHANNEL"); + break; + case LOG_USER: + insert.SetValue("type", "USER"); + break; + case LOG_MODULE: + insert.SetValue("type", "MODULE"); + break; + case LOG_NORMAL: + insert.SetValue("type", "NORMAL"); + break; + default: + return; + } + + insert.SetValue("user", l->u ? l->u->nick : ""); + insert.SetValue("acc", l->nc ? l->nc->GetDisplay() : ""); + insert.SetValue("command", l->c ? l->c->name : ""); + insert.SetValue("channel", l->ci ? l->ci->GetName() : ""); + insert.SetValue("msg", msg); + + SQL->Run(NULL, insert); + } +}; + +MODULE_INIT(SQLLog) diff --git a/modules/extra/sql_oper.cpp b/modules/extra/sql_oper.cpp new file mode 100644 index 000000000..6bddbf0eb --- /dev/null +++ b/modules/extra/sql_oper.cpp @@ -0,0 +1,148 @@ +#include "module.h" +#include "modules/sql.h" + +struct SQLOper : Oper +{ + SQLOper(const Anope::string &n, OperType *o) : Oper(n, o) { } +}; + +class SQLOperResult : public SQL::Interface +{ + Reference user; + + struct SQLOperResultDeleter + { + SQLOperResult *res; + SQLOperResultDeleter(SQLOperResult *r) : res(r) { } + ~SQLOperResultDeleter() { delete res; } + }; + + public: + SQLOperResult(Module *m, User *u) : SQL::Interface(m), user(u) { } + + void OnResult(const SQL::Result &r) override + { + SQLOperResultDeleter d(this); + + if (!user || !user->Account() || r.Rows() == 0) + return; + + Anope::string opertype; + try + { + opertype = r.Get(0, "opertype"); + } + catch (const SQL::Exception &) + { + return; + } + + Log(LOG_DEBUG) << "m_sql_oper: Got result for " << user->nick << ", opertype " << opertype; + + Anope::string modes; + try + { + modes = r.Get(0, "modes"); + } + catch (const SQL::Exception &) { } + + ServiceBot *OperServ = Config->GetClient("OperServ"); + if (opertype.empty()) + { + if (user->Account() && user->Account()->o && dynamic_cast(user->Account()->o)) + { + delete user->Account()->o; + user->Account()->o = NULL; + + Log(this->owner) << "m_sql_oper: Removed services operator from " << user->nick << " (" << user->Account()->GetDisplay() << ")"; + user->RemoveMode(OperServ, "OPER"); // Probably not set, just incase + } + return; + } + + OperType *ot = OperType::Find(opertype); + if (ot == NULL) + { + Log(this->owner) << "m_sql_oper: Oper " << user->nick << " has type " << opertype << ", but this opertype does not exist?"; + return; + } + + if (!user->Account()->o || user->Account()->o->ot != ot) + { + Log(this->owner) << "m_sql_oper: Tieing oper " << user->nick << " to type " << opertype; + user->Account()->o = new SQLOper(user->Account()->GetDisplay(), ot); + } + + if (!user->HasMode("OPER")) + { + IRCD->SendOper(user); + + if (!modes.empty()) + user->SetModes(OperServ, "%s", modes.c_str()); + } + } + + void OnError(const SQL::Result &r) override + { + SQLOperResultDeleter d(this); + Log(this->owner) << "m_sql_oper: Error executing query " << r.GetQuery().query << ": " << r.GetError(); + } +}; + +class ModuleSQLOper : public Module + , public EventHook +{ + Anope::string engine; + Anope::string query; + + ServiceReference SQL; + + public: + ModuleSQLOper(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), + EventHook() + { + } + + ~ModuleSQLOper() + { + for (nickcore_map::const_iterator it = NickServ::AccountList->begin(), it_end = NickServ::AccountList->end(); it != it_end; ++it) + { + NickServ::Account *nc = it->second; + + if (nc->o && dynamic_cast(nc->o)) + { + delete nc->o; + nc->o = NULL; + } + } + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + + this->engine = config->Get("engine"); + this->query = config->Get("query"); + + this->SQL = ServiceReference("SQL::Provider", this->engine); + } + + void OnNickIdentify(User *u) override + { + if (!this->SQL) + { + Log() << "Unable to find SQL engine"; + return; + } + + SQL::Query q(this->query); + q.SetValue("a", u->Account()->GetDisplay()); + q.SetValue("i", u->ip.addr()); + + this->SQL->Run(new SQLOperResult(this, u), q); + + Log(LOG_DEBUG) << "m_sql_oper: Checking authentication for " << u->Account()->GetDisplay(); + } +}; + +MODULE_INIT(ModuleSQLOper) diff --git a/modules/extra/sqlite.cpp b/modules/extra/sqlite.cpp new file mode 100644 index 000000000..9370a0d77 --- /dev/null +++ b/modules/extra/sqlite.cpp @@ -0,0 +1,380 @@ +/* RequiredLibraries: sqlite3 */ +/* RequiredWindowsLibraries: sqlite3 */ + +#include "module.h" +#include "modules/sql.h" +#include + +using namespace SQL; + +/* SQLite3 API, based from InspiRCd */ + +/** A SQLite result + */ +class SQLiteResult : public Result +{ + public: + SQLiteResult(sqlite3 *sql, unsigned int id, const Query &q, const Anope::string &fq, sqlite3_stmt *stmt) : Result(id, q, fq) + { + int cols = sqlite3_column_count(stmt); + for (int i = 0; i < cols; ++i) + this->columns.push_back(sqlite3_column_name(stmt, i)); + + int err; + while ((err = sqlite3_step(stmt)) == SQLITE_ROW) + { + std::vector values; + + for (int i = 0; i < cols; ++i) + { + const char *data = reinterpret_cast(sqlite3_column_text(stmt, i)); + + Value v; + v.null = !data; + v.value = data ? data : ""; + values.push_back(v); + } + + this->values.push_back(values); + } + + if (err != SQLITE_DONE) + { + error = sqlite3_errmsg(sql); + } + } + + SQLiteResult(const Query &q, const Anope::string &fq, const Anope::string &err) : Result(0, q, fq, err) + { + } +}; + +/** A SQLite database, there can be multiple + */ +class SQLiteService : public Provider +{ + std::map > active_schema, indexes; + + Anope::string database; + + sqlite3 *sql; + + Anope::string Escape(const Anope::string &query); + + public: + SQLiteService(Module *o, const Anope::string &n, const Anope::string &d); + + ~SQLiteService(); + + void Run(Interface *i, const Query &query) override; + + Result RunQuery(const Query &query); + + std::vector InitSchema(const Anope::string &prefix) override; + std::vector Replace(const Anope::string &table, const Query &, const std::set &) override; + std::vector CreateTable(const Anope::string &, const Anope::string &table) override; + std::vector AlterTable(const Anope::string &, const Anope::string &table, const Anope::string &field, bool) override; + std::vector CreateIndex(const Anope::string &table, const Anope::string &field) override; + + Query BeginTransaction() override; + Query Commit() override; + + Serialize::ID GetID(const Anope::string &) override; + + Query GetTables(const Anope::string &prefix); + + Anope::string BuildQuery(const Query &q); +}; + +class ModuleSQLite : public Module +{ + /* SQL connections */ + std::map SQLiteServices; + + public: + ModuleSQLite(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + { + } + + ~ModuleSQLite() + { + for (std::map::iterator it = this->SQLiteServices.begin(); it != this->SQLiteServices.end(); ++it) + delete it->second; + SQLiteServices.clear(); + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + + for (std::map::iterator it = this->SQLiteServices.begin(); it != this->SQLiteServices.end();) + { + const Anope::string &cname = it->first; + SQLiteService *s = it->second; + int i, num; + ++it; + + for (i = 0, num = config->CountBlock("sqlite"); i < num; ++i) + if (config->GetBlock("sqlite", i)->Get("name", "sqlite/main") == cname) + break; + + if (i == num) + { + Log(LOG_NORMAL, "sqlite") << "SQLite: Removing server connection " << cname; + + delete s; + this->SQLiteServices.erase(cname); + } + } + + for (int i = 0; i < config->CountBlock("sqlite"); ++i) + { + Configuration::Block *block = config->GetBlock("sqlite", i); + Anope::string connname = block->Get("name", "sqlite/main"); + + if (this->SQLiteServices.find(connname) == this->SQLiteServices.end()) + { + Anope::string database = Anope::DataDir + "/" + block->Get("database", "anope"); + + try + { + SQLiteService *ss = new SQLiteService(this, connname, database); + this->SQLiteServices[connname] = ss; + + Log(LOG_NORMAL, "sqlite") << "SQLite: Successfully added database " << database; + } + catch (const SQL::Exception &ex) + { + Log(LOG_NORMAL, "sqlite") << "SQLite: " << ex.GetReason(); + } + } + } + } +}; + +SQLiteService::SQLiteService(Module *o, const Anope::string &n, const Anope::string &d) +: Provider(o, n), database(d), sql(NULL) +{ + int db = sqlite3_open_v2(database.c_str(), &this->sql, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); + if (db != SQLITE_OK) + throw SQL::Exception("Unable to open SQLite database " + database + ": " + sqlite3_errmsg(this->sql)); +} + +SQLiteService::~SQLiteService() +{ + sqlite3_interrupt(this->sql); + sqlite3_close(this->sql); +} + +void SQLiteService::Run(Interface *i, const Query &query) +{ + Result res = this->RunQuery(query); + if (!res.GetError().empty()) + i->OnError(res); + else + i->OnResult(res); +} + +Result SQLiteService::RunQuery(const Query &query) +{ + Anope::string real_query = this->BuildQuery(query); + sqlite3_stmt *stmt; + int err = sqlite3_prepare_v2(this->sql, real_query.c_str(), real_query.length(), &stmt, NULL); + if (err != SQLITE_OK) + { + return SQLiteResult(query, real_query, sqlite3_errmsg(this->sql)); + } + + int id = sqlite3_last_insert_rowid(this->sql); + SQLiteResult result(this->sql, id, query, real_query, stmt); + + sqlite3_finalize(stmt); + + return result; +} + +std::vector SQLiteService::InitSchema(const Anope::string &prefix) +{ + std::vector queries; + + Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + "id` (" + "`id`" + ")"; + queries.push_back(t); + + t = "CREATE TABLE IF NOT EXISTS `" + prefix + "objects` (`id` PRIMARY KEY, `type`)"; + queries.push_back(t); + + t = "CREATE TABLE IF NOT EXISTS `" + prefix + "edges` (" + "`id`," + "`field`," + "`other_id`," + "PRIMARY KEY (`id`, `field`)" + ")"; + queries.push_back(t); + + t = "CREATE INDEX IF NOT EXISTS idx_edge ON `" + prefix + "edges` (other_id)"; + queries.push_back(t); + + return queries; +} + +std::vector SQLiteService::Replace(const Anope::string &table, const Query &q, const std::set &keys) +{ + std::vector queries; + + Anope::string query_text = "INSERT OR IGNORE INTO `" + table + "` ("; + for (const std::pair &p : q.parameters) + query_text += "`" + p.first + "`,"; + query_text.erase(query_text.length() - 1); + query_text += ") VALUES ("; + for (const std::pair &p : q.parameters) + query_text += "@" + p.first + "@,"; + query_text.erase(query_text.length() - 1); + query_text += ")"; + + Query query(query_text); + query.parameters = q.parameters; + queries.push_back(query); + + query_text = "UPDATE `" + table + "` SET "; + for (const std::pair &p : q.parameters) + if (!keys.count(p.first)) + query_text += "`" + p.first + "` = @" + p.first + "@,"; + query_text.erase(query_text.length() - 1); + unsigned int i = 0; + for (const Anope::string &key : keys) + { + if (!i++) + query_text += " WHERE "; + else + query_text += " AND "; + query_text += "`" + key + "` = @" + key + "@"; + } + + query = query_text; + query.parameters = q.parameters; + queries.push_back(query); + + return queries; +} + +std::vector SQLiteService::CreateTable(const Anope::string &prefix, const Anope::string &table) +{ + std::vector queries; + + if (active_schema.find(prefix + table) == active_schema.end()) + { + Query t = "CREATE TABLE IF NOT EXISTS `" + prefix + table + "` (`id` bigint(20) NOT NULL, PRIMARY KEY (`id`))"; + queries.push_back(t); + + active_schema[prefix + table]; + } + + return queries; +} + +std::vector SQLiteService::AlterTable(const Anope::string &prefix, const Anope::string &table, const Anope::string &field, bool) +{ + std::vector queries; + std::set &s = active_schema[prefix + table]; + + if (!s.count(field)) + { + Query t = "ALTER TABLE `" + prefix + table + "` ADD `" + field + "` COLLATE NOCASE"; + queries.push_back(t); + s.insert(field); + } + + return queries; +} + +std::vector SQLiteService::CreateIndex(const Anope::string &table, const Anope::string &field) +{ + std::vector queries; + + if (indexes[table].count(field)) + return queries; + + Query t = "CREATE INDEX IF NOT EXISTS idx_" + field + " ON `" + table + "` (" + field + ")"; + queries.push_back(t); + + indexes[table].insert(field); + + return queries; +} + +Query SQLiteService::BeginTransaction() +{ + return Query("BEGIN TRANSACTION"); +} + +Query SQLiteService::Commit() +{ + return Query("COMMIT"); +} + +Serialize::ID SQLiteService::GetID(const Anope::string &prefix) +{ + /* must be in a deferred or reserved transaction here for atomic row update */ + + Query query("SELECT `id` FROM `" + prefix + "id`"); + Serialize::ID id; + + Result res = RunQuery(query); + if (res.Rows()) + { + id = convertTo(res.Get(0, "id")); + + Query update_query("UPDATE `" + prefix + "id` SET `id` = `id` + 1"); + RunQuery(update_query); + } + else + { + id = 0; + + Query insert_query("INSERT INTO `" + prefix + "id` (id) VALUES(@id@)"); + insert_query.SetValue("id", 1); + RunQuery(insert_query); + } + + return id; +} + +Query SQLiteService::GetTables(const Anope::string &prefix) +{ + return Query("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '" + prefix + "%';"); +} + +Anope::string SQLiteService::Escape(const Anope::string &query) +{ + char *e = sqlite3_mprintf("%q", query.c_str()); + Anope::string buffer = e; + sqlite3_free(e); + return buffer; +} + +Anope::string SQLiteService::BuildQuery(const Query &q) +{ + Anope::string real_query = q.query; + + for (std::map::const_iterator it = q.parameters.begin(), it_end = q.parameters.end(); it != it_end; ++it) + { + const QueryData& qd = it->second; + Anope::string replacement; + + if (qd.null) + replacement = "NULL"; + else if (!qd.escape) + replacement = qd.data; + else + replacement = "'" + this->Escape(qd.data) + "'"; + + real_query = real_query.replace_all_cs("@" + it->first + "@", replacement); + } + + return real_query; +} + +MODULE_INIT(ModuleSQLite) + diff --git a/modules/extra/ssl_gnutls.cpp b/modules/extra/ssl_gnutls.cpp new file mode 100644 index 000000000..93a56f03a --- /dev/null +++ b/modules/extra/ssl_gnutls.cpp @@ -0,0 +1,658 @@ +/* + * (C) 2014 Attila Molnar + * (C) 2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +/* RequiredLibraries: gnutls */ +/* RequiredWindowsLibraries: libgnutls-28 */ + +#include "module.h" +#include "modules/ssl.h" + +#include +#include +#include + +class GnuTLSModule; +static GnuTLSModule *me; + +namespace GnuTLS { class X509CertCredentials; } + +class MySSLService : public SSLService +{ + public: + MySSLService(Module *o, const Anope::string &n); + + /** Initialize a socket to use SSL + * @param s The socket + */ + void Init(Socket *s) override; +}; + +class SSLSocketIO : public SocketIO +{ + public: + gnutls_session_t sess; + GnuTLS::X509CertCredentials* mycreds; + + /** Constructor + */ + SSLSocketIO(); + + /** Really receive something from the buffer + * @param s The socket + * @param buf The buf to read to + * @param sz How much to read + * @return Number of bytes received + */ + int Recv(Socket *s, char *buf, size_t sz) override; + + /** Write something to the socket + * @param s The socket + * @param buf The data to write + * @param size The length of the data + */ + int Send(Socket *s, const char *buf, size_t sz) override; + + /** Accept a connection from a socket + * @param s The socket + * @return The new socket + */ + ClientSocket *Accept(ListenSocket *s) override; + + /** Finished accepting a connection from a socket + * @param s The socket + * @return SF_ACCEPTED if accepted, SF_ACCEPTING if still in process, SF_DEAD on error + */ + SocketFlag FinishAccept(ClientSocket *cs) override; + + /** Connect the socket + * @param s THe socket + * @param target IP to connect to + * @param port to connect to + */ + void Connect(ConnectionSocket *s, const Anope::string &target, int port) override; + + /** Called to potentially finish a pending connection + * @param s The socket + * @return SF_CONNECTED on success, SF_CONNECTING if still pending, and SF_DEAD on error. + */ + SocketFlag FinishConnect(ConnectionSocket *s) override; + + /** Called when the socket is destructing + */ + void Destroy() override; +}; + +namespace GnuTLS +{ + class Init + { + public: + Init() { gnutls_global_init(); } + ~Init() { gnutls_global_deinit(); } + }; + + /** Used to create a gnutls_datum_t* from an Anope::string + */ + class Datum + { + gnutls_datum_t datum; + + public: + Datum(const Anope::string &dat) + { + datum.data = reinterpret_cast(const_cast(dat.data())); + datum.size = static_cast(dat.length()); + } + + const gnutls_datum_t *get() const { return &datum; } + }; + + class DHParams + { + gnutls_dh_params_t dh_params; + + public: + DHParams() : dh_params(NULL) { } + + void Import(const Anope::string &dhstr) + { + if (dh_params != NULL) + { + gnutls_dh_params_deinit(dh_params); + dh_params = NULL; + } + + int ret = gnutls_dh_params_init(&dh_params); + if (ret < 0) + throw ConfigException("Unable to initialize DH parameters"); + + ret = gnutls_dh_params_import_pkcs3(dh_params, Datum(dhstr).get(), GNUTLS_X509_FMT_PEM); + if (ret < 0) + { + gnutls_dh_params_deinit(dh_params); + dh_params = NULL; + throw ConfigException("Unable to import DH parameters"); + } + } + + ~DHParams() + { + if (dh_params) + gnutls_dh_params_deinit(dh_params); + } + + gnutls_dh_params_t get() const { return dh_params; } + }; + + class X509Key + { + /** Ensure that the key is deinited in case the constructor of X509Key throws + */ + class RAIIKey + { + public: + gnutls_x509_privkey_t key; + + RAIIKey() + { + int ret = gnutls_x509_privkey_init(&key); + if (ret < 0) + throw ConfigException("gnutls_x509_privkey_init() failed"); + } + + ~RAIIKey() + { + gnutls_x509_privkey_deinit(key); + } + } key; + + public: + /** Import */ + X509Key(const Anope::string &keystr) + { + int ret = gnutls_x509_privkey_import(key.key, Datum(keystr).get(), GNUTLS_X509_FMT_PEM); + if (ret < 0) + throw ConfigException("Error loading private key: " + Anope::string(gnutls_strerror(ret))); + } + + gnutls_x509_privkey_t& get() { return key.key; } + }; + + class X509CertList + { + std::vector certs; + + public: + /** Import */ + X509CertList(const Anope::string &certstr) + { + unsigned int certcount = 3; + certs.resize(certcount); + Datum datum(certstr); + + int ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); + if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) + { + // the buffer wasn't big enough to hold all certs but gnutls changed certcount to the number of available certs, + // try again with a bigger buffer + certs.resize(certcount); + ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); + } + + if (ret < 0) + throw ConfigException("Unable to load certificates" + Anope::string(gnutls_strerror(ret))); + + // Resize the vector to the actual number of certs because we rely on its size being correct + // when deallocating the certs + certs.resize(certcount); + } + + ~X509CertList() + { + for (std::vector::iterator i = certs.begin(); i != certs.end(); ++i) + gnutls_x509_crt_deinit(*i); + } + + gnutls_x509_crt_t* raw() { return &certs[0]; } + unsigned int size() const { return certs.size(); } + }; + + class X509CertCredentials + { + unsigned int refcount; + gnutls_certificate_credentials_t cred; + DHParams dh; + + static Anope::string LoadFile(const Anope::string &filename) + { + std::ifstream ifs(filename.c_str()); + const Anope::string ret((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + return ret; + } + + #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12)) + static int cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr_st* st); + #else + static int cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr2_st* st); + #endif + + public: + X509CertList certs; + X509Key key; + + X509CertCredentials(const Anope::string &certfile, const Anope::string &keyfile) + : refcount(0), certs(LoadFile(certfile)), key(LoadFile(keyfile)) + { + if (gnutls_certificate_allocate_credentials(&cred) < 0) + throw ConfigException("Cannot allocate certificate credentials"); + + int ret = gnutls_certificate_set_x509_key(cred, certs.raw(), certs.size(), key.get()); + if (ret < 0) + { + gnutls_certificate_free_credentials(cred); + throw ConfigException("Unable to set cert/key pair"); + } + + #if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12)) + gnutls_certificate_client_set_retrieve_function(cred, cert_callback); + #else + gnutls_certificate_set_retrieve_function(cred, cert_callback); + #endif + } + + ~X509CertCredentials() + { + gnutls_certificate_free_credentials(cred); + } + + void SetupSession(gnutls_session_t sess) + { + gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred); + gnutls_set_default_priority(sess); + } + + void SetDH(const Anope::string &dhfile) + { + const Anope::string dhdata = LoadFile(dhfile); + dh.Import(dhdata); + gnutls_certificate_set_dh_params(cred, dh.get()); + } + + bool HasDH() const + { + return (dh.get() != NULL); + } + + void incrref() { refcount++; } + void decrref() { if (!--refcount) delete this; } + }; +} + +class GnuTLSModule : public Module + , public EventHook +{ + GnuTLS::Init libinit; + + public: + GnuTLS::X509CertCredentials *cred; + MySSLService service; + + GnuTLSModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + , cred(NULL) + , service(this, "ssl") + { + me = this; + this->SetPermanent(true); + } + + ~GnuTLSModule() + { + for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) + { + Socket *s = it->second; + ++it; + + if (dynamic_cast(s->io)) + delete s; + } + + if (cred) + cred->decrref(); + } + + static void CheckFile(const Anope::string &filename) + { + if (!Anope::IsFile(filename.c_str())) + { + Log() << "File does not exist: " << filename; + throw ConfigException("Error loading certificate/private key"); + } + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + + const Anope::string certfile = config->Get("cert", "data/anope.crt"); + const Anope::string keyfile = config->Get("key", "data/anope.key"); + const Anope::string dhfile = config->Get("dh", "data/dhparams.pem"); + + CheckFile(certfile); + CheckFile(keyfile); + + GnuTLS::X509CertCredentials *newcred = new GnuTLS::X509CertCredentials(certfile, keyfile); + + // DH params is not mandatory + if (Anope::IsFile(dhfile.c_str())) + { + try + { + newcred->SetDH(dhfile); + } + catch (...) + { + delete newcred; + throw; + } + Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded DH parameters from " << dhfile; + } + + if (cred) + cred->decrref(); + cred = newcred; + cred->incrref(); + + Log(LOG_DEBUG) << "m_ssl_gnutls: Successfully loaded certificate " << certfile << " and private key " << keyfile; + } + + void OnPreServerConnect() override + { + Configuration::Block *config = Config->GetBlock("uplink", Anope::CurrentUplink); + + if (config->Get("ssl")) + { + this->service.Init(UplinkSock); + } + } +}; + +MySSLService::MySSLService(Module *o, const Anope::string &n) : SSLService(o, n) +{ +} + +void MySSLService::Init(Socket *s) +{ + if (s->io != &NormalSocketIO) + throw CoreException("Socket initializing SSL twice"); + + s->io = new SSLSocketIO(); +} + +int SSLSocketIO::Recv(Socket *s, char *buf, size_t sz) +{ + int ret = gnutls_record_recv(this->sess, buf, sz); + + if (ret > 0) + TotalRead += ret; + else if (ret < 0) + { + switch (ret) + { + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + SocketEngine::SetLastError(EAGAIN); + break; + default: + if (s == UplinkSock) + { + // Log and fake an errno because this is a fatal error on the uplink socket + Log() << "SSL error: " << gnutls_strerror(ret); + } + SocketEngine::SetLastError(ECONNRESET); + } + } + + return ret; +} + +int SSLSocketIO::Send(Socket *s, const char *buf, size_t sz) +{ + int ret = gnutls_record_send(this->sess, buf, sz); + + if (ret > 0) + TotalWritten += ret; + else + { + switch (ret) + { + case 0: + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + SocketEngine::SetLastError(EAGAIN); + break; + default: + if (s == UplinkSock) + { + // Log and fake an errno because this is a fatal error on the uplink socket + Log() << "SSL error: " << gnutls_strerror(ret); + } + SocketEngine::SetLastError(ECONNRESET); + } + } + + return ret; +} + +ClientSocket *SSLSocketIO::Accept(ListenSocket *s) +{ + if (s->io == &NormalSocketIO) + throw SocketException("Attempting to accept on uninitialized socket with SSL"); + + sockaddrs conaddr; + + socklen_t size = sizeof(conaddr); + int newsock = accept(s->GetFD(), &conaddr.sa, &size); + +#ifndef INVALID_SOCKET + const int INVALID_SOCKET = -1; +#endif + + if (newsock < 0 || newsock == INVALID_SOCKET) + throw SocketException("Unable to accept connection: " + Anope::LastError()); + + ClientSocket *newsocket = s->OnAccept(newsock, conaddr); + me->service.Init(newsocket); + SSLSocketIO *io = anope_dynamic_static_cast(newsocket->io); + + if (gnutls_init(&io->sess, GNUTLS_SERVER) != GNUTLS_E_SUCCESS) + throw SocketException("Unable to initialize SSL socket"); + + me->cred->SetupSession(io->sess); + gnutls_transport_set_ptr(io->sess, reinterpret_cast(newsock)); + + newsocket->flags[SF_ACCEPTING] = true; + this->FinishAccept(newsocket); + + return newsocket; +} + +SocketFlag SSLSocketIO::FinishAccept(ClientSocket *cs) +{ + if (cs->io == &NormalSocketIO) + throw SocketException("Attempting to finish connect uninitialized socket with SSL"); + else if (cs->flags[SF_ACCEPTED]) + return SF_ACCEPTED; + else if (!cs->flags[SF_ACCEPTING]) + throw SocketException("SSLSocketIO::FinishAccept called for a socket not accepted nor accepting?"); + + SSLSocketIO *io = anope_dynamic_static_cast(cs->io); + + int ret = gnutls_handshake(io->sess); + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) + { + // gnutls_handshake() wants to read or write again; + // if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write. + if (gnutls_record_get_direction(io->sess) == 0) + { + SocketEngine::Change(cs, false, SF_WRITABLE); + SocketEngine::Change(cs, true, SF_READABLE); + } + else + { + SocketEngine::Change(cs, true, SF_WRITABLE); + SocketEngine::Change(cs, false, SF_READABLE); + } + return SF_ACCEPTING; + } + else + { + cs->OnError(Anope::string(gnutls_strerror(ret))); + cs->flags[SF_DEAD] = true; + cs->flags[SF_ACCEPTING] = false; + return SF_DEAD; + } + } + else + { + cs->flags[SF_ACCEPTED] = true; + cs->flags[SF_ACCEPTING] = false; + SocketEngine::Change(cs, false, SF_WRITABLE); + SocketEngine::Change(cs, true, SF_READABLE); + cs->OnAccept(); + return SF_ACCEPTED; + } +} + +void SSLSocketIO::Connect(ConnectionSocket *s, const Anope::string &target, int port) +{ + if (s->io == &NormalSocketIO) + throw SocketException("Attempting to connect uninitialized socket with SSL"); + + s->flags[SF_CONNECTING] = s->flags[SF_CONNECTED] = false; + + s->conaddr.pton(s->IsIPv6() ? AF_INET6 : AF_INET, target, port); + int c = connect(s->GetFD(), &s->conaddr.sa, s->conaddr.size()); + if (c == -1) + { + if (Anope::LastErrorCode() != EINPROGRESS) + { + s->OnError(Anope::LastError()); + s->flags[SF_DEAD] = true; + return; + } + else + { + SocketEngine::Change(s, true, SF_WRITABLE); + s->flags[SF_CONNECTING] = true; + return; + } + } + else + { + s->flags[SF_CONNECTING] = true; + this->FinishConnect(s); + } +} + +SocketFlag SSLSocketIO::FinishConnect(ConnectionSocket *s) +{ + if (s->io == &NormalSocketIO) + throw SocketException("Attempting to finish connect uninitialized socket with SSL"); + else if (s->flags[SF_CONNECTED]) + return SF_CONNECTED; + else if (!s->flags[SF_CONNECTING]) + throw SocketException("SSLSocketIO::FinishConnect called for a socket not connected nor connecting?"); + + SSLSocketIO *io = anope_dynamic_static_cast(s->io); + + if (io->sess == NULL) + { + if (gnutls_init(&io->sess, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS) + throw SocketException("Unable to initialize SSL socket"); + me->cred->SetupSession(io->sess); + gnutls_transport_set_ptr(io->sess, reinterpret_cast(s->GetFD())); + } + + int ret = gnutls_handshake(io->sess); + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) + { + // gnutls_handshake() wants to read or write again; + // if gnutls_record_get_direction() returns 0 it wants to read, otherwise it wants to write. + if (gnutls_record_get_direction(io->sess) == 0) + { + SocketEngine::Change(s, false, SF_WRITABLE); + SocketEngine::Change(s, true, SF_READABLE); + } + else + { + SocketEngine::Change(s, true, SF_WRITABLE); + SocketEngine::Change(s, false, SF_READABLE); + } + + return SF_CONNECTING; + } + else + { + s->OnError(Anope::string(gnutls_strerror(ret))); + s->flags[SF_CONNECTING] = false; + s->flags[SF_DEAD] = true; + return SF_DEAD; + } + } + else + { + s->flags[SF_CONNECTING] = false; + s->flags[SF_CONNECTED] = true; + SocketEngine::Change(s, false, SF_WRITABLE); + SocketEngine::Change(s, true, SF_READABLE); + s->OnConnect(); + return SF_CONNECTED; + } +} + +void SSLSocketIO::Destroy() +{ + if (this->sess) + { + gnutls_bye(this->sess, GNUTLS_SHUT_WR); + gnutls_deinit(this->sess); + } + + mycreds->decrref(); + + delete this; +} + +SSLSocketIO::SSLSocketIO() : sess(NULL), mycreds(me->cred) +{ + mycreds->incrref(); +} + +#if (GNUTLS_VERSION_MAJOR < 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12)) +int GnuTLS::X509CertCredentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr_st* st) +{ + st->type = GNUTLS_CRT_X509; +#else +int GnuTLS::X509CertCredentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, gnutls_retr2_st* st) +{ + st->cert_type = GNUTLS_CRT_X509; + st->key_type = GNUTLS_PRIVKEY_X509; +#endif + st->ncerts = me->cred->certs.size(); + st->cert.x509 = me->cred->certs.raw(); + st->key.x509 = me->cred->key.get(); + st->deinit_all = 0; + + return 0; +} + +MODULE_INIT(GnuTLSModule) diff --git a/modules/extra/ssl_openssl.cpp b/modules/extra/ssl_openssl.cpp new file mode 100644 index 000000000..c5e2feefc --- /dev/null +++ b/modules/extra/ssl_openssl.cpp @@ -0,0 +1,416 @@ +/* RequiredLibraries: ssl,crypto */ +/* RequiredWindowsLibraries: ssleay32,libeay32 */ + +#include "module.h" +#include "modules/ssl.h" + +#define OPENSSL_NO_SHA512 +#include +#include +#include +#include +#include + +static SSL_CTX *server_ctx, *client_ctx; + +class MySSLService : public SSLService +{ + public: + MySSLService(Module *o, const Anope::string &n); + + /** Initialize a socket to use SSL + * @param s The socket + */ + void Init(Socket *s) override; +}; + +class SSLSocketIO : public SocketIO +{ + public: + /* The SSL socket for this socket */ + SSL *sslsock; + + /** Constructor + */ + SSLSocketIO(); + + /** Really receive something from the buffer + * @param s The socket + * @param buf The buf to read to + * @param sz How much to read + * @return Number of bytes received + */ + int Recv(Socket *s, char *buf, size_t sz) override; + + /** Write something to the socket + * @param s The socket + * @param buf The data to write + * @param size The length of the data + */ + int Send(Socket *s, const char *buf, size_t sz) override; + + /** Accept a connection from a socket + * @param s The socket + * @return The new socket + */ + ClientSocket *Accept(ListenSocket *s) override; + + /** Finished accepting a connection from a socket + * @param s The socket + * @return SF_ACCEPTED if accepted, SF_ACCEPTING if still in process, SF_DEAD on error + */ + SocketFlag FinishAccept(ClientSocket *cs) override; + + /** Connect the socket + * @param s THe socket + * @param target IP to connect to + * @param port to connect to + */ + void Connect(ConnectionSocket *s, const Anope::string &target, int port) override; + + /** Called to potentially finish a pending connection + * @param s The socket + * @return SF_CONNECTED on success, SF_CONNECTING if still pending, and SF_DEAD on error. + */ + SocketFlag FinishConnect(ConnectionSocket *s) override; + + /** Called when the socket is destructing + */ + void Destroy() override; +}; + +class SSLModule; +static SSLModule *me; +class SSLModule : public Module + , public EventHook +{ + Anope::string certfile, keyfile; + + public: + MySSLService service; + + SSLModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + , service(this, "ssl") + { + me = this; + + this->SetPermanent(true); + + SSL_library_init(); + SSL_load_error_strings(); + + client_ctx = SSL_CTX_new(SSLv23_client_method()); + server_ctx = SSL_CTX_new(SSLv23_server_method()); + + if (!client_ctx || !server_ctx) + throw ModuleException("Error initializing SSL CTX"); + + long opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_CIPHER_SERVER_PREFERENCE; + SSL_CTX_set_options(client_ctx, opts); + SSL_CTX_set_options(server_ctx, opts); + + SSL_CTX_set_mode(client_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_mode(server_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + Anope::string context_name = "Anope"; + SSL_CTX_set_session_id_context(client_ctx, reinterpret_cast(context_name.c_str()), context_name.length()); + SSL_CTX_set_session_id_context(server_ctx, reinterpret_cast(context_name.c_str()), context_name.length()); + } + + ~SSLModule() + { + for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) + { + Socket *s = it->second; + ++it; + + if (dynamic_cast(s->io)) + delete s; + } + + SSL_CTX_free(client_ctx); + SSL_CTX_free(server_ctx); + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = conf->GetModule(this); + + this->certfile = config->Get("cert", "data/anope.crt"); + this->keyfile = config->Get("key", "data/anope.key"); + + if (Anope::IsFile(this->certfile.c_str())) + { + if (!SSL_CTX_use_certificate_file(client_ctx, this->certfile.c_str(), SSL_FILETYPE_PEM) || !SSL_CTX_use_certificate_file(server_ctx, this->certfile.c_str(), SSL_FILETYPE_PEM)) + throw ConfigException("Error loading certificate"); + else + Log(LOG_DEBUG) << "m_ssl_openssl: Successfully loaded certificate " << this->certfile; + } + else + Log() << "Unable to open certificate " << this->certfile; + + if (Anope::IsFile(this->keyfile.c_str())) + { + if (!SSL_CTX_use_PrivateKey_file(client_ctx, this->keyfile.c_str(), SSL_FILETYPE_PEM) || !SSL_CTX_use_PrivateKey_file(server_ctx, this->keyfile.c_str(), SSL_FILETYPE_PEM)) + throw ConfigException("Error loading private key"); + else + Log(LOG_DEBUG) << "m_ssl_openssl: Successfully loaded private key " << this->keyfile; + } + else + { + if (Anope::IsFile(this->certfile.c_str())) + throw ConfigException("Error loading private key " + this->keyfile + " - file not found"); + else + Log() << "Unable to open private key " << this->keyfile; + } + + // Allow disabling SSLv3 + if (!config->Get("sslv3").empty()) + { + if (config->Get("sslv3")) + { + SSL_CTX_clear_options(client_ctx, SSL_OP_NO_SSLv3); + SSL_CTX_clear_options(server_ctx, SSL_OP_NO_SSLv3); + } + else + { + SSL_CTX_set_options(client_ctx, SSL_OP_NO_SSLv3); + SSL_CTX_set_options(server_ctx, SSL_OP_NO_SSLv3); + } + } + } + + void OnPreServerConnect() override + { + Configuration::Block *config = Config->GetBlock("uplink", Anope::CurrentUplink); + + if (config->Get("ssl")) + { + this->service.Init(UplinkSock); + } + } +}; + +MySSLService::MySSLService(Module *o, const Anope::string &n) : SSLService(o, n) +{ +} + +void MySSLService::Init(Socket *s) +{ + if (s->io != &NormalSocketIO) + throw CoreException("Socket initializing SSL twice"); + + s->io = new SSLSocketIO(); +} + +SSLSocketIO::SSLSocketIO() +{ + this->sslsock = NULL; +} + +int SSLSocketIO::Recv(Socket *s, char *buf, size_t sz) +{ + int i = SSL_read(this->sslsock, buf, sz); + if (i > 0) + TotalRead += i; + else if (i < 0) + { + int err = SSL_get_error(this->sslsock, i); + switch (err) + { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + SocketEngine::SetLastError(EAGAIN); + } + } + + return i; +} + +int SSLSocketIO::Send(Socket *s, const char *buf, size_t sz) +{ + int i = SSL_write(this->sslsock, buf, sz); + if (i > 0) + TotalWritten += i; + else if (i < 0) + { + int err = SSL_get_error(this->sslsock, i); + switch (err) + { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + SocketEngine::SetLastError(EAGAIN); + } + } + return i; +} + +ClientSocket *SSLSocketIO::Accept(ListenSocket *s) +{ + if (s->io == &NormalSocketIO) + throw SocketException("Attempting to accept on uninitialized socket with SSL"); + + sockaddrs conaddr; + + socklen_t size = sizeof(conaddr); + int newsock = accept(s->GetFD(), &conaddr.sa, &size); + +#ifndef INVALID_SOCKET + const int INVALID_SOCKET = -1; +#endif + + if (newsock < 0 || newsock == INVALID_SOCKET) + throw SocketException("Unable to accept connection: " + Anope::LastError()); + + ClientSocket *newsocket = s->OnAccept(newsock, conaddr); + me->service.Init(newsocket); + SSLSocketIO *io = anope_dynamic_static_cast(newsocket->io); + + io->sslsock = SSL_new(server_ctx); + if (!io->sslsock) + throw SocketException("Unable to initialize SSL socket"); + + SSL_set_accept_state(io->sslsock); + + if (!SSL_set_fd(io->sslsock, newsocket->GetFD())) + throw SocketException("Unable to set SSL fd"); + + newsocket->flags[SF_ACCEPTING] = true; + this->FinishAccept(newsocket); + + return newsocket; +} + +SocketFlag SSLSocketIO::FinishAccept(ClientSocket *cs) +{ + if (cs->io == &NormalSocketIO) + throw SocketException("Attempting to finish connect uninitialized socket with SSL"); + else if (cs->flags[SF_ACCEPTED]) + return SF_ACCEPTED; + else if (!cs->flags[SF_ACCEPTING]) + throw SocketException("SSLSocketIO::FinishAccept called for a socket not accepted nor accepting?"); + + SSLSocketIO *io = anope_dynamic_static_cast(cs->io); + + int ret = SSL_accept(io->sslsock); + if (ret <= 0) + { + int error = SSL_get_error(io->sslsock, ret); + if (ret == -1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)) + { + SocketEngine::Change(cs, error == SSL_ERROR_WANT_WRITE, SF_WRITABLE); + SocketEngine::Change(cs, error == SSL_ERROR_WANT_READ, SF_READABLE); + return SF_ACCEPTING; + } + else + { + cs->OnError(ERR_error_string(ERR_get_error(), NULL)); + cs->flags[SF_DEAD] = true; + cs->flags[SF_ACCEPTING] = false; + return SF_DEAD; + } + } + else + { + cs->flags[SF_ACCEPTED] = true; + cs->flags[SF_ACCEPTING] = false; + SocketEngine::Change(cs, false, SF_WRITABLE); + SocketEngine::Change(cs, true, SF_READABLE); + cs->OnAccept(); + return SF_ACCEPTED; + } +} + +void SSLSocketIO::Connect(ConnectionSocket *s, const Anope::string &target, int port) +{ + if (s->io == &NormalSocketIO) + throw SocketException("Attempting to connect uninitialized socket with SSL"); + + s->flags[SF_CONNECTING] = s->flags[SF_CONNECTED] = false; + + s->conaddr.pton(s->IsIPv6() ? AF_INET6 : AF_INET, target, port); + int c = connect(s->GetFD(), &s->conaddr.sa, s->conaddr.size()); + if (c == -1) + { + if (Anope::LastErrorCode() != EINPROGRESS) + { + s->OnError(Anope::LastError()); + s->flags[SF_DEAD] = true; + return; + } + else + { + SocketEngine::Change(s, true, SF_WRITABLE); + s->flags[SF_CONNECTING] = true; + return; + } + } + else + { + s->flags[SF_CONNECTING] = true; + this->FinishConnect(s); + } +} + +SocketFlag SSLSocketIO::FinishConnect(ConnectionSocket *s) +{ + if (s->io == &NormalSocketIO) + throw SocketException("Attempting to finish connect uninitialized socket with SSL"); + else if (s->flags[SF_CONNECTED]) + return SF_CONNECTED; + else if (!s->flags[SF_CONNECTING]) + throw SocketException("SSLSocketIO::FinishConnect called for a socket not connected nor connecting?"); + + SSLSocketIO *io = anope_dynamic_static_cast(s->io); + + if (io->sslsock == NULL) + { + io->sslsock = SSL_new(client_ctx); + if (!io->sslsock) + throw SocketException("Unable to initialize SSL socket"); + + if (!SSL_set_fd(io->sslsock, s->GetFD())) + throw SocketException("Unable to set SSL fd"); + } + + int ret = SSL_connect(io->sslsock); + if (ret <= 0) + { + int error = SSL_get_error(io->sslsock, ret); + if (ret == -1 && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)) + { + SocketEngine::Change(s, error == SSL_ERROR_WANT_WRITE, SF_WRITABLE); + SocketEngine::Change(s, error == SSL_ERROR_WANT_READ, SF_READABLE); + return SF_CONNECTING; + } + else + { + s->OnError(ERR_error_string(ERR_get_error(), NULL)); + s->flags[SF_CONNECTING] = false; + s->flags[SF_DEAD] = true; + return SF_DEAD; + } + } + else + { + s->flags[SF_CONNECTING] = false; + s->flags[SF_CONNECTED] = true; + SocketEngine::Change(s, false, SF_WRITABLE); + SocketEngine::Change(s, true, SF_READABLE); + s->OnConnect(); + return SF_CONNECTED; + } +} + +void SSLSocketIO::Destroy() +{ + if (this->sslsock) + { + SSL_shutdown(this->sslsock); + SSL_free(this->sslsock); + } + + delete this; +} + +MODULE_INIT(SSLModule) diff --git a/modules/extra/stats/chanstats.cpp b/modules/extra/stats/chanstats.cpp new file mode 100644 index 000000000..687c077b5 --- /dev/null +++ b/modules/extra/stats/chanstats.cpp @@ -0,0 +1,648 @@ +#include "module.h" +#include "modules/sql.h" + +class CommandCSSetChanstats : public Command +{ + public: + CommandCSSetChanstats(Module *creator) : Command(creator, "chanserv/set/chanstats", 2, 2) + { + this->SetDesc(_("Turn chanstats statistics on or off")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + ChanServ::Channel *ci = ChanServ::Find(params[0]); + if (!ci) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, params[1]); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->Extend("CS_STATS"); + source.Reply(_("Chanstats statistics are now enabled for this channel.")); + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable chanstats"; + } + else if (params[1].equals_ci("OFF")) + { + Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable chanstats"; + ci->Shrink("CS_STATS"); + source.Reply(_("Chanstats statistics are now disabled for this channel.")); + } + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply("Turn Chanstats channel statistics ON or OFF."); + return true; + } +}; + +class CommandNSSetChanstats : public Command +{ + public: + CommandNSSetChanstats(Module *creator, const Anope::string &sname = "nickserv/set/chanstats", size_t min = 1 ) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Turn chanstats statistics on or off")); + this->SetSyntax("{ON | OFF}"); + } + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m, bool saset = false) + { + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, na->GetAccount(), param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + Log(na->GetAccount() == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable chanstats for " << na->GetAccount()->GetDisplay(); + na->GetAccount()->Extend("NS_STATS"); + if (saset) + source.Reply(_("Chanstats statistics are now enabled for %s"), na->GetAccount()->GetDisplay().c_str()); + else + source.Reply(_("Chanstats statistics are now enabled for your nick.")); + } + else if (param.equals_ci("OFF")) + { + Log(na->GetAccount() == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable chanstats for " << na->GetAccount()->GetDisplay(); + na->GetAccount()->Shrink("NS_STATS"); + if (saset) + source.Reply(_("Chanstats statistics are now disabled for %s"), na->GetAccount()->GetDisplay().c_str()); + else + source.Reply(_("Chanstats statistics are now disabled for your nick.")); + } + else + this->OnSyntaxError(source, "CHANSTATS"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Turns Chanstats statistics ON or OFF.")); + return true; + } +}; + +class CommandNSSASetChanstats : public CommandNSSetChanstats +{ + public: + CommandNSSASetChanstats(Module *creator) : CommandNSSetChanstats(creator, "nickserv/saset/chanstats", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params[1], true); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Turns chanstats channel statistics ON or OFF for this user.")); + return true; + } +}; + +class MySQLInterface : public SQL::Interface +{ + public: + MySQLInterface(Module *o) : SQL::Interface(o) { } + + void OnResult(const SQL::Result &r) override + { + } + + void OnError(const SQL::Result &r) override + { + if (!r.GetQuery().query.empty()) + Log(LOG_DEBUG) << "Chanstats: Error executing query " << r.finished_query << ": " << r.GetError(); + else + Log(LOG_DEBUG) << "Chanstats: Error executing query: " << r.GetError(); + } +}; + +class MChanstats : public Module +{ + Serialize::Field cs_stats, ns_stats; + + CommandCSSetChanstats commandcssetchanstats; + + CommandNSSetChanstats commandnssetchanstats; + CommandNSSASetChanstats commandnssasetchanstats; + + ServiceReference sql; + MySQLInterface sqlinterface; + SQL::Query query; + Anope::string SmileysHappy, SmileysSad, SmileysOther, prefix; + std::vector TableList, ProcedureList, EventList; + bool NSDefChanstats, CSDefChanstats; + + void RunQuery(const SQL::Query &q) + { + if (sql) + sql->Run(&sqlinterface, q); + } + + size_t CountWords(const Anope::string &msg) + { + size_t words = 0; + for (size_t pos = 0; pos != Anope::string::npos; pos = msg.find(" ", pos+1)) + words++; + return words; + } + size_t CountSmileys(const Anope::string &msg, const Anope::string &smileylist) + { + size_t smileys = 0; + spacesepstream sep(smileylist); + Anope::string buf; + + while (sep.GetToken(buf) && !buf.empty()) + { + for (size_t pos = msg.find(buf, 0); pos != Anope::string::npos; pos = msg.find(buf, pos+1)) + smileys++; + } + return smileys; + } + + const Anope::string GetDisplay(User *u) + { + if (u && u->Account() && ns_stats.HasExt(u->Account())) + return u->Account()->GetDisplay(); + else + return ""; + } + + void GetTables() + { + TableList.clear(); + ProcedureList.clear(); + EventList.clear(); + if (!sql) + return; + + SQL::Result r = this->sql->RunQuery(this->sql->GetTables(prefix)); + for (int i = 0; i < r.Rows(); ++i) + { + const std::map &map = r.Row(i); + for (std::map::const_iterator it = map.begin(); it != map.end(); ++it) + TableList.push_back(it->second); + } + query = "SHOW PROCEDURE STATUS WHERE `Db` = Database();"; + r = this->sql->RunQuery(query); + for (int i = 0; i < r.Rows(); ++i) + { + ProcedureList.push_back(r.Get(i, "Name")); + } + query = "SHOW EVENTS WHERE `Db` = Database();"; + r = this->sql->RunQuery(query); + for (int i = 0; i < r.Rows(); ++i) + { + EventList.push_back(r.Get(i, "Name")); + } + } + + bool HasTable(const Anope::string &table) + { + for (std::vector::const_iterator it = TableList.begin(); it != TableList.end(); ++it) + if (*it == table) + return true; + return false; + } + + bool HasProcedure(const Anope::string &table) + { + for (std::vector::const_iterator it = ProcedureList.begin(); it != ProcedureList.end(); ++it) + if (*it == table) + return true; + return false; + } + + bool HasEvent(const Anope::string &table) + { + for (std::vector::const_iterator it = EventList.begin(); it != EventList.end(); ++it) + if (*it == table) + return true; + return false; + } + + + void CheckTables() + { + this->GetTables(); + if (!this->HasTable(prefix +"chanstats")) + { + query = "CREATE TABLE `" + prefix + "chanstats` (" + "`id` int(11) NOT NULL AUTO_INCREMENT," + "`chan` varchar(64) NOT NULL DEFAULT ''," + "`nick` varchar(64) NOT NULL DEFAULT ''," + "`type` ENUM('total', 'monthly', 'weekly', 'daily') NOT NULL," + "`letters` int(10) unsigned NOT NULL DEFAULT '0'," + "`words` int(10) unsigned NOT NULL DEFAULT '0'," + "`line` int(10) unsigned NOT NULL DEFAULT '0'," + "`actions` int(10) unsigned NOT NULL DEFAULT '0'," + "`smileys_happy` int(10) unsigned NOT NULL DEFAULT '0'," + "`smileys_sad` int(10) unsigned NOT NULL DEFAULT '0'," + "`smileys_other` int(10) unsigned NOT NULL DEFAULT '0'," + "`kicks` int(10) unsigned NOT NULL DEFAULT '0'," + "`kicked` int(10) unsigned NOT NULL DEFAULT '0'," + "`modes` int(10) unsigned NOT NULL DEFAULT '0'," + "`topics` int(10) unsigned NOT NULL DEFAULT '0'," + "`time0` int(10) unsigned NOT NULL default '0'," + "`time1` int(10) unsigned NOT NULL default '0'," + "`time2` int(10) unsigned NOT NULL default '0'," + "`time3` int(10) unsigned NOT NULL default '0'," + "`time4` int(10) unsigned NOT NULL default '0'," + "`time5` int(10) unsigned NOT NULL default '0'," + "`time6` int(10) unsigned NOT NULL default '0'," + "`time7` int(10) unsigned NOT NULL default '0'," + "`time8` int(10) unsigned NOT NULL default '0'," + "`time9` int(10) unsigned NOT NULL default '0'," + "`time10` int(10) unsigned NOT NULL default '0'," + "`time11` int(10) unsigned NOT NULL default '0'," + "`time12` int(10) unsigned NOT NULL default '0'," + "`time13` int(10) unsigned NOT NULL default '0'," + "`time14` int(10) unsigned NOT NULL default '0'," + "`time15` int(10) unsigned NOT NULL default '0'," + "`time16` int(10) unsigned NOT NULL default '0'," + "`time17` int(10) unsigned NOT NULL default '0'," + "`time18` int(10) unsigned NOT NULL default '0'," + "`time19` int(10) unsigned NOT NULL default '0'," + "`time20` int(10) unsigned NOT NULL default '0'," + "`time21` int(10) unsigned NOT NULL default '0'," + "`time22` int(10) unsigned NOT NULL default '0'," + "`time23` int(10) unsigned NOT NULL default '0'," + "PRIMARY KEY (`id`)," + "UNIQUE KEY `chan` (`chan`,`nick`,`type`)," + "KEY `nick` (`nick`)," + "KEY `chan_` (`chan`)," + "KEY `type` (`type`)" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; + this->RunQuery(query); + } + /* There is no CREATE OR REPLACE PROCEDURE in MySQL */ + if (this->HasProcedure(prefix + "chanstats_proc_update")) + { + query = "DROP PROCEDURE " + prefix + "chanstats_proc_update"; + this->RunQuery(query); + } + query = "CREATE PROCEDURE `" + prefix + "chanstats_proc_update`" + "(chan_ VARCHAR(255), nick_ VARCHAR(255), line_ INT(10), letters_ INT(10)," + "words_ INT(10), actions_ INT(10), sm_h_ INT(10), sm_s_ INT(10), sm_o_ INT(10)," + "kicks_ INT(10), kicked_ INT(10), modes_ INT(10), topics_ INT(10))" + "BEGIN " + "DECLARE time_ VARCHAR(20);" + "SET time_ = CONCAT('time', hour(now()));" + "INSERT IGNORE INTO `" + prefix + "chanstats` (`nick`,`chan`, `type`) VALUES " + "('', chan_, 'total'), ('', chan_, 'monthly')," + "('', chan_, 'weekly'), ('', chan_, 'daily');" + "IF nick_ != '' THEN " + "INSERT IGNORE INTO `" + prefix + "chanstats` (`nick`,`chan`, `type`) VALUES " + "(nick_, chan_, 'total'), (nick_, chan_, 'monthly')," + "(nick_, chan_, 'weekly'),(nick_, chan_, 'daily')," + "(nick_, '', 'total'), (nick_, '', 'monthly')," + "(nick_, '', 'weekly'), (nick_, '', 'daily');" + "END IF;" + "SET @update_query = CONCAT('UPDATE `" + prefix + "chanstats` SET line=line+', line_, '," + "letters=letters+', letters_, ' , words=words+', words_, ', actions=actions+', actions_, ', " + "smileys_happy=smileys_happy+', sm_h_, ', smileys_sad=smileys_sad+', sm_s_, ', " + "smileys_other=smileys_other+', sm_o_, ', kicks=kicks+', kicks_, ', kicked=kicked+', kicked_, ', " + "modes=modes+', modes_, ', topics=topics+', topics_, ', ', time_ , '=', time_, '+', line_ ,' " + "WHERE (nick='''' OR nick=''', nick_, ''') AND (chan='''' OR chan=''', chan_, ''')');" + "PREPARE update_query FROM @update_query;" + "EXECUTE update_query;" + "DEALLOCATE PREPARE update_query;" + "END"; + this->RunQuery(query); + + if (this->HasProcedure(prefix + "chanstats_proc_chgdisplay")) + { + query = "DROP PROCEDURE " + prefix + "chanstats_proc_chgdisplay;"; + this->RunQuery(query); + } + query = "CREATE PROCEDURE `" + prefix + "chanstats_proc_chgdisplay`" + "(old_nick varchar(255), new_nick varchar(255))" + "BEGIN " + "DECLARE res_count int(10) unsigned;" + "SELECT COUNT(nick) INTO res_count FROM `" + prefix + "chanstats` WHERE nick = new_nick;" + "IF res_count = 0 THEN " + "UPDATE `" + prefix + "chanstats` SET `nick` = new_nick WHERE `nick` = old_nick;" + "ELSE " + "my_cursor: BEGIN " + "DECLARE no_more_rows BOOLEAN DEFAULT FALSE;" + "DECLARE chan_ VARCHAR(255);" + "DECLARE type_ ENUM('total', 'monthly', 'weekly', 'daily');" + "DECLARE letters_, words_, line_, actions_, smileys_happy_," + "smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_," + "time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_, time9_," + "time10_, time11_, time12_, time13_, time14_, time15_, time16_, time17_, time18_," + "time19_, time20_, time21_, time22_, time23_ INT(10) unsigned;" + "DECLARE stats_cursor CURSOR FOR " + "SELECT chan, type, letters, words, line, actions, smileys_happy," + "smileys_sad, smileys_other, kicks, kicked, modes, topics, time0, time1," + "time2, time3, time4, time5, time6, time7, time8, time9, time10, time11," + "time12, time13, time14, time15, time16, time17, time18, time19, time20," + "time21, time22, time23 " + "FROM `" + prefix + "chanstats` " + "WHERE `nick` = old_nick;" + "DECLARE CONTINUE HANDLER FOR NOT FOUND " + "SET no_more_rows = TRUE;" + "OPEN stats_cursor;" + "the_loop: LOOP " + "FETCH stats_cursor " + "INTO chan_, type_, letters_, words_, line_, actions_, smileys_happy_," + "smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_," + "time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_," + "time9_, time10_, time11_, time12_, time13_, time14_, time15_, time16_," + "time17_, time18_, time19_, time20_, time21_, time22_, time23_;" + "IF no_more_rows THEN " + "CLOSE stats_cursor;" + "LEAVE the_loop;" + "END IF;" + "INSERT INTO `" + prefix + "chanstats` " + "(chan, nick, type, letters, words, line, actions, smileys_happy, " + "smileys_sad, smileys_other, kicks, kicked, modes, topics, time0, time1, " + "time2, time3, time4, time5, time6, time7, time8, time9, time10, time11," + "time12, time13, time14, time15, time16, time17, time18, time19, time20," + "time21, time22, time23)" + "VALUES (chan_, new_nick, type_, letters_, words_, line_, actions_, smileys_happy_," + "smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_," + "time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_, " + "time9_, time10_, time11_, time12_, time13_, time14_, time15_, time16_, " + "time17_, time18_, time19_, time20_, time21_, time22_, time23_)" + "ON DUPLICATE KEY UPDATE letters=letters+VALUES(letters), words=words+VALUES(words)," + "line=line+VALUES(line), actions=actions+VALUES(actions)," + "smileys_happy=smileys_happy+VALUES(smileys_happy)," + "smileys_sad=smileys_sad+VALUES(smileys_sad)," + "smileys_other=smileys_other+VALUES(smileys_other)," + "kicks=kicks+VALUES(kicks), kicked=kicked+VALUES(kicked)," + "modes=modes+VALUES(modes), topics=topics+VALUES(topics)," + "time1=time1+VALUES(time1), time2=time2+VALUES(time2), time3=time3+VALUES(time3)," + "time4=time4+VALUES(time4), time5=time5+VALUES(time5), time6=time6+VALUES(time6)," + "time7=time7+VALUES(time7), time8=time8+VALUES(time8), time9=time9+VALUES(time9)," + "time10=time10+VALUES(time10), time11=time11+VALUES(time11), time12=time12+VALUES(time12)," + "time13=time13+VALUES(time13), time14=time14+VALUES(time14), time15=time15+VALUES(time15)," + "time16=time16+VALUES(time16), time17=time17+VALUES(time17), time18=time18+VALUES(time18)," + "time19=time19+VALUES(time19), time20=time20+VALUES(time20), time21=time21+VALUES(time21)," + "time22=time22+VALUES(time22), time23=time23+VALUES(time23);" + "END LOOP;" + "DELETE FROM `" + prefix + "chanstats` WHERE `nick` = old_nick;" + "END my_cursor;" + "END IF;" + "END;"; + this->RunQuery(query); + + /* don't prepend any database prefix to events so we can always delete/change old events */ + if (this->HasEvent("chanstats_event_cleanup_daily")) + { + query = "DROP EVENT chanstats_event_cleanup_daily"; + this->RunQuery(query); + } + query = "CREATE EVENT `chanstats_event_cleanup_daily` " + "ON SCHEDULE EVERY 1 DAY STARTS CURRENT_DATE " + "DO UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0," + "smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0," + "time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0," + "time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0," + "time20=0, time21=0, time22=0, time23=0 " + "WHERE type='daily';"; + this->RunQuery(query); + + if (this->HasEvent("chanstats_event_cleanup_weekly")) + { + query = "DROP EVENT `chanstats_event_cleanup_weekly`"; + this->RunQuery(query); + } + query = "CREATE EVENT `chanstats_event_cleanup_weekly` " + "ON SCHEDULE EVERY 1 WEEK STARTS ADDDATE(CURDATE(), INTERVAL 1-DAYOFWEEK(CURDATE()) DAY) " + "DO UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0," + "smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0," + "time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0," + "time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0," + "time20=0, time21=0, time22=0, time23=0 " + "WHERE type='weekly';"; + this->RunQuery(query); + + if (this->HasEvent("chanstats_event_cleanup_monthly")) + { + query = "DROP EVENT `chanstats_event_cleanup_monthly`;"; + this->RunQuery(query); + } + query = "CREATE EVENT `chanstats_event_cleanup_monthly` " + "ON SCHEDULE EVERY 1 MONTH STARTS LAST_DAY(CURRENT_TIMESTAMP) + INTERVAL 1 DAY " + "DO BEGIN " + "UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0," + "smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0," + "time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0," + "time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0, " + "time20=0, time21=0, time22=0, time23=0 " + "WHERE type='monthly';" + "OPTIMIZE TABLE `" + prefix + "chanstats`;" + "END;"; + this->RunQuery(query); + } + + + public: + MChanstats(const Anope::string &modname, const Anope::string &creator) : + Module(modname, creator, EXTRA | VENDOR), + cs_stats(this, "CS_STATS"), ns_stats(this, "NS_STATS"), + commandcssetchanstats(this), commandnssetchanstats(this), commandnssasetchanstats(this), + sqlinterface(this) + { + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + prefix = block->Get("prefix", "anope_"); + SmileysHappy = block->Get("SmileysHappy"); + SmileysSad = block->Get("SmileysSad"); + SmileysOther = block->Get("SmileysOther"); + NSDefChanstats = block->Get("ns_def_chanstats"); + CSDefChanstats = block->Get("cs_def_chanstats"); + Anope::string engine = block->Get("engine"); + this->sql = ServiceReference("SQL::Provider", engine); + if (sql) + this->CheckTables(); + else + Log(this) << "no database connection to " << engine; + } + + void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override + { + if (!show_all) + return; + if (cs_stats.HasExt(ci)) + info.AddOption(_("Chanstats")); + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override + { + if (!show_hidden) + return; + if (ns_stats.HasExt(na->GetAccount())) + info.AddOption(_("Chanstats")); + } + + void OnTopicUpdated(User *source, Channel *c, const Anope::string &user, const Anope::string &topic) override + { + if (!source || !source->Account() || !c->ci || !cs_stats.HasExt(c->ci)) + return; + query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);"; + query.SetValue("channel", c->name); + query.SetValue("nick", GetDisplay(source)); + this->RunQuery(query); + } + + EventReturn OnChannelModeSet(Channel *c, const MessageSource &setter, ChannelMode *mode, const Anope::string ¶m) override + { + this->OnModeChange(c, setter.GetUser()); + return EVENT_CONTINUE; + } + + EventReturn OnChannelModeUnset(Channel *c, const MessageSource &setter, ChannelMode *, const Anope::string ¶m) override + { + this->OnModeChange(c, setter.GetUser()); + return EVENT_CONTINUE; + } + + private: + void OnModeChange(Channel *c, User *u) + { + if (!u || !u->Account() || !c->ci || !cs_stats.HasExt(c->ci)) + return; + + query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);"; + query.SetValue("channel", c->name); + query.SetValue("nick", GetDisplay(u)); + this->RunQuery(query); + } + + public: + void OnPreUserKicked(const MessageSource &source, ChanUserContainer *cu, const Anope::string &kickmsg) override + { + if (!cu->chan->ci || !cs_stats.HasExt(cu->chan->ci)) + return; + + query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0);"; + query.SetValue("channel", cu->chan->name); + query.SetValue("nick", GetDisplay(cu->user)); + this->RunQuery(query); + + query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0);"; + query.SetValue("channel", cu->chan->name); + query.SetValue("nick", GetDisplay(source.GetUser())); + this->RunQuery(query); + } + + void OnPrivmsg(User *u, Channel *c, Anope::string &msg) override + { + if (!c->ci || !cs_stats.HasExt(c->ci)) + return; + + size_t letters = msg.length(); + size_t words = this->CountWords(msg); + + size_t action = 0; + if (msg.find("\01ACTION")!=Anope::string::npos) + { + action = 1; + letters = letters - 7; + words--; + } + + // count smileys + size_t smileys_happy = CountSmileys(msg, SmileysHappy); + size_t smileys_sad = CountSmileys(msg, SmileysSad); + size_t smileys_other = CountSmileys(msg, SmileysOther); + + // do not count smileys as words + size_t smileys = smileys_happy + smileys_sad + smileys_other; + if (smileys > words) + words = 0; + else + words = words - smileys; + + query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 1, @letters@, @words@, @action@, " + "@smileys_happy@, @smileys_sad@, @smileys_other@, '0', '0', '0', '0');"; + query.SetValue("channel", c->name); + query.SetValue("nick", GetDisplay(u)); + query.SetValue("letters", letters); + query.SetValue("words", words); + query.SetValue("action", action); + query.SetValue("smileys_happy", smileys_happy); + query.SetValue("smileys_sad", smileys_sad); + query.SetValue("smileys_other", smileys_other); + this->RunQuery(query); + } + + void OnDelCore(NickServ::Account *nc) override + { + query = "DELETE FROM `" + prefix + "chanstats` WHERE `nick` = @nick@;"; + query.SetValue("nick", nc->GetDisplay()); + this->RunQuery(query); + } + + void OnChangeCoreDisplay(NickServ::Account *nc, const Anope::string &newdisplay) override + { + query = "CALL " + prefix + "chanstats_proc_chgdisplay(@old_display@, @new_display@);"; + query.SetValue("old_display", nc->GetDisplay()); + query.SetValue("new_display", newdisplay); + this->RunQuery(query); + } + + void OnDelChan(ChanServ::Channel *ci) override + { + query = "DELETE FROM `" + prefix + "chanstats` WHERE `chan` = @channel@;"; + query.SetValue("channel", ci->GetName()); + this->RunQuery(query); + } + + void OnChanRegistered(ChanServ::Channel *ci) + { + if (CSDefChanstats) + ci->Extend("CS_STATS"); + } + + void OnNickRegister(User *user, NickAlias *na, const Anope::string &) + { + if (NSDefChanstats) + na->GetAccount()->Extend("NS_STATS"); + } +}; + +MODULE_INIT(MChanstats) + diff --git a/modules/extra/stats/m_chanstats.cpp b/modules/extra/stats/m_chanstats.cpp deleted file mode 100644 index 687c077b5..000000000 --- a/modules/extra/stats/m_chanstats.cpp +++ /dev/null @@ -1,648 +0,0 @@ -#include "module.h" -#include "modules/sql.h" - -class CommandCSSetChanstats : public Command -{ - public: - CommandCSSetChanstats(Module *creator) : Command(creator, "chanserv/set/chanstats", 2, 2) - { - this->SetDesc(_("Turn chanstats statistics on or off")); - this->SetSyntax(_("\037channel\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - ChanServ::Channel *ci = ChanServ::Find(params[0]); - if (!ci) - { - source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetChannelOption(&Event::SetChannelOption::OnSetChannelOption, source, this, ci, params[1]); - if (MOD_RESULT == EVENT_STOP) - return; - - if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration")) - { - source.Reply(ACCESS_DENIED); - return; - } - - if (params[1].equals_ci("ON")) - { - ci->Extend("CS_STATS"); - source.Reply(_("Chanstats statistics are now enabled for this channel.")); - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable chanstats"; - } - else if (params[1].equals_ci("OFF")) - { - Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable chanstats"; - ci->Shrink("CS_STATS"); - source.Reply(_("Chanstats statistics are now disabled for this channel.")); - } - else - this->OnSyntaxError(source, ""); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply("Turn Chanstats channel statistics ON or OFF."); - return true; - } -}; - -class CommandNSSetChanstats : public Command -{ - public: - CommandNSSetChanstats(Module *creator, const Anope::string &sname = "nickserv/set/chanstats", size_t min = 1 ) : Command(creator, sname, min, min + 1) - { - this->SetDesc(_("Turn chanstats statistics on or off")); - this->SetSyntax("{ON | OFF}"); - } - void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m, bool saset = false) - { - NickServ::Nick *na = NickServ::FindNick(user); - if (!na) - { - source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); - return; - } - - EventReturn MOD_RESULT; - MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, na->GetAccount(), param); - if (MOD_RESULT == EVENT_STOP) - return; - - if (param.equals_ci("ON")) - { - Log(na->GetAccount() == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable chanstats for " << na->GetAccount()->GetDisplay(); - na->GetAccount()->Extend("NS_STATS"); - if (saset) - source.Reply(_("Chanstats statistics are now enabled for %s"), na->GetAccount()->GetDisplay().c_str()); - else - source.Reply(_("Chanstats statistics are now enabled for your nick.")); - } - else if (param.equals_ci("OFF")) - { - Log(na->GetAccount() == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable chanstats for " << na->GetAccount()->GetDisplay(); - na->GetAccount()->Shrink("NS_STATS"); - if (saset) - source.Reply(_("Chanstats statistics are now disabled for %s"), na->GetAccount()->GetDisplay().c_str()); - else - source.Reply(_("Chanstats statistics are now disabled for your nick.")); - } - else - this->OnSyntaxError(source, "CHANSTATS"); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, source.nc->GetDisplay(), params[0]); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Turns Chanstats statistics ON or OFF.")); - return true; - } -}; - -class CommandNSSASetChanstats : public CommandNSSetChanstats -{ - public: - CommandNSSASetChanstats(Module *creator) : CommandNSSetChanstats(creator, "nickserv/saset/chanstats", 2) - { - this->ClearSyntax(); - this->SetSyntax(_("\037nickname\037 {ON | OFF}")); - } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - this->Run(source, params[0], params[1], true); - } - - bool OnHelp(CommandSource &source, const Anope::string &) override - { - this->SendSyntax(source); - source.Reply(" "); - source.Reply(_("Turns chanstats channel statistics ON or OFF for this user.")); - return true; - } -}; - -class MySQLInterface : public SQL::Interface -{ - public: - MySQLInterface(Module *o) : SQL::Interface(o) { } - - void OnResult(const SQL::Result &r) override - { - } - - void OnError(const SQL::Result &r) override - { - if (!r.GetQuery().query.empty()) - Log(LOG_DEBUG) << "Chanstats: Error executing query " << r.finished_query << ": " << r.GetError(); - else - Log(LOG_DEBUG) << "Chanstats: Error executing query: " << r.GetError(); - } -}; - -class MChanstats : public Module -{ - Serialize::Field cs_stats, ns_stats; - - CommandCSSetChanstats commandcssetchanstats; - - CommandNSSetChanstats commandnssetchanstats; - CommandNSSASetChanstats commandnssasetchanstats; - - ServiceReference sql; - MySQLInterface sqlinterface; - SQL::Query query; - Anope::string SmileysHappy, SmileysSad, SmileysOther, prefix; - std::vector TableList, ProcedureList, EventList; - bool NSDefChanstats, CSDefChanstats; - - void RunQuery(const SQL::Query &q) - { - if (sql) - sql->Run(&sqlinterface, q); - } - - size_t CountWords(const Anope::string &msg) - { - size_t words = 0; - for (size_t pos = 0; pos != Anope::string::npos; pos = msg.find(" ", pos+1)) - words++; - return words; - } - size_t CountSmileys(const Anope::string &msg, const Anope::string &smileylist) - { - size_t smileys = 0; - spacesepstream sep(smileylist); - Anope::string buf; - - while (sep.GetToken(buf) && !buf.empty()) - { - for (size_t pos = msg.find(buf, 0); pos != Anope::string::npos; pos = msg.find(buf, pos+1)) - smileys++; - } - return smileys; - } - - const Anope::string GetDisplay(User *u) - { - if (u && u->Account() && ns_stats.HasExt(u->Account())) - return u->Account()->GetDisplay(); - else - return ""; - } - - void GetTables() - { - TableList.clear(); - ProcedureList.clear(); - EventList.clear(); - if (!sql) - return; - - SQL::Result r = this->sql->RunQuery(this->sql->GetTables(prefix)); - for (int i = 0; i < r.Rows(); ++i) - { - const std::map &map = r.Row(i); - for (std::map::const_iterator it = map.begin(); it != map.end(); ++it) - TableList.push_back(it->second); - } - query = "SHOW PROCEDURE STATUS WHERE `Db` = Database();"; - r = this->sql->RunQuery(query); - for (int i = 0; i < r.Rows(); ++i) - { - ProcedureList.push_back(r.Get(i, "Name")); - } - query = "SHOW EVENTS WHERE `Db` = Database();"; - r = this->sql->RunQuery(query); - for (int i = 0; i < r.Rows(); ++i) - { - EventList.push_back(r.Get(i, "Name")); - } - } - - bool HasTable(const Anope::string &table) - { - for (std::vector::const_iterator it = TableList.begin(); it != TableList.end(); ++it) - if (*it == table) - return true; - return false; - } - - bool HasProcedure(const Anope::string &table) - { - for (std::vector::const_iterator it = ProcedureList.begin(); it != ProcedureList.end(); ++it) - if (*it == table) - return true; - return false; - } - - bool HasEvent(const Anope::string &table) - { - for (std::vector::const_iterator it = EventList.begin(); it != EventList.end(); ++it) - if (*it == table) - return true; - return false; - } - - - void CheckTables() - { - this->GetTables(); - if (!this->HasTable(prefix +"chanstats")) - { - query = "CREATE TABLE `" + prefix + "chanstats` (" - "`id` int(11) NOT NULL AUTO_INCREMENT," - "`chan` varchar(64) NOT NULL DEFAULT ''," - "`nick` varchar(64) NOT NULL DEFAULT ''," - "`type` ENUM('total', 'monthly', 'weekly', 'daily') NOT NULL," - "`letters` int(10) unsigned NOT NULL DEFAULT '0'," - "`words` int(10) unsigned NOT NULL DEFAULT '0'," - "`line` int(10) unsigned NOT NULL DEFAULT '0'," - "`actions` int(10) unsigned NOT NULL DEFAULT '0'," - "`smileys_happy` int(10) unsigned NOT NULL DEFAULT '0'," - "`smileys_sad` int(10) unsigned NOT NULL DEFAULT '0'," - "`smileys_other` int(10) unsigned NOT NULL DEFAULT '0'," - "`kicks` int(10) unsigned NOT NULL DEFAULT '0'," - "`kicked` int(10) unsigned NOT NULL DEFAULT '0'," - "`modes` int(10) unsigned NOT NULL DEFAULT '0'," - "`topics` int(10) unsigned NOT NULL DEFAULT '0'," - "`time0` int(10) unsigned NOT NULL default '0'," - "`time1` int(10) unsigned NOT NULL default '0'," - "`time2` int(10) unsigned NOT NULL default '0'," - "`time3` int(10) unsigned NOT NULL default '0'," - "`time4` int(10) unsigned NOT NULL default '0'," - "`time5` int(10) unsigned NOT NULL default '0'," - "`time6` int(10) unsigned NOT NULL default '0'," - "`time7` int(10) unsigned NOT NULL default '0'," - "`time8` int(10) unsigned NOT NULL default '0'," - "`time9` int(10) unsigned NOT NULL default '0'," - "`time10` int(10) unsigned NOT NULL default '0'," - "`time11` int(10) unsigned NOT NULL default '0'," - "`time12` int(10) unsigned NOT NULL default '0'," - "`time13` int(10) unsigned NOT NULL default '0'," - "`time14` int(10) unsigned NOT NULL default '0'," - "`time15` int(10) unsigned NOT NULL default '0'," - "`time16` int(10) unsigned NOT NULL default '0'," - "`time17` int(10) unsigned NOT NULL default '0'," - "`time18` int(10) unsigned NOT NULL default '0'," - "`time19` int(10) unsigned NOT NULL default '0'," - "`time20` int(10) unsigned NOT NULL default '0'," - "`time21` int(10) unsigned NOT NULL default '0'," - "`time22` int(10) unsigned NOT NULL default '0'," - "`time23` int(10) unsigned NOT NULL default '0'," - "PRIMARY KEY (`id`)," - "UNIQUE KEY `chan` (`chan`,`nick`,`type`)," - "KEY `nick` (`nick`)," - "KEY `chan_` (`chan`)," - "KEY `type` (`type`)" - ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; - this->RunQuery(query); - } - /* There is no CREATE OR REPLACE PROCEDURE in MySQL */ - if (this->HasProcedure(prefix + "chanstats_proc_update")) - { - query = "DROP PROCEDURE " + prefix + "chanstats_proc_update"; - this->RunQuery(query); - } - query = "CREATE PROCEDURE `" + prefix + "chanstats_proc_update`" - "(chan_ VARCHAR(255), nick_ VARCHAR(255), line_ INT(10), letters_ INT(10)," - "words_ INT(10), actions_ INT(10), sm_h_ INT(10), sm_s_ INT(10), sm_o_ INT(10)," - "kicks_ INT(10), kicked_ INT(10), modes_ INT(10), topics_ INT(10))" - "BEGIN " - "DECLARE time_ VARCHAR(20);" - "SET time_ = CONCAT('time', hour(now()));" - "INSERT IGNORE INTO `" + prefix + "chanstats` (`nick`,`chan`, `type`) VALUES " - "('', chan_, 'total'), ('', chan_, 'monthly')," - "('', chan_, 'weekly'), ('', chan_, 'daily');" - "IF nick_ != '' THEN " - "INSERT IGNORE INTO `" + prefix + "chanstats` (`nick`,`chan`, `type`) VALUES " - "(nick_, chan_, 'total'), (nick_, chan_, 'monthly')," - "(nick_, chan_, 'weekly'),(nick_, chan_, 'daily')," - "(nick_, '', 'total'), (nick_, '', 'monthly')," - "(nick_, '', 'weekly'), (nick_, '', 'daily');" - "END IF;" - "SET @update_query = CONCAT('UPDATE `" + prefix + "chanstats` SET line=line+', line_, '," - "letters=letters+', letters_, ' , words=words+', words_, ', actions=actions+', actions_, ', " - "smileys_happy=smileys_happy+', sm_h_, ', smileys_sad=smileys_sad+', sm_s_, ', " - "smileys_other=smileys_other+', sm_o_, ', kicks=kicks+', kicks_, ', kicked=kicked+', kicked_, ', " - "modes=modes+', modes_, ', topics=topics+', topics_, ', ', time_ , '=', time_, '+', line_ ,' " - "WHERE (nick='''' OR nick=''', nick_, ''') AND (chan='''' OR chan=''', chan_, ''')');" - "PREPARE update_query FROM @update_query;" - "EXECUTE update_query;" - "DEALLOCATE PREPARE update_query;" - "END"; - this->RunQuery(query); - - if (this->HasProcedure(prefix + "chanstats_proc_chgdisplay")) - { - query = "DROP PROCEDURE " + prefix + "chanstats_proc_chgdisplay;"; - this->RunQuery(query); - } - query = "CREATE PROCEDURE `" + prefix + "chanstats_proc_chgdisplay`" - "(old_nick varchar(255), new_nick varchar(255))" - "BEGIN " - "DECLARE res_count int(10) unsigned;" - "SELECT COUNT(nick) INTO res_count FROM `" + prefix + "chanstats` WHERE nick = new_nick;" - "IF res_count = 0 THEN " - "UPDATE `" + prefix + "chanstats` SET `nick` = new_nick WHERE `nick` = old_nick;" - "ELSE " - "my_cursor: BEGIN " - "DECLARE no_more_rows BOOLEAN DEFAULT FALSE;" - "DECLARE chan_ VARCHAR(255);" - "DECLARE type_ ENUM('total', 'monthly', 'weekly', 'daily');" - "DECLARE letters_, words_, line_, actions_, smileys_happy_," - "smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_," - "time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_, time9_," - "time10_, time11_, time12_, time13_, time14_, time15_, time16_, time17_, time18_," - "time19_, time20_, time21_, time22_, time23_ INT(10) unsigned;" - "DECLARE stats_cursor CURSOR FOR " - "SELECT chan, type, letters, words, line, actions, smileys_happy," - "smileys_sad, smileys_other, kicks, kicked, modes, topics, time0, time1," - "time2, time3, time4, time5, time6, time7, time8, time9, time10, time11," - "time12, time13, time14, time15, time16, time17, time18, time19, time20," - "time21, time22, time23 " - "FROM `" + prefix + "chanstats` " - "WHERE `nick` = old_nick;" - "DECLARE CONTINUE HANDLER FOR NOT FOUND " - "SET no_more_rows = TRUE;" - "OPEN stats_cursor;" - "the_loop: LOOP " - "FETCH stats_cursor " - "INTO chan_, type_, letters_, words_, line_, actions_, smileys_happy_," - "smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_," - "time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_," - "time9_, time10_, time11_, time12_, time13_, time14_, time15_, time16_," - "time17_, time18_, time19_, time20_, time21_, time22_, time23_;" - "IF no_more_rows THEN " - "CLOSE stats_cursor;" - "LEAVE the_loop;" - "END IF;" - "INSERT INTO `" + prefix + "chanstats` " - "(chan, nick, type, letters, words, line, actions, smileys_happy, " - "smileys_sad, smileys_other, kicks, kicked, modes, topics, time0, time1, " - "time2, time3, time4, time5, time6, time7, time8, time9, time10, time11," - "time12, time13, time14, time15, time16, time17, time18, time19, time20," - "time21, time22, time23)" - "VALUES (chan_, new_nick, type_, letters_, words_, line_, actions_, smileys_happy_," - "smileys_sad_, smileys_other_, kicks_, kicked_, modes_, topics_," - "time0_, time1_, time2_, time3_, time4_, time5_, time6_, time7_, time8_, " - "time9_, time10_, time11_, time12_, time13_, time14_, time15_, time16_, " - "time17_, time18_, time19_, time20_, time21_, time22_, time23_)" - "ON DUPLICATE KEY UPDATE letters=letters+VALUES(letters), words=words+VALUES(words)," - "line=line+VALUES(line), actions=actions+VALUES(actions)," - "smileys_happy=smileys_happy+VALUES(smileys_happy)," - "smileys_sad=smileys_sad+VALUES(smileys_sad)," - "smileys_other=smileys_other+VALUES(smileys_other)," - "kicks=kicks+VALUES(kicks), kicked=kicked+VALUES(kicked)," - "modes=modes+VALUES(modes), topics=topics+VALUES(topics)," - "time1=time1+VALUES(time1), time2=time2+VALUES(time2), time3=time3+VALUES(time3)," - "time4=time4+VALUES(time4), time5=time5+VALUES(time5), time6=time6+VALUES(time6)," - "time7=time7+VALUES(time7), time8=time8+VALUES(time8), time9=time9+VALUES(time9)," - "time10=time10+VALUES(time10), time11=time11+VALUES(time11), time12=time12+VALUES(time12)," - "time13=time13+VALUES(time13), time14=time14+VALUES(time14), time15=time15+VALUES(time15)," - "time16=time16+VALUES(time16), time17=time17+VALUES(time17), time18=time18+VALUES(time18)," - "time19=time19+VALUES(time19), time20=time20+VALUES(time20), time21=time21+VALUES(time21)," - "time22=time22+VALUES(time22), time23=time23+VALUES(time23);" - "END LOOP;" - "DELETE FROM `" + prefix + "chanstats` WHERE `nick` = old_nick;" - "END my_cursor;" - "END IF;" - "END;"; - this->RunQuery(query); - - /* don't prepend any database prefix to events so we can always delete/change old events */ - if (this->HasEvent("chanstats_event_cleanup_daily")) - { - query = "DROP EVENT chanstats_event_cleanup_daily"; - this->RunQuery(query); - } - query = "CREATE EVENT `chanstats_event_cleanup_daily` " - "ON SCHEDULE EVERY 1 DAY STARTS CURRENT_DATE " - "DO UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0," - "smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0," - "time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0," - "time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0," - "time20=0, time21=0, time22=0, time23=0 " - "WHERE type='daily';"; - this->RunQuery(query); - - if (this->HasEvent("chanstats_event_cleanup_weekly")) - { - query = "DROP EVENT `chanstats_event_cleanup_weekly`"; - this->RunQuery(query); - } - query = "CREATE EVENT `chanstats_event_cleanup_weekly` " - "ON SCHEDULE EVERY 1 WEEK STARTS ADDDATE(CURDATE(), INTERVAL 1-DAYOFWEEK(CURDATE()) DAY) " - "DO UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0," - "smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0," - "time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0," - "time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0," - "time20=0, time21=0, time22=0, time23=0 " - "WHERE type='weekly';"; - this->RunQuery(query); - - if (this->HasEvent("chanstats_event_cleanup_monthly")) - { - query = "DROP EVENT `chanstats_event_cleanup_monthly`;"; - this->RunQuery(query); - } - query = "CREATE EVENT `chanstats_event_cleanup_monthly` " - "ON SCHEDULE EVERY 1 MONTH STARTS LAST_DAY(CURRENT_TIMESTAMP) + INTERVAL 1 DAY " - "DO BEGIN " - "UPDATE `" + prefix + "chanstats` SET letters=0, words=0, line=0, actions=0, smileys_happy=0," - "smileys_sad=0, smileys_other=0, kicks=0, modes=0, topics=0, time0=0, time1=0, time2=0," - "time3=0, time4=0, time5=0, time6=0, time7=0, time8=0, time9=0, time10=0, time11=0," - "time12=0, time13=0, time14=0, time15=0, time16=0, time17=0, time18=0, time19=0, " - "time20=0, time21=0, time22=0, time23=0 " - "WHERE type='monthly';" - "OPTIMIZE TABLE `" + prefix + "chanstats`;" - "END;"; - this->RunQuery(query); - } - - - public: - MChanstats(const Anope::string &modname, const Anope::string &creator) : - Module(modname, creator, EXTRA | VENDOR), - cs_stats(this, "CS_STATS"), ns_stats(this, "NS_STATS"), - commandcssetchanstats(this), commandnssetchanstats(this), commandnssasetchanstats(this), - sqlinterface(this) - { - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - prefix = block->Get("prefix", "anope_"); - SmileysHappy = block->Get("SmileysHappy"); - SmileysSad = block->Get("SmileysSad"); - SmileysOther = block->Get("SmileysOther"); - NSDefChanstats = block->Get("ns_def_chanstats"); - CSDefChanstats = block->Get("cs_def_chanstats"); - Anope::string engine = block->Get("engine"); - this->sql = ServiceReference("SQL::Provider", engine); - if (sql) - this->CheckTables(); - else - Log(this) << "no database connection to " << engine; - } - - void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override - { - if (!show_all) - return; - if (cs_stats.HasExt(ci)) - info.AddOption(_("Chanstats")); - } - - void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override - { - if (!show_hidden) - return; - if (ns_stats.HasExt(na->GetAccount())) - info.AddOption(_("Chanstats")); - } - - void OnTopicUpdated(User *source, Channel *c, const Anope::string &user, const Anope::string &topic) override - { - if (!source || !source->Account() || !c->ci || !cs_stats.HasExt(c->ci)) - return; - query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);"; - query.SetValue("channel", c->name); - query.SetValue("nick", GetDisplay(source)); - this->RunQuery(query); - } - - EventReturn OnChannelModeSet(Channel *c, const MessageSource &setter, ChannelMode *mode, const Anope::string ¶m) override - { - this->OnModeChange(c, setter.GetUser()); - return EVENT_CONTINUE; - } - - EventReturn OnChannelModeUnset(Channel *c, const MessageSource &setter, ChannelMode *, const Anope::string ¶m) override - { - this->OnModeChange(c, setter.GetUser()); - return EVENT_CONTINUE; - } - - private: - void OnModeChange(Channel *c, User *u) - { - if (!u || !u->Account() || !c->ci || !cs_stats.HasExt(c->ci)) - return; - - query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);"; - query.SetValue("channel", c->name); - query.SetValue("nick", GetDisplay(u)); - this->RunQuery(query); - } - - public: - void OnPreUserKicked(const MessageSource &source, ChanUserContainer *cu, const Anope::string &kickmsg) override - { - if (!cu->chan->ci || !cs_stats.HasExt(cu->chan->ci)) - return; - - query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0);"; - query.SetValue("channel", cu->chan->name); - query.SetValue("nick", GetDisplay(cu->user)); - this->RunQuery(query); - - query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0);"; - query.SetValue("channel", cu->chan->name); - query.SetValue("nick", GetDisplay(source.GetUser())); - this->RunQuery(query); - } - - void OnPrivmsg(User *u, Channel *c, Anope::string &msg) override - { - if (!c->ci || !cs_stats.HasExt(c->ci)) - return; - - size_t letters = msg.length(); - size_t words = this->CountWords(msg); - - size_t action = 0; - if (msg.find("\01ACTION")!=Anope::string::npos) - { - action = 1; - letters = letters - 7; - words--; - } - - // count smileys - size_t smileys_happy = CountSmileys(msg, SmileysHappy); - size_t smileys_sad = CountSmileys(msg, SmileysSad); - size_t smileys_other = CountSmileys(msg, SmileysOther); - - // do not count smileys as words - size_t smileys = smileys_happy + smileys_sad + smileys_other; - if (smileys > words) - words = 0; - else - words = words - smileys; - - query = "CALL " + prefix + "chanstats_proc_update(@channel@, @nick@, 1, @letters@, @words@, @action@, " - "@smileys_happy@, @smileys_sad@, @smileys_other@, '0', '0', '0', '0');"; - query.SetValue("channel", c->name); - query.SetValue("nick", GetDisplay(u)); - query.SetValue("letters", letters); - query.SetValue("words", words); - query.SetValue("action", action); - query.SetValue("smileys_happy", smileys_happy); - query.SetValue("smileys_sad", smileys_sad); - query.SetValue("smileys_other", smileys_other); - this->RunQuery(query); - } - - void OnDelCore(NickServ::Account *nc) override - { - query = "DELETE FROM `" + prefix + "chanstats` WHERE `nick` = @nick@;"; - query.SetValue("nick", nc->GetDisplay()); - this->RunQuery(query); - } - - void OnChangeCoreDisplay(NickServ::Account *nc, const Anope::string &newdisplay) override - { - query = "CALL " + prefix + "chanstats_proc_chgdisplay(@old_display@, @new_display@);"; - query.SetValue("old_display", nc->GetDisplay()); - query.SetValue("new_display", newdisplay); - this->RunQuery(query); - } - - void OnDelChan(ChanServ::Channel *ci) override - { - query = "DELETE FROM `" + prefix + "chanstats` WHERE `chan` = @channel@;"; - query.SetValue("channel", ci->GetName()); - this->RunQuery(query); - } - - void OnChanRegistered(ChanServ::Channel *ci) - { - if (CSDefChanstats) - ci->Extend("CS_STATS"); - } - - void OnNickRegister(User *user, NickAlias *na, const Anope::string &) - { - if (NSDefChanstats) - na->GetAccount()->Extend("NS_STATS"); - } -}; - -MODULE_INIT(MChanstats) - diff --git a/modules/global/CMakeLists.txt b/modules/global/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/global/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/global/global.cpp b/modules/global/global.cpp new file mode 100644 index 000000000..d6106b2be --- /dev/null +++ b/modules/global/global.cpp @@ -0,0 +1,58 @@ +/* Global core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/global.h" + +class CommandGLGlobal : public Command +{ + public: + CommandGLGlobal(Module *creator) : Command(creator, "global/global", 1, 1) + { + this->SetDesc(_("Send a message to all users")); + this->SetSyntax(_("\037message\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &msg = params[0]; + + if (!Global::service) + { + source.Reply("No global reference, is global loaded?"); + return; + } + + Log(LOG_ADMIN, source, this); + Global::service->SendGlobal(NULL, source.GetNick(), msg); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows Services Operators to send a message to all users on the network." + " The message will be sent from \002{0}\002."), source.service->nick); + return true; + } +}; + +class GLGlobal : public Module +{ + CommandGLGlobal commandglglobal; + + public: + GLGlobal(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandglglobal(this) + { + + } +}; + +MODULE_INIT(GLGlobal) diff --git a/modules/global/main/CMakeLists.txt b/modules/global/main/CMakeLists.txt new file mode 100644 index 000000000..781f0ef1f --- /dev/null +++ b/modules/global/main/CMakeLists.txt @@ -0,0 +1 @@ +build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/global/main/global.cpp b/modules/global/main/global.cpp new file mode 100644 index 000000000..f31605acb --- /dev/null +++ b/modules/global/main/global.cpp @@ -0,0 +1,107 @@ +/* Global core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/help.h" +#include "modules/global.h" + +class GlobalCore : public Module + , public Global::GlobalService + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + Reference Global; + + void ServerGlobal(ServiceBot *sender, Server *s, const Anope::string &message) + { + if (s != Me && !s->IsJuped()) + s->Notice(sender, message); + for (unsigned i = 0, j = s->GetLinks().size(); i < j; ++i) + this->ServerGlobal(sender, s->GetLinks()[i], message); + } + + public: + GlobalCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) + , GlobalService(this) + { + } + + void SendGlobal(ServiceBot *sender, const Anope::string &source, const Anope::string &message) override + { + if (Me->GetLinks().empty()) + return; + if (!sender) + sender = Global; + if (!sender) + return; + + Anope::string rmessage; + + if (!source.empty() && !Config->GetModule("global")->Get("anonymousglobal")) + rmessage = "[" + source + "] " + message; + else + rmessage = message; + + this->ServerGlobal(sender, Servers::GetUplink(), rmessage); + } + + void OnReload(Configuration::Conf *conf) override + { + const Anope::string &glnick = conf->GetModule(this)->Get("client"); + + if (glnick.empty()) + throw ConfigException(Module::name + ": must be defined"); + + ServiceBot *bi = ServiceBot::Find(glnick, true); + if (!bi) + throw ConfigException(Module::name + ": no bot named " + glnick); + + Global = bi; + } + + void OnRestart() override + { + const Anope::string &gl = Config->GetModule(this)->Get("globaloncycledown"); + if (!gl.empty()) + this->SendGlobal(Global, "", gl); + } + + void OnShutdown() override + { + const Anope::string &gl = Config->GetModule(this)->Get("globaloncycledown"); + if (!gl.empty()) + this->SendGlobal(Global, "", gl); + } + + void OnNewServer(Server *s) override + { + const Anope::string &gl = Config->GetModule(this)->Get("globaloncycleup"); + if (!gl.empty() && !Me->IsSynced()) + s->Notice(Global, gl); + } + + EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override + { + if (!params.empty() || source.c || source.service != *Global) + return EVENT_CONTINUE; + source.Reply(_("%s commands:"), Global->nick.c_str()); + return EVENT_CONTINUE; + } + + void OnPostHelp(CommandSource &source, const std::vector ¶ms) override + { + } +}; + +MODULE_INIT(GlobalCore) + diff --git a/modules/greet.cpp b/modules/greet.cpp new file mode 100644 index 000000000..ebc8c1612 --- /dev/null +++ b/modules/greet.cpp @@ -0,0 +1,212 @@ +/* + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_info.h" +#include "modules/bs_info.h" +#include "modules/ns_set.h" + +class CommandBSSetGreet : public Command +{ + public: + CommandBSSetGreet(Module *creator, const Anope::string &sname = "botserv/set/greet") : Command(creator, sname, 2, 2) + { + this->SetDesc(_("Enable greet messages")); + this->SetSyntax(_("\037channel\037 {\037ON|OFF\037}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &value = params[1]; + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!source.HasPriv("botserv/administration") && !source.AccessFor(ci).HasPriv("SET")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "SET", ci->GetName()); + return; + } + + if (value.equals_ci("ON")) + { + bool override = !source.AccessFor(ci).HasPriv("SET"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to enable greets"; + + ci->SetS("BS_GREET", true); + source.Reply(_("Greet mode for \002{0}\002 is now \002on\002."), ci->GetName()); + } + else if (value.equals_ci("OFF")) + { + bool override = !source.AccessFor(ci).HasPriv("SET"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable greets"; + + ci->UnsetS("BS_GREET"); + source.Reply(_("Greet mode for \002{0}\002 is now \002off\002."), ci->GetName()); + } + else + this->OnSyntaxError(source, source.command); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables \002greet\002 mode on \037channel\037." + " When \002greet\002 mode is enabled, the assigned service bot will display the greet messages of users joining the channel, if they have the \002{0}\002 privilege."), + "GREET"); + return true; + } +}; + +class CommandNSSetGreet : public Command +{ + public: + CommandNSSetGreet(Module *creator, const Anope::string &sname = "nickserv/set/greet", size_t min = 0) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Associate a greet message with your nickname")); + this->SetSyntax(_("\037message\037")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (!param.empty()) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the greet of " << nc->GetDisplay(); + nc->SetS("greet", param); + source.Reply(_("Greet message for \002{0}\002 changed to \002{1}\002."), nc->GetDisplay(), param); + } + else + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to unset the greet of " << nc->GetDisplay(); + nc->UnsetS("greet"); + source.Reply(_("Greet message for \002{0}\002 unset."), nc->GetDisplay()); + } + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params.size() > 0 ? params[0] : ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Sets your greet to \037message\037. Your greet is displayed when you enter channels that have the greet option enabled if you have the \002{0}\002 privilege on the channel."), + "GREET"); + return true; + } +}; + +class CommandNSSASetGreet : public CommandNSSetGreet +{ + public: + CommandNSSASetGreet(Module *creator) : CommandNSSetGreet(creator, "nickserv/saset/greet", 1) + { + this->ClearSyntax(); + this->SetSyntax(_("\037user\037 \037message\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params.size() > 1 ? params[1] : ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Sets the greet for \037user\037 to \037message\037. Greet messages are displayed when users enter channels that have the greet option enabiled if they have the \002{0}\002 privilege on the channel."), + "GREET"); + return true; + } +}; + +class Greet : public Module + , public EventHook + , public EventHook + , public EventHook +{ + /* channel setting for whether or not greet should be shown */ + Serialize::Field bs_greet; + /* user greets */ + Serialize::Field ns_greet; + + CommandBSSetGreet commandbssetgreet; + CommandNSSetGreet commandnssetgreet; + CommandNSSASetGreet commandnssasetgreet; + + public: + Greet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , bs_greet(this, ChanServ::channel, "BS_GREET") + , ns_greet(this, NickServ::account, "greet") + , commandbssetgreet(this) + , commandnssetgreet(this) + , commandnssasetgreet(this) + { + } + + void OnJoinChannel(User *user, Channel *c) override + { + /* Only display the greet if the main uplink we're connected + * to has synced, or we'll get greet-floods when the net + * recovers from a netsplit. -GD + */ + if (!c->ci || !c->ci->GetBot() || !user->server->IsSynced() || !user->Account()) + return; + + Anope::string greet = ns_greet.Get(user->Account()); + if (bs_greet.HasExt(c->ci) && !greet.empty() && c->FindUser(c->ci->GetBot()) && c->ci->AccessFor(user).HasPriv("GREET")) + { + IRCD->SendPrivmsg(c->ci->GetBot(), c->name, "[%s] %s", user->Account()->GetDisplay().c_str(), greet.c_str()); + c->ci->GetBot()->lastmsg = Anope::CurTime; + } + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override + { + Anope::string greet = ns_greet.Get(na->GetAccount()); + if (!greet.empty()) + info[_("Greet")] = greet; + } + + void OnServiceBot(CommandSource &source, ServiceBot *bi, ChanServ::Channel *ci, InfoFormatter &info) override + { + if (bs_greet.HasExt(ci)) + info.AddOption(_("Greet")); + } +}; + +MODULE_INIT(Greet) diff --git a/modules/help.cpp b/modules/help.cpp new file mode 100644 index 000000000..debb25837 --- /dev/null +++ b/modules/help.cpp @@ -0,0 +1,197 @@ +/* Core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/help.h" + +class CommandHelp : public Command +{ + static CommandGroup *FindGroup(const Anope::string &name) + { + for (unsigned i = 0; i < Config->CommandGroups.size(); ++i) + { + CommandGroup &gr = Config->CommandGroups[i]; + if (gr.name == name) + return &gr; + } + + return NULL; + } + + EventHandlers &onhelp; + + public: + CommandHelp(Module *creator, EventHandlers &event) : Command(creator, "generic/help", 0), onhelp(event) + { + this->SetDesc(_("Displays this list and give information about commands")); + this->AllowUnregistered(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + EventReturn MOD_RESULT = this->onhelp(&Event::Help::OnPreHelp, source, params); + if (MOD_RESULT == EVENT_STOP) + return; + + Anope::string source_command = source.command; + const ServiceBot *bi = source.service; + const CommandInfo::map &map = source.c ? Config->Fantasy : bi->commands; + bool hide_privileged_commands = Config->GetBlock("options")->Get("hideprivilegedcommands"), + hide_registered_commands = Config->GetBlock("options")->Get("hideregisteredcommands"); + + if (params.empty() || params[0].equals_ci("ALL")) + { + bool all = !params.empty() && params[0].equals_ci("ALL"); + typedef std::map > GroupInfo; + GroupInfo groups; + + if (all) + source.Reply(_("All available commands for \002{0}\002:"), source.service->nick); + + for (CommandInfo::map::const_iterator it = map.begin(), it_end = map.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + const CommandInfo &info = it->second; + + if (info.hide) + continue; + + // Smaller command exists + Anope::string cmd; + spacesepstream(c_name).GetToken(cmd, 0); + if (cmd != it->first && map.count(cmd)) + continue; + + ServiceReference c("Command", info.name); + if (!c) + continue; + + if (hide_registered_commands && !c->AllowUnregistered() && !source.GetAccount()) + continue; + + if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission)) + continue; + + if (!info.group.empty() && !all) + { + CommandGroup *gr = FindGroup(info.group); + if (gr != NULL) + { + groups[gr].push_back(c_name); + continue; + } + } + + source.command = c_name; + c->OnServHelp(source); + + } + + for (GroupInfo::iterator it = groups.begin(), it_end = groups.end(); it != it_end; ++it) + { + CommandGroup *gr = it->first; + + source.Reply(" "); + source.Reply("{0}", gr->description); + + Anope::string buf; + for (std::list::iterator it2 = it->second.begin(), it2_end = it->second.end(); it2 != it2_end; ++it2) + { + const Anope::string &c_name = *it2; + buf += ", " + c_name; + } + if (buf.length() > 2) + source.Reply(" {0}", buf.substr(2)); + } + if (!groups.empty()) + { + source.Reply(" "); + source.Reply(_("Use the \002{0} ALL\002 command to list all commands and their descriptions."), source_command); + } + } + else + { + bool helped = false; + for (unsigned max = params.size(); max > 0; --max) + { + Anope::string full_command; + for (unsigned i = 0; i < max; ++i) + full_command += " " + params[i]; + full_command.erase(full_command.begin()); + + CommandInfo::map::const_iterator it = map.find(full_command); + if (it == map.end()) + continue; + + const CommandInfo &info = it->second; + + ServiceReference c("Command", info.name); + if (!c) + continue; + + if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission)) + continue; + + // Allow unregistered users to see help for commands that they explicitly request help for + + const Anope::string &subcommand = params.size() > max ? params[max] : ""; + source.command = it->first; + + c->SendSyntax(source); + if (!c->OnHelp(source, subcommand)) + continue; + + helped = true; + + /* Inform the user what permission is required to use the command */ + if (!info.permission.empty()) + { + source.Reply(" "); + source.Reply(_("Access to this command requires the permission \002{0}\002 to be present in your opertype."), info.permission); + } + if (!c->AllowUnregistered() && !source.nc) + { + if (info.permission.empty()) + source.Reply(" "); + source.Reply( _("You need to be identified to use this command.")); + } + /* User doesn't have the proper permission to use this command */ + else if (!info.permission.empty() && !source.HasCommand(info.permission)) + { + source.Reply(_("You cannot use this command.")); + } + + break; + } + + if (helped == false) + source.Reply(_("No help available for \002{0}\002."), params[0]); + } + + this->onhelp(&Event::Help::OnPostHelp, source, params); + } +}; + +class Help : public Module +{ + CommandHelp commandhelp; + EventHandlers onhelp; + + public: + Help(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandhelp(this, onhelp) + , onhelp(this) + { + + } +}; + +MODULE_INIT(Help) diff --git a/modules/helpchan.cpp b/modules/helpchan.cpp new file mode 100644 index 000000000..3897b2b5a --- /dev/null +++ b/modules/helpchan.cpp @@ -0,0 +1,32 @@ +/* + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" + +class HelpChannel : public Module + , public EventHook +{ + public: + HelpChannel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + { + } + + EventReturn OnChannelModeSet(Channel *c, const MessageSource &, ChannelMode *mode, const Anope::string ¶m) override + { + if (mode->name == "OP" && c && c->ci && c->name.equals_ci(Config->GetModule(this)->Get("helpchannel"))) + { + User *u = User::Find(param); + + if (u && c->ci->AccessFor(u).HasPriv("OPME")) + u->SetMode(Config->GetClient("OperServ"), "HELPOP"); + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(HelpChannel) diff --git a/modules/hostserv/CMakeLists.txt b/modules/hostserv/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/hostserv/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/hostserv/del.cpp b/modules/hostserv/del.cpp new file mode 100644 index 000000000..b45c10a61 --- /dev/null +++ b/modules/hostserv/del.cpp @@ -0,0 +1,109 @@ +/* HostServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/hs_del.h" + +class CommandHSDel : public Command +{ + EventHandlers &OnDeleteVhost; + + public: + CommandHSDel(Module *creator, EventHandlers &onDeleteVhost) : Command(creator, "hostserv/del", 1, 1), OnDeleteVhost(onDeleteVhost) + { + this->SetDesc(_("Delete the vhost of another user")); + this->SetSyntax(_("\037user\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + const Anope::string &nick = params[0]; + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + Log(LOG_ADMIN, source, this) << "for user " << na->GetNick(); + this->OnDeleteVhost(&Event::DeleteVhost::OnDeleteVhost, na); + na->RemoveVhost(); + source.Reply(_("Vhost for \002{0}\002 has been removed."), na->GetNick()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Removes the vhost of \037user\037.")); + return true; + } +}; + +class CommandHSDelAll : public Command +{ + EventHandlers &ondeletevhost; + + public: + CommandHSDelAll(Module *creator, EventHandlers &event) : Command(creator, "hostserv/delall", 1, 1), ondeletevhost(event) + { + this->SetDesc(_("Delete the vhost for all nicks in a group")); + this->SetSyntax(_("\037group\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + const Anope::string &nick = params[0]; + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + this->ondeletevhost(&Event::DeleteVhost::OnDeleteVhost, na); + + NickServ::Account *nc = na->GetAccount(); + for (NickServ::Nick *na2 : nc->GetRefs(NickServ::nick)) + na2->RemoveVhost(); + Log(LOG_ADMIN, source, this) << "for all nicks in group " << nc->GetDisplay(); + source.Reply(_("Vhosts for group \002{0}\002 have been removed."), nc->GetDisplay()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Removes the vhost of all nicks in the group \037group\037.")); + return true; + } +}; + +class HSDel : public Module +{ + CommandHSDel commandhsdel; + CommandHSDelAll commandhsdelall; + EventHandlers ondeletevhost; + + public: + HSDel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandhsdel(this, ondeletevhost) + , commandhsdelall(this, ondeletevhost) + , ondeletevhost(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } +}; + +MODULE_INIT(HSDel) diff --git a/modules/hostserv/group.cpp b/modules/hostserv/group.cpp new file mode 100644 index 000000000..c3971fdea --- /dev/null +++ b/modules/hostserv/group.cpp @@ -0,0 +1,117 @@ +/* HostServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_group.h" + +class CommandHSGroup : public Command +{ + bool setting; + + public: + void Sync(NickServ::Nick *na) + { + if (setting) + return; + + if (!na || !na->HasVhost()) + return; + + setting = true; + for (NickServ::Nick *nick : na->GetAccount()->GetRefs(NickServ::nick)) + { + nick->SetVhost(na->GetVhostIdent(), na->GetVhostHost(), na->GetVhostCreator()); + Event::OnSetVhost(&Event::SetVhost::OnSetVhost, nick); + } + setting = false; + } + + CommandHSGroup(Module *creator) : Command(creator, "hostserv/group", 0, 0), setting(false) + { + this->SetDesc(_("Syncs the vhost for all nicks in a group")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(source.GetNick()); + if (!na || na->GetAccount() != source.GetAccount()) + { + source.Reply(_("Access denied.")); + return; + } + + if (!na->HasVhost()) + { + source.Reply(_("There is no vhost assigned to this nickname.")); + return; + } + + this->Sync(na); + if (!na->GetVhostIdent().empty()) + source.Reply(_("All vhosts in the group \002{0}\002 have been set to \002{1}\002@\002{2}\002."), source.nc->GetDisplay(), na->GetVhostIdent(), na->GetVhostHost()); + else + source.Reply(_("All vhosts in the group \002{0}\002 have been set to \002{1}\002."), source.nc->GetDisplay(), na->GetVhostHost()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets the vhost of all nicks in your group to the vhost of your current nick.")); + return true; + } +}; + +class HSGroup : public Module + , public EventHook + , public EventHook +{ + CommandHSGroup commandhsgroup; + bool syncongroup; + bool synconset; + + public: + HSGroup(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandhsgroup(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } + + void OnSetVhost(NickServ::Nick *na) override + { + if (!synconset) + return; + + commandhsgroup.Sync(na); + } + + void OnNickGroup(User *u, NickServ::Nick *na) override + { + if (!syncongroup) + return; + + commandhsgroup.Sync(na); + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + syncongroup = block->Get("syncongroup"); + synconset = block->Get("synconset"); + } +}; + +MODULE_INIT(HSGroup) diff --git a/modules/hostserv/list.cpp b/modules/hostserv/list.cpp new file mode 100644 index 000000000..738f9f1bd --- /dev/null +++ b/modules/hostserv/list.cpp @@ -0,0 +1,159 @@ +/* HostServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandHSList : public Command +{ + public: + CommandHSList(Module *creator) : Command(creator, "hostserv/list", 0, 1) + { + this->SetDesc(_("Displays one or more vhost entries")); + this->SetSyntax(_("[\037key\037|\037#X-Y\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &key = !params.empty() ? params[0] : ""; + int from = 0, to = 0, counter = 1; + + /** + * Do a check for a range here, then in the next loop + * we'll only display what has been requested.. + **/ + if (!key.empty() && key[0] == '#') + { + size_t tmp = key.find('-'); + if (tmp == Anope::string::npos || tmp == key.length() || tmp == 1) + { + source.Reply(_("Incorrect range specified. The correct syntax is \002#\037from\037-\037to\037\002.")); + return; + } + for (unsigned i = 1, end = key.length(); i < end; ++i) + { + if (!isdigit(key[i]) && i != tmp) + { + source.Reply(_("Incorrect range specified. The correct syntax is \002#\037from\037-\037to\037\002.")); + return; + } + try + { + from = convertTo(key.substr(1, tmp - 1)); + to = convertTo(key.substr(tmp + 1)); + } + catch (const ConvertException &) { } + } + } + + unsigned display_counter = 0, listmax = Config->GetModule(this->owner)->Get("listmax", "50"); + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Nick")).AddColumn(_("Vhost")).AddColumn(_("Creator")).AddColumn(_("Created")); + + for (NickServ::Nick *na : NickServ::service->GetNickList()) + { + if (!na->HasVhost()) + continue; + + if (!key.empty() && key[0] != '#') + { + if ((Anope::Match(na->GetNick(), key) || Anope::Match(na->GetVhostHost(), key)) && display_counter < listmax) + { + ++display_counter; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(display_counter); + entry["Nick"] = na->GetNick(); + if (!na->GetVhostIdent().empty()) + entry["Vhost"] = na->GetVhostIdent() + "@" + na->GetVhostHost(); + else + entry["Vhost"] = na->GetVhostHost(); + entry["Creator"] = na->GetVhostCreator(); + entry["Created"] = Anope::strftime(na->GetVhostCreated(), NULL, true); + list.AddEntry(entry); + } + } + else + { + /** + * List the host if its in the display range, and not more + * than NSListMax records have been displayed... + **/ + if (((counter >= from && counter <= to) || (!from && !to)) && display_counter < listmax) + { + ++display_counter; + ListFormatter::ListEntry entry; + entry["Number"] = stringify(display_counter); + entry["Nick"] = na->GetNick(); + if (!na->GetVhostIdent().empty()) + entry["Vhost"] = na->GetVhostIdent() + "@" + na->GetVhostHost(); + else + entry["Vhost"] = na->GetVhostHost(); + entry["Creator"] = na->GetVhostCreator(); + entry["Created"] = Anope::strftime(na->GetVhostCreated(), NULL, true); + list.AddEntry(entry); + } + } + ++counter; + } + + if (!display_counter) + { + source.Reply(_("No records to display.")); + return; + } + + if (!key.empty()) + source.Reply(_("Displayed records matching key \002{0}\002 (count: \002{1}\002)."), key, display_counter); + else + { + if (from) + source.Reply(_("Displayed records from \002{0}\002 to \002{1}\002."), from, to); + else + source.Reply(_("Displayed all records (count: \002{0}\002)."), display_counter); + } + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Lists all vhosts. If \037key\037 is specified, only entries whose nick or vhost match the pattern given in \037key\037 are displayed." + "If a \037#X-Y\037 style is used, only entries between the range of \002X\002 and \002Y\002 will be displayed.\n" + "\n" + "Examples:\n" + " {0} Rob*\n" + " Lists all entries with the nick or vhost beginning with \"Rob\"\n" + "\n" + " {0} #1-3\n" + " Lists the first three entries."), + source.command); + return true; + } +}; + +class HSList : public Module +{ + CommandHSList commandhslist; + + public: + HSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandhslist(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } +}; + +MODULE_INIT(HSList) diff --git a/modules/hostserv/main/CMakeLists.txt b/modules/hostserv/main/CMakeLists.txt new file mode 100644 index 000000000..781f0ef1f --- /dev/null +++ b/modules/hostserv/main/CMakeLists.txt @@ -0,0 +1 @@ +build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/hostserv/main/hostserv.cpp b/modules/hostserv/main/hostserv.cpp new file mode 100644 index 000000000..285ea893b --- /dev/null +++ b/modules/hostserv/main/hostserv.cpp @@ -0,0 +1,135 @@ +/* HostServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_update.h" +#include "modules/hs_del.h" +#include "modules/help.h" + +class HostServCore : public Module + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + Reference HostServ; + + public: + HostServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } + + void OnReload(Configuration::Conf *conf) override + { + const Anope::string &hsnick = conf->GetModule(this)->Get("client"); + + if (hsnick.empty()) + throw ConfigException(Module::name + ": must be defined"); + + ServiceBot *bi = ServiceBot::Find(hsnick, true); + if (!bi) + throw ConfigException(Module::name + ": no bot named " + hsnick); + + HostServ = bi; + } + + void OnUserLogin(User *u) override + { + if (!IRCD->CanSetVHost) + return; + + NickServ::Nick *na = NickServ::FindNick(u->nick); + if (!na || na->GetAccount() != u->Account() || !na->HasVhost()) + na = NickServ::FindNick(u->Account()->GetDisplay()); + if (!na || !na->HasVhost()) + return; + + if (u->vhost.empty() || !u->vhost.equals_cs(na->GetVhostHost()) || (!na->GetVhostIdent().empty() && !u->GetVIdent().equals_cs(na->GetVhostIdent()))) + { + IRCD->SendVhost(u, na->GetVhostIdent(), na->GetVhostHost()); + + u->vhost = na->GetVhostHost(); + u->UpdateHost(); + + if (IRCD->CanSetVIdent && !na->GetVhostIdent().empty()) + u->SetVIdent(na->GetVhostIdent()); + + if (HostServ) + { + if (!na->GetVhostIdent().empty()) + u->SendMessage(*HostServ, _("Your vhost of \002%s\002@\002%s\002 is now activated."), na->GetVhostIdent().c_str(), na->GetVhostHost().c_str()); + else + u->SendMessage(*HostServ, _("Your vhost of \002%s\002 is now activated."), na->GetVhostHost().c_str()); + } + } + } + + void OnNickUpdate(User *u) override + { + this->OnUserLogin(u); + } + + EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override + { + if (!params.empty() || source.c || source.service != *HostServ) + return EVENT_CONTINUE; + source.Reply(_("%s commands:"), HostServ->nick.c_str()); + return EVENT_CONTINUE; + } + + void OnPostHelp(CommandSource &source, const std::vector ¶ms) override + { + } + + void OnSetVhost(NickServ::Nick *na) override + { + if (Config->GetModule(this)->Get("activate_on_set")) + { + User *u = User::Find(na->GetNick()); + + if (u && u->Account() == na->GetAccount()) + { + IRCD->SendVhost(u, na->GetVhostIdent(), na->GetVhostHost()); + + u->vhost = na->GetVhostHost(); + u->UpdateHost(); + + if (IRCD->CanSetVIdent && !na->GetVhostIdent().empty()) + u->SetVIdent(na->GetVhostIdent()); + + if (HostServ) + { + if (!na->GetVhostIdent().empty()) + u->SendMessage(*HostServ, _("Your vhost of \002%s\002@\002%s\002 is now activated."), na->GetVhostIdent().c_str(), na->GetVhostHost().c_str()); + else + u->SendMessage(*HostServ, _("Your vhost of \002%s\002 is now activated."), na->GetVhostHost().c_str()); + } + } + } + } + + void OnDeleteVhost(NickServ::Nick *na) override + { + if (Config->GetModule(this)->Get("activate_on_set")) + { + User *u = User::Find(na->GetNick()); + + if (u && u->Account() == na->GetAccount()) + IRCD->SendVhostDel(u); + } + } +}; + +MODULE_INIT(HostServCore) + diff --git a/modules/hostserv/off.cpp b/modules/hostserv/off.cpp new file mode 100644 index 000000000..8f3c7e3da --- /dev/null +++ b/modules/hostserv/off.cpp @@ -0,0 +1,60 @@ +/* HostServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandHSOff : public Command +{ + public: + CommandHSOff(Module *creator) : Command(creator, "hostserv/off", 0, 0) + { + this->SetDesc(_("Deactivates your assigned vhost")); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + User *u = source.GetUser(); + NickServ::Nick *na = NickServ::FindNick(u->nick); + + if (!na || !na->HasVhost() || na->GetAccount() != source.GetAccount()) + { + source.Reply(_("There is no vhost assigned to this nickname.")); + return; + } + + u->vhost.clear(); + IRCD->SendVhostDel(u); + Log(LOG_COMMAND, source, this) << "to disable their vhost"; + source.Reply(_("Your vhost was removed and the normal cloaking restored.")); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Deactivates your vhost.")); + return true; + } +}; + +class HSOff : public Module +{ + CommandHSOff commandhsoff; + + public: + HSOff(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandhsoff(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } +}; + +MODULE_INIT(HSOff) diff --git a/modules/hostserv/on.cpp b/modules/hostserv/on.cpp new file mode 100644 index 000000000..409d56ac5 --- /dev/null +++ b/modules/hostserv/on.cpp @@ -0,0 +1,70 @@ +/* HostServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandHSOn : public Command +{ + public: + CommandHSOn(Module *creator) : Command(creator, "hostserv/on", 0, 0) + { + this->SetDesc(_("Activates your assigned vhost")); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (!IRCD->CanSetVHost) + return; // HostServ wouldn't even be loaded at this point + + User *u = source.GetUser(); + NickServ::Nick *na = NickServ::FindNick(u->nick); + + if (!na || !na->HasVhost() || na->GetAccount() != u->Account()) + { + source.Reply(_("There is no vhost assigned to this nickname.")); + return; + } + + if (!na->GetVhostIdent().empty()) + source.Reply(_("Your vhost of \002{0}\002@\002{1}\002 is now activated."), na->GetVhostIdent(), na->GetVhostHost()); + else + source.Reply(_("Your vhost of \002{0}\002 is now activated."), na->GetVhostHost()); + + Log(LOG_COMMAND, source, this) << "to enable their vhost of " << (!na->GetVhostIdent().empty() ? na->GetVhostIdent() + "@" : "") << na->GetVhostHost(); + IRCD->SendVhost(u, na->GetVhostIdent(), na->GetVhostHost()); + u->vhost = na->GetVhostHost(); + if (IRCD->CanSetVIdent && !na->GetVhostIdent().empty()) + u->SetVIdent(na->GetVhostIdent()); + u->UpdateHost(); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Activates your vhost.")); + return true; + } +}; + +class HSOn : public Module +{ + CommandHSOn commandhson; + + public: + HSOn(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandhson(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } +}; + +MODULE_INIT(HSOn) diff --git a/modules/hostserv/request.cpp b/modules/hostserv/request.cpp new file mode 100644 index 000000000..8a064a84c --- /dev/null +++ b/modules/hostserv/request.cpp @@ -0,0 +1,414 @@ +/* + * + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + */ + +#include "module.h" +#include "modules/memoserv.h" + +static void req_send_memos(Module *me, CommandSource &source, const Anope::string &vIdent, const Anope::string &vHost); + +class HostRequest : public Serialize::Object +{ + public: + HostRequest(Serialize::TypeBase *type) : Serialize::Object(type) { } + HostRequest(Serialize::TypeBase *type, Serialize::ID id) : Serialize::Object(type, id) { } + + NickServ::Nick *GetNick(); + void SetNick(NickServ::Nick *na); + + Anope::string GetIdent(); + void SetIdent(const Anope::string &i); + + Anope::string GetHost(); + void SetHost(const Anope::string &h); + + time_t GetTime(); + void SetTime(const time_t &t); +}; + +class HostRequestType : public Serialize::Type +{ + public: + Serialize::ObjectField na; + Serialize::Field ident, host; + Serialize::Field time; + + HostRequestType(Module *me) : Serialize::Type(me, "HostRequest") + , na(this, "na", true) + , ident(this, "ident") + , host(this, "host") + , time(this, "time") + { + } +}; + +NickServ::Nick *HostRequest::GetNick() +{ + return Get(&HostRequestType::na); +} + +void HostRequest::SetNick(NickServ::Nick *na) +{ + Set(&HostRequestType::na, na); +} + +Anope::string HostRequest::GetIdent() +{ + return Get(&HostRequestType::ident); +} + +void HostRequest::SetIdent(const Anope::string &i) +{ + Set(&HostRequestType::ident, i); +} + +Anope::string HostRequest::GetHost() +{ + return Get(&HostRequestType::host); +} + +void HostRequest::SetHost(const Anope::string &h) +{ + Set(&HostRequestType::host, h); +} + +time_t HostRequest::GetTime() +{ + return Get(&HostRequestType::time); +} + +void HostRequest::SetTime(const time_t &t) +{ + Set(&HostRequestType::time, t); +} + +static Serialize::TypeReference hostrequest("HostRequest"); + +class CommandHSRequest : public Command +{ + public: + CommandHSRequest(Module *creator) : Command(creator, "hostserv/request", 1, 1) + { + this->SetDesc(_("Request a vHost for your nick")); + this->SetSyntax(_("vhost")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + User *u = source.GetUser(); + NickServ::Nick *na = NickServ::FindNick(source.GetNick()); + if (!na || na->GetAccount() != source.GetAccount()) + { + source.Reply(_("Access denied.")); //XXX with nonickownership this should be allowed. + return; + } + + if (source.GetAccount()->HasFieldS("UNCONFIRMED")) + { + source.Reply(_("You must confirm your account before you may request a vhost.")); + return; + } + + Anope::string rawhostmask = params[0]; + + Anope::string user, host; + size_t a = rawhostmask.find('@'); + + if (a == Anope::string::npos) + host = rawhostmask; + else + { + user = rawhostmask.substr(0, a); + host = rawhostmask.substr(a + 1); + } + + if (host.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + if (!user.empty()) + { + if (user.length() > Config->GetBlock("networkinfo")->Get("userlen")) + { + source.Reply(_("The username \002{0}\002 is too long, please use a username shorter than %d characters."), Config->GetBlock("networkinfo")->Get("userlen")); + return; + } + + if (!IRCD->CanSetVIdent) + { + source.Reply(_("Vhosts may not contain a username.")); + return; + } + + if (!IRCD->IsIdentValid(user)) + { + source.Reply(_("The requested username is not valid.")); + return; + } + } + + if (host.length() > Config->GetBlock("networkinfo")->Get("hostlen")) + { + source.Reply(_("The requested vhost is too long, please use a hostname no longer than {0} characters."), Config->GetBlock("networkinfo")->Get("hostlen")); + return; + } + + if (!IRCD->IsHostValid(host)) + { + source.Reply(_("The requested hostname is not valid.")); + return; + } + + time_t send_delay = Config->GetModule("memoserv")->Get("senddelay"); + if (Config->GetModule(this->owner)->Get("memooper") && send_delay > 0 && u && u->lastmemosend + send_delay > Anope::CurTime) + { + source.Reply(_("Please wait %d seconds before requesting a new vHost."), send_delay); + u->lastmemosend = Anope::CurTime; + return; + } + + HostRequest *req = hostrequest.Create(); + req->SetNick(na); + req->SetIdent(user); + req->SetHost(host); + req->SetTime(Anope::CurTime); + + source.Reply(_("Your vhost has been requested.")); + req_send_memos(owner, source, user, host); + Log(LOG_COMMAND, source, this) << "to request new vhost " << (!user.empty() ? user + "@" : "") << host; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Request the given \036vhost\037 to be actived for your nick by the network administrators. Please be patient while your request is being considered.")); + return true; + } +}; + +class CommandHSActivate : public Command +{ + public: + CommandHSActivate(Module *creator) : Command(creator, "hostserv/activate", 1, 1) + { + this->SetDesc(_("Approve the requested vHost of a user")); + this->SetSyntax(_("\037user\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + const Anope::string &nick = params[0]; + NickServ::Nick *na = NickServ::FindNick(nick); + + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + HostRequest *req = na->GetExt("hostrequest"); + if (!req) + { + source.Reply(_("\002{0}\002 does not have a pending vhost request."), na->GetNick()); + return; + } + + na->SetVhost(req->GetIdent(), req->GetHost(), source.GetNick(), req->GetTime()); + Event::OnSetVhost(&Event::SetVhost::OnSetVhost, na); + + if (Config->GetModule(this->owner)->Get("memouser") && MemoServ::service) + MemoServ::service->Send(source.service->nick, na->GetNick(), _("[auto memo] Your requested vHost has been approved."), true); + + source.Reply(_("Vhost for \002{0}\002 has been activated."), na->GetNick()); + Log(LOG_COMMAND, source, this) << "for " << na->GetNick() << " for vhost " << (!req->GetIdent().empty() ? req->GetIdent() + "@" : "") << req->GetHost(); + req->Delete(); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Activate the requested vhost for the given user.")); + if (Config->GetModule(this->owner)->Get("memouser")) + source.Reply(_("A memo informing the user will also be sent.")); + + return true; + } +}; + +class CommandHSReject : public Command +{ + public: + CommandHSReject(Module *creator) : Command(creator, "hostserv/reject", 1, 2) + { + this->SetDesc(_("Reject the requested vHost of a user")); + this->SetSyntax(_("\037user\037 [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + const Anope::string &nick = params[0]; + const Anope::string &reason = params.size() > 1 ? params[1] : ""; + + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + HostRequest *req = na->GetExt("hostrequest"); + if (!req) + { + source.Reply(_("\002{0}\002 does not have a pending vhost request."), na->GetNick()); + return; + } + + req->Delete(); + + if (Config->GetModule(this->owner)->Get("memouser") && MemoServ::service) + { + Anope::string message; + if (!reason.empty()) + message = Anope::printf(_("[auto memo] Your requested vHost has been rejected. Reason: %s"), reason.c_str()); + else + message = _("[auto memo] Your requested vHost has been rejected."); + + MemoServ::service->Send(source.service->nick, nick, Language::Translate(source.GetAccount(), message.c_str()), true); + } + + source.Reply(_("Vhost for \002{0}\002 has been rejected."), na->GetNick()); + Log(LOG_COMMAND, source, this) << "to reject vhost for " << nick << " (" << (!reason.empty() ? reason : "no reason") << ")"; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Reject the requested vhost for the given user.")); + if (Config->GetModule(this->owner)->Get("memouser")) + source.Reply(_("A memo informing the user will also be sent, which includes the reason for the rejection if supplied.")); + + return true; + } +}; + +class CommandHSWaiting : public Command +{ + public: + CommandHSWaiting(Module *creator) : Command(creator, "hostserv/waiting", 0, 0) + { + this->SetDesc(_("Retrieves the vhost requests")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + unsigned counter = 0; + unsigned display_counter = 0, listmax = Config->GetModule(this->owner)->Get("listmax"); + ListFormatter list(source.GetAccount()); + + list.AddColumn(_("Number")).AddColumn(_("Nick")).AddColumn(_("Vhost")).AddColumn(_("Created")); + + for (HostRequest *hr : Serialize::GetObjects(hostrequest)) + { + if (!listmax || display_counter < listmax) + { + ++display_counter; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(display_counter); + entry["Nick"] = hr->GetNick()->GetNick(); + if (!hr->GetIdent().empty()) + entry["Vhost"] = hr->GetIdent() + "@" + hr->GetHost(); + else + entry["Vhost"] = hr->GetHost(); + entry["Created"] = Anope::strftime(hr->GetTime(), NULL, true); + list.AddEntry(entry); + } + ++counter; + } + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("Displayed \002{0}\002 records (\002{1}\002 total)."), display_counter, counter); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Lists the vhost requests.")); + + return true; + } +}; + +class HSRequest : public Module +{ + CommandHSRequest commandhsrequest; + CommandHSActivate commandhsactive; + CommandHSReject commandhsreject; + CommandHSWaiting commandhswaiting; + + HostRequestType hrtype; + + public: + HSRequest(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandhsrequest(this) + , commandhsactive(this) + , commandhsreject(this) + , commandhswaiting(this) + , hrtype(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } +}; + +static void req_send_memos(Module *me, CommandSource &source, const Anope::string &vIdent, const Anope::string &vHost) +{ + Anope::string host; + std::list >::iterator it, it_end; + + if (!vIdent.empty()) + host = vIdent + "@" + vHost; + else + host = vHost; + + if (Config->GetModule(me)->Get("memooper") && MemoServ::service) + for (Oper *o : Serialize::GetObjects(operblock)) + { + NickServ::Nick *na = NickServ::FindNick(o->GetName()); + if (!na) + continue; + + Anope::string message = Anope::printf(_("[auto memo] vHost \002%s\002 has been requested by %s."), host.c_str(), source.GetNick().c_str()); + + MemoServ::service->Send(source.service->nick, na->GetNick(), message, true); + } +} + +MODULE_INIT(HSRequest) diff --git a/modules/hostserv/set.cpp b/modules/hostserv/set.cpp new file mode 100644 index 000000000..a86afead4 --- /dev/null +++ b/modules/hostserv/set.cpp @@ -0,0 +1,216 @@ +/* HostServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandHSSet : public Command +{ + public: + CommandHSSet(Module *creator) : Command(creator, "hostserv/set", 2, 2) + { + this->SetDesc(_("Set the vhost of another user")); + this->SetSyntax(_("\037user\037 \037hostmask\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + const Anope::string &nick = params[0]; + + NickServ::Nick *na = NickServ::FindNick(nick); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + Anope::string rawhostmask = params[1]; + + Anope::string user, host; + size_t a = rawhostmask.find('@'); + + if (a == Anope::string::npos) + host = rawhostmask; + else + { + user = rawhostmask.substr(0, a); + host = rawhostmask.substr(a + 1); + } + + if (host.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + if (!user.empty()) + { + if (!IRCD->CanSetVIdent) + { + source.Reply(_("Vhosts may not contain a username.")); + return; + } + + if (!IRCD->IsIdentValid(user)) + { + source.Reply(_("The requested username is not valid.")); + return; + } + } + + if (host.length() > Config->GetBlock("networkinfo")->Get("hostlen")) + { + source.Reply(_("The requested vhost is too long, please use a hostname no longer than {0} characters."), Config->GetBlock("networkinfo")->Get("hostlen")); + return; + } + + if (!IRCD->IsHostValid(host)) + { + source.Reply(_("The requested hostname is not valid.")); + return; + } + + Log(LOG_ADMIN, source, this) << "to set the vhost of " << na->GetNick() << " to " << (!user.empty() ? user + "@" : "") << host; + + na->SetVhost(user, host, source.GetNick()); + Event::OnSetVhost(&Event::SetVhost::OnSetVhost, na); + if (!user.empty()) + source.Reply(_("Vhost for \002{0}\002 set to \002{0}\002@\002{1}\002."), nick, user, host); + else + source.Reply(_("Vhost for \002{0}\002 set to \002{0}\002."), nick, host); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets the vhost of the given \037user\037 to the given \037hostmask\037.")); + return true; + } +}; + +class CommandHSSetAll : public Command +{ + void Sync(NickServ::Nick *na) + { + if (!na || !na->HasVhost()) + return; + + for (NickServ::Nick *nick : na->GetAccount()->GetRefs(NickServ::nick)) + nick->SetVhost(na->GetVhostIdent(), na->GetVhostHost(), na->GetVhostCreator()); + } + + public: + CommandHSSetAll(Module *creator) : Command(creator, "hostserv/setall", 2, 2) + { + this->SetDesc(_("Set the vhost for all nicks in a group")); + this->SetSyntax(_("\037user\037 \037hostmask\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + const Anope::string &nick = params[0]; + NickServ::Nick *na = NickServ::FindNick(nick); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + Anope::string rawhostmask = params[1]; + + Anope::string user, host; + size_t a = rawhostmask.find('@'); + + if (a == Anope::string::npos) + host = rawhostmask; + else + { + user = rawhostmask.substr(0, a); + host = rawhostmask.substr(a + 1); + } + + if (host.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + if (!user.empty()) + { + if (!IRCD->CanSetVIdent) + { + source.Reply(_("Vhosts may not contain a username.")); + return; + } + + if (!IRCD->IsIdentValid(user)) + { + source.Reply(_("The requested username is not valid.")); + return; + } + } + + if (host.length() > Config->GetBlock("networkinfo")->Get("hostlen")) + { + source.Reply(_("The requested vhost is too long, please use a hostname no longer than {0} characters."), Config->GetBlock("networkinfo")->Get("hostlen")); + return; + } + + if (!IRCD->IsHostValid(host)) + { + source.Reply(_("The requested hostname is not valid.")); + return; + } + + Log(LOG_ADMIN, source, this) << "to set the vhost of " << na->GetNick() << " to " << (!user.empty() ? user + "@" : "") << host; + + na->SetVhost(user, host, source.GetNick()); + this->Sync(na); + Event::OnSetVhost(&Event::SetVhost::OnSetVhost, na); + if (!user.empty()) + source.Reply(_("Vhost for group \002{0}\002 set to \002{1}\002@\002{2}\002."), nick.c_str(), user.c_str(), host.c_str()); + else + source.Reply(_("host for group \002{0}\002 set to \002{1}\002."), nick.c_str(), host.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets the vhost for all nicknames in the group of \037user\037.")); + return true; + } +}; + +class HSSet : public Module +{ + CommandHSSet commandhsset; + CommandHSSetAll commandhssetall; + + public: + HSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandhsset(this) + , commandhssetall(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } +}; + +MODULE_INIT(HSSet) diff --git a/modules/httpd.cpp b/modules/httpd.cpp new file mode 100644 index 000000000..17f80d376 --- /dev/null +++ b/modules/httpd.cpp @@ -0,0 +1,473 @@ +/* + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" +#include "modules/httpd.h" +#include "modules/ssl.h" + +static Anope::string BuildDate() +{ + char timebuf[64]; + struct tm *tm = localtime(&Anope::CurTime); + strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", tm); + return timebuf; +} + +static Anope::string GetStatusFromCode(HTTPError err) +{ + switch (err) + { + case HTTP_ERROR_OK: + return "200 OK"; + case HTTP_FOUND: + return "302 Found"; + case HTTP_BAD_REQUEST: + return "400 Bad Request"; + case HTTP_PAGE_NOT_FOUND: + return "404 Not Found"; + case HTTP_NOT_SUPPORTED: + return "505 HTTP Version Not Supported"; + } + + return "501 Not Implemented"; +} + +class MyHTTPClient : public HTTPClient +{ + HTTPProvider *provider; + HTTPMessage message; + bool header_done, served; + Anope::string page_name; + Reference page; + Anope::string ip; + + unsigned content_length; + + enum + { + ACTION_NONE, + ACTION_GET, + ACTION_POST + } action; + + void Serve() + { + if (this->served) + return; + this->served = true; + + if (!this->page) + { + this->SendError(HTTP_PAGE_NOT_FOUND, "Page not found"); + return; + } + + if (this->ip == this->provider->ext_ip) + { + for (unsigned i = 0; i < this->provider->ext_headers.size(); ++i) + { + const Anope::string &token = this->provider->ext_headers[i]; + + if (this->message.headers.count(token)) + { + this->ip = this->message.headers[token]; + Log(LOG_DEBUG, "httpd") << "m_httpd: IP for connection " << this->GetFD() << " changed to " << this->ip; + break; + } + } + } + + Log(LOG_DEBUG, "httpd") << "m_httpd: Serving page " << this->page_name << " to " << this->ip; + + HTTPReply reply; + reply.content_type = this->page->GetContentType(); + + if (this->page->OnRequest(this->provider, this->page_name, this, this->message, reply)) + this->SendReply(&reply); + } + + public: + time_t created; + + MyHTTPClient(HTTPProvider *l, int f, const sockaddrs &a) : Socket(f, l->IsIPv6()), HTTPClient(l, f, a), provider(l), header_done(false), served(false), ip(a.addr()), content_length(0), action(ACTION_NONE), created(Anope::CurTime) + { + Log(LOG_DEBUG, "httpd") << "Accepted connection " << f << " from " << a.addr(); + } + + ~MyHTTPClient() + { + Log(LOG_DEBUG, "httpd") << "Closing connection " << this->GetFD() << " from " << this->ip; + } + + /* Close connection once all data is written */ + bool ProcessWrite() override + { + return !BinarySocket::ProcessWrite() || this->write_buffer.empty() ? false : true; + } + + const Anope::string GetIP() override + { + return this->ip; + } + + bool Read(const char *buffer, size_t l) override + { + message.content.append(buffer, l); + + for (size_t nl; !this->header_done && (nl = message.content.find('\n')) != Anope::string::npos;) + { + Anope::string token = message.content.substr(0, nl).trim(); + message.content = message.content.substr(nl + 1); + + if (token.empty()) + this->header_done = true; + else + this->Read(token); + } + + if (!this->header_done) + return true; + + if (this->message.content.length() >= this->content_length) + { + sepstream sep(this->message.content, '&'); + Anope::string token; + + while (sep.GetToken(token)) + { + size_t sz = token.find('='); + if (sz == Anope::string::npos || !sz || sz + 1 >= token.length()) + continue; + this->message.post_data[token.substr(0, sz)] = HTTPUtils::URLDecode(token.substr(sz + 1)); + Log(LOG_DEBUG_2) << "HTTP POST from " << this->clientaddr.addr() << ": " << token.substr(0, sz) << ": " << this->message.post_data[token.substr(0, sz)]; + } + + this->Serve(); + } + + return true; + } + + bool Read(const Anope::string &buf) + { + Log(LOG_DEBUG_2) << "HTTP from " << this->clientaddr.addr() << ": " << buf; + + if (this->action == ACTION_NONE) + { + std::vector params; + spacesepstream(buf).GetTokens(params); + + if (params.empty() || (params[0] != "GET" && params[0] != "POST")) + { + this->SendError(HTTP_BAD_REQUEST, "Unknown operation"); + return true; + } + + if (params.size() != 3) + { + this->SendError(HTTP_BAD_REQUEST, "Invalid parameters"); + return true; + } + + if (params[0] == "GET") + this->action = ACTION_GET; + else if (params[0] == "POST") + this->action = ACTION_POST; + + Anope::string targ = params[1]; + size_t q = targ.find('?'); + if (q != Anope::string::npos) + { + sepstream sep(targ.substr(q + 1), '&'); + targ = targ.substr(0, q); + + Anope::string token; + while (sep.GetToken(token)) + { + size_t sz = token.find('='); + if (sz == Anope::string::npos || !sz || sz + 1 >= token.length()) + continue; + this->message.get_data[token.substr(0, sz)] = HTTPUtils::URLDecode(token.substr(sz + 1)); + } + } + + this->page = this->provider->FindPage(targ); + this->page_name = targ; + } + else if (buf.find("Cookie: ") == 0) + { + spacesepstream sep(buf.substr(8)); + Anope::string token; + + while (sep.GetToken(token)) + { + size_t sz = token.find('='); + if (sz == Anope::string::npos || !sz || sz + 1 >= token.length()) + continue; + size_t end = token.length() - (sz + 1); + if (!sep.StreamEnd()) + --end; // Remove trailing ; + this->message.cookies[token.substr(0, sz)] = token.substr(sz + 1, end); + } + } + else if (buf.find("Content-Length: ") == 0) + { + try + { + this->content_length = convertTo(buf.substr(16)); + } + catch (const ConvertException &ex) { } + } + else if (buf.find(':') != Anope::string::npos) + { + size_t sz = buf.find(':'); + if (sz + 2 < buf.length()) + this->message.headers[buf.substr(0, sz)] = buf.substr(sz + 2); + } + + return true; + } + + void SendError(HTTPError err, const Anope::string &msg) override + { + HTTPReply h; + + h.error = err; + + h.Write(msg); + + this->SendReply(&h); + } + + void SendReply(HTTPReply *msg) override + { + this->WriteClient("HTTP/1.1 " + GetStatusFromCode(msg->error)); + this->WriteClient("Date: " + BuildDate()); + this->WriteClient("Server: Anope-" + Anope::VersionShort()); + if (msg->content_type.empty()) + this->WriteClient("Content-Type: text/html"); + else + this->WriteClient("Content-Type: " + msg->content_type); + this->WriteClient("Content-Length: " + stringify(msg->length)); + + for (unsigned i = 0; i < msg->cookies.size(); ++i) + { + Anope::string buf = "Set-Cookie:"; + + for (HTTPReply::cookie::iterator it = msg->cookies[i].begin(), it_end = msg->cookies[i].end(); it != it_end; ++it) + buf += " " + it->first + "=" + it->second + ";"; + + buf.erase(buf.length() - 1); + + this->WriteClient(buf); + } + + typedef std::map map; + for (map::iterator it = msg->headers.begin(), it_end = msg->headers.end(); it != it_end; ++it) + this->WriteClient(it->first + ": " + it->second); + + this->WriteClient("Connection: Close"); + this->WriteClient(""); + + for (unsigned i = 0; i < msg->out.size(); ++i) + { + HTTPReply::Data* d = msg->out[i]; + + this->Write(d->buf, d->len); + + delete d; + } + + msg->out.clear(); + } +}; + +class MyHTTPProvider : public HTTPProvider, public Timer +{ + int timeout; + std::map pages; + std::list > clients; + + public: + MyHTTPProvider(Module *c, const Anope::string &n, const Anope::string &i, const unsigned short p, const int t, bool s) : Socket(-1, i.find(':') != Anope::string::npos), HTTPProvider(c, n, i, p, s), Timer(c, 10, Anope::CurTime, true), timeout(t) { } + + void Tick(time_t) override + { + while (!this->clients.empty()) + { + Reference& c = this->clients.front(); + if (c && c->created + this->timeout >= Anope::CurTime) + break; + + delete c; + this->clients.pop_front(); + } + } + + ClientSocket* OnAccept(int fd, const sockaddrs &addr) override + { + MyHTTPClient *c = new MyHTTPClient(this, fd, addr); + this->clients.push_back(c); + return c; + } + + bool RegisterPage(HTTPPage *page) override + { + return this->pages.insert(std::make_pair(page->GetURL(), page)).second; + } + + void UnregisterPage(HTTPPage *page) override + { + this->pages.erase(page->GetURL()); + } + + HTTPPage* FindPage(const Anope::string &pname) + { + if (this->pages.count(pname) == 0) + return NULL; + return this->pages[pname]; + } +}; + +class HTTPD : public Module + , public EventHook +{ + ServiceReference sslref; + std::map providers; + public: + HTTPD(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + , sslref("SSLService", "ssl") + { + + } + + ~HTTPD() + { + for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) + { + Socket *s = it->second; + ++it; + + if (dynamic_cast(s) || dynamic_cast(s)) + delete s; + } + + this->providers.clear(); + } + + void OnReload(Configuration::Conf *config) override + { + Configuration::Block *conf = config->GetModule(this); + std::set existing; + + for (int i = 0; i < conf->CountBlock("httpd"); ++i) + { + Configuration::Block *block = conf->GetBlock("httpd", i); + + + const Anope::string &hname = block->Get("name", "httpd/main"); + existing.insert(hname); + + Anope::string ip = block->Get("ip"); + int port = block->Get("port", "8080"); + int timeout = block->Get("timeout", "30"); + bool ssl = block->Get("ssl", "no"); + Anope::string ext_ip = block->Get("extforward_ip"); + Anope::string ext_header = block->Get("extforward_header"); + + if (ip.empty()) + { + Log(this) << "You must configure a bind IP for HTTP server " << hname; + continue; + } + else if (port <= 0 || port > 65535) + { + Log(this) << "You must configure a (valid) listen port for HTTP server " << hname; + continue; + } + + MyHTTPProvider *p; + if (this->providers.count(hname) == 0) + { + try + { + p = new MyHTTPProvider(this, hname, ip, port, timeout, ssl); + if (ssl && sslref) + sslref->Init(p); + } + catch (const SocketException &ex) + { + Log(this) << "Unable to create HTTP server " << hname << ": " << ex.GetReason(); + continue; + } + this->providers[hname] = p; + + Log(this) << "Created HTTP server " << hname; + } + else + { + p = this->providers[hname]; + + if (p->GetIP() != ip || p->GetPort() != port) + { + delete p; + this->providers.erase(hname); + + Log(this) << "Changing HTTP server " << hname << " to " << ip << ":" << port; + + try + { + p = new MyHTTPProvider(this, hname, ip, port, timeout, ssl); + if (ssl && sslref) + sslref->Init(p); + } + catch (const SocketException &ex) + { + Log(this) << "Unable to create HTTP server " << hname << ": " << ex.GetReason(); + continue; + } + + this->providers[hname] = p; + } + } + + + p->ext_ip = ext_ip; + spacesepstream(ext_header).GetTokens(p->ext_headers); + } + + for (std::map::iterator it = this->providers.begin(), it_end = this->providers.end(); it != it_end;) + { + HTTPProvider *p = it->second; + ++it; + + if (existing.count(p->name) == 0) + { + Log(this) << "Removing HTTP server " << p->name; + this->providers.erase(p->name); + delete p; + } + } + } + + void OnModuleLoad(User *u, Module *m) override + { + for (std::map::iterator it = this->providers.begin(), it_end = this->providers.end(); it != it_end; ++it) + { + MyHTTPProvider *p = it->second; + + if (p->IsSSL() && sslref) + try + { + sslref->Init(p); + } + catch (const CoreException &) { } // Throws on reinitialization + } + } +}; + +MODULE_INIT(HTTPD) diff --git a/modules/m_dns.cpp b/modules/m_dns.cpp deleted file mode 100644 index 1ea70a6ed..000000000 --- a/modules/m_dns.cpp +++ /dev/null @@ -1,1136 +0,0 @@ -/* - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - * - */ - -#include "module.h" -#include "modules/dns.h" - -using namespace DNS; - -namespace -{ - Anope::string admin, nameservers; - int refresh; - time_t timeout; -} - -/** A full packet sent or received to/from the nameserver - */ -class Packet : public Query -{ - static bool IsValidName(const Anope::string &name) - { - return name.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-") == Anope::string::npos; - } - - void PackName(unsigned char *output, unsigned short output_size, unsigned short &pos, const Anope::string &name) - { - if (pos + name.length() + 2 > output_size) - throw SocketException("Unable to pack name"); - - Log(LOG_DEBUG_2) << "Resolver: PackName packing " << name; - - sepstream sep(name, '.'); - Anope::string token; - - while (sep.GetToken(token)) - { - output[pos++] = token.length(); - memcpy(&output[pos], token.c_str(), token.length()); - pos += token.length(); - } - - output[pos++] = 0; - } - - Anope::string UnpackName(const unsigned char *input, unsigned short input_size, unsigned short &pos) - { - Anope::string name; - unsigned short pos_ptr = pos, lowest_ptr = input_size; - bool compressed = false; - - if (pos_ptr >= input_size) - throw SocketException("Unable to unpack name - no input"); - - while (input[pos_ptr] > 0) - { - unsigned short offset = input[pos_ptr]; - - if (offset & POINTER) - { - if ((offset & POINTER) != POINTER) - throw SocketException("Unable to unpack name - bogus compression header"); - if (pos_ptr + 1 >= input_size) - throw SocketException("Unable to unpack name - bogus compression header"); - - /* Place pos at the second byte of the first (farthest) compression pointer */ - if (compressed == false) - { - ++pos; - compressed = true; - } - - pos_ptr = (offset & LABEL) << 8 | input[pos_ptr + 1]; - - /* Pointers can only go back */ - if (pos_ptr >= lowest_ptr) - throw SocketException("Unable to unpack name - bogus compression pointer"); - lowest_ptr = pos_ptr; - } - else - { - if (pos_ptr + offset + 1 >= input_size) - throw SocketException("Unable to unpack name - offset too large"); - if (!name.empty()) - name += "."; - for (unsigned i = 1; i <= offset; ++i) - name += input[pos_ptr + i]; - - pos_ptr += offset + 1; - if (compressed == false) - /* Move up pos */ - pos = pos_ptr; - } - } - - /* +1 pos either to one byte after the compression pointer or one byte after the ending \0 */ - ++pos; - - if (name.empty()) - throw SocketException("Unable to unpack name - no name"); - - Log(LOG_DEBUG_2) << "Resolver: UnpackName successfully unpacked " << name; - - return name; - } - - Question UnpackQuestion(const unsigned char *input, unsigned short input_size, unsigned short &pos) - { - Question question; - - question.name = this->UnpackName(input, input_size, pos); - - if (pos + 4 > input_size) - throw SocketException("Unable to unpack question"); - - if (!IsValidName(question.name)) - throw SocketException("Invalid question name"); - - question.type = static_cast(input[pos] << 8 | input[pos + 1]); - pos += 2; - - question.qclass = input[pos] << 8 | input[pos + 1]; - pos += 2; - - return question; - } - - ResourceRecord UnpackResourceRecord(const unsigned char *input, unsigned short input_size, unsigned short &pos) - { - ResourceRecord record = static_cast(this->UnpackQuestion(input, input_size, pos)); - - if (pos + 6 > input_size) - throw SocketException("Unable to unpack resource record"); - - record.ttl = (input[pos] << 24) | (input[pos + 1] << 16) | (input[pos + 2] << 8) | input[pos + 3]; - pos += 4; - - //record.rdlength = input[pos] << 8 | input[pos + 1]; - pos += 2; - - switch (record.type) - { - case QUERY_A: - { - if (pos + 4 > input_size) - throw SocketException("Unable to unpack resource record"); - - in_addr a; - a.s_addr = input[pos] | (input[pos + 1] << 8) | (input[pos + 2] << 16) | (input[pos + 3] << 24); - pos += 4; - - sockaddrs addrs; - addrs.ntop(AF_INET, &a); - if (!addrs.valid()) - throw SocketException("Invalid IP"); - - record.rdata = addrs.addr(); - break; - } - case QUERY_AAAA: - { - if (pos + 16 > input_size) - throw SocketException("Unable to unpack resource record"); - - in6_addr a; - for (int j = 0; j < 16; ++j) - a.s6_addr[j] = input[pos + j]; - pos += 16; - - sockaddrs addrs; - addrs.ntop(AF_INET6, &a); - if (!addrs.valid()) - throw SocketException("Invalid IP"); - - record.rdata = addrs.addr(); - break; - } - case QUERY_CNAME: - case QUERY_PTR: - { - record.rdata = this->UnpackName(input, input_size, pos); - - if (!IsValidName(record.rdata)) - throw SocketException("Invalid cname/ptr record data"); - - break; - } - default: - break; - } - - Log(LOG_DEBUG_2) << "Resolver: " << record.name << " -> " << record.rdata; - - return record; - } - - public: - static const int POINTER = 0xC0; - static const int LABEL = 0x3F; - static const int HEADER_LENGTH = 12; - - Manager *manager; - /* Source or destination of the packet */ - sockaddrs addr; - /* ID for this packet */ - unsigned short id; - /* Flags on the packet */ - unsigned short flags; - - Packet(Manager *m, sockaddrs *a) : manager(m), id(0), flags(0) - { - if (a) - addr = *a; - } - - void Fill(const unsigned char *input, const unsigned short len) - { - if (len < HEADER_LENGTH) - throw SocketException("Unable to fill packet"); - - unsigned short packet_pos = 0; - - this->id = (input[packet_pos] << 8) | input[packet_pos + 1]; - packet_pos += 2; - - this->flags = (input[packet_pos] << 8) | input[packet_pos + 1]; - packet_pos += 2; - - unsigned short qdcount = (input[packet_pos] << 8) | input[packet_pos + 1]; - packet_pos += 2; - - unsigned short ancount = (input[packet_pos] << 8) | input[packet_pos + 1]; - packet_pos += 2; - - unsigned short nscount = (input[packet_pos] << 8) | input[packet_pos + 1]; - packet_pos += 2; - - unsigned short arcount = (input[packet_pos] << 8) | input[packet_pos + 1]; - packet_pos += 2; - - Log(LOG_DEBUG_2) << "Resolver: qdcount: " << qdcount << " ancount: " << ancount << " nscount: " << nscount << " arcount: " << arcount; - - for (unsigned i = 0; i < qdcount; ++i) - this->questions.push_back(this->UnpackQuestion(input, len, packet_pos)); - - for (unsigned i = 0; i < ancount; ++i) - this->answers.push_back(this->UnpackResourceRecord(input, len, packet_pos)); - - for (unsigned i = 0; i < nscount; ++i) - this->authorities.push_back(this->UnpackResourceRecord(input, len, packet_pos)); - - for (unsigned i = 0; i < arcount; ++i) - this->additional.push_back(this->UnpackResourceRecord(input, len, packet_pos)); - } - - unsigned short Pack(unsigned char *output, unsigned short output_size) - { - if (output_size < HEADER_LENGTH) - throw SocketException("Unable to pack packet"); - - unsigned short pos = 0; - - output[pos++] = this->id >> 8; - output[pos++] = this->id & 0xFF; - output[pos++] = this->flags >> 8; - output[pos++] = this->flags & 0xFF; - output[pos++] = this->questions.size() >> 8; - output[pos++] = this->questions.size() & 0xFF; - output[pos++] = this->answers.size() >> 8; - output[pos++] = this->answers.size() & 0xFF; - output[pos++] = this->authorities.size() >> 8; - output[pos++] = this->authorities.size() & 0xFF; - output[pos++] = this->additional.size() >> 8; - output[pos++] = this->additional.size() & 0xFF; - - for (unsigned i = 0; i < this->questions.size(); ++i) - { - Question &q = this->questions[i]; - - if (q.type == QUERY_PTR) - { - sockaddrs ip(q.name); - if (!ip.valid()) - throw SocketException("Invalid IP"); - - if (q.name.find(':') != Anope::string::npos) - { - const char *const hex = "0123456789abcdef"; - char reverse_ip[128]; - unsigned reverse_ip_count = 0; - for (int j = 15; j >= 0; --j) - { - reverse_ip[reverse_ip_count++] = hex[ip.sa6.sin6_addr.s6_addr[j] & 0xF]; - reverse_ip[reverse_ip_count++] = '.'; - reverse_ip[reverse_ip_count++] = hex[ip.sa6.sin6_addr.s6_addr[j] >> 4]; - reverse_ip[reverse_ip_count++] = '.'; - } - reverse_ip[reverse_ip_count++] = 0; - - q.name = reverse_ip; - q.name += "ip6.arpa"; - } - else - { - unsigned long forward = ip.sa4.sin_addr.s_addr; - in_addr reverse; - reverse.s_addr = forward << 24 | (forward & 0xFF00) << 8 | (forward & 0xFF0000) >> 8 | forward >> 24; - - ip.ntop(AF_INET, &reverse); - - q.name = ip.addr() + ".in-addr.arpa"; - } - } - - this->PackName(output, output_size, pos, q.name); - - if (pos + 4 >= output_size) - throw SocketException("Unable to pack packet"); - - short s = htons(q.type); - memcpy(&output[pos], &s, 2); - pos += 2; - - s = htons(q.qclass); - memcpy(&output[pos], &s, 2); - pos += 2; - } - - std::vector types[] = { this->answers, this->authorities, this->additional }; - for (int i = 0; i < 3; ++i) - for (unsigned j = 0; j < types[i].size(); ++j) - { - ResourceRecord &rr = types[i][j]; - - this->PackName(output, output_size, pos, rr.name); - - if (pos + 8 >= output_size) - throw SocketException("Unable to pack packet"); - - short s = htons(rr.type); - memcpy(&output[pos], &s, 2); - pos += 2; - - s = htons(rr.qclass); - memcpy(&output[pos], &s, 2); - pos += 2; - - long l = htonl(rr.ttl); - memcpy(&output[pos], &l, 4); - pos += 4; - - switch (rr.type) - { - case QUERY_A: - { - if (pos + 6 > output_size) - throw SocketException("Unable to pack packet"); - - sockaddrs a(rr.rdata); - if (!a.valid()) - throw SocketException("Invalid IP"); - - s = htons(4); - memcpy(&output[pos], &s, 2); - pos += 2; - - memcpy(&output[pos], &a.sa4.sin_addr, 4); - pos += 4; - break; - } - case QUERY_AAAA: - { - if (pos + 18 > output_size) - throw SocketException("Unable to pack packet"); - - sockaddrs a(rr.rdata); - if (!a.valid()) - throw SocketException("Invalid IP"); - - s = htons(16); - memcpy(&output[pos], &s, 2); - pos += 2; - - memcpy(&output[pos], &a.sa6.sin6_addr, 16); - pos += 16; - break; - } - case QUERY_NS: - case QUERY_CNAME: - case QUERY_PTR: - { - if (pos + 2 >= output_size) - throw SocketException("Unable to pack packet"); - - unsigned short packet_pos_save = pos; - pos += 2; - - this->PackName(output, output_size, pos, rr.rdata); - - s = htons(pos - packet_pos_save - 2); - memcpy(&output[packet_pos_save], &s, 2); - break; - } - case QUERY_SOA: - { - if (pos + 2 >= output_size) - throw SocketException("Unable to pack packet"); - - unsigned short packet_pos_save = pos; - pos += 2; - - std::vector ns; - spacesepstream(nameservers).GetTokens(ns); - this->PackName(output, output_size, pos, !ns.empty() ? ns[0] : ""); - this->PackName(output, output_size, pos, admin.replace_all_cs('@', '.')); - - if (pos + 20 >= output_size) - throw SocketException("Unable to pack SOA"); - - l = htonl(manager->GetSerial()); - memcpy(&output[pos], &l, 4); - pos += 4; - - l = htonl(refresh); // Refresh - memcpy(&output[pos], &l, 4); - pos += 4; - - l = htonl(refresh); // Retry - memcpy(&output[pos], &l, 4); - pos += 4; - - l = htonl(604800); // Expire - memcpy(&output[pos], &l, 4); - pos += 4; - - l = htonl(0); // Minimum - memcpy(&output[pos], &l, 4); - pos += 4; - - s = htons(pos - packet_pos_save - 2); - memcpy(&output[packet_pos_save], &s, 2); - - break; - } - default: - break; - } - } - - return pos; - } -}; - -namespace DNS -{ - class ReplySocket : public virtual Socket - { - public: - virtual ~ReplySocket() { } - virtual void Reply(Packet *p) = 0; - }; -} - -/* Listens for TCP requests */ -class TCPSocket : public ListenSocket -{ - Manager *manager; - - public: - /* A TCP client */ - class Client : public ClientSocket, public Timer, public ReplySocket - { - Manager *manager; - Packet *packet; - unsigned char packet_buffer[524]; - int length; - - public: - Client(Manager *m, TCPSocket *l, int fd, const sockaddrs &addr) : Socket(fd, l->IsIPv6()), ClientSocket(l, addr), Timer(5), - manager(m), packet(NULL), length(0) - { - Log(LOG_DEBUG_2) << "Resolver: New client from " << addr.addr(); - } - - ~Client() - { - Log(LOG_DEBUG_2) << "Resolver: Exiting client from " << clientaddr.addr(); - delete packet; - } - - /* Times out after a few seconds */ - void Tick(time_t) override { } - - void Reply(Packet *p) override - { - delete packet; - packet = p; - SocketEngine::Change(this, true, SF_WRITABLE); - } - - bool ProcessRead() override - { - Log(LOG_DEBUG_2) << "Resolver: Reading from DNS TCP socket"; - - int i = recv(this->GetFD(), reinterpret_cast(packet_buffer) + length, sizeof(packet_buffer) - length, 0); - if (i <= 0) - return false; - - length += i; - - unsigned short want_len = packet_buffer[0] << 8 | packet_buffer[1]; - if (length >= want_len + 2) - { - int len = length - 2; - length -= want_len + 2; - return this->manager->HandlePacket(this, packet_buffer + 2, len, NULL); - } - return true; - } - - bool ProcessWrite() override - { - Log(LOG_DEBUG_2) << "Resolver: Writing to DNS TCP socket"; - - if (packet != NULL) - { - try - { - unsigned char buffer[65535]; - unsigned short len = packet->Pack(buffer + 2, sizeof(buffer) - 2); - - short s = htons(len); - memcpy(buffer, &s, 2); - len += 2; - - send(this->GetFD(), reinterpret_cast(buffer), len, 0); - } - catch (const SocketException &) { } - - delete packet; - packet = NULL; - } - - SocketEngine::Change(this, false, SF_WRITABLE); - return true; /* Do not return false here, bind is unhappy we close the connection so soon after sending */ - } - }; - - TCPSocket(Manager *m, const Anope::string &ip, int port) : Socket(-1, ip.find(':') != Anope::string::npos), ListenSocket(ip, port, ip.find(':') != Anope::string::npos), manager(m) { } - - ClientSocket *OnAccept(int fd, const sockaddrs &addr) override - { - return new Client(this->manager, this, fd, addr); - } -}; - -/* Listens for UDP requests */ -class UDPSocket : public ReplySocket -{ - Manager *manager; - std::deque packets; - - public: - UDPSocket(Manager *m, const Anope::string &ip, int port) : Socket(-1, ip.find(':') != Anope::string::npos, SOCK_DGRAM), manager(m) { } - - ~UDPSocket() - { - for (unsigned i = 0; i < packets.size(); ++i) - delete packets[i]; - } - - void Reply(Packet *p) override - { - packets.push_back(p); - SocketEngine::Change(this, true, SF_WRITABLE); - } - - std::deque& GetPackets() { return packets; } - - bool ProcessRead() override - { - Log(LOG_DEBUG_2) << "Resolver: Reading from DNS UDP socket"; - - unsigned char packet_buffer[524]; - sockaddrs from_server; - socklen_t x = sizeof(from_server); - int length = recvfrom(this->GetFD(), reinterpret_cast(&packet_buffer), sizeof(packet_buffer), 0, &from_server.sa, &x); - return this->manager->HandlePacket(this, packet_buffer, length, &from_server); - } - - bool ProcessWrite() override - { - Log(LOG_DEBUG_2) << "Resolver: Writing to DNS UDP socket"; - - Packet *r = !packets.empty() ? packets.front() : NULL; - if (r != NULL) - { - try - { - unsigned char buffer[524]; - unsigned short len = r->Pack(buffer, sizeof(buffer)); - - sendto(this->GetFD(), reinterpret_cast(buffer), len, 0, &r->addr.sa, r->addr.size()); - } - catch (const SocketException &) { } - - delete r; - packets.pop_front(); - } - - if (packets.empty()) - SocketEngine::Change(this, false, SF_WRITABLE); - - return true; - } -}; - -class NotifySocket : public Socket -{ - Packet *packet; - public: - NotifySocket(bool v6, Packet *p) : Socket(-1, v6, SOCK_DGRAM), packet(p) - { - SocketEngine::Change(this, false, SF_READABLE); - SocketEngine::Change(this, true, SF_WRITABLE); - } - - bool ProcessWrite() override - { - if (!packet) - return false; - - Log(LOG_DEBUG_2) << "Resolver: Notifying slave " << packet->addr.addr(); - - try - { - unsigned char buffer[524]; - unsigned short len = packet->Pack(buffer, sizeof(buffer)); - - sendto(this->GetFD(), reinterpret_cast(buffer), len, 0, &packet->addr.sa, packet->addr.size()); - } - catch (const SocketException &) { } - - delete packet; - packet = NULL; - - return false; - } -}; - -class MyManager : public Manager, public Timer -{ - uint32_t serial; - - typedef std::unordered_map cache_map; - cache_map cache; - - TCPSocket *tcpsock; - UDPSocket *udpsock; - - bool listen; - sockaddrs addrs; - - std::vector > notify; - public: - std::map requests; - - MyManager(Module *creator) : Manager(creator), Timer(300, Anope::CurTime, true), serial(Anope::CurTime), tcpsock(NULL), udpsock(NULL), - listen(false), cur_id(rand()) - { - } - - ~MyManager() - { - delete udpsock; - delete tcpsock; - - for (std::map::iterator it = this->requests.begin(), it_end = this->requests.end(); it != it_end;) - { - Request *request = it->second; - ++it; - - Query rr(*request); - rr.error = ERROR_UNKNOWN; - request->OnError(&rr); - - delete request; - } - this->requests.clear(); - - this->cache.clear(); - } - - void SetIPPort(const Anope::string &nameserver, const Anope::string &ip, unsigned short port, std::vector > n) - { - delete udpsock; - delete tcpsock; - - udpsock = NULL; - tcpsock = NULL; - - try - { - this->addrs.pton(nameserver.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, nameserver, 53); - - udpsock = new UDPSocket(this, ip, port); - - if (!ip.empty()) - { - udpsock->Bind(ip, port); - tcpsock = new TCPSocket(this, ip, port); - listen = true; - } - } - catch (const SocketException &ex) - { - Log() << "Unable to bind dns to " << ip << ":" << port << ": " << ex.GetReason(); - } - - notify = n; - } - - private: - unsigned short cur_id; - - unsigned short GetID() - { - if (this->udpsock->GetPackets().size() == 65535) - throw SocketException("DNS queue full"); - - do - cur_id = (cur_id + 1) & 0xFFFF; - while (!cur_id || this->requests.count(cur_id)); - - return cur_id; - } - - public: - void Process(Request *req) override - { - Log(LOG_DEBUG_2) << "Resolver: Processing request to lookup " << req->name << ", of type " << req->type; - - if (req->use_cache && this->CheckCache(req)) - { - Log(LOG_DEBUG_2) << "Resolver: Using cached result"; - delete req; - return; - } - - if (!this->udpsock) - throw SocketException("No dns socket"); - - req->id = GetID(); - this->requests[req->id] = req; - - req->SetSecs(timeout); - - Packet *p = new Packet(this, &this->addrs); - p->flags = QUERYFLAGS_RD; - p->id = req->id; - p->questions.push_back(*req); - - this->udpsock->Reply(p); - } - - void RemoveRequest(Request *req) override - { - this->requests.erase(req->id); - } - - bool HandlePacket(ReplySocket *s, const unsigned char *const packet_buffer, int length, sockaddrs *from) override - { - if (length < Packet::HEADER_LENGTH) - return true; - - Packet recv_packet(this, from); - - try - { - recv_packet.Fill(packet_buffer, length); - } - catch (const SocketException &ex) - { - Log(LOG_DEBUG_2) << ex.GetReason(); - return true; - } - - if (!(recv_packet.flags & QUERYFLAGS_QR)) - { - if (!listen) - return true; - else if (recv_packet.questions.empty()) - { - Log(LOG_DEBUG_2) << "Resolver: Received a question with no questions?"; - return true; - } - - Packet *packet = new Packet(recv_packet); - packet->flags |= QUERYFLAGS_QR; /* This is a response */ - packet->flags |= QUERYFLAGS_AA; /* And we are authoritative */ - - packet->answers.clear(); - packet->authorities.clear(); - packet->additional.clear(); - - for (unsigned i = 0; i < recv_packet.questions.size(); ++i) - { - const Question& q = recv_packet.questions[i]; - - if (q.type == QUERY_AXFR || q.type == QUERY_SOA) - { - ResourceRecord rr(q.name, QUERY_SOA); - packet->answers.push_back(rr); - - if (q.type == QUERY_AXFR) - { - Anope::string token; - spacesepstream sep(nameservers); - while (sep.GetToken(token)) - { - ResourceRecord rr2(q.name, QUERY_NS); - rr2.rdata = token; - packet->answers.push_back(rr2); - } - } - break; - } - } - - Event::OnDnsRequest(&Event::DnsRequest::OnDnsRequest, recv_packet, packet); - - for (unsigned i = 0; i < recv_packet.questions.size(); ++i) - { - const Question& q = recv_packet.questions[i]; - - if (q.type == QUERY_AXFR) - { - ResourceRecord rr(q.name, QUERY_SOA); - packet->answers.push_back(rr); - break; - } - } - - s->Reply(packet); - return true; - } - - if (from == NULL) - { - Log(LOG_DEBUG_2) << "Resolver: Received an answer over TCP. This is not supported."; - return true; - } - else if (this->addrs != *from) - { - Log(LOG_DEBUG_2) << "Resolver: Received an answer from the wrong nameserver, Bad NAT or DNS forging attempt? '" << this->addrs.addr() << "' != '" << from->addr() << "'"; - return true; - } - - std::map::iterator it = this->requests.find(recv_packet.id); - if (it == this->requests.end()) - { - Log(LOG_DEBUG_2) << "Resolver: Received an answer for something we didn't request"; - return true; - } - Request *request = it->second; - - if (recv_packet.flags & QUERYFLAGS_OPCODE) - { - Log(LOG_DEBUG_2) << "Resolver: Received a nonstandard query"; - recv_packet.error = ERROR_NONSTANDARD_QUERY; - request->OnError(&recv_packet); - } - else if (recv_packet.flags & QUERYFLAGS_RCODE) - { - Error error = ERROR_UNKNOWN; - - switch (recv_packet.flags & QUERYFLAGS_RCODE) - { - case 1: - Log(LOG_DEBUG_2) << "Resolver: format error"; - error = ERROR_FORMAT_ERROR; - break; - case 2: - Log(LOG_DEBUG_2) << "Resolver: server error"; - error = ERROR_SERVER_FAILURE; - break; - case 3: - Log(LOG_DEBUG_2) << "Resolver: domain not found"; - error = ERROR_DOMAIN_NOT_FOUND; - break; - case 4: - Log(LOG_DEBUG_2) << "Resolver: not implemented"; - error = ERROR_NOT_IMPLEMENTED; - break; - case 5: - Log(LOG_DEBUG_2) << "Resolver: refused"; - error = ERROR_REFUSED; - break; - default: - break; - } - - recv_packet.error = error; - request->OnError(&recv_packet); - } - else if (recv_packet.questions.empty() || recv_packet.answers.empty()) - { - Log(LOG_DEBUG_2) << "Resolver: No resource records returned"; - recv_packet.error = ERROR_NO_RECORDS; - request->OnError(&recv_packet); - } - else - { - Log(LOG_DEBUG_2) << "Resolver: Lookup complete for " << request->name; - request->OnLookupComplete(&recv_packet); - this->AddCache(recv_packet); - } - - delete request; - return true; - } - - void UpdateSerial() override - { - serial = Anope::CurTime; - } - - void Notify(const Anope::string &zone) override - { - /* notify slaves of the update */ - for (unsigned i = 0; i < notify.size(); ++i) - { - const Anope::string &ip = notify[i].first; - short port = notify[i].second; - - sockaddrs addr; - addr.pton(ip.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, ip, port); - if (!addr.valid()) - return; - - Packet *packet = new Packet(this, &addr); - packet->flags = QUERYFLAGS_AA | QUERYFLAGS_OPCODE_NOTIFY; - try - { - packet->id = GetID(); - } - catch (const SocketException &) - { - delete packet; - continue; - } - - packet->questions.push_back(Question(zone, QUERY_SOA)); - - new NotifySocket(ip.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, packet); - } - } - - uint32_t GetSerial() const override - { - return serial; - } - - void Tick(time_t now) override - { - Log(LOG_DEBUG_2) << "Resolver: Purging DNS cache"; - - for (cache_map::iterator it = this->cache.begin(), it_next; it != this->cache.end(); it = it_next) - { - const Query &q = it->second; - const ResourceRecord &req = q.answers[0]; - it_next = it; - ++it_next; - - if (req.created + static_cast(req.ttl) < now) - this->cache.erase(it); - } - } - - private: - /** Add a record to the dns cache - * @param r The record - */ - void AddCache(Query &r) - { - const ResourceRecord &rr = r.answers[0]; - Log(LOG_DEBUG_3) << "Resolver cache: added cache for " << rr.name << " -> " << rr.rdata << ", ttl: " << rr.ttl; - this->cache[r.questions[0]] = r; - } - - /** Check the DNS cache to see if request can be handled by a cached result - * @return true if a cached result was found. - */ - bool CheckCache(Request *request) - { - cache_map::iterator it = this->cache.find(*request); - if (it != this->cache.end()) - { - Query &record = it->second; - Log(LOG_DEBUG_3) << "Resolver: Using cached result for " << request->name; - request->OnLookupComplete(&record); - return true; - } - - return false; - } - -}; - -class ModuleDNS : public Module - , public EventHook -{ - MyManager manager; - - Anope::string nameserver; - Anope::string ip; - int port; - - std::vector > notify; - - public: - ModuleDNS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - , manager(this) - { - - } - - ~ModuleDNS() - { - for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) - { - Socket *s = it->second; - ++it; - - if (dynamic_cast(s) || dynamic_cast(s)) - delete s; - } - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - - nameserver = block->Get("nameserver", "127.0.0.1"); - timeout = block->Get("timeout", "5"); - ip = block->Get("ip", "0.0.0.0"); - port = block->Get("port", "53"); - admin = block->Get("admin", "admin@example.com"); - nameservers = block->Get("nameservers", "ns1.example.com"); - refresh = block->Get("refresh", "3600"); - - for (int i = 0; i < block->CountBlock("notify"); ++i) - { - Configuration::Block *n = block->GetBlock("notify", i); - Anope::string nip = n->Get("ip"); - short nport = n->Get("port"); - - notify.push_back(std::make_pair(nip, nport)); - } - - if (Anope::IsFile(nameserver)) - { - std::ifstream f(nameserver.c_str()); - bool success = false; - - if (f.is_open()) - { - for (Anope::string server; std::getline(f, server.str());) - { - if (server.find("nameserver") == 0) - { - size_t i = server.find_first_of("123456789"); - if (i != Anope::string::npos) - { - if (server.substr(i).is_pos_number_only()) - { - nameserver = server.substr(i); - Log(LOG_DEBUG) << "Nameserver set to " << nameserver; - success = true; - break; - } - } - } - } - - f.close(); - } - - if (!success) - { - Log() << "Unable to find nameserver, defaulting to 127.0.0.1"; - nameserver = "127.0.0.1"; - } - } - - try - { - this->manager.SetIPPort(nameserver, ip, port, notify); - } - catch (const SocketException &ex) - { - throw ModuleException(ex.GetReason()); - } - } - - void OnModuleUnload(User *u, Module *m) override - { - for (std::map::iterator it = this->manager.requests.begin(), it_end = this->manager.requests.end(); it != it_end;) - { - unsigned short id = it->first; - Request *req = it->second; - ++it; - - if (req->creator == m) - { - Query rr(*req); - rr.error = ERROR_UNLOADED; - req->OnError(&rr); - - delete req; - this->manager.requests.erase(id); - } - } - } -}; - -MODULE_INIT(ModuleDNS) - diff --git a/modules/m_dnsbl.cpp b/modules/m_dnsbl.cpp deleted file mode 100644 index 49739a80c..000000000 --- a/modules/m_dnsbl.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - */ - -#include "module.h" -#include "modules/dns.h" - -using namespace DNS; - -static ServiceReference akills("XLineManager", "xlinemanager/sgline"); - -struct Blacklist -{ - struct Reply - { - int code; - Anope::string reason; - bool allow_account; - - Reply() : code(0), allow_account(false) { } - }; - - Anope::string name; - time_t bantime; - Anope::string reason; - std::vector replies; - - Blacklist() : bantime(0) { } - - Reply *Find(int code) - { - for (unsigned int i = 0; i < replies.size(); ++i) - if (replies[i].code == code) - return &replies[i]; - return NULL; - } -}; - -class DNSBLResolver : public Request -{ - Reference user; - Blacklist blacklist; - bool add_to_akill; - - public: - DNSBLResolver(Module *c, User *u, const Blacklist &b, const Anope::string &host, bool add_akill) : Request(DNS::manager, c, host, QUERY_A, true), user(u), blacklist(b), add_to_akill(add_akill) { } - - void OnLookupComplete(const Query *record) override - { - if (!user || user->Quitting()) - return; - - const ResourceRecord &ans_record = record->answers[0]; - // Replies should be in 127.0.0.0/24 - if (ans_record.rdata.find("127.0.0.") != 0) - return; - - sockaddrs sresult; - sresult.pton(AF_INET, ans_record.rdata); - int result = sresult.sa4.sin_addr.s_addr >> 24; - - Blacklist::Reply *reply = blacklist.Find(result); - if (!blacklist.replies.empty() && !reply) - return; - - if (reply && reply->allow_account && user->Account()) - return; - - Anope::string reason = this->blacklist.reason, addr = user->ip.addr(); - reason = reason.replace_all_cs("%n", user->nick); - reason = reason.replace_all_cs("%u", user->GetIdent()); - reason = reason.replace_all_cs("%g", user->realname); - reason = reason.replace_all_cs("%h", user->host); - reason = reason.replace_all_cs("%i", addr); - reason = reason.replace_all_cs("%r", reply ? reply->reason : ""); - reason = reason.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); - - ServiceBot *OperServ = Config->GetClient("OperServ"); - Log(creator, "dnsbl", OperServ) << user->GetMask() << " (" << addr << ") appears in " << this->blacklist.name; - - XLine *x = new XLine(xline); - x->SetMask("*@" + addr); - x->SetBy(OperServ ? OperServ->nick : "m_dnsbl"); - x->SetCreated(Anope::CurTime); - x->SetExpires(Anope::CurTime + this->blacklist.bantime); - x->SetReason(reason); - x->SetID(XLineManager::GenerateUID()); - - if (this->add_to_akill && akills) - { - akills->AddXLine(x); - akills->Send(NULL, x); - } - else - { - IRCD->SendAkill(NULL, x); - delete x; - } - } -}; - -class ModuleDNSBL : public Module - , public EventHook -{ - std::vector blacklists; - std::set exempts; - bool check_on_connect; - bool check_on_netburst; - bool add_to_akill; - - public: - ModuleDNSBL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) - { - - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - this->check_on_connect = block->Get("check_on_connect"); - this->check_on_netburst = block->Get("check_on_netburst"); - this->add_to_akill = block->Get("add_to_akill", "yes"); - - this->blacklists.clear(); - for (int i = 0; i < block->CountBlock("blacklist"); ++i) - { - Configuration::Block *bl = block->GetBlock("blacklist", i); - Blacklist blacklist; - - blacklist.name = bl->Get("name"); - if (blacklist.name.empty()) - continue; - blacklist.bantime = bl->Get("time", "4h"); - blacklist.reason = bl->Get("reason"); - - for (int j = 0; j < bl->CountBlock("reply"); ++j) - { - Configuration::Block *reply = bl->GetBlock("reply", j); - Blacklist::Reply r; - - r.code = reply->Get("code"); - r.reason = reply->Get("reason"); - r.allow_account = reply->Get("allow_account"); - - blacklist.replies.push_back(r); - } - - this->blacklists.push_back(blacklist); - } - - this->exempts.clear(); - for (int i = 0; i < block->CountBlock("exempt"); ++i) - this->exempts.insert(block->Get("ip")); - } - - void OnUserConnect(User *user, bool &exempt) override - { - if (exempt || user->Quitting() || (!this->check_on_connect && !Me->IsSynced()) || !DNS::manager) - return; - - if (!this->check_on_netburst && !user->server->IsSynced()) - return; - - /* At this time we only support IPv4 */ - if (!user->ip.valid() || user->ip.sa.sa_family != AF_INET) - /* User doesn't have a valid IPv4 IP (ipv6/spoof/etc) */ - return; - - if (this->exempts.count(user->ip.addr())) - return; - - const unsigned long &ip = user->ip.sa4.sin_addr.s_addr; - unsigned long reverse_ip = (ip << 24) | ((ip & 0xFF00) << 8) | ((ip & 0xFF0000) >> 8) | (ip >> 24); - - sockaddrs reverse = user->ip; - reverse.sa4.sin_addr.s_addr = reverse_ip; - - for (unsigned i = 0; i < this->blacklists.size(); ++i) - { - const Blacklist &b = this->blacklists[i]; - - Anope::string dnsbl_host = reverse.addr() + "." + b.name; - DNSBLResolver *res = NULL; - try - { - res = new DNSBLResolver(this, user, b, dnsbl_host, this->add_to_akill); - DNS::manager->Process(res); - } - catch (const SocketException &ex) - { - delete res; - Log(this) << ex.GetReason(); - } - } - } -}; - -MODULE_INIT(ModuleDNSBL) - diff --git a/modules/m_helpchan.cpp b/modules/m_helpchan.cpp deleted file mode 100644 index 3897b2b5a..000000000 --- a/modules/m_helpchan.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - */ - -#include "module.h" - -class HelpChannel : public Module - , public EventHook -{ - public: - HelpChannel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - { - } - - EventReturn OnChannelModeSet(Channel *c, const MessageSource &, ChannelMode *mode, const Anope::string ¶m) override - { - if (mode->name == "OP" && c && c->ci && c->name.equals_ci(Config->GetModule(this)->Get("helpchannel"))) - { - User *u = User::Find(param); - - if (u && c->ci->AccessFor(u).HasPriv("OPME")) - u->SetMode(Config->GetClient("OperServ"), "HELPOP"); - } - - return EVENT_CONTINUE; - } -}; - -MODULE_INIT(HelpChannel) diff --git a/modules/m_httpd.cpp b/modules/m_httpd.cpp deleted file mode 100644 index 17f80d376..000000000 --- a/modules/m_httpd.cpp +++ /dev/null @@ -1,473 +0,0 @@ -/* - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - */ - -#include "module.h" -#include "modules/httpd.h" -#include "modules/ssl.h" - -static Anope::string BuildDate() -{ - char timebuf[64]; - struct tm *tm = localtime(&Anope::CurTime); - strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", tm); - return timebuf; -} - -static Anope::string GetStatusFromCode(HTTPError err) -{ - switch (err) - { - case HTTP_ERROR_OK: - return "200 OK"; - case HTTP_FOUND: - return "302 Found"; - case HTTP_BAD_REQUEST: - return "400 Bad Request"; - case HTTP_PAGE_NOT_FOUND: - return "404 Not Found"; - case HTTP_NOT_SUPPORTED: - return "505 HTTP Version Not Supported"; - } - - return "501 Not Implemented"; -} - -class MyHTTPClient : public HTTPClient -{ - HTTPProvider *provider; - HTTPMessage message; - bool header_done, served; - Anope::string page_name; - Reference page; - Anope::string ip; - - unsigned content_length; - - enum - { - ACTION_NONE, - ACTION_GET, - ACTION_POST - } action; - - void Serve() - { - if (this->served) - return; - this->served = true; - - if (!this->page) - { - this->SendError(HTTP_PAGE_NOT_FOUND, "Page not found"); - return; - } - - if (this->ip == this->provider->ext_ip) - { - for (unsigned i = 0; i < this->provider->ext_headers.size(); ++i) - { - const Anope::string &token = this->provider->ext_headers[i]; - - if (this->message.headers.count(token)) - { - this->ip = this->message.headers[token]; - Log(LOG_DEBUG, "httpd") << "m_httpd: IP for connection " << this->GetFD() << " changed to " << this->ip; - break; - } - } - } - - Log(LOG_DEBUG, "httpd") << "m_httpd: Serving page " << this->page_name << " to " << this->ip; - - HTTPReply reply; - reply.content_type = this->page->GetContentType(); - - if (this->page->OnRequest(this->provider, this->page_name, this, this->message, reply)) - this->SendReply(&reply); - } - - public: - time_t created; - - MyHTTPClient(HTTPProvider *l, int f, const sockaddrs &a) : Socket(f, l->IsIPv6()), HTTPClient(l, f, a), provider(l), header_done(false), served(false), ip(a.addr()), content_length(0), action(ACTION_NONE), created(Anope::CurTime) - { - Log(LOG_DEBUG, "httpd") << "Accepted connection " << f << " from " << a.addr(); - } - - ~MyHTTPClient() - { - Log(LOG_DEBUG, "httpd") << "Closing connection " << this->GetFD() << " from " << this->ip; - } - - /* Close connection once all data is written */ - bool ProcessWrite() override - { - return !BinarySocket::ProcessWrite() || this->write_buffer.empty() ? false : true; - } - - const Anope::string GetIP() override - { - return this->ip; - } - - bool Read(const char *buffer, size_t l) override - { - message.content.append(buffer, l); - - for (size_t nl; !this->header_done && (nl = message.content.find('\n')) != Anope::string::npos;) - { - Anope::string token = message.content.substr(0, nl).trim(); - message.content = message.content.substr(nl + 1); - - if (token.empty()) - this->header_done = true; - else - this->Read(token); - } - - if (!this->header_done) - return true; - - if (this->message.content.length() >= this->content_length) - { - sepstream sep(this->message.content, '&'); - Anope::string token; - - while (sep.GetToken(token)) - { - size_t sz = token.find('='); - if (sz == Anope::string::npos || !sz || sz + 1 >= token.length()) - continue; - this->message.post_data[token.substr(0, sz)] = HTTPUtils::URLDecode(token.substr(sz + 1)); - Log(LOG_DEBUG_2) << "HTTP POST from " << this->clientaddr.addr() << ": " << token.substr(0, sz) << ": " << this->message.post_data[token.substr(0, sz)]; - } - - this->Serve(); - } - - return true; - } - - bool Read(const Anope::string &buf) - { - Log(LOG_DEBUG_2) << "HTTP from " << this->clientaddr.addr() << ": " << buf; - - if (this->action == ACTION_NONE) - { - std::vector params; - spacesepstream(buf).GetTokens(params); - - if (params.empty() || (params[0] != "GET" && params[0] != "POST")) - { - this->SendError(HTTP_BAD_REQUEST, "Unknown operation"); - return true; - } - - if (params.size() != 3) - { - this->SendError(HTTP_BAD_REQUEST, "Invalid parameters"); - return true; - } - - if (params[0] == "GET") - this->action = ACTION_GET; - else if (params[0] == "POST") - this->action = ACTION_POST; - - Anope::string targ = params[1]; - size_t q = targ.find('?'); - if (q != Anope::string::npos) - { - sepstream sep(targ.substr(q + 1), '&'); - targ = targ.substr(0, q); - - Anope::string token; - while (sep.GetToken(token)) - { - size_t sz = token.find('='); - if (sz == Anope::string::npos || !sz || sz + 1 >= token.length()) - continue; - this->message.get_data[token.substr(0, sz)] = HTTPUtils::URLDecode(token.substr(sz + 1)); - } - } - - this->page = this->provider->FindPage(targ); - this->page_name = targ; - } - else if (buf.find("Cookie: ") == 0) - { - spacesepstream sep(buf.substr(8)); - Anope::string token; - - while (sep.GetToken(token)) - { - size_t sz = token.find('='); - if (sz == Anope::string::npos || !sz || sz + 1 >= token.length()) - continue; - size_t end = token.length() - (sz + 1); - if (!sep.StreamEnd()) - --end; // Remove trailing ; - this->message.cookies[token.substr(0, sz)] = token.substr(sz + 1, end); - } - } - else if (buf.find("Content-Length: ") == 0) - { - try - { - this->content_length = convertTo(buf.substr(16)); - } - catch (const ConvertException &ex) { } - } - else if (buf.find(':') != Anope::string::npos) - { - size_t sz = buf.find(':'); - if (sz + 2 < buf.length()) - this->message.headers[buf.substr(0, sz)] = buf.substr(sz + 2); - } - - return true; - } - - void SendError(HTTPError err, const Anope::string &msg) override - { - HTTPReply h; - - h.error = err; - - h.Write(msg); - - this->SendReply(&h); - } - - void SendReply(HTTPReply *msg) override - { - this->WriteClient("HTTP/1.1 " + GetStatusFromCode(msg->error)); - this->WriteClient("Date: " + BuildDate()); - this->WriteClient("Server: Anope-" + Anope::VersionShort()); - if (msg->content_type.empty()) - this->WriteClient("Content-Type: text/html"); - else - this->WriteClient("Content-Type: " + msg->content_type); - this->WriteClient("Content-Length: " + stringify(msg->length)); - - for (unsigned i = 0; i < msg->cookies.size(); ++i) - { - Anope::string buf = "Set-Cookie:"; - - for (HTTPReply::cookie::iterator it = msg->cookies[i].begin(), it_end = msg->cookies[i].end(); it != it_end; ++it) - buf += " " + it->first + "=" + it->second + ";"; - - buf.erase(buf.length() - 1); - - this->WriteClient(buf); - } - - typedef std::map map; - for (map::iterator it = msg->headers.begin(), it_end = msg->headers.end(); it != it_end; ++it) - this->WriteClient(it->first + ": " + it->second); - - this->WriteClient("Connection: Close"); - this->WriteClient(""); - - for (unsigned i = 0; i < msg->out.size(); ++i) - { - HTTPReply::Data* d = msg->out[i]; - - this->Write(d->buf, d->len); - - delete d; - } - - msg->out.clear(); - } -}; - -class MyHTTPProvider : public HTTPProvider, public Timer -{ - int timeout; - std::map pages; - std::list > clients; - - public: - MyHTTPProvider(Module *c, const Anope::string &n, const Anope::string &i, const unsigned short p, const int t, bool s) : Socket(-1, i.find(':') != Anope::string::npos), HTTPProvider(c, n, i, p, s), Timer(c, 10, Anope::CurTime, true), timeout(t) { } - - void Tick(time_t) override - { - while (!this->clients.empty()) - { - Reference& c = this->clients.front(); - if (c && c->created + this->timeout >= Anope::CurTime) - break; - - delete c; - this->clients.pop_front(); - } - } - - ClientSocket* OnAccept(int fd, const sockaddrs &addr) override - { - MyHTTPClient *c = new MyHTTPClient(this, fd, addr); - this->clients.push_back(c); - return c; - } - - bool RegisterPage(HTTPPage *page) override - { - return this->pages.insert(std::make_pair(page->GetURL(), page)).second; - } - - void UnregisterPage(HTTPPage *page) override - { - this->pages.erase(page->GetURL()); - } - - HTTPPage* FindPage(const Anope::string &pname) - { - if (this->pages.count(pname) == 0) - return NULL; - return this->pages[pname]; - } -}; - -class HTTPD : public Module - , public EventHook -{ - ServiceReference sslref; - std::map providers; - public: - HTTPD(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - , sslref("SSLService", "ssl") - { - - } - - ~HTTPD() - { - for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) - { - Socket *s = it->second; - ++it; - - if (dynamic_cast(s) || dynamic_cast(s)) - delete s; - } - - this->providers.clear(); - } - - void OnReload(Configuration::Conf *config) override - { - Configuration::Block *conf = config->GetModule(this); - std::set existing; - - for (int i = 0; i < conf->CountBlock("httpd"); ++i) - { - Configuration::Block *block = conf->GetBlock("httpd", i); - - - const Anope::string &hname = block->Get("name", "httpd/main"); - existing.insert(hname); - - Anope::string ip = block->Get("ip"); - int port = block->Get("port", "8080"); - int timeout = block->Get("timeout", "30"); - bool ssl = block->Get("ssl", "no"); - Anope::string ext_ip = block->Get("extforward_ip"); - Anope::string ext_header = block->Get("extforward_header"); - - if (ip.empty()) - { - Log(this) << "You must configure a bind IP for HTTP server " << hname; - continue; - } - else if (port <= 0 || port > 65535) - { - Log(this) << "You must configure a (valid) listen port for HTTP server " << hname; - continue; - } - - MyHTTPProvider *p; - if (this->providers.count(hname) == 0) - { - try - { - p = new MyHTTPProvider(this, hname, ip, port, timeout, ssl); - if (ssl && sslref) - sslref->Init(p); - } - catch (const SocketException &ex) - { - Log(this) << "Unable to create HTTP server " << hname << ": " << ex.GetReason(); - continue; - } - this->providers[hname] = p; - - Log(this) << "Created HTTP server " << hname; - } - else - { - p = this->providers[hname]; - - if (p->GetIP() != ip || p->GetPort() != port) - { - delete p; - this->providers.erase(hname); - - Log(this) << "Changing HTTP server " << hname << " to " << ip << ":" << port; - - try - { - p = new MyHTTPProvider(this, hname, ip, port, timeout, ssl); - if (ssl && sslref) - sslref->Init(p); - } - catch (const SocketException &ex) - { - Log(this) << "Unable to create HTTP server " << hname << ": " << ex.GetReason(); - continue; - } - - this->providers[hname] = p; - } - } - - - p->ext_ip = ext_ip; - spacesepstream(ext_header).GetTokens(p->ext_headers); - } - - for (std::map::iterator it = this->providers.begin(), it_end = this->providers.end(); it != it_end;) - { - HTTPProvider *p = it->second; - ++it; - - if (existing.count(p->name) == 0) - { - Log(this) << "Removing HTTP server " << p->name; - this->providers.erase(p->name); - delete p; - } - } - } - - void OnModuleLoad(User *u, Module *m) override - { - for (std::map::iterator it = this->providers.begin(), it_end = this->providers.end(); it != it_end; ++it) - { - MyHTTPProvider *p = it->second; - - if (p->IsSSL() && sslref) - try - { - sslref->Init(p); - } - catch (const CoreException &) { } // Throws on reinitialization - } - } -}; - -MODULE_INIT(HTTPD) diff --git a/modules/m_proxyscan.cpp b/modules/m_proxyscan.cpp deleted file mode 100644 index 398e021db..000000000 --- a/modules/m_proxyscan.cpp +++ /dev/null @@ -1,380 +0,0 @@ -/* - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - */ - -#include "module.h" - -struct ProxyCheck -{ - std::set types; - std::vector ports; - time_t duration; - Anope::string reason; -}; - -static Anope::string ProxyCheckString; -static Anope::string target_ip; -static unsigned short target_port; -static bool add_to_akill; - -class ProxyCallbackListener : public ListenSocket -{ - class ProxyCallbackClient : public ClientSocket, public BufferedSocket - { - public: - ProxyCallbackClient(ListenSocket *l, int f, const sockaddrs &a) : Socket(f, l->IsIPv6()), ClientSocket(l, a), BufferedSocket() - { - } - - void OnAccept() override - { - this->Write(ProxyCheckString); - } - - bool ProcessWrite() override - { - return !BufferedSocket::ProcessWrite() || this->write_buffer.empty() ? false : true; - } - }; - - public: - ProxyCallbackListener(const Anope::string &b, int p) : Socket(-1, b.find(':') != Anope::string::npos), ListenSocket(b, p, false) - { - } - - ClientSocket *OnAccept(int fd, const sockaddrs &addr) override - { - return new ProxyCallbackClient(this, fd, addr); - } -}; - -class ProxyConnect : public ConnectionSocket -{ - static ServiceReference akills; - - public: - static std::set proxies; - - ProxyCheck proxy; - unsigned short port; - time_t created; - - ProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ConnectionSocket(), proxy(p), - port(po), created(Anope::CurTime) - { - proxies.insert(this); - } - - ~ProxyConnect() - { - proxies.erase(this); - } - - virtual void OnConnect() override = 0; - virtual const Anope::string GetType() const = 0; - - protected: - void Ban() - { - Anope::string reason = this->proxy.reason; - - reason = reason.replace_all_cs("%t", this->GetType()); - reason = reason.replace_all_cs("%i", this->conaddr.addr()); - reason = reason.replace_all_cs("%p", stringify(this->conaddr.port())); - - ServiceBot *OperServ = Config->GetClient("OperServ"); - Log(OperServ) << "PROXYSCAN: Open " << this->GetType() << " proxy found on " << this->conaddr.addr() << ":" << this->conaddr.port() << " (" << reason << ")"; - - XLine *x = new XLine("*@" + this->conaddr.addr(), OperServ ? OperServ->nick : "", Anope::CurTime + this->proxy.duration, reason, XLineManager::GenerateUID()); - if (add_to_akill && akills) - { - akills->AddXLine(x); - akills->Send(NULL, x); - } - else - { - if (IRCD->CanSZLine) - IRCD->SendSZLine(NULL, x); - else - IRCD->SendAkill(NULL, x); - delete x; - } - } -}; -ServiceReference ProxyConnect::akills("XLineManager", "xlinemanager/sgline"); -std::set ProxyConnect::proxies; - -class HTTPProxyConnect : public ProxyConnect, public BufferedSocket -{ - public: - HTTPProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ProxyConnect(p, po), BufferedSocket() - { - } - - void OnConnect() override - { - this->Write("CONNECT %s:%d HTTP/1.0", target_ip.c_str(), target_port); - this->Write("Content-length: 0"); - this->Write("Connection: close"); - this->Write(""); - } - - const Anope::string GetType() const override - { - return "HTTP"; - } - - bool ProcessRead() override - { - bool b = BufferedSocket::ProcessRead(); - if (this->GetLine() == ProxyCheckString) - { - this->Ban(); - return false; - } - return b; - } -}; - -class SOCKS5ProxyConnect : public ProxyConnect, public BinarySocket -{ - public: - SOCKS5ProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ProxyConnect(p, po), BinarySocket() - { - } - - void OnConnect() override - { - sockaddrs target_addr; - char buf[4 + sizeof(target_addr.sa4.sin_addr.s_addr) + sizeof(target_addr.sa4.sin_port)]; - int ptr = 0; - target_addr.pton(AF_INET, target_ip, target_port); - if (!target_addr.valid()) - return; - - buf[ptr++] = 5; // Version - buf[ptr++] = 1; // # of methods - buf[ptr++] = 0; // No authentication - - this->Write(buf, ptr); - - ptr = 1; - buf[ptr++] = 1; // Connect - buf[ptr++] = 0; // Reserved - buf[ptr++] = 1; // IPv4 - memcpy(buf + ptr, &target_addr.sa4.sin_addr.s_addr, sizeof(target_addr.sa4.sin_addr.s_addr)); - ptr += sizeof(target_addr.sa4.sin_addr.s_addr); - memcpy(buf + ptr, &target_addr.sa4.sin_port, sizeof(target_addr.sa4.sin_port)); - ptr += sizeof(target_addr.sa4.sin_port); - - this->Write(buf, ptr); - } - - const Anope::string GetType() const override - { - return "SOCKS5"; - } - - bool Read(const char *buffer, size_t l) override - { - if (l >= ProxyCheckString.length() && !strncmp(buffer, ProxyCheckString.c_str(), ProxyCheckString.length())) - { - this->Ban(); - return false; - } - return true; - } -}; - -class ModuleProxyScan : public Module - , public EventHook -{ - Anope::string listen_ip; - unsigned short listen_port; - Anope::string con_notice, con_source; - std::vector proxyscans; - - ProxyCallbackListener *listener; - - class ConnectionTimeout : public Timer - { - public: - ConnectionTimeout(Module *c, long timeout) : Timer(c, timeout, Anope::CurTime, true) - { - } - - void Tick(time_t) override - { - for (std::set::iterator it = ProxyConnect::proxies.begin(), it_end = ProxyConnect::proxies.end(); it != it_end;) - { - ProxyConnect *p = *it; - ++it; - - if (p->created + this->GetSecs() < Anope::CurTime) - delete p; - } - } - } connectionTimeout; - - public: - ModuleProxyScan(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - , connectionTimeout(this, 5) - { - - - this->listener = NULL; - } - - ~ModuleProxyScan() - { - for (std::set::iterator it = ProxyConnect::proxies.begin(), it_end = ProxyConnect::proxies.end(); it != it_end;) - { - ProxyConnect *p = *it; - ++it; - delete p; - } - - for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) - { - Socket *s = it->second; - ++it; - - ClientSocket *cs = dynamic_cast(s); - if (cs != NULL && cs->ls == this->listener) - delete s; - } - - delete this->listener; - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *config = Config->GetModule(this); - - Anope::string s_target_ip = config->Get("target_ip"); - if (s_target_ip.empty()) - throw ConfigException(this->name + " target_ip may not be empty"); - - int s_target_port = config->Get("target_port", "-1"); - if (s_target_port <= 0) - throw ConfigException(this->name + " target_port may not be empty and must be a positive number"); - - Anope::string s_listen_ip = config->Get("listen_ip"); - if (s_listen_ip.empty()) - throw ConfigException(this->name + " listen_ip may not be empty"); - - int s_listen_port = config->Get("listen_port", "-1"); - if (s_listen_port <= 0) - throw ConfigException(this->name + " listen_port may not be empty and must be a positive number"); - - target_ip = s_target_ip; - target_port = s_target_port; - this->listen_ip = s_listen_ip; - this->listen_port = s_listen_port; - this->con_notice = config->Get("connect_notice"); - this->con_source = config->Get("connect_source"); - add_to_akill = config->Get("add_to_akill", "true"); - this->connectionTimeout.SetSecs(config->Get("timeout", "5s")); - - ProxyCheckString = Config->GetBlock("networkinfo")->Get("networkname") + " proxy check"; - delete this->listener; - this->listener = NULL; - try - { - this->listener = new ProxyCallbackListener(this->listen_ip, this->listen_port); - } - catch (const SocketException &ex) - { - throw ConfigException("m_proxyscan: " + ex.GetReason()); - } - - this->proxyscans.clear(); - for (int i = 0; i < config->CountBlock("proxyscan"); ++i) - { - Configuration::Block *block = config->GetBlock("proxyscan", i); - ProxyCheck p; - Anope::string token; - - commasepstream sep(block->Get("type")); - while (sep.GetToken(token)) - { - if (!token.equals_ci("HTTP") && !token.equals_ci("SOCKS5")) - continue; - p.types.insert(token); - } - if (p.types.empty()) - continue; - - commasepstream sep2(block->Get("port")); - while (sep2.GetToken(token)) - { - try - { - unsigned short port = convertTo(token); - p.ports.push_back(port); - } - catch (const ConvertException &) { } - } - if (p.ports.empty()) - continue; - - p.duration = block->Get("time", "4h"); - p.reason = block->Get("reason"); - if (p.reason.empty()) - continue; - - this->proxyscans.push_back(p); - } - } - - void OnUserConnect(User *user, bool &exempt) override - { - if (exempt || user->Quitting() || !Me->IsSynced() || !user->server->IsSynced()) - return; - - /* At this time we only support IPv4 */ - if (!user->ip.valid() || user->ip.sa.sa_family != AF_INET) - /* User doesn't have a valid IPv4 IP (ipv6/spoof/etc) */ - return; - - if (!this->con_notice.empty() && !this->con_source.empty()) - { - ServiceBot *bi = ServiceBot::Find(this->con_source, true); - if (bi) - user->SendMessage(bi, this->con_notice); - } - - for (unsigned i = this->proxyscans.size(); i > 0; --i) - { - ProxyCheck &p = this->proxyscans[i - 1]; - - for (std::set::iterator it = p.types.begin(), it_end = p.types.end(); it != it_end; ++it) - { - for (unsigned k = 0; k < p.ports.size(); ++k) - { - try - { - ProxyConnect *con = NULL; - if (it->equals_ci("HTTP")) - con = new HTTPProxyConnect(p, p.ports[k]); - else if (it->equals_ci("SOCKS5")) - con = new SOCKS5ProxyConnect(p, p.ports[k]); - else - continue; - con->Connect(user->ip.addr(), p.ports[k]); - } - catch (const SocketException &ex) - { - Log(LOG_DEBUG) << "m_proxyscan: " << ex.GetReason(); - } - } - } - } - } -}; - -MODULE_INIT(ModuleProxyScan) - diff --git a/modules/m_redis.cpp b/modules/m_redis.cpp deleted file mode 100644 index 81c958f7c..000000000 --- a/modules/m_redis.cpp +++ /dev/null @@ -1,598 +0,0 @@ -/* - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * - */ - -#include "module.h" -#include "modules/redis.h" - -using namespace Redis; - -class MyRedisService; - -class RedisSocket : public BinarySocket, public ConnectionSocket -{ - size_t ParseReply(Reply &r, const char *buf, size_t l); - public: - MyRedisService *provider; - std::deque interfaces; - std::map subinterfaces; - - RedisSocket(MyRedisService *pro, bool v6) : Socket(-1, v6), provider(pro) { } - - ~RedisSocket(); - - void OnConnect() override; - void OnError(const Anope::string &error) override; - - bool Read(const char *buffer, size_t l) override; -}; - -class Transaction : public Interface -{ - public: - std::deque interfaces; - - Transaction(Module *creator) : Interface(creator) { } - - ~Transaction() - { - for (unsigned i = 0; i < interfaces.size(); ++i) - { - Interface *inter = interfaces[i]; - - if (!inter) - continue; - - inter->OnError("Interface going away"); - } - } - - void OnResult(const Reply &r) override - { - /* This is a multi bulk reply of the results of the queued commands - * in this transaction - */ - - Log(LOG_DEBUG_2) << "redis: transaction complete with " << r.multi_bulk.size() << " results"; - - for (unsigned i = 0; i < r.multi_bulk.size(); ++i) - { - const Reply *reply = r.multi_bulk[i]; - - if (interfaces.empty()) - break; - - Interface *inter = interfaces.front(); - interfaces.pop_front(); - - if (inter) - inter->OnResult(*reply); - } - } -}; - -class MyRedisService : public Provider -{ - public: - Anope::string host; - int port; - unsigned db; - - RedisSocket *sock, *sub; - - Transaction ti; - bool in_transaction; - - MyRedisService(Module *c, const Anope::string &n, const Anope::string &h, int p, unsigned d) : Provider(c, n), host(h), port(p), db(d), sock(NULL), sub(NULL), - ti(c), in_transaction(false) - { - sock = new RedisSocket(this, host.find(':') != Anope::string::npos); - sock->Connect(host, port); - - sub = new RedisSocket(this, host.find(':') != Anope::string::npos); - sub->Connect(host, port); - } - - ~MyRedisService() - { - if (sock) - { - sock->flags[SF_DEAD] = true; - sock->provider = NULL; - } - - if (sub) - { - sub->flags[SF_DEAD] = true; - sub->provider = NULL; - } - } - - private: - inline void Pack(std::vector &buffer, const char *buf, size_t sz = 0) - { - if (!sz) - sz = strlen(buf); - - size_t old_size = buffer.size(); - buffer.resize(old_size + sz); - std::copy(buf, buf + sz, buffer.begin() + old_size); - } - - void Send(RedisSocket *s, Interface *i, const std::vector > &args) - { - std::vector buffer; - - Pack(buffer, "*"); - Pack(buffer, stringify(args.size()).c_str()); - Pack(buffer, "\r\n"); - - for (unsigned j = 0; j < args.size(); ++j) - { - const std::pair &pair = args[j]; - - Pack(buffer, "$"); - Pack(buffer, stringify(pair.second).c_str()); - Pack(buffer, "\r\n"); - - Pack(buffer, pair.first, pair.second); - Pack(buffer, "\r\n"); - } - - if (buffer.empty()) - return; - - s->Write(&buffer[0], buffer.size()); - if (in_transaction) - { - ti.interfaces.push_back(i); - s->interfaces.push_back(NULL); // For the +Queued response - } - else - s->interfaces.push_back(i); - } - - public: - void SendCommand(RedisSocket *s, Interface *i, const std::vector &cmds) - { - std::vector > args; - for (unsigned j = 0; j < cmds.size(); ++j) - args.push_back(std::make_pair(cmds[j].c_str(), cmds[j].length())); - this->Send(s, i, args); - } - - void Send(Interface *i, const std::vector > &args) - { - if (!sock) - { - sock = new RedisSocket(this, host.find(':') != Anope::string::npos); - sock->Connect(host, port); - } - - this->Send(sock, i, args); - } - - void SendCommand(Interface *i, const std::vector &cmds) override - { - std::vector > args; - for (unsigned j = 0; j < cmds.size(); ++j) - args.push_back(std::make_pair(cmds[j].c_str(), cmds[j].length())); - this->Send(i, args); - } - - void SendCommand(Interface *i, const Anope::string &str) override - { - std::vector args; - spacesepstream(str).GetTokens(args); - this->SendCommand(i, args); - } - - bool BlockAndProcess() override - { - this->sock->ProcessWrite(); - this->sock->SetBlocking(true); - this->sock->ProcessRead(); - this->sock->SetBlocking(false); - return !this->sock->interfaces.empty(); - } - - void Subscribe(Interface *i, const Anope::string &ch) override - { - if (sub == NULL) - { - sub = new RedisSocket(this, host.find(':') != Anope::string::npos); - sub->Connect(host, port); - } - - std::vector args = { "SUBSCRIBE", ch }; - this->SendCommand(sub, NULL, args); - - sub->subinterfaces[ch] = i; - } - - void Unsubscribe(const Anope::string &pattern) override - { - if (sub) - sub->subinterfaces.erase(pattern); - } - - void StartTransaction() override - { - if (in_transaction) - throw CoreException(); - - this->SendCommand(NULL, "MULTI"); - in_transaction = true; - } - - void CommitTransaction() override - { - /* The result of the transaction comes back to the reply of EXEC as a multi bulk. - * The reply to the individual commands that make up the transaction when executed - * is a simple +QUEUED - */ - in_transaction = false; - this->SendCommand(&this->ti, "EXEC"); - } -}; - -RedisSocket::~RedisSocket() -{ - if (provider) - { - if (provider->sock == this) - provider->sock = NULL; - else if (provider->sub == this) - provider->sub = NULL; - } - - for (unsigned i = 0; i < interfaces.size(); ++i) - { - Interface *inter = interfaces[i]; - - if (!inter) - continue; - - inter->OnError("Interface going away"); - } -} - -void RedisSocket::OnConnect() -{ - Log() << "redis: Successfully connected to " << provider->name << (this == this->provider->sub ? " (sub)" : ""); - - this->provider->SendCommand(NULL, "CLIENT SETNAME Anope"); - this->provider->SendCommand(NULL, "SELECT " + stringify(provider->db)); -} - -void RedisSocket::OnError(const Anope::string &error) -{ - Log() << "redis: Error on " << provider->name << (this == this->provider->sub ? " (sub)" : "") << ": " << error; -} - -size_t RedisSocket::ParseReply(Reply &r, const char *buffer, size_t l) -{ - size_t used = 0; - - if (!l) - return used; - - if (r.type == Reply::MULTI_BULK) - goto multi_bulk_cont; - - switch (*buffer) - { - case '+': - { - Anope::string reason(buffer, 1, l - 1); - size_t nl = reason.find("\r\n"); - Log(LOG_DEBUG_2) << "redis: status ok: " << reason.substr(0, nl); - if (nl != Anope::string::npos) - { - r.type = Reply::OK; - used = 1 + nl + 2; - } - break; - } - case '-': - { - Anope::string reason(buffer, 1, l - 1); - size_t nl = reason.find("\r\n"); - Log(LOG_DEBUG) << "redis: status error: " << reason.substr(0, nl); - if (nl != Anope::string::npos) - { - r.type = Reply::NOT_OK; - used = 1 + nl + 2; - } - break; - } - case ':': - { - Anope::string ibuf(buffer, 1, l - 1); - size_t nl = ibuf.find("\r\n"); - if (nl != Anope::string::npos) - { - try - { - r.i = convertTo(ibuf.substr(0, nl)); - } - catch (const ConvertException &) { } - - r.type = Reply::INT; - used = 1 + nl + 2; - } - break; - } - case '$': - { - Anope::string reply(buffer + 1, l - 1); - /* This assumes one bulk can always fit in our recv buffer */ - size_t nl = reply.find("\r\n"); - if (nl != Anope::string::npos) - { - int len; - try - { - len = convertTo(reply.substr(0, nl)); - if (len >= 0) - { - if (1 + nl + 2 + len + 2 <= l) - { - used = 1 + nl + 2 + len + 2; - r.bulk = reply.substr(nl + 2, len); - r.type = Reply::BULK; - } - } - else - { - used = 1 + nl + 2 + 2; - r.type = Reply::BULK; - } - } - catch (const ConvertException &) { } - } - break; - } - multi_bulk_cont: - case '*': - { - if (r.type != Reply::MULTI_BULK) - { - Anope::string reply(buffer + 1, l - 1); - size_t nl = reply.find("\r\n"); - if (nl != Anope::string::npos) - { - r.type = Reply::MULTI_BULK; - try - { - r.multi_bulk_size = convertTo(reply.substr(0, nl)); - } - catch (const ConvertException &) { } - - used = 1 + nl + 2; - } - else - break; - } - else if (r.multi_bulk_size >= 0 && r.multi_bulk.size() == static_cast(r.multi_bulk_size)) - { - /* This multi bulk is already complete, so check the sub bulks */ - for (unsigned i = 0; i < r.multi_bulk.size(); ++i) - if (r.multi_bulk[i]->type == Reply::MULTI_BULK) - ParseReply(*r.multi_bulk[i], buffer + used, l - used); - break; - } - - for (int i = r.multi_bulk.size(); i < r.multi_bulk_size; ++i) - { - Reply *reply = new Reply(); - size_t u = ParseReply(*reply, buffer + used, l - used); - if (!u) - { - Log(LOG_DEBUG) << "redis: ran out of data to parse"; - delete reply; - break; - } - r.multi_bulk.push_back(reply); - used += u; - } - break; - } - default: - Log(LOG_DEBUG) << "redis: unknown reply " << *buffer; - } - - return used; -} - -bool RedisSocket::Read(const char *buffer, size_t l) -{ - static std::vector save; - std::vector copy; - - if (!save.empty()) - { - std::copy(buffer, buffer + l, std::back_inserter(save)); - - copy = save; - - buffer = ©[0]; - l = copy.size(); - } - - while (l) - { - static Reply r; - - size_t used = this->ParseReply(r, buffer, l); - if (!used) - { - Log(LOG_DEBUG) << "redis: used == 0 ?"; - r.Clear(); - break; - } - else if (used > l) - { - Log(LOG_DEBUG) << "redis: used > l ?"; - r.Clear(); - break; - } - - /* Full result is not here yet */ - if (r.type == Reply::MULTI_BULK && static_cast(r.multi_bulk_size) != r.multi_bulk.size()) - { - buffer += used; - l -= used; - break; - } - - if (this == provider->sub) - { - std::map::iterator it = this->subinterfaces.find(r.multi_bulk[1]->bulk); - if (it != this->subinterfaces.end()) - it->second->OnResult(r); - } - else - { - if (this->interfaces.empty()) - { - Log(LOG_DEBUG) << "redis: no interfaces?"; - } - else - { - Interface *i = this->interfaces.front(); - this->interfaces.pop_front(); - - if (i) - { - if (r.type != Reply::NOT_OK) - i->OnResult(r); - else - i->OnError(r.bulk); - } - } - } - - buffer += used; - l -= used; - - r.Clear(); - } - - if (l) - { - save.resize(l); - std::copy(buffer, buffer + l, save.begin()); - } - else - std::vector().swap(save); - - return true; -} - - -class ModuleRedis : public Module - , public EventHook -{ - std::map services; - - public: - ModuleRedis(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - { - } - - ~ModuleRedis() - { - for (std::map::iterator it = services.begin(); it != services.end(); ++it) - { - MyRedisService *p = it->second; - - delete p->sock; - p->sock = NULL; - delete p->sub; - p->sub = NULL; - - delete p; - } - } - - void OnReload(Configuration::Conf *conf) override - { - Configuration::Block *block = conf->GetModule(this); - std::vector new_services; - - for (int i = 0; i < block->CountBlock("redis"); ++i) - { - Configuration::Block *redis = block->GetBlock("redis", i); - - const Anope::string &n = redis->Get("name"), - &ip = redis->Get("ip"); - int port = redis->Get("port"); - unsigned db = redis->Get("db"); - - delete services[n]; - services[n] = new MyRedisService(this, n, ip, port, db); - new_services.push_back(n); - } - - for (std::map::iterator it = services.begin(); it != services.end();) - { - Provider *p = it->second; - ++it; - - if (std::find(new_services.begin(), new_services.end(), p->name) == new_services.end()) - delete it->second; - } - } - - void OnModuleUnload(User *, Module *m) override - { - for (std::map::iterator it = services.begin(); it != services.end(); ++it) - { - MyRedisService *p = it->second; - - if (p->sock) - for (unsigned i = p->sock->interfaces.size(); i > 0; --i) - { - Interface *inter = p->sock->interfaces[i - 1]; - - if (inter && inter->owner == m) - { - inter->OnError(m->name + " being unloaded"); - p->sock->interfaces.erase(p->sock->interfaces.begin() + i - 1); - } - } - - if (p->sub) - for (unsigned i = p->sub->interfaces.size(); i > 0; --i) - { - Interface *inter = p->sub->interfaces[i - 1]; - - if (inter && inter->owner == m) - { - inter->OnError(m->name + " being unloaded"); - p->sub->interfaces.erase(p->sub->interfaces.begin() + i - 1); - } - } - - for (unsigned i = p->ti.interfaces.size(); i > 0; --i) - { - Interface *inter = p->ti.interfaces[i - 1]; - - if (inter && inter->owner == m) - { - inter->OnError(m->name + " being unloaded"); - p->ti.interfaces.erase(p->ti.interfaces.begin() + i - 1); - } - } - } - } -}; - -MODULE_INIT(ModuleRedis) diff --git a/modules/m_rewrite.cpp b/modules/m_rewrite.cpp deleted file mode 100644 index 37c58d5a9..000000000 --- a/modules/m_rewrite.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/* - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - */ - -#include "module.h" - -struct Rewrite -{ - Anope::string client, source_message, target_message, desc; - - bool Matches(const std::vector &message) - { - std::vector sm; - spacesepstream(this->source_message).GetTokens(sm); - - for (unsigned i = 0; i < sm.size(); ++i) - if (i >= message.size() || (sm[i] != "$" && !sm[i].equals_ci(message[i]))) - return false; - - return true; - } - - Anope::string Process(CommandSource &source, const std::vector ¶ms) - { - spacesepstream sep(this->target_message); - Anope::string token, message; - - while (sep.GetToken(token)) - { - if (token[0] != '$') - message += " " + token; - else if (token == "$me") - message += " " + source.GetNick(); - else - { - int num = -1, end = -1; - try - { - Anope::string num_str = token.substr(1); - size_t hy = num_str.find('-'); - if (hy == Anope::string::npos) - { - num = convertTo(num_str); - end = num + 1; - } - else - { - num = convertTo(num_str.substr(0, hy)); - if (hy == num_str.length() - 1) - end = params.size(); - else - end = convertTo(num_str.substr(hy + 1)) + 1; - } - } - catch (const ConvertException &) - { - continue; - } - - for (int i = num; i < end && static_cast(i) < params.size(); ++i) - message += " " + params[i]; - } - } - - message.trim(); - return message; - } - - static std::vector rewrites; - - static Rewrite *Find(const Anope::string &client, const Anope::string &cmd) - { - for (unsigned i = 0; i < rewrites.size(); ++i) - { - Rewrite &r = rewrites[i]; - - if ((client.empty() || r.client.equals_ci(client)) && (r.source_message.equals_ci(cmd) || r.source_message.find_ci(cmd + " ") == 0)) - return &r; - } - - return NULL; - } - - static Rewrite *Match(const Anope::string &client, const std::vector ¶ms) - { - for (unsigned i = 0; i < rewrites.size(); ++i) - { - Rewrite &r = rewrites[i]; - - if ((client.empty() || r.client.equals_ci(client)) && r.Matches(params)) - return &r; - } - - return NULL; - } -}; - -std::vector Rewrite::rewrites; - -class RewriteCommand : public Command -{ - public: - RewriteCommand(Module *creator) : Command(creator, "rewrite", 0, 0) { } - - void Execute(CommandSource &source, const std::vector ¶ms) override - { - std::vector full_params = params; - full_params.insert(full_params.begin(), source.command); - - Rewrite *r = Rewrite::Match(!source.c ? source.service->nick : "", full_params); - if (r != NULL) - { - Anope::string new_message = r->Process(source, full_params); - Log(LOG_DEBUG) << "m_rewrite: Rewrote '" << source.command << (!params.empty() ? " " + params[0] : "") << "' to '" << new_message << "' using '" << r->source_message << "'"; - source.service = ServiceBot::Find(r->client, true); - if (!source.service) - return; - Command::Run(source, new_message); - } - else - Log() << "m_rewrite: Unable to rewrite '" << source.command << (!params.empty() ? " " + params[0] : "") << "'"; - } - - void OnServHelp(CommandSource &source) override - { - Rewrite *r = Rewrite::Find(!source.c ? source.service->nick : "", source.command); - if (r != NULL && !r->desc.empty()) - { - this->SetDesc(r->desc); - Command::OnServHelp(source); - } - } - - bool OnHelp(CommandSource &source, const Anope::string &subcommand) override - { - Rewrite *r = Rewrite::Find(!source.c ? source.service->nick : "", source.command); - if (r != NULL && !r->desc.empty()) - { - source.Reply(r->desc); - size_t sz = r->target_message.find(' '); - source.Reply(_("This command is an alias to the command %s."), sz != Anope::string::npos ? r->target_message.substr(0, sz).c_str() : r->target_message.c_str()); - return true; - } - return false; - } -}; - -class ModuleRewrite : public Module -{ - RewriteCommand cmdrewrite; - - public: - ModuleRewrite(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) - , cmdrewrite(this) - { - } - - void OnReload(Configuration::Conf *conf) override - { - Rewrite::rewrites.clear(); - - for (int i = 0; i < conf->CountBlock("command"); ++i) - { - Configuration::Block *block = conf->GetBlock("command", i); - - if (!block->Get("rewrite")) - continue; - - Rewrite rw; - - rw.client = block->Get("service"); - rw.source_message = block->Get("rewrite_source"); - rw.target_message = block->Get("rewrite_target"); - rw.desc = block->Get("rewrite_description"); - - if (rw.client.empty() || rw.source_message.empty() || rw.target_message.empty()) - continue; - - Rewrite::rewrites.push_back(rw); - } - } -}; - -MODULE_INIT(ModuleRewrite) diff --git a/modules/m_sasl.cpp b/modules/m_sasl.cpp deleted file mode 100644 index f12cae8b8..000000000 --- a/modules/m_sasl.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/* - * - * (C) 2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - */ - -#include "module.h" -#include "modules/sasl.h" -#include "modules/ns_cert.h" - -using namespace SASL; - -class Plain : public Mechanism -{ - public: - Plain(Module *o) : Mechanism(o, "PLAIN") { } - - void ProcessMessage(Session *sess, const SASL::Message &m) override - { - if (m.type == "S") - { - sasl->SendMessage(sess, "C", "+"); - } - else if (m.type == "C") - { - Anope::string decoded; - Anope::B64Decode(m.data, decoded); - - size_t p = decoded.find('\0'); - if (p == Anope::string::npos) - { - sasl->Fail(sess); - delete sess; - return; - } - decoded = decoded.substr(p + 1); - - p = decoded.find('\0'); - if (p == Anope::string::npos) - { - sasl->Fail(sess); - delete sess; - return; - } - - Anope::string acc = decoded.substr(0, p), - pass = decoded.substr(p + 1); - - if (!NickServ::service || acc.empty() || pass.empty() || !IRCD->IsNickValid(acc) || pass.find_first_of("\r\n") != Anope::string::npos) - { - sasl->Fail(sess); - delete sess; - return; - } - - NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new IdentifyRequestListener(m.source), this->owner, acc, pass); - Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req); - req->Dispatch(); - } - } -}; - -class External : public Mechanism -{ - ServiceReference certs; - - struct Session : SASL::Session - { - Anope::string cert; - - Session(Mechanism *m, const Anope::string &u) : SASL::Session(m, u) { } - }; - - public: - External(Module *o) : Mechanism(o, "EXTERNAL"), certs("CertService", "certs") - { - if (!IRCD || !IRCD->CanCertFP) - throw ModuleException("No CertFP"); - } - - Session* CreateSession(const Anope::string &uid) override - { - return new Session(this, uid); - } - - void ProcessMessage(SASL::Session *sess, const SASL::Message &m) override - { - Session *mysess = anope_dynamic_static_cast(sess); - - if (m.type == "S") - { - mysess->cert = m.ext; - - sasl->SendMessage(sess, "C", "+"); - } - else if (m.type == "C") - { - if (!certs) - { - sasl->Fail(sess); - delete sess; - return; - } - - NickServ::Account *nc = certs->FindAccountFromCert(mysess->cert); - if (!nc || nc->HasFieldS("NS_SUSPENDED")) - { - sasl->Fail(sess); - delete sess; - return; - } - - Log(Config->GetClient("NickServ")) << "A user identified to account " << nc->GetDisplay() << " using SASL EXTERNAL"; - sasl->Succeed(sess, nc); - delete sess; - } - } -}; - -class SASLService : public SASL::Service, public Timer -{ - std::map sessions; - - public: - SASLService(Module *o) : SASL::Service(o), Timer(o, 60, Anope::CurTime, true) { } - - ~SASLService() - { - for (std::map::iterator it = sessions.begin(); it != sessions.end(); it++) - delete it->second; - } - - void ProcessMessage(const SASL::Message &m) override - { - if (m.target != "*") - { - Server *s = Server::Find(m.target); - if (s != Me) - { - User *u = User::Find(m.target); - if (!u || u->server != Me) - return; - } - } - - Session* &session = sessions[m.source]; - - if (m.type == "S") - { - ServiceReference mech("SASL::Mechanism", m.data); - if (!mech) - { - Session tmp(NULL, m.source); - - sasl->SendMechs(&tmp); - sasl->Fail(&tmp); - return; - } - - if (!session) - session = mech->CreateSession(m.source); - } - else if (m.type == "D") - { - delete session; - sessions.erase(m.source); - return; - } - - if (session && session->mech) - session->mech->ProcessMessage(session, m); - } - - Anope::string GetAgent() override - { - Anope::string agent = Config->GetModule(Service::owner)->Get("agent", "NickServ"); - ServiceBot *bi = Config->GetClient(agent); - if (bi) - agent = bi->GetUID(); - return agent; - } - - Session* GetSession(const Anope::string &uid) override - { - std::map::iterator it = sessions.find(uid); - if (it != sessions.end()) - return it->second; - return NULL; - } - - void RemoveSession(Session *sess) override - { - sessions.erase(sess->uid); - } - - void DeleteSessions(Mechanism *mech, bool da) override - { - for (std::map::iterator it = sessions.begin(); it != sessions.end();) - { - std::map::iterator del = it++; - if (*del->second->mech == mech) - { - if (da) - this->SendMessage(del->second, "D", "A"); - delete del->second; - } - } - } - - void SendMessage(Session *session, const Anope::string &mtype, const Anope::string &data) override - { - SASL::Message msg; - msg.source = this->GetAgent(); - msg.target = session->uid; - msg.type = mtype; - msg.data = data; - - IRCD->SendSASLMessage(msg); - } - - void Succeed(Session *session, NickServ::Account *nc) override - { - IRCD->SendSVSLogin(session->uid, nc->GetDisplay()); - this->SendMessage(session, "D", "S"); - } - - void Fail(Session *session) override - { - this->SendMessage(session, "D", "F"); - } - - void SendMechs(Session *session) override - { - std::vector mechs = Service::GetServiceKeys("SASL::Mechanism"); - Anope::string buf; - for (unsigned j = 0; j < mechs.size(); ++j) - buf += "," + mechs[j]; - - this->SendMessage(session, "M", buf.empty() ? "" : buf.substr(1)); - } - - void Tick(time_t) override - { - for (std::map::iterator it = sessions.begin(); it != sessions.end();) - { - Anope::string key = it->first; - Session *s = it->second; - ++it; - - if (!s || !s->mech || s->created + 60 < Anope::CurTime) - { - delete s; - sessions.erase(key); - } - } - } -}; - -class ModuleSASL : public Module -{ - SASLService sasl; - - Plain plain; - External *external; - - public: - ModuleSASL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , sasl(this) - , plain(this) - , external(NULL) - { - try - { - external = new External(this); - } - catch (ModuleException &) { } - } - - ~ModuleSASL() - { - delete external; - } -}; - -MODULE_INIT(ModuleSASL) diff --git a/modules/m_xmlrpc.cpp b/modules/m_xmlrpc.cpp deleted file mode 100644 index 2d1cd2c0e..000000000 --- a/modules/m_xmlrpc.cpp +++ /dev/null @@ -1,221 +0,0 @@ - -#include "module.h" -#include "modules/xmlrpc.h" -#include "modules/httpd.h" - -static struct special_chars -{ - Anope::string character; - Anope::string replace; - - special_chars(const Anope::string &c, const Anope::string &r) : character(c), replace(r) { } -} -special[] = { - special_chars("&", "&"), - special_chars("\"", """), - special_chars("<", "<"), - special_chars(">", "&qt;"), - special_chars("'", "'"), - special_chars("\n", " "), - special_chars("\002", ""), // bold - special_chars("\003", ""), // color - special_chars("\035", ""), // italics - special_chars("\037", ""), // underline - special_chars("\026", ""), // reverses - special_chars("", "") -}; - -class MyXMLRPCServiceInterface : public XMLRPCServiceInterface, public HTTPPage -{ - std::deque events; - - public: - MyXMLRPCServiceInterface(Module *creator, const Anope::string &sname) : XMLRPCServiceInterface(creator, sname), HTTPPage("/xmlrpc", "text/xml") { } - - void Register(XMLRPCEvent *event) - { - this->events.push_back(event); - } - - void Unregister(XMLRPCEvent *event) - { - std::deque::iterator it = std::find(this->events.begin(), this->events.end(), event); - - if (it != this->events.end()) - this->events.erase(it); - } - - Anope::string Sanitize(const Anope::string &string) override - { - Anope::string ret = string; - for (int i = 0; special[i].character.empty() == false; ++i) - ret = ret.replace_all_cs(special[i].character, special[i].replace); - return ret; - } - - static Anope::string Unescape(const Anope::string &string) - { - Anope::string ret = string; - for (int i = 0; special[i].character.empty() == false; ++i) - if (!special[i].replace.empty()) - ret = ret.replace_all_cs(special[i].replace, special[i].character); - - for (size_t i, last = 0; (i = string.find("&#", last)) != Anope::string::npos;) - { - last = i + 1; - - size_t end = string.find(';', i); - if (end == Anope::string::npos) - break; - - Anope::string ch = string.substr(i + 2, end - (i + 2)); - - if (ch.empty()) - continue; - - long l; - if (!ch.empty() && ch[0] == 'x') - l = strtol(ch.substr(1).c_str(), NULL, 16); - else - l = strtol(ch.c_str(), NULL, 10); - - if (l > 0 && l < 256) - ret = ret.replace_all_cs("&#" + ch + ";", Anope::string(l)); - } - - return ret; - } - - private: - static bool GetData(Anope::string &content, Anope::string &tag, Anope::string &data) - { - if (content.empty()) - return false; - - Anope::string prev, cur; - bool istag; - - do - { - prev = cur; - cur.clear(); - - int len = 0; - istag = false; - - if (content[0] == '<') - { - len = content.find_first_of('>'); - istag = true; - } - else if (content[0] != '>') - { - len = content.find_first_of('<'); - } - - if (len) - { - if (istag) - { - cur = content.substr(1, len - 1); - content.erase(0, len + 1); - while (!content.empty() && content[0] == ' ') - content.erase(content.begin()); - } - else - { - cur = content.substr(0,len); - content.erase(0, len); - } - } - } - while (istag && !content.empty()); - - tag = Unescape(prev); - data = Unescape(cur); - return !istag && !data.empty(); - } - - public: - bool OnRequest(HTTPProvider *provider, const Anope::string &page_name, HTTPClient *client, HTTPMessage &message, HTTPReply &reply) override - { - Anope::string content = message.content, tname, data; - XMLRPCRequest request(reply); - - while (GetData(content, tname, data)) - { - Log(LOG_DEBUG) << "m_xmlrpc: Tag name: " << tname << ", data: " << data; - if (tname == "methodName") - request.name = data; - else if (tname == "name" && data == "id") - { - GetData(content, tname, data); - request.id = data; - } - else if (tname == "string") - request.data.push_back(data); - } - - for (unsigned i = 0; i < this->events.size(); ++i) - { - XMLRPCEvent *e = this->events[i]; - - if (!e->Run(this, client, request)) - return false; - else if (!request.get_replies().empty()) - { - this->Reply(request); - return true; - } - } - - reply.error = HTTP_PAGE_NOT_FOUND; - reply.Write("Unrecognized query"); - return true; - } - - void Reply(XMLRPCRequest &request) - { - if (!request.id.empty()) - request.reply("id", request.id); - - Anope::string r = "\n\n\n\n\n\n"; - for (std::map::const_iterator it = request.get_replies().begin(); it != request.get_replies().end(); ++it) - r += "\n" + it->first + "\n\n" + this->Sanitize(it->second) + "\n\n\n"; - r += "\n\n\n\n"; - - request.r.Write(r); - } -}; - -class ModuleXMLRPC : public Module -{ - ServiceReference httpref; - public: - MyXMLRPCServiceInterface xmlrpcinterface; - - ModuleXMLRPC(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) - , xmlrpcinterface(this, "xmlrpc") - { - - } - - ~ModuleXMLRPC() - { - if (httpref) - httpref->UnregisterPage(&xmlrpcinterface); - } - - void OnReload(Configuration::Conf *conf) override - { - if (httpref) - httpref->UnregisterPage(&xmlrpcinterface); - this->httpref = ServiceReference("HTTPProvider", conf->GetModule(this)->Get("server", "httpd/main")); - if (!httpref) - throw ConfigException("Unable to find http reference, is m_httpd loaded?"); - httpref->RegisterPage(&xmlrpcinterface); - } -}; - -MODULE_INIT(ModuleXMLRPC) - diff --git a/modules/m_xmlrpc_main.cpp b/modules/m_xmlrpc_main.cpp deleted file mode 100644 index af0253028..000000000 --- a/modules/m_xmlrpc_main.cpp +++ /dev/null @@ -1,279 +0,0 @@ -#include "module.h" -#include "modules/xmlrpc.h" - -static Module *me; - -class XMLRPCIdentifyRequest : public NickServ::IdentifyRequestListener -{ - XMLRPCRequest request; - HTTPReply repl; /* Request holds a reference to the HTTPReply, because we might exist long enough to invalidate it - we'll copy it here then reset the reference before we use it */ - Reference client; - Reference xinterface; - - public: - XMLRPCIdentifyRequest(XMLRPCRequest& req, HTTPClient *c, XMLRPCServiceInterface* iface) : request(req), repl(request.r), client(c), xinterface(iface) { } - - void OnSuccess(NickServ::IdentifyRequest *req) override - { - if (!xinterface || !client) - return; - - request.r = this->repl; - - request.reply("result", "Success"); - request.reply("account", req->GetAccount()); - - xinterface->Reply(request); - client->SendReply(&request.r); - } - - void OnFail(NickServ::IdentifyRequest *req) override - { - if (!xinterface || !client) - return; - - request.r = this->repl; - - request.reply("error", "Invalid password"); - - xinterface->Reply(request); - client->SendReply(&request.r); - } -}; - -class MyXMLRPCEvent : public XMLRPCEvent -{ - public: - bool Run(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) override - { - if (request.name == "command") - this->DoCommand(iface, client, request); - else if (request.name == "checkAuthentication") - return this->DoCheckAuthentication(iface, client, request); - else if (request.name == "stats") - this->DoStats(iface, client, request); - else if (request.name == "channel") - this->DoChannel(iface, client, request); - else if (request.name == "user") - this->DoUser(iface, client, request); - else if (request.name == "opers") - this->DoOperType(iface, client, request); - - return true; - } - - private: - void DoCommand(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) - { - Anope::string service = request.data.size() > 0 ? request.data[0] : ""; - Anope::string user = request.data.size() > 1 ? request.data[1] : ""; - Anope::string command = request.data.size() > 2 ? request.data[2] : ""; - - if (service.empty() || user.empty() || command.empty()) - request.reply("error", "Invalid parameters"); - else - { - ServiceBot *bi = ServiceBot::Find(service, true); - if (!bi) - request.reply("error", "Invalid service"); - else - { - request.reply("result", "Success"); - - NickServ::Nick *na = NickServ::FindNick(user); - - Anope::string out; - - struct XMLRPCommandReply : CommandReply - { - Anope::string &str; - - XMLRPCommandReply(Anope::string &s) : str(s) { } - - void SendMessage(const MessageSource &, const Anope::string &msg) override - { - str += msg + "\n"; - }; - } - reply(out); - - CommandSource source(user, NULL, na ? na->GetAccount() : NULL, &reply, bi); - Command::Run(source, command); - - if (!out.empty()) - request.reply("return", iface->Sanitize(out)); - } - } - } - - bool DoCheckAuthentication(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) - { - Anope::string username = request.data.size() > 0 ? request.data[0] : ""; - Anope::string password = request.data.size() > 1 ? request.data[1] : ""; - - if (username.empty() || password.empty() || !NickServ::service) - request.reply("error", "Invalid parameters"); - else - { - NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new XMLRPCIdentifyRequest(request, client, iface), me, username, password); - Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req); - req->Dispatch(); - return false; - } - - return true; - } - - void DoStats(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) - { - request.reply("uptime", stringify(Anope::CurTime - Anope::StartTime)); - request.reply("uplinkname", Me->GetLinks().front()->GetName()); - { - Anope::string buf; - for (std::set::iterator it = Servers::Capab.begin(); it != Servers::Capab.end(); ++it) - buf += " " + *it; - if (!buf.empty()) - buf.erase(buf.begin()); - request.reply("uplinkcapab", buf); - } - request.reply("usercount", stringify(UserListByNick.size())); - request.reply("maxusercount", stringify(MaxUserCount)); - request.reply("channelcount", stringify(ChannelList.size())); - } - - void DoChannel(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) - { - if (request.data.empty()) - return; - - Channel *c = Channel::Find(request.data[0]); - - request.reply("name", iface->Sanitize(c ? c->name : request.data[0])); - - if (c) - { - request.reply("bancount", stringify(c->HasMode("BAN"))); - int count = 0; - std::vector v = c->GetModeList("BAN"); - for (unsigned int i = 0; i < v.size(); ++i) - request.reply("ban" + stringify(++count), iface->Sanitize(v[i])); - - request.reply("exceptcount", stringify(c->HasMode("EXCEPT"))); - count = 0; - v = c->GetModeList("EXCEPT"); - for (unsigned int i = 0; i < v.size(); ++i) - request.reply("except" + stringify(++count), iface->Sanitize(v[i])); - - request.reply("invitecount", stringify(c->HasMode("INVITEOVERRIDE"))); - count = 0; - v = c->GetModeList("INVITEOVERRIDE"); - for (unsigned int i = 0; i < v.size(); ++i) - request.reply("invite" + stringify(++count), iface->Sanitize(v[i])); - - Anope::string users; - for (Channel::ChanUserList::const_iterator it = c->users.begin(); it != c->users.end(); ++it) - { - ChanUserContainer *uc = it->second; - users += uc->status.BuildModePrefixList() + uc->user->nick + " "; - } - if (!users.empty()) - { - users.erase(users.length() - 1); - request.reply("users", iface->Sanitize(users)); - } - - if (!c->topic.empty()) - request.reply("topic", iface->Sanitize(c->topic)); - - if (!c->topic_setter.empty()) - request.reply("topicsetter", iface->Sanitize(c->topic_setter)); - - request.reply("topictime", stringify(c->topic_time)); - request.reply("topicts", stringify(c->topic_ts)); - } - } - - void DoUser(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) - { - if (request.data.empty()) - return; - - User *u = User::Find(request.data[0]); - - request.reply("nick", iface->Sanitize(u ? u->nick : request.data[0])); - - if (u) - { - request.reply("ident", iface->Sanitize(u->GetIdent())); - request.reply("vident", iface->Sanitize(u->GetVIdent())); - request.reply("host", iface->Sanitize(u->host)); - if (!u->vhost.empty()) - request.reply("vhost", iface->Sanitize(u->vhost)); - if (!u->chost.empty()) - request.reply("chost", iface->Sanitize(u->chost)); - request.reply("ip", u->ip.addr()); - request.reply("timestamp", stringify(u->timestamp)); - request.reply("signon", stringify(u->signon)); - if (u->Account()) - { - request.reply("account", iface->Sanitize(u->Account()->GetDisplay())); - if (u->Account()->o) - request.reply("opertype", iface->Sanitize(u->Account()->o->GetType()->GetName())); - } - - Anope::string channels; - for (User::ChanUserList::const_iterator it = u->chans.begin(); it != u->chans.end(); ++it) - { - ChanUserContainer *cc = it->second; - channels += cc->status.BuildModePrefixList() + cc->chan->name + " "; - } - if (!channels.empty()) - { - channels.erase(channels.length() - 1); - request.reply("channels", channels); - } - } - } - - void DoOperType(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) - { - for (unsigned i = 0; i < Config->MyOperTypes.size(); ++i) - { - OperType *ot = Config->MyOperTypes[i]; - Anope::string perms; - for (std::list::const_iterator it2 = ot->GetPrivs().begin(), it2_end = ot->GetPrivs().end(); it2 != it2_end; ++it2) - perms += " " + *it2; - for (std::list::const_iterator it2 = ot->GetCommands().begin(), it2_end = ot->GetCommands().end(); it2 != it2_end; ++it2) - perms += " " + *it2; - request.reply(ot->GetName(), perms); - } - } -}; - -class ModuleXMLRPCMain : public Module -{ - ServiceReference xmlrpc; - - MyXMLRPCEvent stats; - - public: - ModuleXMLRPCMain(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), xmlrpc("XMLRPCServiceInterface", "xmlrpc") - { - me = this; - - if (!xmlrpc) - throw ModuleException("Unable to find xmlrpc reference, is m_xmlrpc loaded?"); - - xmlrpc->Register(&stats); - } - - ~ModuleXMLRPCMain() - { - if (xmlrpc) - xmlrpc->Unregister(&stats); - } -}; - -MODULE_INIT(ModuleXMLRPCMain) - diff --git a/modules/memoserv/CMakeLists.txt b/modules/memoserv/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/memoserv/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/memoserv/cancel.cpp b/modules/memoserv/cancel.cpp new file mode 100644 index 000000000..e3da82581 --- /dev/null +++ b/modules/memoserv/cancel.cpp @@ -0,0 +1,91 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/memoserv.h" + +class CommandMSCancel : public Command +{ + public: + CommandMSCancel(Module *creator) : Command(creator, "memoserv/cancel", 1, 1) + { + this->SetDesc(_("Cancel the last memo you sent")); + this->SetSyntax(_("{\037user\037 | \037channel\037}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + const Anope::string &nname = params[0]; + + bool ischan, isregistered; + MemoServ::MemoInfo *mi = MemoServ::service->GetMemoInfo(name, ischan, isregistered, false); + + if (!isregistered) + { + if (ischan) + source.Reply(_("Channel \002{0}\002 isn't registered."), nname); + else + source.Reply(_("\002{0}\002 isn't registered."), nname); + } + else + { + ChanServ::Channel *ci = NULL; + NickServ::Nick *na = NULL; + if (ischan) + ci = ChanServ::Find(nname); + else + na = NickServ::FindNick(nname); + if (mi) + { + auto memos = mi->GetMemos(); + for (int i = memos.size() - 1; i >= 0; --i) + { + MemoServ::Memo *m = memos[i]; + if (m->GetUnread() && source.nc->GetDisplay().equals_ci(m->GetSender())) + { + if (MemoServ::Event::OnMemoDel) + MemoServ::Event::OnMemoDel(&MemoServ::Event::MemoDel::OnMemoDel, ischan ? ci->GetName() : na->GetAccount()->GetDisplay(), mi, m); + mi->Del(i); + source.Reply(_("Your last memo to \002{0}\002 has been cancelled."), nname); + return; + } + } + } + + source.Reply(_("No memo to \002{0}\002 was cancelable."), nname); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Cancels the last memo you sent to \037user\037, provided it has not yet been read.")); + return true; + } +}; + +class MSCancel : public Module +{ + CommandMSCancel commandmscancel; + + public: + MSCancel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmscancel(this) + { + } +}; + +MODULE_INIT(MSCancel) diff --git a/modules/memoserv/check.cpp b/modules/memoserv/check.cpp new file mode 100644 index 000000000..75a8ee3d0 --- /dev/null +++ b/modules/memoserv/check.cpp @@ -0,0 +1,80 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandMSCheck : public Command +{ + public: + CommandMSCheck(Module *creator) : Command(creator, "memoserv/check", 1, 1) + { + this->SetDesc(_("Checks if last memo to a user was read")); + this->SetSyntax(_("\037user\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &recipient = params[0]; + + bool found = false; + + NickServ::Nick *na = NickServ::FindNick(recipient); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), recipient); + return; + } + + MemoServ::MemoInfo *mi = na->GetAccount()->GetMemos(); + + if (mi) + /* find the last memo */ + for (unsigned i = mi->GetMemos().size(); i > 0; --i) + { + MemoServ::Memo *m = mi->GetMemo(i - 1); + NickServ::Nick *na2 = NickServ::FindNick(m->GetSender()); + + if (na2 != NULL && na2->GetAccount() == source.GetAccount()) + { + found = true; /* Yes, we've found the memo */ + + if (m->GetUnread()) + source.Reply(_("The last memo you sent to \002{0}\002 (sent on \002{1}\002) has not yet been read."), na->GetNick(), Anope::strftime(m->GetTime(), source.GetAccount())); + else + source.Reply(_("The last memo you sent to \002{0}\002 (sent on \002{1}\002) has been read."), na->GetNick(), Anope::strftime(m->GetTime(), source.GetAccount())); + break; + } + } + + if (!found) + source.Reply(_("\002{0}\002 doesn't have a memo from you."), na->GetNick()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Checks whether the last memo you sent to \037user\037 has been read or not.")); + return true; + } +}; + +class MSCheck : public Module +{ + CommandMSCheck commandmscheck; + + public: + MSCheck(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmscheck(this) + { + + } +}; + +MODULE_INIT(MSCheck) diff --git a/modules/memoserv/del.cpp b/modules/memoserv/del.cpp new file mode 100644 index 000000000..79af11adc --- /dev/null +++ b/modules/memoserv/del.cpp @@ -0,0 +1,148 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/memoserv.h" + +class CommandMSDel : public Command +{ + public: + CommandMSDel(Module *creator) : Command(creator, "memoserv/del", 0, 2) + { + this->SetDesc(_("Delete a memo or memos")); + this->SetSyntax(_("[\037channel\037] {\037num\037 | \037list\037 | LAST | ALL}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + MemoServ::MemoInfo *mi; + ChanServ::Channel *ci = NULL; + Anope::string numstr = !params.empty() ? params[0] : "", chan; + + if (!numstr.empty() && numstr[0] == '#') + { + chan = numstr; + numstr = params.size() > 1 ? params[1] : ""; + + ci = ChanServ::Find(chan); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); + return; + } + + mi = ci->GetMemos(); + } + else + mi = source.nc->GetMemos(); + + if (numstr.empty() || (!isdigit(numstr[0]) && !numstr.equals_ci("ALL") && !numstr.equals_ci("LAST"))) + { + this->OnSyntaxError(source, numstr); + return; + } + + if (!mi || mi->GetMemos().empty()) + { + if (!chan.empty()) + source.Reply(_("\002{0}\002 has no memos."), chan); + else + source.Reply(_("You have no memos.")); + return; + } + + auto memos = mi->GetMemos(); + + if (isdigit(numstr[0])) + { + NumberList(numstr, true, + [&](unsigned int number) + { + if (!number || number > memos.size()) + return; + + if (MemoServ::Event::OnMemoDel) + MemoServ::Event::OnMemoDel(&MemoServ::Event::MemoDel::OnMemoDel, ci ? ci->GetName() : source.nc->GetDisplay(), mi, mi->GetMemo(number - 1)); + + mi->Del(number - 1); + source.Reply(_("Memo \002{0}\002 has been deleted."), number); + }, + [](){}); + } + else if (numstr.equals_ci("LAST")) + { + /* Delete last memo. */ + if (MemoServ::Event::OnMemoDel) + MemoServ::Event::OnMemoDel(&MemoServ::Event::MemoDel::OnMemoDel, ci ? ci->GetName() : source.nc->GetDisplay(), mi, mi->GetMemo(memos.size() - 1)); + mi->Del(memos.size() - 1); + source.Reply(_("Memo \002{0}\002 has been deleted."), memos.size() + 1); + } + else + { + /* Delete all memos. */ + std::for_each(memos.begin(), memos.end(), + [&](MemoServ::Memo *m) + { + if (MemoServ::Event::OnMemoDel) + MemoServ::Event::OnMemoDel(&MemoServ::Event::MemoDel::OnMemoDel, ci ? ci->GetName() : source.nc->GetDisplay(), mi, m); + delete m; + }); + if (!chan.empty()) + source.Reply(_("All memos for channel \002{0}\002 have been deleted."), chan); + else + source.Reply(_("All of your memos have been deleted.")); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Deletes the specified memo or memos." + " You can supply multiple memo numbers or ranges of numbers instead of a single number, as in the second example below." + " If \002LAST\002 is given, the last memo will be deleted." + " If \002ALL\002 is given, deletes all of your memos.\n" + "\n" + "Examples:\n" + "\n" + " {0} 1\n" + " Deletes your first memo.\n" + "\n" + " {0} 2-5,7-9\n" + " Deletes memos numbered 2 through 5 and 7 through 9."), + source.command); + return true; + } +}; + +class MSDel : public Module +{ + CommandMSDel commandmsdel; + + public: + MSDel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmsdel(this) + { + + } +}; + +MODULE_INIT(MSDel) diff --git a/modules/memoserv/ignore.cpp b/modules/memoserv/ignore.cpp new file mode 100644 index 000000000..c454444ea --- /dev/null +++ b/modules/memoserv/ignore.cpp @@ -0,0 +1,147 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandMSIgnore : public Command +{ + public: + CommandMSIgnore(Module *creator) : Command(creator, "memoserv/ignore", 1, 3) + { + this->SetDesc(_("Manage the memo ignore list")); + this->SetSyntax(_("[\037channel\037] ADD \037entry\037")); + this->SetSyntax(_("[\037channel\037] DEL \037entry\037")); + this->SetSyntax(_("[\037channel\037] LIST")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + if (!MemoServ::service) + return; + + Anope::string channel = params[0]; + Anope::string command = (params.size() > 1 ? params[1] : ""); + Anope::string param = (params.size() > 2 ? params[2] : ""); + + if (channel[0] != '#') + { + param = command; + command = channel; + channel = source.GetNick(); + } + + bool ischan, isregistered; + MemoServ::MemoInfo *mi = MemoServ::service->GetMemoInfo(channel, ischan, isregistered, true); + ChanServ::Channel *ci = ChanServ::Find(channel); + if (!isregistered) + { + if (ischan) + source.Reply(_("Channel \002{0}\002 isn't registered."), channel); + else + source.Reply(_("\002{0}\002 isn't registered."), channel); + return; + } + + if (ischan && !source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); + return; + } + + auto ignores = mi->GetIgnores(); + + if (command.equals_ci("ADD") && !param.empty()) + { + if (ignores.size() >= Config->GetModule(this->owner)->Get("max", "32")) + { + source.Reply(_("Sorry, the memo ignore list for \002{0}\002 is full."), channel); + return; + } + + for (MemoServ::Ignore *ign : ignores) + if (ign->GetMask().equals_ci(param)) + { + source.Reply(_("\002{0}\002 is already on your memo ignore list."), param); + return; + } + + MemoServ::Ignore *ign = MemoServ::service->CreateIgnore(); + ign->SetMemoInfo(mi); + ign->SetMask(param); + + source.Reply(_("\002{0}\002 has been added to your memo ignore list."), param); + } + else if (command.equals_ci("DEL") && !param.empty()) + { + for (MemoServ::Ignore *ign : ignores) + if (ign->GetMask().equals_ci(param)) + { + delete ign; + source.Reply(_("\002{0}\002 has been removed from your memo ignore list."), param); + return; + } + + source.Reply(_("\002{0}\002 is not on your memo ignore list."), param); + } + else if (command.equals_ci("LIST")) + { + if (ignores.empty()) + { + source.Reply(_("Your memo ignore list is empty.")); + return; + } + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Mask")); + for (MemoServ::Ignore *ign : ignores) + { + ListFormatter::ListEntry entry; + entry["Mask"] = ign->GetMask(); + list.AddEntry(entry); + } + + source.Reply(_("Memo ignore list:")); + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows you to ignore users from memoing you or a channel." + " If someone on the memo ignore list tries to memo you or a channel, they will not be told that you have them ignored.")); + return true; + } +}; + +class MSIgnore : public Module +{ + CommandMSIgnore commandmsignore; + + public: + MSIgnore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmsignore(this) + { + } +}; + +MODULE_INIT(MSIgnore) diff --git a/modules/memoserv/info.cpp b/modules/memoserv/info.cpp new file mode 100644 index 000000000..d9bee1cb5 --- /dev/null +++ b/modules/memoserv/info.cpp @@ -0,0 +1,203 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandMSInfo : public Command +{ + public: + CommandMSInfo(Module *creator) : Command(creator, "memoserv/info", 0, 1) + { + this->SetDesc(_("Displays information about your memos")); + this->SetSyntax(_("[\037user\037 | \037channel\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + NickServ::Account *nc = source.nc; + MemoServ::MemoInfo *mi; + NickServ::Nick *na = NULL; + ChanServ::Channel *ci = NULL; + const Anope::string &nname = !params.empty() ? params[0] : ""; + bool hardmax; + + if (!nname.empty() && nname[0] != '#' && source.HasPriv("memoserv/info")) + { + na = NickServ::FindNick(nname); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nname); + return; + } + mi = na->GetAccount()->GetMemos(); + hardmax = na->GetAccount()->HasFieldS("MEMO_HARDMAX"); + } + else if (!nname.empty() && nname[0] == '#') + { + ci = ChanServ::Find(nname); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), nname); + return; + } + + if (!source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); + return; + } + + mi = ci->GetMemos(); + hardmax = ci->HasFieldS("MEMO_HARDMAX"); + } + else if (!nname.empty()) /* It's not a chan and we aren't an oper */ + { + source.Reply(_("Access denied. You do not have the correct operator privilege to see the memo info of \002{0}\002."), nname); + return; + } + else + { + mi = nc->GetMemos(); + hardmax = nc->HasFieldS("MEMO_HARDMAX"); + } + if (!mi) + return; + + auto memos = mi->GetMemos(); + + if (!nname.empty() && (ci || na->GetAccount() != nc)) + { + if (memos.empty()) + source.Reply(_("%s currently has no memos."), nname.c_str()); + else if (memos.size() == 1) + { + if (mi->GetMemo(0)->GetUnread()) + source.Reply(_("\002{0}\002 currently has \0021\002 memo, and it has not yet been read."), nname); + else + source.Reply(_("\002{0}\002 currently has \0021\002 memo."), nname); + } + else + { + unsigned count = 0, i, end; + for (i = 0, end = memos.size(); i < end; ++i) + if (mi->GetMemo(i)->GetUnread()) + ++count; + if (count == memos.size()) + source.Reply(_("\002{0}\002 currently has \002{1]\002 memos; all of them are unread."), nname, count); + else if (!count) + source.Reply(_("\002{0}\002 currently has \002{1}\002 memos."), nname, memos.size()); + else if (count == 1) + source.Reply(_("\002{0}\002 currently has \002{1}\002 memos, of which \0021\002 is unread."), nname, memos.size()); + else + source.Reply(_("\002{0}\002 currently has \002{1]\002 memos, of which \002{2}\002 are unread."), nname, memos.size(), count); + } + if (mi->GetMemoMax() >= 0) + { + if (hardmax) + source.Reply(_("The memo limit of \002{0}\002 is \002{1}\002, and may not be changed."), nname, mi->GetMemoMax()); + else + source.Reply(_("The memo limit of \002{0}\002 is \002{1}\002."), nname, mi->GetMemoMax()); + } + else + source.Reply(_("\002{0}\002 has no memo limit."), nname); + + if (na) + { + if (na->GetAccount()->HasFieldS("MEMO_RECEIVE") && na->GetAccount()->HasFieldS("MEMO_SIGNON")) + source.Reply(_("\002{0}\002 is notified of new memos at logon and when they arrive."), nname); + else if (na->GetAccount()->HasFieldS("MEMO_RECEIVE")) + source.Reply(_("\002{0}\002 is notified when new memos arrive."), nname); + else if (na->GetAccount()->HasFieldS("MEMO_SIGNON")) + source.Reply(_("\002{0}\002 is notified of news memos at logon."), nname); + else + source.Reply(_("\002{0}\002 is not notified of new memos."), nname); + } + } + else + { + if (memos.empty()) + source.Reply(_("You currently have no memos.")); + else if (memos.size() == 1) + { + if (mi->GetMemo(0)->GetUnread()) + source.Reply(_("You currently have \0021\002 memo, and it has not yet been read.")); + else + source.Reply(_("You currently have \0021\002 memo.")); + } + else + { + unsigned count = 0, i, end; + for (i = 0, end = memos.size(); i < end; ++i) + if (mi->GetMemo(i)->GetUnread()) + ++count; + if (count == memos.size()) + source.Reply(_("You currently have \002{0}\002 memos; all of them are unread."), count); + else if (!count) + source.Reply(_("You currently have \002{0}\002 memos."), memos.size()); + else if (count == 1) + source.Reply(_("You currently have \002{0}\002 memos, of which \0021\002 is unread."), memos.size()); + else + source.Reply(_("You currently have \002{0}\002 memos, of which \002{1}\002 are unread."), memos.size(), count); + } + + if (!mi->GetMemoMax()) + { + if (!source.IsServicesOper() && hardmax) + source.Reply(_("Your memo limit is \0020\002; you will not receive any new memos. You cannot change this limit.")); + else + source.Reply(_("Your memo limit is \0020\002; you will not receive any new memos.")); + } + else if (mi->GetMemoMax() > 0) + { + if (!source.IsServicesOper() && hardmax) + source.Reply(_("Your memo limit is \002{0}\002, and may not be changed."), mi->GetMemoMax()); + else + source.Reply(_("Your memo limit is \002{0}\002."), mi->GetMemoMax()); + } + else + source.Reply(_("You have no limit on the number of memos you may keep.")); + + if (nc->HasFieldS("MEMO_RECEIVE") && nc->HasFieldS("MEMO_SIGNON")) + source.Reply(_("You will be notified of new memos at logon and when they arrive.")); + else if (nc->HasFieldS("MEMO_RECEIVE")) + source.Reply(_("You will be notified when new memos arrive.")); + else if (nc->HasFieldS("MEMO_SIGNON")) + source.Reply(_("You will be notified of new memos at logon.")); + else + source.Reply(_("You will not be notified of new memos.")); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Without a parameter, displays information on the number of memos you have, how many of them are unread, and how many total memos you can receive." + " With a parameter, displays the same information for the given \037user\037 or \037channel\037, if you have the appropriate privilege.\n" + "\n" + "Use of this command on a channel requires the \002{0}\002 privilege on \037channel\037."), + source.command); + + return true; + } +}; + +class MSInfo : public Module +{ + CommandMSInfo commandmsinfo; + + public: + MSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmsinfo(this) + { + + } +}; + +MODULE_INIT(MSInfo) diff --git a/modules/memoserv/list.cpp b/modules/memoserv/list.cpp new file mode 100644 index 000000000..8215315b0 --- /dev/null +++ b/modules/memoserv/list.cpp @@ -0,0 +1,163 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandMSList : public Command +{ + public: + CommandMSList(Module *creator) : Command(creator, "memoserv/list", 0, 2) + { + this->SetDesc(_("List your memos")); + this->SetSyntax(_("[\037channel\037] [\037list\037 | NEW]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + + Anope::string param = !params.empty() ? params[0] : "", chan; + ChanServ::Channel *ci = NULL; + MemoServ::MemoInfo *mi; + + if (!param.empty() && param[0] == '#') + { + chan = param; + param = params.size() > 1 ? params[1] : ""; + + ci = ChanServ::Find(chan); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); + return; + } + + mi = ci->GetMemos(); + } + else + mi = source.nc->GetMemos(); + + if (!param.empty() && !isdigit(param[0]) && !param.equals_ci("NEW")) + { + this->OnSyntaxError(source, param); + return; + } + + if (!mi) + return; + + auto memos = mi->GetMemos(); + + if (!memos.size()) + { + if (!chan.empty()) + source.Reply(_("\002{0}\002 has no memos."), chan); + else + source.Reply(_("You have no memos.")); + return; + } + + ListFormatter list(source.GetAccount()); + + list.AddColumn(_("Number")).AddColumn(_("Sender")).AddColumn(_("Date/Time")); + + if (!param.empty() && isdigit(param[0])) + { + NumberList(param, false, + [&](unsigned int number) + { + if (!number || number > memos.size()) + return; + + MemoServ::Memo *m = mi->GetMemo(number - 1); + + ListFormatter::ListEntry entry; + entry["Number"] = (m->GetUnread() ? "* " : " ") + stringify(number); + entry["Sender"] = m->GetSender(); + entry["Date/Time"] = Anope::strftime(m->GetTime(), source.GetAccount()); + list.AddEntry(entry); + }, + []{}); + } + else + { + if (!param.empty()) + { + unsigned i, end; + for (i = 0, end = memos.size(); i < end; ++i) + if (mi->GetMemo(i)->GetUnread()) + break; + if (i == end) + { + if (!chan.empty()) + source.Reply(_("\002{0}\002 has no new memos."), chan); + else + source.Reply(_("You have no new memos.")); + return; + } + } + + for (unsigned i = 0, end = memos.size(); i < end; ++i) + { + if (!param.empty() && !mi->GetMemo(i)->GetUnread()) + continue; + + MemoServ::Memo *m = mi->GetMemo(i); + + ListFormatter::ListEntry entry; + entry["Number"] = (m->GetUnread() ? "* " : " ") + stringify(i + 1); + entry["Sender"] = m->GetSender(); + entry["Date/Time"] = Anope::strftime(m->GetTime(), source.GetAccount()); + list.AddEntry(entry); + } + } + + std::vector replies; + list.Process(replies); + + source.Reply(_("Memos for \002{0}\002:"), ci ? ci->GetName().c_str() : source.GetNick().c_str()); + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Lists the memos you currently have." + " With \002NEW\002, lists only new (unread) memos." + " Unread memos are marked with a \"*\" to the left of the memo number." + " You can also specify a list of numbers, as in the example below:\n" + "\n" + "Example:\n" + "\n" + " {0} 2-5,7-9\n" + " Lists memos numbered 2 through 5 and 7 through 9.")); + return true; + } +}; + +class MSList : public Module +{ + CommandMSList commandmslist; + + public: + MSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmslist(this) + { + + } +}; + +MODULE_INIT(MSList) diff --git a/modules/memoserv/main/CMakeLists.txt b/modules/memoserv/main/CMakeLists.txt new file mode 100644 index 000000000..781f0ef1f --- /dev/null +++ b/modules/memoserv/main/CMakeLists.txt @@ -0,0 +1 @@ +build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/memoserv/main/ignore.cpp b/modules/memoserv/main/ignore.cpp new file mode 100644 index 000000000..e85011c00 --- /dev/null +++ b/modules/memoserv/main/ignore.cpp @@ -0,0 +1,27 @@ +#include "memo.h" +#include "ignoretype.h" + +IgnoreImpl::~IgnoreImpl() +{ +} + +MemoServ::MemoInfo *IgnoreImpl::GetMemoInfo() +{ + return Get(&IgnoreType::mi); +} + +void IgnoreImpl::SetMemoInfo(MemoServ::MemoInfo *m) +{ + Set(&IgnoreType::mi, m); +} + +Anope::string IgnoreImpl::GetMask() +{ + return Get(&IgnoreType::mask); +} + +void IgnoreImpl::SetMask(const Anope::string &mask) +{ + Set(&IgnoreType::mask, mask); +} + diff --git a/modules/memoserv/main/ignore.h b/modules/memoserv/main/ignore.h new file mode 100644 index 000000000..c98b44adf --- /dev/null +++ b/modules/memoserv/main/ignore.h @@ -0,0 +1,15 @@ +#include "modules/memoserv.h" + +class IgnoreImpl : public MemoServ::Ignore +{ + public: + IgnoreImpl(Serialize::TypeBase *type) : MemoServ::Ignore(type) { } + IgnoreImpl(Serialize::TypeBase *type, Serialize::ID id) : MemoServ::Ignore(type, id) { } + ~IgnoreImpl(); + + MemoServ::MemoInfo *GetMemoInfo() override; + void SetMemoInfo(MemoServ::MemoInfo *) override; + + Anope::string GetMask() override; + void SetMask(const Anope::string &mask) override; +}; diff --git a/modules/memoserv/main/ignoretype.cpp b/modules/memoserv/main/ignoretype.cpp new file mode 100644 index 000000000..375f83272 --- /dev/null +++ b/modules/memoserv/main/ignoretype.cpp @@ -0,0 +1,10 @@ +#include "module.h" +#include "ignoretype.h" + +IgnoreType::IgnoreType(Module *me) : Serialize::Type(me, "MemoIgnore") + , mi(this, "mi", true) + , mask(this, "mask") +{ + +} + diff --git a/modules/memoserv/main/ignoretype.h b/modules/memoserv/main/ignoretype.h new file mode 100644 index 000000000..5c656f9b8 --- /dev/null +++ b/modules/memoserv/main/ignoretype.h @@ -0,0 +1,10 @@ +#include "ignore.h" + +class IgnoreType : public Serialize::Type +{ + public: + Serialize::ObjectField mi; + Serialize::Field mask; + + IgnoreType(Module *); +}; diff --git a/modules/memoserv/main/memo.cpp b/modules/memoserv/main/memo.cpp new file mode 100644 index 000000000..bfae64284 --- /dev/null +++ b/modules/memoserv/main/memo.cpp @@ -0,0 +1,72 @@ +//#include "memo.h" +#include "memotype.h" + +/*MemoImpl::MemoImpl() +{ + //unread = receipt = false; +}*/ + +MemoImpl::~MemoImpl() +{ +} + +MemoServ::MemoInfo *MemoImpl::GetMemoInfo() +{ + return Get(&MemoType::mi); +} + +void MemoImpl::SetMemoInfo(MemoServ::MemoInfo *mi) +{ + Set(&MemoType::mi, mi); +} + +time_t MemoImpl::GetTime() +{ + return Get(&MemoType::time); +} + +void MemoImpl::SetTime(const time_t &t) +{ + Set(&MemoType::time, t); +} + +Anope::string MemoImpl::GetSender() +{ + return Get(&MemoType::sender); +} + +void MemoImpl::SetSender(const Anope::string &s) +{ + Set(&MemoType::sender, s); +} + +Anope::string MemoImpl::GetText() +{ + return Get(&MemoType::text); +} + +void MemoImpl::SetText(const Anope::string &t) +{ + Set(&MemoType::text, t); +} + +bool MemoImpl::GetUnread() +{ + return Get(&MemoType::unread); +} + +void MemoImpl::SetUnread(const bool &b) +{ + Set(&MemoType::unread, b); +} + +bool MemoImpl::GetReceipt() +{ + return Get(&MemoType::receipt); +} + +void MemoImpl::SetReceipt(const bool &b) +{ + Set(&MemoType::receipt, b); +} + diff --git a/modules/memoserv/main/memo.h b/modules/memoserv/main/memo.h new file mode 100644 index 000000000..1a230c1e9 --- /dev/null +++ b/modules/memoserv/main/memo.h @@ -0,0 +1,29 @@ +#include "modules/memoserv.h" + +class MemoImpl : public MemoServ::Memo +{ + public: + MemoImpl(Serialize::TypeBase *type) : MemoServ::Memo(type) { } + MemoImpl(Serialize::TypeBase *type, Serialize::ID id) : MemoServ::Memo(type, id) { } + //using MemoServ::Memo::Memo; +// MemoImpl(); + ~MemoImpl(); + + MemoServ::MemoInfo *GetMemoInfo() override; + void SetMemoInfo(MemoServ::MemoInfo *) override; + + time_t GetTime() override; + void SetTime(const time_t &) override; + + Anope::string GetSender() override; + void SetSender(const Anope::string &) override; + + Anope::string GetText() override; + void SetText(const Anope::string &) override; + + bool GetUnread() override; + void SetUnread(const bool &) override; + + bool GetReceipt() override; + void SetReceipt(const bool &) override; +}; diff --git a/modules/memoserv/main/memoinfo.cpp b/modules/memoserv/main/memoinfo.cpp new file mode 100644 index 000000000..45d95533e --- /dev/null +++ b/modules/memoserv/main/memoinfo.cpp @@ -0,0 +1,63 @@ +#include "memoinfotype.h" + +MemoServ::Memo *MemoInfoImpl::GetMemo(unsigned index) +{ + auto memos = GetMemos(); + return index >= memos.size() ? nullptr : memos[index]; +} + +unsigned MemoInfoImpl::GetIndex(MemoServ::Memo *m) +{ + auto memos = GetMemos(); + for (unsigned i = 0; i < memos.size(); ++i) + if (this->GetMemo(i) == m) + return i; + return -1; // XXX wtf? +} + +void MemoInfoImpl::Del(unsigned index) +{ + delete GetMemo(index); +} + +bool MemoInfoImpl::HasIgnore(User *u) +{ + for (MemoServ::Ignore *ign : GetIgnores()) + { + const Anope::string &mask = ign->GetMask(); + if (u->nick.equals_ci(mask) || (u->Account() && u->Account()->GetDisplay().equals_ci(mask)) || Anope::Match(u->GetMask(), mask)) + return true; + } + return false; +} + +Serialize::Object *MemoInfoImpl::GetOwner() +{ + return Get(&MemoInfoType::owner); +} + +void MemoInfoImpl::SetOwner(Serialize::Object *o) +{ + Set(&MemoInfoType::owner, o); +} + +int16_t MemoInfoImpl::GetMemoMax() +{ + return Get(&MemoInfoType::memomax); +} + +void MemoInfoImpl::SetMemoMax(const int16_t &i) +{ + Set(&MemoInfoType::memomax, i); +} + +std::vector MemoInfoImpl::GetMemos() +{ + return GetRefs(MemoServ::memo); +} + +std::vector MemoInfoImpl::GetIgnores() +{ + return GetRefs(MemoServ::ignore); +} + diff --git a/modules/memoserv/main/memoinfo.h b/modules/memoserv/main/memoinfo.h new file mode 100644 index 000000000..22ea0734f --- /dev/null +++ b/modules/memoserv/main/memoinfo.h @@ -0,0 +1,23 @@ +#include "modules/memoserv.h" + +class MemoInfoImpl : public MemoServ::MemoInfo +{ + public: + MemoInfoImpl(Serialize::TypeBase *type) : MemoServ::MemoInfo(type) { } + MemoInfoImpl(Serialize::TypeBase *type, Serialize::ID id) : MemoServ::MemoInfo(type, id) { } + + MemoServ::Memo *GetMemo(unsigned index) override; + unsigned GetIndex(MemoServ::Memo *m) override; + void Del(unsigned index) override; + bool HasIgnore(User *u) override; + + Serialize::Object *GetOwner() override; + void SetOwner(Serialize::Object *) override; + + int16_t GetMemoMax() override; + void SetMemoMax(const int16_t &) override; + + std::vector GetMemos() override; + std::vector GetIgnores() override; +}; + diff --git a/modules/memoserv/main/memoinfotype.cpp b/modules/memoserv/main/memoinfotype.cpp new file mode 100644 index 000000000..7eb3af72c --- /dev/null +++ b/modules/memoserv/main/memoinfotype.cpp @@ -0,0 +1,10 @@ +#include "module.h" +#include "memoinfotype.h" + +MemoInfoType::MemoInfoType(Module *me) : Serialize::Type(me, "MemoInfo") + , owner(this, "owner", true) + , memomax(this, "memomax") +{ + +} + diff --git a/modules/memoserv/main/memoinfotype.h b/modules/memoserv/main/memoinfotype.h new file mode 100644 index 000000000..724a898b5 --- /dev/null +++ b/modules/memoserv/main/memoinfotype.h @@ -0,0 +1,10 @@ +#include "memoinfo.h" + +class MemoInfoType : public Serialize::Type +{ + public: + Serialize::ObjectField owner; + Serialize::Field memomax; + + MemoInfoType(Module *); +}; diff --git a/modules/memoserv/main/memoserv.cpp b/modules/memoserv/main/memoserv.cpp new file mode 100644 index 000000000..a52ee746c --- /dev/null +++ b/modules/memoserv/main/memoserv.cpp @@ -0,0 +1,298 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_update.h" +#include "modules/help.h" +#include "modules/bs_bot.h" +#include "modules/memoserv.h" +//#include "memoinfo.h" +//#include "memo.h" +//#include "ignore.h" +#include "memotype.h" +#include "memoinfotype.h" +#include "ignoretype.h" + +class MemoServCore : public Module, public MemoServ::MemoServService + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + Reference MemoServ; + EventHandlers onmemosend; + EventHandlers onmemodel; + + MemoInfoType memoinfo_type; + MemoType memo_type; + IgnoreType ignore_type; + + bool SendMemoMail(NickServ::Account *nc, MemoServ::MemoInfo *mi, MemoServ::Memo *m) + { + Anope::string subject = Language::Translate(nc, Config->GetBlock("mail")->Get("memo_subject").c_str()), + message = Language::Translate(Config->GetBlock("mail")->Get("memo_message").c_str()); + + subject = subject.replace_all_cs("%n", nc->GetDisplay()); + subject = subject.replace_all_cs("%s", m->GetSender()); + subject = subject.replace_all_cs("%d", stringify(mi->GetIndex(m) + 1)); + subject = subject.replace_all_cs("%t", m->GetText()); + subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); + + message = message.replace_all_cs("%n", nc->GetDisplay()); + message = message.replace_all_cs("%s", m->GetSender()); + message = message.replace_all_cs("%d", stringify(mi->GetIndex(m) + 1)); + message = message.replace_all_cs("%t", m->GetText()); + message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); + + return Mail::Send(nc, subject, message); + } + + public: + MemoServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) + , MemoServ::MemoServService(this) + , onmemosend(this) + , onmemodel(this) + , memoinfo_type(this) + , memo_type(this) + , ignore_type(this) + { + } + + MemoResult Send(const Anope::string &source, const Anope::string &target, const Anope::string &message, bool force) override + { + bool ischan, isregistered; + MemoServ::MemoInfo *mi = GetMemoInfo(target, ischan, isregistered, true); + + if (mi == NULL) + return MEMO_INVALID_TARGET; + + User *sender = User::Find(source); + if (sender != NULL && !sender->HasPriv("memoserv/no-limit") && !force) + { + time_t send_delay = Config->GetModule("memoserv")->Get("senddelay"); + if (send_delay > 0 && sender->lastmemosend + send_delay > Anope::CurTime) + return MEMO_TOO_FAST; + else if (!mi->GetMemoMax()) + return MEMO_TARGET_FULL; + else if (mi->GetMemoMax() > 0 && mi->GetMemos().size() >= static_cast(mi->GetMemoMax())) + return MEMO_TARGET_FULL; + else if (mi->HasIgnore(sender)) + return MEMO_SUCCESS; + } + + if (sender != NULL) + sender->lastmemosend = Anope::CurTime; + + MemoServ::Memo *m = new MemoImpl(&memo_type); + m->SetMemoInfo(mi); + m->SetSender(source); + m->SetTime(Anope::CurTime); + m->SetText(message); + m->SetUnread(true); + + this->onmemosend(&MemoServ::Event::MemoSend::OnMemoSend, source, target, mi, m); + + if (ischan) + { + ChanServ::Channel *ci = ChanServ::Find(target); + + if (ci->c) + { + for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + { + ChanUserContainer *cu = it->second; + + if (ci->AccessFor(cu->user).HasPriv("MEMO")) + { + if (cu->user->Account() && cu->user->Account()->HasFieldS("MEMO_RECEIVE")) + cu->user->SendMessage(*MemoServ, _("There is a new memo on channel \002{0}\002. Type \002{1}{2} READ {3} {4}\002 to read it."), ci->GetName(), Config->StrictPrivmsg, MemoServ->nick, ci->GetName(), mi->GetMemos().size()); // XXX + } + } + } + } + else + { + NickServ::Account *nc = NickServ::FindNick(target)->GetAccount(); + + if (nc->HasFieldS("MEMO_RECEIVE")) + for (User *u : nc->users) + u->SendMessage(*MemoServ, _("You have a new memo from \002{0}\002. Type \002{1}{2} READ {3}\002 to read it."), source, Config->StrictPrivmsg, MemoServ->nick, mi->GetMemos().size());//XXX + + /* let's get out the mail if set in the nickcore - certus */ + if (nc->HasFieldS("MEMO_MAIL")) + SendMemoMail(nc, mi, m); + } + + return MEMO_SUCCESS; + } + + void Check(User *u) override + { + NickServ::Account *nc = u->Account(); + if (!nc || !nc->GetMemos()) + return; + + auto memos = nc->GetMemos()->GetMemos(); + unsigned i = 0, end = memos.size(), newcnt = 0; + for (; i < end; ++i) + if (memos[i]->GetUnread()) + ++newcnt; + if (newcnt > 0) + u->SendMessage(*MemoServ, newcnt == 1 ? _("You have 1 new memo.") : _("You have %d new memos."), newcnt); + if (nc->GetMemos()->GetMemoMax() > 0 && memos.size() >= static_cast(nc->GetMemos()->GetMemoMax())) + { + if (memos.size() > static_cast(nc->GetMemos()->GetMemoMax())) + u->SendMessage(*MemoServ, _("You are over your maximum number of memos (%d). You will be unable to receive any new memos until you delete some of your current ones."), nc->GetMemos()->GetMemoMax()); + else + u->SendMessage(*MemoServ, _("You have reached your maximum number of memos (%d). You will be unable to receive any new memos until you delete some of your current ones."), nc->GetMemos()->GetMemoMax()); + } + } + + MemoServ::Memo *CreateMemo() override + { + return new MemoImpl(&memo_type); + } + + MemoServ::Ignore *CreateIgnore() override + { + return new IgnoreImpl(&ignore_type); + } + + MemoServ::MemoInfo *GetMemoInfo(const Anope::string &target, bool &is_registered, bool &ischan, bool create) override + { + if (!target.empty() && target[0] == '#') + { + ischan = true; + ChanServ::Channel *ci = ChanServ::Find(target); + if (ci != NULL) + { + is_registered = true; + if (create && !ci->GetMemos()) + { + MemoServ::MemoInfo *mi = new MemoInfoImpl(&memoinfo_type); + mi->SetOwner(ci); + } + return ci->GetMemos(); + } + else + is_registered = false; + } + else + { + ischan = false; + NickServ::Nick *na = NickServ::FindNick(target); + if (na != NULL) + { + is_registered = true; + if (create && !na->GetAccount()->GetMemos()) + { + MemoServ::MemoInfo *mi = new MemoInfoImpl(&memoinfo_type); + mi->SetOwner(na->GetAccount()); + } + return na->GetAccount()->GetMemos(); + } + else + is_registered = false; + } + + return NULL; + } + + void OnReload(Configuration::Conf *conf) override + { + const Anope::string &msnick = conf->GetModule(this)->Get("client"); + + if (msnick.empty()) + throw ConfigException(Module::name + ": must be defined"); + + ServiceBot *bi = ServiceBot::Find(msnick, true); + if (!bi) + throw ConfigException(Module::name + ": no bot named " + msnick); + + MemoServ = bi; + } + + void OnNickCoreCreate(NickServ::Account *nc) override + { + MemoServ::MemoInfo *mi = new MemoInfoImpl(&memoinfo_type); + mi->SetOwner(mi); + mi->SetMemoMax(Config->GetModule(this)->Get("maxmemos")); + } + + void OnCreateChan(ChanServ::Channel *ci) override + { + MemoServ::MemoInfo *mi = new MemoInfoImpl(&memoinfo_type); + mi->SetOwner(ci); + mi->SetMemoMax(Config->GetModule(this)->Get("maxmemos")); + } + + void OnBotDelete(ServiceBot *bi) override + { + if (bi == MemoServ) + MemoServ = NULL; + } + + void OnNickIdentify(User *u) override + { + this->Check(u); + } + + void OnJoinChannel(User *u, Channel *c) override + { + if (u->server && u->server->IsSynced() && c->ci && c->ci->GetMemos() && !c->ci->GetMemos()->GetMemos().empty() && c->ci->AccessFor(u).HasPriv("MEMO")) + { + if (c->ci->GetMemos()->GetMemos().size() == 1) + u->SendMessage(*MemoServ, _("There is \002{0}\002 memo on channel \002{1}\002."), c->ci->GetMemos()->GetMemos().size(), c->ci->GetName()); + else + u->SendMessage(*MemoServ, _("There are \002{0}\002 memos on channel \002{1}\002."), c->ci->GetMemos()->GetMemos().size(), c->ci->GetName()); + } + } + + void OnUserAway(User *u, const Anope::string &message) override + { + if (message.empty()) + this->Check(u); + } + + void OnNickUpdate(User *u) override + { + this->Check(u); + } + + EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override + { + if (!params.empty() || source.c || source.service != *MemoServ) + return EVENT_CONTINUE; + source.Reply(_("\002%s\002 is a utility allowing IRC users to send short\n" + "messages to other IRC users, whether they are online at\n" + "the time or not, or to channels(*). Both the sender's\n" + "nickname and the target nickname or channel must be\n" + "registered in order to send a memo.\n" + "%s's commands include:"), MemoServ->nick.c_str(), MemoServ->nick.c_str()); + return EVENT_CONTINUE; + } + + void OnPostHelp(CommandSource &source, const std::vector ¶ms) override + { + if (!params.empty() || source.c || source.service != *MemoServ) + return; + source.Reply(_(" \n" + "Type \002%s%s HELP \037command\037\002 for help on any of the\n" + "above commands."), Config->StrictPrivmsg.c_str(), MemoServ->nick.c_str()); + } +}; + +MODULE_INIT(MemoServCore) + diff --git a/modules/memoserv/main/memotype.cpp b/modules/memoserv/main/memotype.cpp new file mode 100644 index 000000000..c9144bbaa --- /dev/null +++ b/modules/memoserv/main/memotype.cpp @@ -0,0 +1,14 @@ +#include "module.h" +#include "memotype.h" + +MemoType::MemoType(Module *me) : Serialize::Type(me, "Memo") + , mi(this, "mi", true) + , time(this, "time") + , sender(this, "sender") + , text(this, "text") + , unread(this, "unread") + , receipt(this, "receipt") +{ + +} + diff --git a/modules/memoserv/main/memotype.h b/modules/memoserv/main/memotype.h new file mode 100644 index 000000000..16ac6f082 --- /dev/null +++ b/modules/memoserv/main/memotype.h @@ -0,0 +1,13 @@ +#include "memo.h" + +class MemoType : public Serialize::Type +{ + public: + Serialize::ObjectField mi; + Serialize::Field time; + Serialize::Field sender; + Serialize::Field text; + Serialize::Field unread, receipt; + + MemoType(Module *); +}; diff --git a/modules/memoserv/read.cpp b/modules/memoserv/read.cpp new file mode 100644 index 000000000..687843124 --- /dev/null +++ b/modules/memoserv/read.cpp @@ -0,0 +1,203 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/memoserv.h" + +static void rsend_notify(CommandSource &source, MemoServ::MemoInfo *mi, MemoServ::Memo *m, const Anope::string &targ) +{ + /* Only send receipt if memos are allowed */ + if (MemoServ::service && !Anope::ReadOnly) + { + /* Get nick alias for sender */ + NickServ::Nick *na = NickServ::FindNick(m->GetSender()); + + if (!na) + return; + + /* Get nick core for sender */ + NickServ::Account *nc = na->GetAccount(); + + if (!nc) + return; + + /* Text of the memo varies if the recipient was a + nick or channel */ + Anope::string text = Anope::printf(Language::Translate(na->GetAccount(), _("\002[auto-memo]\002 The memo you sent to \002%s\002 has been viewed.")), targ.c_str()); + + /* Send notification */ + MemoServ::service->Send(source.GetNick(), m->GetSender(), text, true); + + /* Notify recipient of the memo that a notification has + been sent to the sender */ + source.Reply(_("A notification memo has been sent to \002{0}\002 informing him/her you have read his/her memo."), nc->GetDisplay()); + } + + /* Remove receipt flag from the original memo */ + m->SetReceipt(false); +} + +class CommandMSRead : public Command +{ + static void DoRead(CommandSource &source, MemoServ::MemoInfo *mi, ChanServ::Channel *ci, unsigned index) + { + MemoServ::Memo *m = mi->GetMemo(index); + if (!m) + return; + + if (ci) + source.Reply(_("Memo \002{0}\002 from \002{1}\002 (\002{2}\002)."), index + 1, m->GetSender(), Anope::strftime(m->GetTime(), source.GetAccount())); + else + source.Reply(_("Memo \002{0}\002 from \002{1}\002 (\002{2}\002)."), index + 1, m->GetSender(), Anope::strftime(m->GetTime(), source.GetAccount())); + + ServiceBot *bi; + Anope::string cmd; + if (Command::FindCommandFromService("memoserv/del", bi, cmd)) + { + if (ci) + source.Reply(_("To delete, use \002{0}{1} {2} {3} {4}\002"), Config->StrictPrivmsg, bi->nick, cmd, ci->GetName(), index + 1); + else + source.Reply(_("To delete, use \002{0}{1} {2} {3}\002"), Config->StrictPrivmsg, bi->nick, cmd, index + 1); + } + + source.Reply(m->GetText()); + m->SetUnread(false); + + /* Check if a receipt notification was requested */ + if (m->GetReceipt()) + rsend_notify(source, mi, m, ci ? ci->GetName() : source.GetNick()); + } + + public: + CommandMSRead(Module *creator) : Command(creator, "memoserv/read", 1, 2) + { + this->SetDesc(_("Read a memo or memos")); + this->SetSyntax(_("[\037channel\037] {\037num\037 | \037list\037 | LAST | NEW}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + + MemoServ::MemoInfo *mi; + ChanServ::Channel *ci = NULL; + Anope::string numstr = params[0], chan; + + if (!numstr.empty() && numstr[0] == '#') + { + chan = numstr; + numstr = params.size() > 1 ? params[1] : ""; + + ci = ChanServ::Find(chan); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); + return; + } + + mi = ci->GetMemos(); + } + else + mi = source.nc->GetMemos(); + + if (numstr.empty() || (!numstr.equals_ci("LAST") && !numstr.equals_ci("NEW") && !numstr.is_number_only())) + { + this->OnSyntaxError(source, numstr); + return; + } + + if (!mi) + return; + + auto memos = mi->GetMemos(); + + if (memos.empty()) + { + if (!chan.empty()) + source.Reply(_("\002{0}\002 has no memos."), chan); + else + source.Reply(_("You have no memos.")); + return; + } + + int i, end; + + if (numstr.equals_ci("NEW")) + { + int readcount = 0; + for (i = 0, end = memos.size(); i < end; ++i) + if (mi->GetMemo(i)->GetUnread()) + { + DoRead(source, mi, ci, i); + ++readcount; + } + if (!readcount) + { + if (!chan.empty()) + source.Reply(_("\002{0}\002 has no new memos."), chan); + else + source.Reply(_("You have no new memos.")); + } + } + else if (numstr.equals_ci("LAST")) + { + for (i = 0, end = memos.size() - 1; i < end; ++i); + DoRead(source, mi, ci, i); + } + else /* number[s] */ + { + NumberList(numstr, false, + [&](unsigned int number) + { + if (!number || number > memos.size()) + return; + + DoRead(source, mi, ci, number - 1); + }, + []{}); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sends you the text of the memos specified." + " If LAST is given, sends you the memo you most recently received." + " If NEW is given, sends you all of your new memos." + " Otherwise, sends you memo number \037num\037." + " You can also give a list of numbers, as in the example:\n" + "\n" + "Example:\n" + "\n" + " {0} 2-5,7-9\n" + " Displays memos numbered 2 through 5 and 7 through 9."), + source.command); + return true; + } +}; + +class MSRead : public Module +{ + CommandMSRead commandmsread; + + public: + MSRead(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmsread(this) + { + + } +}; + +MODULE_INIT(MSRead) diff --git a/modules/memoserv/rsend.cpp b/modules/memoserv/rsend.cpp new file mode 100644 index 000000000..16f6105fa --- /dev/null +++ b/modules/memoserv/rsend.cpp @@ -0,0 +1,105 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/memoserv.h" + +class CommandMSRSend : public Command +{ + public: + CommandMSRSend(Module *creator) : Command(creator, "memoserv/rsend", 2, 2) + { + this->SetDesc(_("Sends a memo and requests a read receipt")); + this->SetSyntax(_("{\037nick\037 | \037channel\037} \037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { +#if 0 + if (!MemoServ::service) + return; + + if (Anope::ReadOnly && !source.IsOper()) + { + source.Reply(_("Sorry, memo sending is temporarily disabled.")); + return; + } + + const Anope::string &nick = params[0]; + const Anope::string &text = params[1]; + const NickServ::Nick *na = NULL; + + /* prevent user from rsend to themselves */ + if ((na = NickServ::FindNick(nick)) && na->GetAccount() == source.GetAccount()) + { + source.Reply(_("You can not request a receipt when sending a memo to yourself.")); + return; + } + + if (Config->GetModule(this->owner)->Get("operonly") && !source.IsServicesOper()) + source.Reply(_("Access denied. This command is for operators only.")); + else + { + MemoServ::MemoServService::MemoResult result = MemoServ::service->Send(source.GetNick(), nick, text); + if (result == MemoServ::MemoServService::MEMO_INVALID_TARGET) + source.Reply(_("\002{0}\002 isn't registered."), nick); + else if (result == MemoServ::MemoServService::MEMO_TOO_FAST) + source.Reply(_("Please wait \002{0}\002 seconds before using the \002{1}\002 command again."), Config->GetModule("memoserv")->Get("senddelay"), source.command); + else if (result == MemoServ::MemoServService::MEMO_TARGET_FULL) + source.Reply(_("Sorry, \002{0}\002 currently has too many memos and cannot receive more."), nick); + else + { + source.Reply(_("Memo sent to \002{0}\002."), nick); + + bool ischan, isregistered; + MemoServ::MemoInfo *mi = MemoServ::service->GetMemoInfo(nick, ischan, isregistered, false); + if (mi == NULL) + throw CoreException("NULL mi in ms_rsend"); + MemoServ::Memo *m = (mi->memos->size() ? mi->GetMemo(mi->memos->size() - 1) : NULL); + if (m != NULL) + m->receipt = true; + } + } +#endif + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { +#if 0 + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sends the named \037nick\037 or \037channel\037 a memo containing\n" + "\037memo-text\037. When sending to a nickname, the recipient will\n" + "receive a notice that he/she has a new memo. The target\n" + "nickname/channel must be registered.\n" + "Once the memo is read by its recipient, an automatic notification\n" + "memo will be sent to the sender informing him/her that the memo\n" + "has been read.")); + return true; +#endif + } +}; + +class MSRSend : public Module +{ + CommandMSRSend commandmsrsend; + + public: + MSRSend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmsrsend(this) + { + if (!MemoServ::service) + throw ModuleException("No MemoServ!"); + throw ModuleException("XXX"); + } +}; + +MODULE_INIT(MSRSend) diff --git a/modules/memoserv/send.cpp b/modules/memoserv/send.cpp new file mode 100644 index 000000000..0d26b8684 --- /dev/null +++ b/modules/memoserv/send.cpp @@ -0,0 +1,81 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/memoserv.h" + +class CommandMSSend : public Command +{ + public: + CommandMSSend(Module *creator) : Command(creator, "memoserv/send", 2, 2) + { + this->SetDesc(_("Send a memo to a nick or channel")); + this->SetSyntax(_("{\037user\037 | \037channel\037} \037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (!MemoServ::service) + return; + + const Anope::string &nick = params[0]; + const Anope::string &text = params[1]; + + if (Anope::ReadOnly && !source.IsOper()) + { + source.Reply(_("Sorry, memo sending is temporarily disabled.")); + return; + } + + if (source.GetAccount()->HasFieldS("UNCONFIRMED")) + { + source.Reply(_("You must confirm your account before you may send a memo.")); + return; + } + + MemoServ::MemoServService::MemoResult result = MemoServ::service->Send(source.GetNick(), nick, text); + if (result == MemoServ::MemoServService::MEMO_SUCCESS) + { + source.Reply(_("Memo sent to \002%s\002."), nick.c_str()); + Log(LOG_COMMAND, source, this) << "to send a memo to " << nick; + } + else if (result == MemoServ::MemoServService::MEMO_INVALID_TARGET) + source.Reply(_("\002{0}\002 is not a registered unforbidden nick or channel."), nick); + else if (result == MemoServ::MemoServService::MEMO_TOO_FAST) + source.Reply(_("Please wait \002{0}\002 seconds before using the \002{1}\002 command again."), Config->GetModule("memoserv")->Get("senddelay"), source.command); + else if (result == MemoServ::MemoServService::MEMO_TARGET_FULL) + source.Reply(_("Sorry, \002{0}\002 currently has too many memos and cannot receive more."), nick); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sends the named \037user\037 or \037channel\037 a memo containing \037memo-text\037." + " The recipient will receive a notice that they have a new memo." + " The target user or channel must be registered.")); + return true; + } +}; + +class MSSend : public Module +{ + CommandMSSend commandmssend; + + public: + MSSend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmssend(this) + { + + if (!MemoServ::service) + throw ModuleException("No MemoServ!"); + } +}; + +MODULE_INIT(MSSend) diff --git a/modules/memoserv/sendall.cpp b/modules/memoserv/sendall.cpp new file mode 100644 index 000000000..7c5365c4d --- /dev/null +++ b/modules/memoserv/sendall.cpp @@ -0,0 +1,60 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/memoserv.h" + +class CommandMSSendAll : public Command +{ + public: + CommandMSSendAll(Module *creator) : Command(creator, "memoserv/sendall", 1, 1) + { + this->SetDesc(_("Send a memo to all registered users")); + this->SetSyntax(_("\037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (!MemoServ::service) + return; + + const Anope::string &text = params[0]; + + Log(LOG_ADMIN, source, this) << "to send " << text; + + for (NickServ::Account *nc : NickServ::service->GetAccountList()) + if (nc != source.nc) + MemoServ::service->Send(source.GetNick(), nc->GetDisplay(), text); + + source.Reply(_("A mass memo has been sent to all registered users.")); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sends all registered users a memo containing \037memo-text\037.")); + return true; + } +}; + +class MSSendAll : public Module +{ + CommandMSSendAll commandmssendall; + + public: + MSSendAll(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmssendall(this) + { + if (!MemoServ::service) + throw ModuleException("No MemoServ!"); + } +}; + +MODULE_INIT(MSSendAll) diff --git a/modules/memoserv/set.cpp b/modules/memoserv/set.cpp new file mode 100644 index 000000000..c924d0ea0 --- /dev/null +++ b/modules/memoserv/set.cpp @@ -0,0 +1,314 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandMSSet : public Command +{ + private: + void DoNotify(CommandSource &source, const std::vector ¶ms, MemoServ::MemoInfo *mi) + { + const Anope::string ¶m = params[1]; + NickServ::Account *nc = source.nc; + ServiceBot *MemoServ = Config->GetClient("MemoServ"); + + if (!MemoServ) + return; + + if (param.equals_ci("ON")) + { + nc->SetS("MEMO_SIGNON", true); + nc->SetS("MEMO_RECEIVE", true); + source.Reply(_("\002{0}\002 will now notify you of memos when you log on and when they are sent to you."), MemoServ->nick); + } + else if (param.equals_ci("LOGON")) + { + nc->SetS("MEMO_SIGNON", true); + nc->UnsetS("MEMO_RECEIVE"); + source.Reply(_("\002{0}\002 will now notify you of memos when you log on or unset /AWAY."), MemoServ->nick); + } + else if (param.equals_ci("NEW")) + { + nc->UnsetS("MEMO_SIGNON"); + nc->SetS("MEMO_RECEIVE", true); + source.Reply(_("\002{0}\002 will now notify you of memos when they are sent to you."), MemoServ->nick); + } + else if (param.equals_ci("MAIL")) + { + if (!nc->GetEmail().empty()) + { + nc->SetS("MEMO_MAIL", true); + source.Reply(_("You will now be informed about new memos via email.")); + } + else + source.Reply(_("There's no email address set for your nick.")); + } + else if (param.equals_ci("NOMAIL")) + { + nc->UnsetS("MEMO_MAIL"); + source.Reply(_("You will no longer be informed via email.")); + } + else if (param.equals_ci("OFF")) + { + nc->UnsetS("MEMO_SIGNON"); + nc->UnsetS("MEMO_RECEIVE"); + nc->UnsetS("MEMO_MAIL"); + source.Reply(_("\002{0}\002 will not send you any notification of memos."), MemoServ->nick); + } + else + this->OnSyntaxError(source, ""); + } + + void DoLimit(CommandSource &source, const std::vector ¶ms, MemoServ::MemoInfo *mi) + { + + Anope::string p1 = params[1]; + Anope::string p2 = params.size() > 2 ? params[2] : ""; + Anope::string p3 = params.size() > 3 ? params[3] : ""; + Anope::string user, chan; + int16_t limit; + NickServ::Account *nc = source.nc; + ChanServ::Channel *ci = NULL; + bool is_servadmin = source.HasPriv("memoserv/set-limit"); + + if (p1[0] == '#') + { + chan = p1; + p1 = p2; + p2 = p3; + p3 = params.size() > 4 ? params[4] : ""; + + ci = ChanServ::Find(chan); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + if (!is_servadmin && !source.AccessFor(ci).HasPriv("MEMO")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "MEMO", ci->GetName()); + return; + } + mi = ci->GetMemos(); + } + if (is_servadmin) + { + if (!p2.empty() && !p2.equals_ci("HARD") && chan.empty()) + { + NickServ::Nick *na; + if (!(na = NickServ::FindNick(p1))) + { + source.Reply(_("\002{0}\002 isn't registered."), p1); + return; + } + user = p1; + mi = na->GetAccount()->GetMemos(); + nc = na->GetAccount(); + p1 = p2; + p2 = p3; + } + else if (p1.empty() || (!p1.is_pos_number_only() && !p1.equals_ci("NONE")) || (!p2.empty() && !p2.equals_ci("HARD"))) + { + this->OnSyntaxError(source, ""); + return; + } + if (!chan.empty()) + { + if (!p2.empty()) + ci->SetS("MEMO_HARDMAX", true); + else + ci->UnsetS("MEMO_HARDMAX"); + } + else + { + if (!p2.empty()) + nc->SetS("MEMO_HARDMAX", true); + else + nc->UnsetS("MEMO_HARDMAX"); + } + limit = -1; + try + { + limit = convertTo(p1); + } + catch (const ConvertException &) { } + } + else + { + if (p1.empty() || !p2.empty() || !isdigit(p1[0])) + { + this->OnSyntaxError(source, ""); + return; + } + if (!chan.empty() && ci->HasFieldS("MEMO_HARDMAX")) + { + source.Reply(_("The memo limit for \002{0}\002 may not be changed."), chan); + return; + } + if (chan.empty() && nc->HasFieldS("MEMO_HARDMAX")) + { + source.Reply(_("You are not permitted to change your memo limit.")); + return; + } + int max_memos = Config->GetModule("memoserv")->Get("maxmemos"); + limit = -1; + try + { + limit = convertTo(p1); + } + catch (const ConvertException &) { } + /* The first character is a digit, but we could still go negative + * from overflow... watch out! */ + if (limit < 0 || (max_memos > 0 && limit > max_memos)) + { + if (!chan.empty()) + source.Reply(_("You cannot set the memo limit for \002{0}\002 higher than \002{1}\002."), chan, max_memos); + else + source.Reply(_("You cannot set your memo limit higher than \002{0}\002."), max_memos); + return; + } + } + mi->SetMemoMax(limit); + if (limit > 0) + { + if (chan.empty() && nc == source.nc) + source.Reply(_("Your memo limit has been set to \002{0}\002."), limit); + else + source.Reply(_("Memo limit for \002{0}\002 set to \002{1}\002."), !chan.empty() ? chan : user, limit); + } + else if (!limit) + { + if (chan.empty() && nc == source.nc) + source.Reply(_("You will no longer be able to receive memos.")); + else + source.Reply(_("Memo limit for \002{0}\002 set to \0020\002."), !chan.empty() ? chan : user); + } + else + { + if (chan.empty() && nc == source.nc) + source.Reply(_("Your memo limit has been disabled.")); + else + source.Reply(_("Memo limit \002disabled\002 for \002{0}\002."), !chan.empty() ? chan : user); + } + } + public: + CommandMSSet(Module *creator) : Command(creator, "memoserv/set", 2, 5) + { + this->SetDesc(_("Set options related to memos")); + this->SetSyntax(_("\037option\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + MemoServ::MemoInfo *mi = source.nc->GetMemos(); + + if (Anope::ReadOnly) + source.Reply(_("Sorry, memo option setting is temporarily disabled.")); + else if (cmd.equals_ci("NOTIFY")) + return this->DoNotify(source, params, mi); + else if (cmd.equals_ci("LIMIT")) + return this->DoLimit(source, params, mi); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.empty()) + { + CommandInfo *help = source.service->FindCommand("generic/help"); + if (!help) + return false; + source.Reply(_("Sets various memo options. \037option\037 can be one of:\n" + "\n" + " NOTIFY Changes when you will be notified about\n" + " new memos (only for nicknames)\n" + " LIMIT Sets the maximum number of memos you can\n" + " receive\n" + "\n" + "Type \002{0}{1} {2} {3} \037option\037\002 for more information on a specific option."), + Config->StrictPrivmsg, source.service->nick, help->cname, source.command); + } + else if (subcommand.equals_ci("NOTIFY")) + source.Reply(_("Syntax: \002NOTIFY {ON | LOGON | NEW | MAIL | NOMAIL | OFF}\002\n" + "\n" + "Changes when you will be notified about new memos:\n" + "\n" + " ON You will be notified of memos when you log on,\n" + " when you unset /AWAY, and when they are sent\n" + " to you.\n" + "\n" + " LOGON You will only be notified of memos when you log\n" + " on or when you unset /AWAY.\n" + "\n" + " NEW You will only be notified of memos when they\n" + " are sent to you.\n" + "\n" + " MAIL You will be notified of memos by email as well as\n" + " any other settings you have.\n" + "\n" + " NOMAIL You will not be notified of memos by email.\n" + "\n" + " OFF You will not receive any notification of memos.\n" + "\n" + "\002ON\002 is essentially \002LOGON\002 and \002NEW\002 combined.")); + else if (subcommand.equals_ci("LIMIT")) + { + int max_memos = Config->GetModule("memoserv")->Get("maxmemos"); + if (source.IsServicesOper()) + source.Reply(_("Syntax: \002LIMIT [\037user\037 | \037channel\037] {\037limit\037 | NONE} [HARD]\002\n" + "\n" + "Sets the maximum number of memos a user or channel is allowed to have." + " Setting the limit to 0 prevents the user from receiving any memos; setting it to \002NONE\002 allows the user to receive and keep as many memos as they want." + " If you do not give a nickname or channel, your own limit is set.\n" + "\n" + "Adding \002HARD\002 prevents the user from changing the limit." + " Not adding \002HARD\002 has the opposite effect, allowing the user to change the limit, even if a previous limit was set.\n" + " \n" + "This use of the \002{0} LIMIT\002 command is limited to \002Services Operators\002." + " Other users may only enter a limit for themselves or a channel on which they have the \002MEMO\002 privilege on, may not remove their limit, may not set a limit above {1}, and may not set a hard limit."), + source.command, max_memos); + else + source.Reply(_("Syntax: \002LIMIT [\037channel\037] \037limit\037\002\n" + "\n" + "Sets the maximum number of memos you, or the given channel, are allowed to have." + " If you set this to 0, no one will be able to send any memos to you." + "However, you cannot set this any higher than {0}."), max_memos); + } + else + return false; + + return true; + } +}; + +class MSSet : public Module +{ + CommandMSSet commandmsset; + Serialize::Field memo_signon, memo_receive, memo_mail, memo_hardmax_nick; + Serialize::Field memo_hardmax_channel; + + public: + MSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmsset(this) + , memo_signon(this, NickServ::account, "MEMO_SIGNON") + , memo_receive(this, NickServ::account, "MEMO_RECEIVE") + , memo_mail(this, NickServ::account, "MEMO_MAIL") + , memo_hardmax_nick(this, NickServ::account, "MEMO_HARDMAX") + , memo_hardmax_channel(this, ChanServ::channel, "MEMO_HARDMAX") + { + + } +}; + +MODULE_INIT(MSSet) diff --git a/modules/memoserv/staff.cpp b/modules/memoserv/staff.cpp new file mode 100644 index 000000000..f563fd9ea --- /dev/null +++ b/modules/memoserv/staff.cpp @@ -0,0 +1,57 @@ +/* MemoServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/memoserv.h" + +class CommandMSStaff : public Command +{ + public: + CommandMSStaff(Module *creator) : Command(creator, "memoserv/staff", 1, 1) + { + this->SetDesc(_("Send a memo to all opers/admins")); + this->SetSyntax(_("\037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (!MemoServ::service) + return; + + const Anope::string &text = params[0]; + + for (NickServ::Account *nc : NickServ::service->GetAccountList()) + if (source.nc != nc && nc->IsServicesOper()) + MemoServ::service->Send(source.GetNick(), nc->GetDisplay(), text, true); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sends all services staff a memo containing \037memo-text\037.")); + + return true; + } +}; + +class MSStaff : public Module +{ + CommandMSStaff commandmsstaff; + + public: + MSStaff(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandmsstaff(this) + { + if (!MemoServ::service) + throw ModuleException("No MemoServ!"); + } +}; + +MODULE_INIT(MSStaff) diff --git a/modules/nickserv/CMakeLists.txt b/modules/nickserv/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/nickserv/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/nickserv/access.cpp b/modules/nickserv/access.cpp new file mode 100644 index 000000000..ba2116ac9 --- /dev/null +++ b/modules/nickserv/access.cpp @@ -0,0 +1,257 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/nickserv.h" +#include "modules/ns_access.h" + +class NickAccessImpl : public NickAccess +{ + public: + NickAccessImpl(Serialize::TypeBase *type) : NickAccess(type) { } + NickAccessImpl(Serialize::TypeBase *type, Serialize::ID id) : NickAccess(type, id) { } + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *) override; + + Anope::string GetMask() override; + void SetMask(const Anope::string &) override; +}; + +class NickAccessType : public Serialize::Type +{ + public: + Serialize::ObjectField account; + Serialize::Field mask; + + NickAccessType(Module *creator) : Serialize::Type(creator, "NSAccess") + , account(this, "account", true) + , mask(this, "mask") + { + } +}; + +NickServ::Account *NickAccessImpl::GetAccount() +{ + return Get(&NickAccessType::account); +} + +void NickAccessImpl::SetAccount(NickServ::Account *acc) +{ + Set(&NickAccessType::account, acc); +} + +Anope::string NickAccessImpl::GetMask() +{ + return Get(&NickAccessType::mask); +} + +void NickAccessImpl::SetMask(const Anope::string &m) +{ + Set(&NickAccessType::mask, m); +} + +class CommandNSAccess : public Command +{ + private: + void DoAdd(CommandSource &source, NickServ::Account *nc, const Anope::string &mask) + { + if (mask.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + std::vector access = nc->GetRefs(nsaccess); + + if (access.size() >= Config->GetModule(this->owner)->Get("accessmax", "32")) + { + source.Reply(_("Sorry, the maximum of \002{0}\002 access entries has been reached."), Config->GetModule(this->owner)->Get("accessmax")); + return; + } + + for (NickAccess *a : access) + if (a->GetMask().equals_ci(mask)) + { + source.Reply(_("Mask \002{0}\002 already present on the access list of \002{1}\002."), mask, nc->GetDisplay()); + return; + } + + NickAccess *a = nsaccess.Create(); + a->SetAccount(nc); + a->SetMask(mask); + + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to ADD mask " << mask << " to " << nc->GetDisplay(); + source.Reply(_("\002{0}\002 added to the access list of \002{1}\002."), mask, nc->GetDisplay()); + } + + void DoDel(CommandSource &source, NickServ::Account *nc, const Anope::string &mask) + { + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + for (NickAccess *a : nc->GetRefs(nsaccess)) + if (a->GetMask().equals_ci(mask)) + { + a->Delete(); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to DELETE mask " << mask << " from " << nc->GetDisplay(); + source.Reply(_("\002{0}\002 deleted from the access list of \002{1}\002."), mask, nc->GetDisplay()); + return; + } + + + source.Reply(_("\002{0}\002 not found on the access list of \002{1}\002."), mask, nc->GetDisplay()); + } + + void DoList(CommandSource &source, NickServ::Account *nc, const Anope::string &mask) + { + std::vector access = nc->GetRefs(nsaccess); + if (access.empty()) + { + source.Reply(_("The access list of \002{0}\002 is empty."), nc->GetDisplay()); + return; + } + + source.Reply(_("Access list for \002{0}\002:"), nc->GetDisplay()); + for (NickAccess *a : access) + { + if (!mask.empty() && !Anope::Match(a->GetMask(), mask)) + continue; + + source.Reply(" {0}", a->GetMask()); + } + } + public: + CommandNSAccess(Module *creator) : Command(creator, "nickserv/access", 1, 3) + { + this->SetDesc(_("Modify the list of authorized addresses")); + this->SetSyntax(_("ADD [\037nickname\037] \037mask\037")); + this->SetSyntax(_("DEL [\037nickname\037] \037mask\037")); + this->SetSyntax(_("LIST [\037nickname\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + Anope::string nick, mask; + + if (cmd.equals_ci("LIST")) + nick = params.size() > 1 ? params[1] : ""; + else + { + nick = params.size() == 3 ? params[1] : ""; + mask = params.size() > 1 ? params[params.size() - 1] : ""; + } + + NickServ::Account *nc; + if (!nick.empty() && source.HasPriv("nickserv/access")) + { + NickServ::Nick *na = NickServ::FindNick(nick); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && source.GetAccount() != na->GetAccount() && na->GetAccount()->IsServicesOper() && !cmd.equals_ci("LIST")) + { + source.Reply(_("You may view but not modify the access list of other Services Operators.")); + return; + } + + nc = na->GetAccount(); + } + else + nc = source.nc; + + if (!mask.empty() && (mask.find('@') == Anope::string::npos || mask.find('!') != Anope::string::npos)) + { + source.Reply(_("Mask must be in the form \037user\037@\037host\037.")); + source.Reply(_("\002%s%s HELP %s\002 for more information."), Config->StrictPrivmsg, source.service->nick, source.command); // XXX + } + else if (cmd.equals_ci("LIST")) + return this->DoList(source, nc, mask); + else if (nc->HasFieldS("NS_SUSPENDED")) + source.Reply(_("\002{0}\002 is suspended."), nc->GetDisplay()); + else if (cmd.equals_ci("ADD")) + return this->DoAdd(source, nc, mask); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, nc, mask); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Modifies or displays the access list for your account." + " The access list is a list of addresses that {1} uses to recognize you." + " If you match one of the hosts on the access list, services will not force you to change your nickname if the \002KILL\002 option is set." + " Furthermore, if the \002SECURE\002 option is disabled, services will recognize you just based on your hostmask, without having to supply a password." + " To gain access to channels when only recognized by your hostmask, the channel must too have the \002SECURE\002 option off." + " Services Operators may provide \037nickname\037 to modify other user's access lists.\n" + "\n" + "Examples:\n" + " \n" + " {command} ADD anyone@*.bepeg.com\n" + " Allows access to user \"anyone\" from any machine in the \"bepeg.com\" domain.\n" + "\n" + " {command} DEL anyone@*.bepeg.com\n" + " Reverses the previous command.\n" + "\n" + " {command} LIST\n" + " Displays the current access list."), + source.command, source.service->nick); + return true; + } +}; + +class NSAccess : public Module + , public EventHook +{ + CommandNSAccess commandnsaccess; + NickAccessType nick_type; + + public: + NSAccess(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsaccess(this) + , nick_type(this) + { + } + + void OnNickRegister(User *u, NickServ::Nick *na, const Anope::string &) override + { + if (u && Config->GetModule(this)->Get("addaccessonreg")) + { + NickAccess *a = nsaccess.Create(); + a->SetAccount(na->GetAccount()); + a->SetMask(u->Mask()); + //XXX? +// source.Reply(_("\002{0}\002 has been registered under your hostmask: \002{1}\002"), u_nick, na->GetAccount()->GetAccess(0)); + } + } +}; + +MODULE_INIT(NSAccess) diff --git a/modules/nickserv/ajoin.cpp b/modules/nickserv/ajoin.cpp new file mode 100644 index 000000000..135beb403 --- /dev/null +++ b/modules/nickserv/ajoin.cpp @@ -0,0 +1,381 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_ajoin.h" + +class AutoJoinImpl : public AutoJoin +{ + public: + AutoJoinImpl(Serialize::TypeBase *type) : AutoJoin(type) { } + AutoJoinImpl(Serialize::TypeBase *type, Serialize::ID id) : AutoJoin(type, id) { } + + NickServ::Account *GetOwner() override; + void SetOwner(NickServ::Account *acc) override; + + Anope::string GetChannel() override; + void SetChannel(const Anope::string &c) override; + + Anope::string GetKey() override; + void SetKey(const Anope::string &k) override; +}; + +class AutoJoinType : public Serialize::Type +{ + public: + Serialize::ObjectField owner; + Serialize::Field channel, key; + + AutoJoinType(Module *me) : Serialize::Type(me, "AutoJoin") + , owner(this, "owner", true) + , channel(this, "channel") + , key(this, "key") + { + } +}; + +NickServ::Account *AutoJoinImpl::GetOwner() +{ + return Get(&AutoJoinType::owner); +} + +void AutoJoinImpl::SetOwner(NickServ::Account *acc) +{ + Set(&AutoJoinType::owner, acc); +} + +Anope::string AutoJoinImpl::GetChannel() +{ + return Get(&AutoJoinType::channel); +} + +void AutoJoinImpl::SetChannel(const Anope::string &c) +{ + Set(&AutoJoinType::channel, c); +} + +Anope::string AutoJoinImpl::GetKey() +{ + return Get(&AutoJoinType::key); +} + +void AutoJoinImpl::SetKey(const Anope::string &k) +{ + Set(&AutoJoinType::key, k); +} + +class CommandNSAJoin : public Command +{ + void DoList(CommandSource &source, NickServ::Account *nc) + { + std::vector channels = nc->GetRefs(autojoin); + + if (channels.empty()) + { + source.Reply(_("The auto join list of \002{0}\002 is empty."), nc->GetDisplay()); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Channel")).AddColumn(_("Key")); + for (unsigned i = 0; i < channels.size(); ++i) + { + AutoJoin *aj = channels[i]; + ListFormatter::ListEntry entry; + entry["Number"] = stringify(i + 1); + entry["Channel"] = aj->GetChannel(); + entry["Key"] = aj->GetKey(); + list.AddEntry(entry); + } + + source.Reply(_("Auto join list of \002{0}\002:"), nc->GetDisplay()); + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + + void DoAdd(CommandSource &source, NickServ::Account *nc, const Anope::string &chans, const Anope::string &keys) + { + std::vector channels = nc->GetRefs(autojoin); + + Anope::string addedchans; + Anope::string alreadyadded; + Anope::string invalidkey; + commasepstream ksep(keys, true); + commasepstream csep(chans); + for (Anope::string chan, key; csep.GetToken(chan);) + { + ksep.GetToken(key); + + unsigned i = 0; + for (; i < channels.size(); ++i) + if (channels[i]->GetChannel().equals_ci(chan)) + break; + + if (channels.size() >= Config->GetModule(this->owner)->Get("ajoinmax")) + { + source.Reply(_("Sorry, the maximum of \002{0}\002 auto join entries has been reached."), Config->GetModule(this->owner)->Get("ajoinmax")); + return; + } + + if (i != channels.size()) + alreadyadded += chan + ", "; + else if (IRCD->IsChannelValid(chan) == false) + source.Reply(_("\002{0}\002 isn't a valid channel."), chan); + else + { + Channel *c = Channel::Find(chan); + Anope::string k; + if (c && c->GetParam("KEY", k) && key != k) + { + invalidkey += chan + ", "; + continue; + } + + AutoJoin *entry = autojoin.Create(); + entry->SetOwner(nc); + entry->SetChannel(chan); + entry->SetKey(key); + + addedchans += chan + ", "; + } + } + + if (!alreadyadded.empty()) + { + alreadyadded = alreadyadded.substr(0, alreadyadded.length() - 2); + source.Reply(_("\002{0}\002 is already on the auto join list of \002{1}\002."), alreadyadded, nc->GetDisplay()); + } + + if (!invalidkey.empty()) + { + invalidkey = invalidkey.substr(0, invalidkey.length() - 2); + source.Reply(_("\002{0}\002 had an invalid key specified, and was ignored."), invalidkey); + } + + if (addedchans.empty()) + return; + + addedchans = addedchans.substr(0, addedchans.length() - 2); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to ADD channel " << addedchans << " to " << nc->GetDisplay(); + source.Reply(_("\002{0}\002 added to the auto join list of \002{1}\002."), addedchans, nc->GetDisplay()); + } + + void DoDel(CommandSource &source, NickServ::Account *nc, const Anope::string &chans) + { + std::vector channels = nc->GetRefs(autojoin); + Anope::string delchans; + Anope::string notfoundchans; + commasepstream sep(chans); + + for (Anope::string chan; sep.GetToken(chan);) + { + unsigned i = 0; + for (; i < channels.size(); ++i) + if (channels[i]->GetChannel().equals_ci(chan)) + break; + + if (i == channels.size()) + notfoundchans += chan + ", "; + else + { + delete channels[i]; + delchans += chan + ", "; + } + } + + if (!notfoundchans.empty()) + { + notfoundchans = notfoundchans.substr(0, notfoundchans.length() - 2); + source.Reply(_("\002{0}\002 was not found on the auto join list of \002{1}\002."), notfoundchans, nc->GetDisplay()); + } + + if (delchans.empty()) + return; + + delchans = delchans.substr(0, delchans.length() - 2); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to DELETE channel " << delchans << " from " << nc->GetDisplay(); + source.Reply(_("\002{0}\002 was removed from the auto join list of \002{1}\002."), delchans, nc->GetDisplay()); + } + + public: + CommandNSAJoin(Module *creator) : Command(creator, "nickserv/ajoin", 1, 4) + { + this->SetDesc(_("Manage your auto join list")); + this->SetSyntax(_("ADD [\037nickname\037] \037channel\037 [\037key\037]")); + this->SetSyntax(_("DEL [\037nickname\037] \037channel\037")); + this->SetSyntax(_("LIST [\037nickname\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + Anope::string nick, param, param2; + + if (cmd.equals_ci("LIST")) + nick = params.size() > 1 ? params[1] : ""; + else + nick = (params.size() > 2 && IRCD->IsChannelValid(params[2])) ? params[1] : ""; + + NickServ::Account *nc; + if (!nick.empty() && !source.HasCommand("nickserv/ajoin")) + { + NickServ::Nick *na = NickServ::FindNick(nick); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + nc = na->GetAccount(); + param = params.size() > 2 ? params[2] : ""; + param2 = params.size() > 3 ? params[3] : ""; + } + else + { + nc = source.nc; + param = params.size() > 1 ? params[1] : ""; + param2 = params.size() > 2 ? params[2] : ""; + } + + if (cmd.equals_ci("LIST")) + return this->DoList(source, nc); + else if (nc->HasFieldS("NS_SUSPENDED")) + source.Reply(_("\002{0}\002 isn't registered."), nc->GetDisplay()); + else if (param.empty()) + this->OnSyntaxError(source, ""); + else if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode.")); + else if (cmd.equals_ci("ADD")) + return this->DoAdd(source, nc, param, param2); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, nc, param); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command manages your auto join list." + " When you identify you will automatically join the channels on your auto join list." + " Services Operators may provide \037nickname\037 to modify other users' auto join lists.")); + return true; + } +}; + +class NSAJoin : public Module + , public EventHook +{ + CommandNSAJoin commandnsajoin; + AutoJoinType ajtype; + + public: + NSAJoin(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsajoin(this) + , ajtype(this) + { + + if (!IRCD || !IRCD->CanSVSJoin) + throw ModuleException("Your IRCd does not support SVSJOIN"); + + } + + void OnUserLogin(User *u) override + { + ServiceBot *NickServ = Config->GetClient("NickServ"); + if (!NickServ) + return; + + std::vector channels = u->Account()->GetRefs(autojoin); + if (channels.empty()) + return; + + /* Set +r now, so we can ajoin users into +R channels */ + ModeManager::ProcessModes(); + + for (AutoJoin *entry : channels) + { + Channel *c = Channel::Find(entry->GetChannel()); + ChanServ::Channel *ci; + + if (c) + ci = c->ci; + else + ci = ChanServ::Find(entry->GetChannel()); + + bool need_invite = false; + Anope::string key = entry->GetKey(); + ChanServ::AccessGroup u_access; + + if (ci != NULL) + { + if (ci->HasFieldS("CS_SUSPENDED")) + continue; + u_access = ci->AccessFor(u); + } + if (c != NULL) + { + if (c->FindUser(u) != NULL) + continue; + else if (c->HasMode("OPERONLY") && !u->HasMode("OPER")) + continue; + else if (c->HasMode("ADMINONLY") && !u->HasMode("ADMIN")) + continue; + else if (c->HasMode("SSL") && !(u->HasMode("SSL") || u->HasExtOK("ssl"))) + continue; + else if (c->MatchesList(u, "BAN") == true && c->MatchesList(u, "EXCEPT") == false) + need_invite = true; + else if (c->HasMode("INVITE") && c->MatchesList(u, "INVITEOVERRIDE") == false) + need_invite = true; + + if (c->HasMode("KEY")) + { + Anope::string k; + if (c->GetParam("KEY", k)) + { + if (u_access.HasPriv("GETKEY")) + key = k; + else if (key != k) + need_invite = true; + } + } + if (c->HasMode("LIMIT")) + { + Anope::string l; + if (c->GetParam("LIMIT", l)) + { + try + { + unsigned limit = convertTo(l); + if (c->users.size() >= limit) + need_invite = true; + } + catch (const ConvertException &) { } + } + } + } + + if (need_invite && c != NULL) + { + if (!u_access.HasPriv("INVITE")) + continue; + IRCD->SendInvite(NickServ, c, u); + } + + IRCD->SendSVSJoin(NickServ, u, entry->GetChannel(), key); + } + } +}; + +MODULE_INIT(NSAJoin) diff --git a/modules/nickserv/alist.cpp b/modules/nickserv/alist.cpp new file mode 100644 index 000000000..e4aaff38b --- /dev/null +++ b/modules/nickserv/alist.cpp @@ -0,0 +1,133 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandNSAList : public Command +{ + static bool ChannelSort(ChanServ::Channel *ci1, ChanServ::Channel *ci2) + { + return ci::less()(ci1->GetName(), ci2->GetName()); + } + + public: + CommandNSAList(Module *creator) : Command(creator, "nickserv/alist", 0, 2) + { + this->SetDesc(_("List channels you have access on")); + this->SetSyntax(_("[\037nickname\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + Anope::string nick = source.GetNick(); + NickServ::Account *nc = source.nc; + + if (params.size() && source.HasPriv("nickserv/alist")) + { + nick = params[0]; + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + nc = na->GetAccount(); + } + + ListFormatter list(source.GetAccount()); + int chan_count = 0; + + list.AddColumn(_("Number")).AddColumn(_("Channel")).AddColumn(_("Access")).AddColumn(_("Description")); + + std::vector chans = nc->GetRefs(ChanServ::channel); + std::sort(chans.begin(), chans.end(), ChannelSort); + for (ChanServ::Channel *ci : chans) + { + ListFormatter::ListEntry entry; + + if (ci->GetFounder() == nc) + { + ++chan_count; + entry["Number"] = stringify(chan_count); + entry["Channel"] = (ci->HasFieldS("CS_NO_EXPIRE") ? "!" : "") + ci->GetName(); + entry["Access"] = Language::Translate(source.GetAccount(), _("Founder")); + entry["Description"] = ci->GetDesc(); + list.AddEntry(entry); + continue; + } + + if (ci->GetSuccessor() == nc) + { + ++chan_count; + entry["Number"] = stringify(chan_count); + entry["Channel"] = (ci->HasFieldS("CS_NO_EXPIRE") ? "!" : "") + ci->GetName(); + entry["Access"] = Language::Translate(source.GetAccount(), _("Successor")); + entry["Description"] = ci->GetDesc(); + list.AddEntry(entry); + continue; + } + + ChanServ::AccessGroup access = ci->AccessFor(nc, false); + if (access.empty()) + continue; + + ++chan_count; + + entry["Number"] = stringify(chan_count); + entry["Channel"] = (ci->HasFieldS("CS_NO_EXPIRE") ? "!" : "") + ci->GetName(); + for (unsigned j = 0; j < access.size(); ++j) + entry["Access"] = entry["Access"] + ", " + access[j]->AccessSerialize(); + entry["Access"] = entry["Access"].substr(2); + entry["Description"] = ci->GetDesc(); + list.AddEntry(entry); + } + + std::vector replies; + list.Process(replies); + + if (!chan_count) + { + source.Reply(_("\002{0}\002 has no access in any channels."), nc->GetDisplay()); + } + else + { + source.Reply(_("Channels that \002{0}\002 has access on:"), nc->GetDisplay()); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of list - \002{0}\002 channels shown."), chan_count); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Lists all channels you have access on.\n" + " \n" + "Channels that have the \037NOEXPIRE\037 option set will be prefixed by an exclamation mark. The nickname parameter is limited to Services Operators")); + + return true; + } +}; + +class NSAList : public Module +{ + CommandNSAList commandnsalist; + + public: + NSAList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsalist(this) + { + + } +}; + +MODULE_INIT(NSAList) diff --git a/modules/nickserv/cert.cpp b/modules/nickserv/cert.cpp new file mode 100644 index 000000000..fc87f8cb0 --- /dev/null +++ b/modules/nickserv/cert.cpp @@ -0,0 +1,364 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_cert.h" +#include "modules/nickserv.h" + +static Anope::hash_map certmap; +static EventHandlers *events; + +class CertServiceImpl : public CertService +{ + public: + CertServiceImpl(Module *o) : CertService(o) { } + + NickServ::Account* FindAccountFromCert(const Anope::string &cert) override + { + Anope::hash_map::iterator it = certmap.find(cert); + if (it != certmap.end()) + return it->second; + return NULL; + } + + bool Matches(User *u, NickServ::Account *nc) override + { + std::vector cl = nc->GetRefs(certentry); + return !u->fingerprint.empty() && FindCert(cl, u->fingerprint); + } + + NSCertEntry *FindCert(const std::vector &cl, const Anope::string &certfp) override + { + for (NSCertEntry *e : cl) + if (e->GetCert() == certfp) + return e; + return nullptr; + } +}; + +class NSCertEntryImpl : public NSCertEntry +{ + public: + NSCertEntryImpl(Serialize::TypeBase *type) : NSCertEntry(type) { } + NSCertEntryImpl(Serialize::TypeBase *type, Serialize::ID id) : NSCertEntry(type, id) { } + ~NSCertEntryImpl(); + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *) override; + + Anope::string GetCert() override; + void SetCert(const Anope::string &) override; +}; + +class NSCertEntryType : public Serialize::Type +{ + public: + struct Account : Serialize::ObjectField + { + using Serialize::ObjectField::ObjectField; + + void SetField(NSCertEntryImpl *s, NickServ::Account *acc) override + { + const Anope::string &cert = s->GetCert(); + if (!cert.empty()) + certmap.erase(cert); + + Serialize::ObjectField::SetField(s, acc); + + if (!cert.empty() && s->GetAccount()) + certmap[cert] = acc; + } + } nc; + + struct Mask : Serialize::Field + { + using Serialize::Field::Field; + + void SetField(NSCertEntryImpl *s, const Anope::string &m) override + { + const Anope::string &old = GetField(s); + if (!old.empty()) + certmap.erase(old); + + Serialize::Field::SetField(s, m); + + if (!m.empty() && s->GetAccount()) + certmap[m] = s->GetAccount(); + } + } mask; + + NSCertEntryType(Module *me) : Serialize::Type(me, "NSCertEntry") + , nc(this, "nc", true) + , mask(this, "mask") + { + } +}; + +NSCertEntryImpl::~NSCertEntryImpl() +{ + const Anope::string &old = GetCert(); + if (!old.empty()) + certmap.erase(old); +} + +NickServ::Account *NSCertEntryImpl::GetAccount() +{ + return Get(&NSCertEntryType::nc); +} + +void NSCertEntryImpl::SetAccount(NickServ::Account *nc) +{ + Set(&NSCertEntryType::nc, nc); +} + +Anope::string NSCertEntryImpl::GetCert() +{ + return Get(&NSCertEntryType::mask); +} + +void NSCertEntryImpl::SetCert(const Anope::string &mask) +{ + Set(&NSCertEntryType::mask, mask); +} + +class CommandNSCert : public Command +{ + NSCertEntry *FindCert(const std::vector &cl, const Anope::string &certfp) + { + for (NSCertEntry *e : cl) + if (e->GetCert() == certfp) + return e; + return nullptr; + } + + void DoAdd(CommandSource &source, NickServ::Account *nc, Anope::string certfp) + { + std::vector cl = nc->GetRefs(certentry); + unsigned max = Config->GetModule(this->owner)->Get("max", "5"); + + if (cl.size() >= max) + { + source.Reply(_("Sorry, the maximum of \002{0}\002 certificate entries has been reached."), max); + return; + } + + if (source.GetAccount() == nc) + { + User *u = source.GetUser(); + + if (!u || u->fingerprint.empty()) + { + source.Reply(_("You are not using a client certificate.")); + return; + } + + certfp = u->fingerprint; + } + + if (FindCert(cl, certfp)) + { + source.Reply(_("Fingerprint \002{0}\002 already present on the certificate list of \002{0}\002."), certfp, nc->GetDisplay()); + return; + } + + if (certmap.find(certfp) != certmap.end()) + { + source.Reply(_("Fingerprint \002{0}\002 is already in use."), certfp); + return; + } + + NSCertEntry *e = certentry.Create(); + e->SetAccount(nc); + e->SetCert(certfp); + + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to ADD certificate fingerprint " << certfp << " to " << nc->GetDisplay(); + source.Reply(_("\002{0}\002 added to the certificate list of \002{1}\002."), certfp, nc->GetDisplay()); + } + + void DoDel(CommandSource &source, NickServ::Account *nc, Anope::string certfp) + { + std::vector cl = nc->GetRefs(certentry); + + if (certfp.empty()) + { + User *u = source.GetUser(); + if (u) + certfp = u->fingerprint; + } + + if (certfp.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + NSCertEntry *cert = FindCert(cl, certfp); + if (!cert) + { + source.Reply(_("\002{0}\002 not found on the certificate list of \002{1}\002."), certfp, nc->GetDisplay()); + return; + } + + cert->Delete(); + + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to DELETE certificate fingerprint " << certfp << " from " << nc->GetDisplay(); + source.Reply(_("\002{0}\002 deleted from the access list of \002{1}\002."), certfp, nc->GetDisplay()); + } + + void DoList(CommandSource &source, NickServ::Account *nc) + { + std::vector cl = nc->GetRefs(certentry); + + if (cl.empty()) + { + source.Reply(_("The certificate list of \002{0}\002 is empty."), nc->GetDisplay()); + return; + } + + source.Reply(_("Certificate list for \002{0}\002:"), nc->GetDisplay()); + for (NSCertEntry *e : cl) + source.Reply(" {0}", e->GetCert()); + } + + public: + CommandNSCert(Module *creator) : Command(creator, "nickserv/cert", 1, 3) + { + this->SetDesc(_("Modify the nickname client certificate list")); + this->SetSyntax(_("ADD [\037nickname\037] [\037fingerprint\037]")); + this->SetSyntax(_("DEL [\037nickname\037] \037fingerprint\037")); + this->SetSyntax(_("LIST [\037nickname\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + Anope::string nick, certfp; + + if (cmd.equals_ci("LIST")) + nick = params.size() > 1 ? params[1] : ""; + else + { + nick = params.size() == 3 ? params[1] : ""; + certfp = params.size() > 1 ? params[params.size() - 1] : ""; + } + + NickServ::Account *nc; + if (!nick.empty() && source.HasPriv("nickserv/access")) + { + NickServ::Nick *na = NickServ::FindNick(nick); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && source.GetAccount() != na->GetAccount() && na->GetAccount()->IsServicesOper() && !cmd.equals_ci("LIST")) + { + source.Reply(_("You may view, but not modify, the certificate list of other Services Operators.")); + return; + } + + nc = na->GetAccount(); + } + else + nc = source.nc; + + if (cmd.equals_ci("LIST")) + return this->DoList(source, nc); + else if (nc->HasFieldS("NS_SUSPENDED")) + source.Reply(_("\002{0}\002 is suspended."), nc->GetDisplay()); + else if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode.")); + else if (cmd.equals_ci("ADD")) + return this->DoAdd(source, nc, certfp); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, nc, certfp); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Modifies or displays the certificate list for your account." + "If you connect to IRC and provide a client certificate with a matching fingerprint in the certificate list, you will be automatically identified to services." + " Services Operators may provide \037nickname\037 to modify other users' certificate lists.\n" + "\n" + "Examples:\n" + "\n" + " {0} ADD\n" + " Adds your current fingerprint to the certificate list and automatically identifies you when you connect to IRC using this certificate.\n" + "\n" + " {0} DEL \n" + " Removes \"\" from your certificate list.")); + return true; + } +}; + +class NSCert : public Module + , public EventHook + , public EventHook +{ + CommandNSCert commandnscert; + CertServiceImpl cs; + + EventHandlers onnickservevents; + + public: + NSCert(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnscert(this) + , cs(this) + , onnickservevents(this) + { + if (!IRCD || !IRCD->CanCertFP) + throw ModuleException("Your IRCd does not support ssl client certificates"); + + events = &onnickservevents; + } + + void OnFingerprint(User *u) override + { + ServiceBot *NickServ = Config->GetClient("NickServ"); + if (!NickServ || u->IsIdentified()) + return; + + NickServ::Account *nc = cs.FindAccountFromCert(u->fingerprint); + if (!nc || nc->HasFieldS("NS_SUSPENDED")) + return; + + NickServ::Nick *na = NickServ::FindNick(u->nick); + if (na && na->GetAccount() == nc) + u->Identify(na); + else + u->Login(nc); + + u->SendMessage(NickServ, _("SSL certificate fingerprint accepted, you are now identified to \002%s\002."), nc->GetDisplay().c_str()); + Log(NickServ) << u->GetMask() << " automatically identified for account " << nc->GetDisplay() << " via SSL certificate fingerprint"; + } + + EventReturn OnNickValidate(User *u, NickServ::Nick *na) override + { + if (u->fingerprint.empty()) + return EVENT_CONTINUE; + + if (cs.Matches(u, na->GetAccount())) + { + ServiceBot *NickServ = Config->GetClient("NickServ"); + u->Identify(na); + u->SendMessage(NickServ, _("SSL certificate fingerprint accepted, you are now identified.")); + Log(NickServ) << u->GetMask() << " automatically identified for account " << na->GetAccount()->GetDisplay() << " via SSL certificate fingerprint"; + return EVENT_ALLOW; + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(NSCert) diff --git a/modules/nickserv/drop.cpp b/modules/nickserv/drop.cpp new file mode 100644 index 000000000..62ee41970 --- /dev/null +++ b/modules/nickserv/drop.cpp @@ -0,0 +1,91 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_drop.h" + +class CommandNSDrop : public Command +{ + EventHandlers &onnickdrop; + + public: + CommandNSDrop(Module *creator, EventHandlers &event) : Command(creator, "nickserv/drop", 1, 1), onnickdrop(event) + { + this->SetSyntax(_("\037nickname\037")); + this->SetDesc(_("Cancel the registration of a nickname")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &nick = params[0]; + + if (Anope::ReadOnly && !source.HasPriv("nickserv/drop")) + { + source.Reply(_("Sorry, nickname de-registration is temporarily disabled.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + bool is_mine = source.GetAccount() == na->GetAccount(); + + if (!is_mine && !source.HasPriv("nickserv/drop")) + { + source.Reply(_("Access denied. You do not have the correct operator privileges to drop other user's nicknames.")); + return; + } + + if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && !is_mine && na->GetAccount()->IsServicesOper()) + { + source.Reply(_("You may not drop other Services Operators' nicknames.")); + return; + } + + this->onnickdrop(&Event::NickDrop::OnNickDrop, source, na); + + Log(!is_mine ? LOG_ADMIN : LOG_COMMAND, source, this) << "to drop nickname " << na->GetNick() << " (group: " << na->GetAccount()->GetDisplay() << ") (email: " << (!na->GetAccount()->GetEmail().empty() ? na->GetAccount()->GetEmail() : "none") << ")"; + na->Delete(); + + source.Reply(_("\002{0}\002 has been dropped."), nick); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Unregisters \037nickname\037. Once your nickname is dropped you may lose all of your access and channels that you may own. Any other user will be free to register \037nickname\037.")); + if (!source.HasPriv("nickserv/drop")) + source.Reply(_("You may drop any nickname within your group.")); + else + source.Reply(_("As a Services Operator, you may drop any nick.")); + + return true; + } +}; + +class NSDrop : public Module +{ + CommandNSDrop commandnsdrop; + EventHandlers onnickdrop; + + public: + NSDrop(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsdrop(this, onnickdrop) + , onnickdrop(this) + { + + } +}; + +MODULE_INIT(NSDrop) diff --git a/modules/nickserv/getemail.cpp b/modules/nickserv/getemail.cpp new file mode 100644 index 000000000..7b94efbea --- /dev/null +++ b/modules/nickserv/getemail.cpp @@ -0,0 +1,66 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + * + * A simple call to check for all emails that a user may have registered + * with. It returns the nicks that match the email you provide. Wild + * Cards are not excepted. Must use user@email-host. + */ + +#include "module.h" + +class CommandNSGetEMail : public Command +{ + public: + CommandNSGetEMail(Module *creator) : Command(creator, "nickserv/getemail", 1, 1) + { + this->SetDesc(_("Matches and returns all users that registered using given email")); + this->SetSyntax(_("\037email\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &email = params[0]; + int j = 0; + + Log(LOG_ADMIN, source, this) << "on " << email; + + for (NickServ::Account *nc : NickServ::service->GetAccountList()) + if (!nc->GetEmail().empty() && nc->GetEmail().equals_ci(email)) + { + ++j; + source.Reply(_("Email matched: \002{0}\002 to \002{1}\002."), nc->GetDisplay(), email); + } + + if (j <= 0) + { + source.Reply(_("There are no accounts with an email that matches \002{0}\002."), email); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Returns the matching accounts whose email address is \037email\037.")); + return true; + } +}; + +class NSGetEMail : public Module +{ + CommandNSGetEMail commandnsgetemail; + + public: + NSGetEMail(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsgetemail(this) + { + + } +}; + +MODULE_INIT(NSGetEMail) diff --git a/modules/nickserv/getpass.cpp b/modules/nickserv/getpass.cpp new file mode 100644 index 000000000..a526986ec --- /dev/null +++ b/modules/nickserv/getpass.cpp @@ -0,0 +1,74 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandNSGetPass : public Command +{ + public: + CommandNSGetPass(Module *creator) : Command(creator, "nickserv/getpass", 1, 1) + { + this->SetDesc(_("Retrieve the password for a nickname")); + this->SetSyntax(_("\037account\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &nick = params[0]; + Anope::string tmp_pass; + NickServ::Nick *na = NickServ::FindNick(nick); + + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && na->GetAccount()->IsServicesOper()) + { + source.Reply(_("You may not get the password of other Services Operators.")); + return; + } + + if (!Anope::Decrypt(na->GetAccount()->GetPassword(), tmp_pass)) + { + source.Reply(_("The \002{0}\002 command is unavailable because encryption is in use."), source.command); + return; + } + + Log(LOG_ADMIN, source, this) << "for " << na->GetNick(); + source.Reply(_("Password of \002{0}\02 is \002%s\002."), na->GetNick(), tmp_pass); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Returns the password for the given account. This command may not be available if password encryption is in use.")); + return true; + } +}; + +class NSGetPass : public Module +{ + CommandNSGetPass commandnsgetpass; + + public: + NSGetPass(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsgetpass(this) + { + + Anope::string tmp_pass = "plain:tmp"; + if (!Anope::Decrypt(tmp_pass, tmp_pass)) + throw ModuleException("Incompatible with the encryption module being used"); + + } +}; + +MODULE_INIT(NSGetPass) diff --git a/modules/nickserv/group.cpp b/modules/nickserv/group.cpp new file mode 100644 index 000000000..9a9b933f4 --- /dev/null +++ b/modules/nickserv/group.cpp @@ -0,0 +1,385 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_cert.h" +#include "modules/ns_group.h" + +class NSGroupRequestListener : public NickServ::IdentifyRequestListener +{ + EventHandlers &onnickgroup; + CommandSource source; + Command *cmd; + Anope::string nick; + Reference target; + + public: + NSGroupRequestListener(EventHandlers &event, CommandSource &src, Command *c, const Anope::string &n, NickServ::Nick *targ) : onnickgroup(event), source(src), cmd(c), nick(n), target(targ) { } + + void OnSuccess(NickServ::IdentifyRequest *) override + { + if (!source.GetUser() || source.GetUser()->nick != nick || !target) + return; + + User *u = source.GetUser(); + NickServ::Nick *na = NickServ::FindNick(nick); + /* If the nick is already registered, drop it. */ + if (na) + { + Event::OnChangeCoreDisplay(&Event::ChangeCoreDisplay::OnChangeCoreDisplay, na->GetAccount(), u->nick); + delete na; + } + + na = NickServ::nick.Create(); + na->SetNick(nick); + na->SetAccount(target->GetAccount()); + na->SetLastUsermask(u->GetIdent() + "@" + u->GetDisplayedHost()); + na->SetLastRealname(u->realname); + na->SetLastSeen(Anope::CurTime); + na->SetTimeRegistered(Anope::CurTime); + + u->Login(target->GetAccount()); + this->onnickgroup(&Event::NickGroup::OnNickGroup, u, target); + + Log(LOG_COMMAND, source, cmd) << "to make " << nick << " join group of " << target->GetNick() << " (" << target->GetAccount()->GetDisplay() << ") (email: " << (!target->GetAccount()->GetEmail().empty() ? target->GetAccount()->GetEmail() : "none") << ")"; + source.Reply(_("You are now in the group of \002{0}\002."), target->GetNick()); + + u->lastnickreg = Anope::CurTime; + + } + + void OnFail(NickServ::IdentifyRequest *) override + { + if (!source.GetUser()) + return; + + Log(LOG_COMMAND, source, cmd) << "and failed to group to " << target->GetNick(); + source.Reply(_("Password incorrect.")); + source.GetUser()->BadPassword(); + } +}; + +class CommandNSGroup : public Command +{ + EventHandlers &onnickgroup; + + public: + CommandNSGroup(Module *creator, EventHandlers &event) : Command(creator, "nickserv/group", 0, 2), onnickgroup(event) + { + this->SetDesc(_("Join a group")); + this->SetSyntax(_("\037[target]\037 \037[password]\037")); + this->AllowUnregistered(true); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + User *u = source.GetUser(); + + Anope::string nick; + if (params.empty()) + { + NickServ::Account* core = u->Account(); + if (core) + nick = core->GetDisplay(); + } + else + nick = params[0]; + + if (nick.empty()) + { + this->SendSyntax(source); + return; + } + + const Anope::string &pass = params.size() > 1 ? params[1] : ""; + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, nickname grouping is temporarily disabled.")); + return; + } + + if (!IRCD->IsNickValid(u->nick)) + { + source.Reply(_("\002{0}\002 may not be registered."), u->nick); + return; + } + + if (Config->GetModule("nickserv")->Get("restrictopernicks")) + for (Oper *o : Serialize::GetObjects(operblock)) + { + if (!u->HasMode("OPER") && u->nick.find_ci(o->GetName()) != Anope::string::npos) + { + source.Reply(_("\002{0}\002 may not be registered because it is too similar to an operator nick."), u->nick); + return; + } + } + + NickServ::Nick *target, *na = NickServ::FindNick(u->nick); + const Anope::string &guestnick = Config->GetModule("nickserv")->Get("guestnickprefix", "Guest"); + time_t reg_delay = Config->GetModule("nickserv")->Get("regdelay"); + unsigned maxaliases = Config->GetModule(this->owner)->Get("maxaliases"); + if (!(target = NickServ::FindNick(nick))) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + if (Anope::CurTime < u->lastnickreg + reg_delay) + { + source.Reply(_("Please wait \002{0}\002 seconds before using the \002{1}\002 command again."), (reg_delay + u->lastnickreg) - Anope::CurTime, source.command); + return; + } + + if (target->GetAccount()->HasFieldS("NS_SUSPENDED")) + { + Log(LOG_COMMAND, source, this) << "and tried to group to suspended nick " << target->GetNick(); + source.Reply(_("\002{0}\002 is suspended."), target->GetNick()); + return; + } + + if (na && Config->GetModule(this->owner)->Get("nogroupchange")) + { + source.Reply(_("Your nick is already registered.")); + return; + } + + if (na && target->GetAccount() == na->GetAccount()) + { + source.Reply(_("You are already a member of the group of \002{0}\002."), target->GetNick()); + return; + } + + if (na && na->GetAccount() != u->Account()) + { + source.Reply(_("\002{0}\002 is already registered."), na->GetNick()); + return; + } + + if (na && Config->GetModule(this->owner)->Get("nogroupchange")) + { + source.Reply(_("You are already registered.")); + return; + } + + if (maxaliases && target->GetAccount()->GetRefs(NickServ::nick).size() >= maxaliases && !target->GetAccount()->IsServicesOper()) + { + source.Reply(_("There are too many nicknames in your group.")); + return; + } + + if (u->nick.length() <= guestnick.length() + 7 && + u->nick.length() >= guestnick.length() + 1 && + !u->nick.find_ci(guestnick) && !u->nick.substr(guestnick.length()).find_first_not_of("1234567890")) + { + source.Reply(_("\002{0}\002 may not be registered."), u->nick); + return; + } + + bool ok = false; + if (!na && u->Account() == target->GetAccount()) + ok = true; + + if (certservice && certservice->Matches(u, target->GetAccount())) + ok = true; + + if (ok == false && !pass.empty()) + { + NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new NSGroupRequestListener(onnickgroup, source, this, u->nick, target), owner, target->GetAccount()->GetDisplay(), pass); + Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, source.GetUser(), req); + req->Dispatch(); + } + else + { + NSGroupRequestListener req(onnickgroup, source, this, u->nick, target); + + if (ok) + req.OnSuccess(nullptr); + else + req.OnFail(nullptr); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command makes your nickname join the \037target\037 nickname's group." + " \037password\037 is the password of the target nickname.\n" + "\n" + "Nicknames in the same group share channel privileges, memos, and most settings, including password." + "\n" + "You may be able to use this command even if you have not registered your nick yet." + " If your nick is already registered, you'll need to identify yourself before using this command.")); + return true; + } +}; + +class CommandNSUngroup : public Command +{ + public: + CommandNSUngroup(Module *creator) : Command(creator, "nickserv/ungroup", 0, 1) + { + this->SetDesc(_("Remove a nick from a group")); + this->SetSyntax(_("[\037nickname\037]")); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + User *u = source.GetUser(); + Anope::string nick = !params.empty() ? params[0] : ""; + NickServ::Nick *na = NickServ::FindNick(!nick.empty() ? nick : u->nick); + + if (u->Account()->GetRefs(NickServ::nick).size() == 1) + { + source.Reply(_("Your nickname is not grouped to anything, so you can't ungroup it.")); + return; + } + + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), !nick.empty() ? nick : u->nick); + return; + } + + if (na->GetAccount() != u->Account()) + { + source.Reply(_("\002{0}\002 is not in your group."), na->GetNick()); + return; + } + + + NickServ::Account *oldcore = na->GetAccount(); + + if (na->GetNick().equals_ci(oldcore->GetDisplay())) + oldcore->SetDisplay(oldcore->GetRef(NickServ::nick)); + + NickServ::Account *nc = NickServ::account.Create(); + nc->SetDisplay(na->GetNick()); + na->SetAccount(nc); + + nc->SetPassword(oldcore->GetPassword()); + if (!oldcore->GetEmail().empty()) + nc->SetEmail(oldcore->GetEmail()); + nc->SetLanguage(oldcore->GetLanguage()); + + source.Reply(_("\002{0}\002 has been ungrouped from \002{1}\002."), na->GetNick(), oldcore->GetDisplay()); + + User *user = User::Find(na->GetNick()); + if (user) + /* The user on the nick who was ungrouped may be identified to the old group, set -r */ + user->RemoveMode(source.service, "REGISTERED"); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command ungroups your nickname, or if given, the specificed \037nickname\037, from the group it is in." + " The ungrouped nick keeps its registration time, password, email, and language. Everything else is reset." + " You may not ungroup yourself if there is only one nickname in your group.")); + return true; + } +}; + +class CommandNSGList : public Command +{ + public: + CommandNSGList(Module *creator) : Command(creator, "nickserv/glist", 0, 1) + { + this->SetDesc(_("Lists all nicknames in your group")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &nick = !params.empty() ? params[0] : ""; + NickServ::Account *nc; + + if (!nick.empty() && source.IsServicesOper()) + { + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + nc = na->GetAccount(); + } + else + nc = source.GetAccount(); + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Nick")).AddColumn(_("Expires")); + time_t nickserv_expire = Config->GetModule("nickserv")->Get("expire", "21d"), + unconfirmed_expire = Config->GetModule("nickserv")->Get("unconfirmedexpire", "1d"); + for (NickServ::Nick *na2 : nc->GetRefs(NickServ::nick)) + { + Anope::string expires; + if (na2->HasFieldS("NS_NO_EXPIRE")) + expires = _("Does not expire"); + else if (!nickserv_expire || Anope::NoExpire) + ; + else if (na2->GetAccount()->HasFieldS("UNCONFIRMED") && unconfirmed_expire) + expires = Anope::strftime(na2->GetTimeRegistered() + unconfirmed_expire, source.GetAccount()); + else + expires = Anope::strftime(na2->GetLastSeen() + nickserv_expire, source.GetAccount()); + + ListFormatter::ListEntry entry; + entry["Nick"] = na2->GetNick(); + entry["Expires"] = expires; + list.AddEntry(entry); + } + + source.Reply(nc != source.GetAccount() ? _("List of nicknames in the group of \002%s\002:") : _("List of nicknames in your group:"), nc->GetDisplay().c_str()); + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("%d nickname(s) in the group."), replies.size()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (source.IsServicesOper()) + source.Reply(_("Without a parameter, lists all nicknames that are in your group.\n" + "\n" + "With a parameter, lists all nicknames that are in the group of the given nick.\n" + "Specifying a nick is limited to \002Services Operators\002."), + source.command); + else + source.Reply(_("Lists all nicknames in your group.")); + + return true; + } +}; + +class NSGroup : public Module +{ + CommandNSGroup commandnsgroup; + CommandNSUngroup commandnsungroup; + CommandNSGList commandnsglist; + + EventHandlers onnickgroup; + + public: + NSGroup(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsgroup(this, onnickgroup) + , commandnsungroup(this) + , commandnsglist(this) + , onnickgroup(this) + { + if (Config->GetModule("nickserv")->Get("nonicknameownership")) + throw ModuleException(modname + " can not be used with options:nonicknameownership enabled"); + } +}; + +MODULE_INIT(NSGroup) diff --git a/modules/nickserv/identify.cpp b/modules/nickserv/identify.cpp new file mode 100644 index 000000000..122378d8a --- /dev/null +++ b/modules/nickserv/identify.cpp @@ -0,0 +1,125 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class NSIdentifyRequestListener : public NickServ::IdentifyRequestListener +{ + CommandSource source; + Command *cmd; + + public: + NSIdentifyRequestListener(CommandSource &s, Command *c) : source(s), cmd(c) { } + + void OnSuccess(NickServ::IdentifyRequest *req) override + { + if (!source.GetUser()) + return; + + User *u = source.GetUser(); + NickServ::Nick *na = NickServ::FindNick(req->GetAccount()); + + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), req->GetAccount()); + return; + } + + if (u->IsIdentified()) + Log(LOG_COMMAND, source, cmd) << "to log out of account " << u->Account()->GetDisplay(); + + Log(LOG_COMMAND, source, cmd) << "and identified for account " << na->GetAccount()->GetDisplay(); + source.Reply(_("Password accepted - you are now recognized as \002{0}\002."), na->GetAccount()->GetDisplay()); + u->Identify(na); + } + + void OnFail(NickServ::IdentifyRequest *req) override + { + if (!source.GetUser()) + return; + + bool accountexists = NickServ::FindNick(req->GetAccount()) != NULL; + Log(LOG_COMMAND, source, cmd) << "and failed to identify to" << (accountexists ? " " : " nonexistent ") << "account " << req->GetAccount(); + if (accountexists) + { + source.Reply(_("Password incorrect.")); + source.GetUser()->BadPassword(); + } + else + source.Reply("\002{0}\002 isn't registered.", req->GetAccount()); + } +}; + +class CommandNSIdentify : public Command +{ + public: + CommandNSIdentify(Module *creator) : Command(creator, "nickserv/identify", 1, 2) + { + this->SetDesc(_("Identify yourself with your password")); + this->SetSyntax(_("[\037account\037] \037password\037")); + this->AllowUnregistered(true); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + User *u = source.GetUser(); + + const Anope::string &nick = params.size() == 2 ? params[0] : u->nick; + Anope::string pass = params[params.size() - 1]; + + NickServ::Nick *na = NickServ::FindNick(nick); + if (na && na->GetAccount()->HasFieldS("NS_SUSPENDED")) + { + source.Reply(_("\002{0}\002 is suspended."), na->GetNick()); + return; + } + + if (u->Account() && na && u->Account() == na->GetAccount()) + { + source.Reply(_("You are already identified.")); + return; + } + + unsigned int maxlogins = Config->GetModule(this->owner)->Get("maxlogins"); + if (na && maxlogins && na->GetAccount()->users.size() >= maxlogins) + { + source.Reply(_("Account \002{0}\002 has already reached the maximum number of simultaneous logins ({1})."), na->GetAccount()->GetDisplay(), maxlogins); + return; + } + + NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new NSIdentifyRequestListener(source, this), owner, na ? na->GetAccount()->GetDisplay() : nick, pass); + Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, u, req); + + req->Dispatch(); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Logs you in to account \037account\037. If no \037account\037 is given, your current nickname is used." + " Many commands require you to authenticate with this command before you use them. \037password\037 should be the same one you registered with.")); + return true; + } +}; + +class NSIdentify : public Module +{ + CommandNSIdentify commandnsidentify; + + public: + NSIdentify(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandnsidentify(this) + { + + } +}; + +MODULE_INIT(NSIdentify) diff --git a/modules/nickserv/info.cpp b/modules/nickserv/info.cpp new file mode 100644 index 000000000..7091e53fd --- /dev/null +++ b/modules/nickserv/info.cpp @@ -0,0 +1,281 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_info.h" +#include "modules/ns_set.h" + +class CommandNSInfo : public Command +{ + EventHandlers &onnickinfo; + + public: + CommandNSInfo(Module *creator, EventHandlers &event) : Command(creator, "nickserv/info", 0, 2), onnickinfo(event) + { + this->SetDesc(_("Displays information about a given nickname")); + this->SetSyntax(_("[\037nickname\037]")); + this->AllowUnregistered(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + + const Anope::string &nick = params.size() ? params[0] : (source.nc ? source.nc->GetDisplay() : source.GetNick()); + NickServ::Nick *na = NickServ::FindNick(nick); + bool has_auspex = source.HasPriv("nickserv/auspex"); + + if (!na) + { + if (ServiceBot::Find(nick, true)) + source.Reply(_("\002{0}\002 is part of this Network's Services."), nick); + else + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + bool nick_online = false, show_hidden = false; + + /* Is the real owner of the nick we're looking up online? -TheShadow */ + User *u2 = User::Find(na->GetNick()); + if (u2 && u2->Account() == na->GetAccount()) + { + nick_online = true; + na->SetLastSeen(Anope::CurTime); + } + + if (has_auspex || na->GetAccount() == source.GetAccount()) + show_hidden = true; + + source.Reply(_("\002{0}\002 is \002{1}\002"), na->GetNick(), na->GetLastRealname()); + + if (na->GetAccount()->HasFieldS("UNCONFIRMED")) + source.Reply(_("\002{0}\002 has not confirmed their account."), na->GetNick()); + + if (na->GetAccount()->IsServicesOper() && (show_hidden || !na->GetAccount()->HasFieldS("HIDE_STATUS"))) + source.Reply(_("\002{0}\002 is a Services Operator of type \002{0}\002."), na->GetNick(), na->GetAccount()->o->GetType()->GetName()); + + InfoFormatter info(source.nc); + + if (nick_online) + { + bool shown = false; + if (show_hidden && !na->GetLastRealhost().empty()) + { + info[_("Online from")] = na->GetLastRealhost(); + shown = true; + } + if ((show_hidden || !na->GetAccount()->HasFieldS("HIDE_MASK")) && (!shown || na->GetLastUsermask() != na->GetLastRealhost())) + info[_("Online from")] = na->GetLastUsermask(); + else + source.Reply(_("\002{0}\002 is currently online."), na->GetNick()); + } + else + { + Anope::string shown; + if (show_hidden || !na->GetAccount()->HasFieldS("HIDE_MASK")) + { + info[_("Last seen address")] = na->GetLastUsermask(); + shown = na->GetLastUsermask(); + } + + if (show_hidden && !na->GetLastRealhost().empty() && na->GetLastRealhost() != shown) + info[_("Last seen address")] = na->GetLastRealhost(); + } + + info[_("Registered")] = Anope::strftime(na->GetTimeRegistered(), source.GetAccount()); + + if (!nick_online) + info[_("Last seen")] = Anope::strftime(na->GetLastSeen(), source.GetAccount()); + + if (!na->GetLastQuit().empty() && (show_hidden || !na->GetAccount()->HasFieldS("HIDE_QUIT"))) + info[_("Last quit message")] = na->GetLastQuit(); + + if (!na->GetAccount()->GetEmail().empty() && (show_hidden || !na->GetAccount()->HasFieldS("HIDE_EMAIL"))) + info[_("Email address")] = na->GetAccount()->GetEmail(); + + if (show_hidden) + { + if (na->HasVhost()) + { + if (IRCD->CanSetVIdent && !na->GetVhostIdent().empty()) + info[_("VHost")] = na->GetVhostIdent() + "@" + na->GetVhostHost(); + else + info[_("VHost")] = na->GetVhostHost(); + } + } + + this->onnickinfo(&Event::NickInfo::OnNickInfo, source, na, info, show_hidden); + + std::vector replies; + info.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Displays information about the given nickname, such as\n" + "the nick's owner, last seen address and time, and nick\n" + "options. If no nick is given, and you are identified,\n" + "your account name is used, else your current nickname is\n" + "used.")); + + return true; + } +}; + + +class CommandNSSetHide : public Command +{ + public: + CommandNSSetHide(Module *creator, const Anope::string &sname = "nickserv/set/hide", size_t min = 2) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Hide certain pieces of nickname information")); + this->SetSyntax("{EMAIL | STATUS | USERMASK | QUIT} {ON | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m, const Anope::string &arg) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + const char *onmsg, *offmsg, *flag; + + if (param.equals_ci("EMAIL")) + { + flag = "HIDE_EMAIL"; + onmsg = _("The \002e-mail address\002 of \002{0}\002 will now be \002hidden\002."); + offmsg = _("The \002e-mail address\002 of \002{0}\002 will now be \002shown\002."); + } + else if (param.equals_ci("USERMASK")) + { + flag = "HIDE_MASK"; + onmsg = _("The \002last seen host mask\002 of \002{0}\002 will now be \002hidden\002."); + offmsg = _("The \002last seen host mask\002 of \002{0}\002 will now be \002shown\002."); + } + else if (param.equals_ci("STATUS")) + { + flag = "HIDE_STATUS"; + onmsg = _("The \002services operator status\002 of \002{0}\002 will now be \002hidden\002."); + offmsg = _("The \002services operator status\002 of \002{0}\002 will now be \002shown\002."); + } + else if (param.equals_ci("QUIT")) + { + flag = "HIDE_QUIT"; + onmsg = _("The \002last quit message\002 of \002{0}\002 will now be \002hidden\002."); + offmsg = _("The \002last quit message\002 of \002{0}\002 will now be \002shown\002."); + } + else + { + this->OnSyntaxError(source, "HIDE"); + return; + } + + if (arg.equals_ci("ON")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change hide " << param.upper() << " to " << arg.upper() << " for " << nc->GetDisplay(); + nc->SetS(flag, true); + source.Reply(onmsg, nc->GetDisplay(), source.service->nick); + } + else if (arg.equals_ci("OFF")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change hide " << param.upper() << " to " << arg.upper() << " for " << nc->GetDisplay(); + nc->UnsetS(flag); + source.Reply(offmsg, nc->GetDisplay(), source.service->nick); + } + else + this->OnSyntaxError(source, "HIDE"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Allows you to prevent certain pieces of information from being displayed when someone does a %s \002INFO\002 on you." //XXX + " You can hide the e-mail address (\002EMAIL\002), last seen hostmask (\002USERMASK\002), the services access status (\002STATUS\002) and last quit message (\002QUIT\002)." + "The second parameter specifies whether the information should\n" + "be displayed (\002OFF\002) or hidden (\002ON\002)."), source.service->nick); + return true; + } +}; + +class CommandNSSASetHide : public CommandNSSetHide +{ + public: + CommandNSSASetHide(Module *creator) : CommandNSSetHide(creator, "nickserv/saset/hide", 3) + { + this->SetSyntax(_("\037nickname\037 {EMAIL | STATUS | USERMASK | QUIT} {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->ClearSyntax(); + this->Run(source, params[0], params[1], params[2]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Allows you to prevent certain pieces of information from being displayed when someone does a %s \002INFO\002 on the \037nickname\037. " + " You can hide the e-mail address (\002EMAIL\002), last seen hostmask (\002USERMASK\002), the services access status (\002STATUS\002) and last quit message (\002QUIT\002)." + " The second parameter specifies whether the information should be displayed (\002OFF\002) or hidden (\002ON\002)."), + source.service->nick); + return true; + } +}; + +class NSInfo : public Module +{ + CommandNSInfo commandnsinfo; + + CommandNSSetHide commandnssethide; + CommandNSSASetHide commandnssasethide; + + EventHandlers onnickinfo; + + Serialize::Field hide_email, hide_usermask, hide_status, hide_quit; + + public: + NSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsinfo(this, onnickinfo) + , commandnssethide(this) + , commandnssasethide(this) + , onnickinfo(this) + , hide_email(this, NickServ::account, "HIDE_EMAIL") + , hide_usermask(this, NickServ::account, "HIDE_MASK") + , hide_status(this, NickServ::account, "HIDE_STATUS") + , hide_quit(this, NickServ::account, "HIDE_QUIT") + { + + } +}; + +MODULE_INIT(NSInfo) diff --git a/modules/nickserv/list.cpp b/modules/nickserv/list.cpp new file mode 100644 index 000000000..2820326d2 --- /dev/null +++ b/modules/nickserv/list.cpp @@ -0,0 +1,290 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_info.h" +#include "modules/ns_set.h" + +class CommandNSList : public Command +{ + public: + CommandNSList(Module *creator) : Command(creator, "nickserv/list", 1, 2) + { + this->SetDesc(_("List all registered nicknames that match a given pattern")); + this->SetSyntax(_("\037pattern\037 [SUSPENDED] [NOEXPIRE] [UNCONFIRMED]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + + Anope::string pattern = params[0]; + const NickServ::Account *mync; + unsigned nnicks; + bool is_servadmin = source.HasCommand("nickserv/list"); + int count = 0, from = 0, to = 0; + bool suspended, nsnoexpire, unconfirmed; + unsigned listmax = Config->GetModule(this->owner)->Get("listmax", "50"); + + suspended = nsnoexpire = unconfirmed = false; + + if (pattern[0] == '#') + { + Anope::string n1, n2; + sepstream(pattern.substr(1), '-').GetToken(n1, 0); + sepstream(pattern, '-').GetToken(n2, 1); + try + { + from = convertTo(n1); + to = convertTo(n2); + } + catch (const ConvertException &) + { + source.Reply(_("Incorrect range specified. The correct syntax is \002#\037from\037-\037to\037\002.")); + return; + } + + pattern = "*"; + } + + nnicks = 0; + + if (is_servadmin && params.size() > 1) + { + Anope::string keyword; + spacesepstream keywords(params[1]); + while (keywords.GetToken(keyword)) + { + if (keyword.equals_ci("NOEXPIRE")) + nsnoexpire = true; + if (keyword.equals_ci("SUSPENDED")) + suspended = true; + if (keyword.equals_ci("UNCONFIRMED")) + unconfirmed = true; + } + } + + mync = source.nc; + ListFormatter list(source.GetAccount()); + + list.AddColumn(_("Nick")).AddColumn(_("Last usermask")); + + // XXX wtf + Anope::map ordered_map; + for (NickServ::Nick *na : NickServ::service->GetNickList()) + ordered_map[na->GetNick()] = na; + + for (Anope::map::const_iterator it = ordered_map.begin(), it_end = ordered_map.end(); it != it_end; ++it) + { + NickServ::Nick *na = it->second; + + /* Don't show private nicks to non-services admins. */ + if (na->GetAccount()->HasFieldS("NS_PRIVATE") && !is_servadmin && na->GetAccount() != mync) + continue; + else if (nsnoexpire && !na->HasFieldS("NS_NO_EXPIRE")) + continue; + else if (suspended && !na->GetAccount()->HasFieldS("NS_SUSPENDED")) + continue; + else if (unconfirmed && !na->GetAccount()->HasFieldS("UNCONFIRMED")) + continue; + + /* We no longer compare the pattern against the output buffer. + * Instead we build a nice nick!user@host buffer to compare. + * The output is then generated separately. -TheShadow */ + Anope::string buf = Anope::printf("%s!%s", na->GetNick().c_str(), !na->GetLastUsermask().empty() ? na->GetLastUsermask().c_str() : "*@*"); + if (na->GetNick().equals_ci(pattern) || Anope::Match(buf, pattern, false, true)) + { + if (((count + 1 >= from && count + 1 <= to) || (!from && !to)) && ++nnicks <= listmax) + { + bool isnoexpire = false; + if (is_servadmin && na->HasFieldS("NS_NO_EXPIRE")) + isnoexpire = true; + + ListFormatter::ListEntry entry; + entry["Nick"] = (isnoexpire ? "!" : "") + na->GetNick(); + if (na->GetAccount()->HasFieldS("HIDE_MASK") && !is_servadmin && na->GetAccount() != mync) + entry["Last usermask"] = Language::Translate(source.GetAccount(), _("[Hostname hidden]")); + else if (na->GetAccount()->HasFieldS("NS_SUSPENDED")) + entry["Last usermask"] = Language::Translate(source.GetAccount(), _("[Suspended]")); + else if (na->GetAccount()->HasFieldS("UNCONFIRMED")) + entry["Last usermask"] = Language::Translate(source.GetAccount(), _("[Unconfirmed]")); + else + entry["Last usermask"] = na->GetLastUsermask(); + list.AddEntry(entry); + } + ++count; + } + } + + source.Reply(_("List of entries matching \002{0}\002:"), pattern); + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of list - \002{0}\002/\002{1}\002 matches shown."), nnicks > listmax ? listmax : nnicks, nnicks); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Lists all registered nicknames which match the given pattern, in \037nick!user@host\037 format." + " Nicks with the \002PRIVATE\002 option set will only be displayed to Services Operators with the proper access." + " Nicks with the \002NOEXPIRE\002 option set will have a \002!\002 prefixed to the nickname for Services Operators to see.\n" + "\n" + "Note that a preceding '#' specifies a range.\n" + "\n" + "If the SUSPENDED, UNCONFIRMED or NOEXPIRE options are given, only\n" + "nicks which, respectively, are SUSPENDED, UNCONFIRMED or have the\n" + "NOEXPIRE flag set will be displayed. If multiple options are\n" + "given, all nicks matching at least one option will be displayed.\n" + "Note that these options are limited to \037Services Operators\037.\n" + "\n" + "Examples:\n" + "\n" + " {0} *!joeuser@foo.com\n" + " Lists all registered nicks owned by joeuser@foo.com.\n" + "\n" + " {0} *Bot*!*@*\n" + " Lists all registered nicks with \002Bot\002 in their names (case insensitive).\n" + "\n" + " {0} * NOEXPIRE\n" + " Lists all registered nicks which have been set to not expire.\n" + "\n" + " {0} #51-100\n" + " Lists all registered nicks within the given range (51-100).")); + + const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); + if (!regexengine.empty()) + { + source.Reply(" "); + source.Reply(_("Regex matches are also supported using the {0} engine. Enclose your pattern in // if this is desired."), regexengine); + } + + return true; + } +}; + + +class CommandNSSetPrivate : public Command +{ + public: + CommandNSSetPrivate(Module *creator, const Anope::string &sname = "nickserv/set/private", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Prevent your account from appearing in the LIST command")); + this->SetSyntax("{ON | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable private for " << nc->GetDisplay(); + nc->SetS("NS_PRIVATE", true); + source.Reply(_("Private option is now \002on\002 for \002{0}\002."), nc->GetDisplay()); + } + else if (param.equals_ci("OFF")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable private for " << nc->GetDisplay(); + nc->UnsetS("NS_PRIVATE"); + source.Reply(_("Private option is now \002off\002 for \002{0}\002."), nc->GetDisplay()); + } + else + this->OnSyntaxError(source, "PRIVATE"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Turns the privacy option on or off for your account." + " When \002PRIVATE\002 is set, your account will not appear in the account list." + " However, anyone who knows your account can still request information about it.")); + return true; + } +}; + +class CommandNSSASetPrivate : public CommandNSSetPrivate +{ + public: + CommandNSSASetPrivate(Module *creator) : CommandNSSetPrivate(creator, "nickserv/saset/private", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Turns the privacy option on or off for \037account\037." + " When \002PRIVATE\002 is set, the account will not appear in the account list." + " However, anyone who knows your account can still request information about it.")); + return true; + } +}; + + +class NSList : public Module + , public EventHook +{ + CommandNSList commandnslist; + + CommandNSSetPrivate commandnssetprivate; + CommandNSSASetPrivate commandnssasetprivate; + + Serialize::Field priv; + + public: + NSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnslist(this) + , commandnssetprivate(this) + , commandnssasetprivate(this) + , priv(this, NickServ::account, "NS_PRIVATE") + { + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_all) override + { + if (!show_all) + return; + + if (priv.HasExt(na->GetAccount())) + info.AddOption(_("Private")); + } +}; + +MODULE_INIT(NSList) diff --git a/modules/nickserv/logout.cpp b/modules/nickserv/logout.cpp new file mode 100644 index 000000000..9bcb543fc --- /dev/null +++ b/modules/nickserv/logout.cpp @@ -0,0 +1,100 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/nickserv.h" + +class CommandNSLogout : public Command +{ + public: + CommandNSLogout(Module *creator) : Command(creator, "nickserv/logout", 0, 2) + { + this->SetDesc(_("Reverses the effect of the IDENTIFY command")); + this->SetSyntax(_("[\037nickname\037 [REVALIDATE]]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + + const Anope::string &nick = !params.empty() ? params[0] : ""; + const Anope::string ¶m = params.size() > 1 ? params[1] : ""; + + if (!source.IsServicesOper() && !nick.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + User *u2 = !nick.empty() ? User::Find(nick, true) : source.GetUser(); + if (!u2) + { + source.Reply(_("\002{0}\002 isn't currently online."), !nick.empty() ? nick : source.GetNick()); + return; + } + + if (!nick.empty() && u2->IsServicesOper()) + { + source.Reply(_("You can't logout \002{0}\002, they are a Services Operator."), nick); + return; + } + +#if 0 + if (!nick.empty() && !param.empty() && param.equals_ci("REVALIDATE") && NickServ::service) + NickServ::service->Validate(u2); +#endif + + u2->super_admin = false; /* Dont let people logout and remain a SuperAdmin */ + Log(LOG_COMMAND, source, this) << "to logout " << u2->nick; + + if (!nick.empty()) + source.Reply(_("\002{0}\002 has been logged out."), nick); + else + source.Reply(_("You have been logged out.")); + + IRCD->SendLogout(u2); + u2->RemoveMode(source.service, "REGISTERED"); + u2->Logout(); + + /* Send out an event */ + Event::OnNickLogout(&Event::NickLogout::OnNickLogout, u2); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Logs you out of your account")); + #if 0 + source.Reply(_("Without a parameter, reverses the effect of the \002IDENTIFY\002\n" + "command, i.e. make you not recognized as the real owner of the nick\n" + "anymore. Note, however, that you won't be asked to reidentify\n" + "yourself.\n" + " \n" + "With a parameter, does the same for the given nick. If you\n" + "specify \002REVALIDATE\002 as well, Services will ask the given nick\n" + "to re-identify. This is limited to \002Services Operators\002.")); + #endif + + return true; + } +}; + +class NSLogout : public Module +{ + CommandNSLogout commandnslogout; + + public: + NSLogout(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnslogout(this) + { + + } +}; + +MODULE_INIT(NSLogout) diff --git a/modules/nickserv/main/CMakeLists.txt b/modules/nickserv/main/CMakeLists.txt new file mode 100644 index 000000000..781f0ef1f --- /dev/null +++ b/modules/nickserv/main/CMakeLists.txt @@ -0,0 +1 @@ +build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/nickserv/main/account.cpp b/modules/nickserv/main/account.cpp new file mode 100644 index 000000000..85adf1a33 --- /dev/null +++ b/modules/nickserv/main/account.cpp @@ -0,0 +1,129 @@ +/* + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + * + */ + +#include "module.h" +#include "accounttype.h" +#include "modules/ns_access.h" + +AccountImpl::~AccountImpl() +{ + NickServ::nickcore_map& map = NickServ::service->GetAccountMap(); + map.erase(this->GetDisplay()); +} + +void AccountImpl::Delete() +{ + Event::OnDelCore(&Event::DelCore::OnDelCore, this); + + for (unsigned i = users.size(); i > 0; --i) + users[i - 1]->Logout(); + + return Serialize::Object::Delete(); +} + +Anope::string AccountImpl::GetDisplay() +{ + return Get(&AccountType::display); +} + +void AccountImpl::SetDisplay(const Anope::string &disp) +{ + Set(&AccountType::display, disp); +} + +Anope::string AccountImpl::GetPassword() +{ + return Get(&AccountType::pass); +} + +void AccountImpl::SetPassword(const Anope::string &pass) +{ + Set(&AccountType::pass, pass); +} + +Anope::string AccountImpl::GetEmail() +{ + return Get(&AccountType::email); +} + +void AccountImpl::SetEmail(const Anope::string &email) +{ + Set(&AccountType::email, email); +} + +Anope::string AccountImpl::GetLanguage() +{ + return Get(&AccountType::language); +} + +void AccountImpl::SetLanguage(const Anope::string &lang) +{ + Set(&AccountType::language, lang); +} + +MemoServ::MemoInfo *AccountImpl::GetMemos() +{ + return GetRef(MemoServ::memoinfo); +} + +void AccountImpl::SetDisplay(NickServ::Nick *na) +{ + if (na->GetAccount() != this || na->GetNick() == this->GetDisplay()) + return; + + Event::OnChangeCoreDisplay(&Event::ChangeCoreDisplay::OnChangeCoreDisplay, this, na->GetNick()); + + NickServ::nickcore_map& map = NickServ::service->GetAccountMap(); + + /* Remove the core from the list */ + map.erase(this->GetDisplay()); + + this->SetDisplay(na->GetNick()); + + NickServ::Account* &nc = map[this->GetDisplay()]; + if (nc) + Log(LOG_DEBUG) << "Duplicate account " << this->GetDisplay() << " in nickcore table?"; + + nc = this; +} + +bool AccountImpl::IsServicesOper() const +{ + return this->o != NULL; +} + +bool AccountImpl::IsOnAccess(User *u) +{ + Anope::string buf = u->GetIdent() + "@" + u->host, buf2, buf3; + if (!u->vhost.empty()) + buf2 = u->GetIdent() + "@" + u->vhost; + if (!u->GetCloakedHost().empty()) + buf3 = u->GetIdent() + "@" + u->GetCloakedHost(); + + for (NickAccess *access : GetRefs(nsaccess)) + { + Anope::string a = access->GetMask(); + if (Anope::Match(buf, a) || (!buf2.empty() && Anope::Match(buf2, a)) || (!buf3.empty() && Anope::Match(buf3, a))) + return true; + } + return false; +} + +unsigned int AccountImpl::GetChannelCount() +{ + unsigned int i = 0; + for (ChanServ::Channel *c : GetRefs(ChanServ::channel)) + if (c->GetFounder() == this) + ++i; + return i; +} + diff --git a/modules/nickserv/main/account.h b/modules/nickserv/main/account.h new file mode 100644 index 000000000..584548563 --- /dev/null +++ b/modules/nickserv/main/account.h @@ -0,0 +1,35 @@ +#include "modules/nickserv.h" + +class AccountImpl : public NickServ::Account +{ + public: + AccountImpl(Serialize::TypeBase *type) : NickServ::Account(type) { } + AccountImpl(Serialize::TypeBase *type, Serialize::ID id) : NickServ::Account(type, id) { } + ~AccountImpl(); + void Delete() override; + + Anope::string GetDisplay() override; + void SetDisplay(const Anope::string &) override; + + Anope::string GetPassword() override; + void SetPassword(const Anope::string &) override; + + Anope::string GetEmail() override; + void SetEmail(const Anope::string &) override; + + Anope::string GetLanguage() override; + void SetLanguage(const Anope::string &) override; + + MemoServ::MemoInfo *GetMemos() override; + + void SetDisplay(NickServ::Nick *na) override; + bool IsServicesOper() const override; + /*void AddAccess(const Anope::string &entry) override; + Anope::string GetAccess(unsigned entry) const override; + unsigned GetAccessCount() const override; + bool FindAccess(const Anope::string &entry) override; + void EraseAccess(const Anope::string &entry) override; + void ClearAccess() override;*/ + bool IsOnAccess(User *u) override; + unsigned int GetChannelCount() override; +}; diff --git a/modules/nickserv/main/accounttype.cpp b/modules/nickserv/main/accounttype.cpp new file mode 100644 index 000000000..3dad436b4 --- /dev/null +++ b/modules/nickserv/main/accounttype.cpp @@ -0,0 +1,42 @@ +#include "module.h" +#include "accounttype.h" +//#include "account.h" + +AccountType::AccountType(Module *me) : Serialize::Type(me, "NickCore") + , display(this, "display") + , pass(this, "pass") + , email(this, "email") + , language(this, "language") +{ + +} + +void AccountType::Display::SetField(AccountImpl *acc, const Anope::string &disp) +{ + NickServ::nickcore_map& map = NickServ::service->GetAccountMap(); + + if (!acc->GetDisplay().empty()) + map.erase(acc->GetDisplay()); + + Serialize::Field::SetField(acc, disp); + + if (!disp.empty()) + map[disp] = acc; + + acc->o = Oper::Find(disp); +} + +NickServ::Account *AccountType::FindAccount(const Anope::string &acc) +{ + Serialize::ID id; + EventReturn result = Event::OnSerialize(&Event::SerializeEvents::OnSerializeFind, this, &this->display, acc, id); + if (result == EVENT_ALLOW) + return RequireID(id); + + NickServ::nickcore_map &map = NickServ::service->GetAccountMap(); + auto it = map.find(acc); + if (it != map.end()) + return it->second; + return nullptr; +} + diff --git a/modules/nickserv/main/accounttype.h b/modules/nickserv/main/accounttype.h new file mode 100644 index 000000000..792629877 --- /dev/null +++ b/modules/nickserv/main/accounttype.h @@ -0,0 +1,23 @@ +#include "account.h" + +class AccountType : public Serialize::Type +{ + public: + /* Name of the account */ + struct Display : Serialize::Field + { + using Serialize::Field::Field; + + void SetField(AccountImpl *s, const Anope::string &) override; + } display; + /* User password in form of hashm:data */ + Serialize::Field pass; + Serialize::Field email; + /* Locale name of the language of the user. Empty means default language */ + Serialize::Field language; + + + AccountType(Module *); + + NickServ::Account *FindAccount(const Anope::string &nick); +}; diff --git a/modules/nickserv/main/identifyrequest.cpp b/modules/nickserv/main/identifyrequest.cpp new file mode 100644 index 000000000..4fc2f9989 --- /dev/null +++ b/modules/nickserv/main/identifyrequest.cpp @@ -0,0 +1,51 @@ +#include "identifyrequest.h" + +IdentifyRequestImpl::IdentifyRequestImpl(NickServ::IdentifyRequestListener *li, Module *o, const Anope::string &acc, const Anope::string &pass) : NickServ::IdentifyRequest(li, o, acc, pass) +{ + std::set &requests = NickServ::service->GetIdentifyRequests(); + requests.insert(this); +} + +IdentifyRequestImpl::~IdentifyRequestImpl() +{ + std::set &requests = NickServ::service->GetIdentifyRequests(); + requests.erase(this); + delete l; +} + +void IdentifyRequestImpl::Hold(Module *m) +{ + holds.insert(m); +} + +void IdentifyRequestImpl::Release(Module *m) +{ + holds.erase(m); + if (holds.empty() && dispatched) + { + if (!success) + l->OnFail(this); + delete this; + } +} + +void IdentifyRequestImpl::Success(Module *m) +{ + if (!success) + { + l->OnSuccess(this); + success = true; + } +} + +void IdentifyRequestImpl::Dispatch() +{ + if (holds.empty()) + { + if (!success) + l->OnFail(this); + delete this; + } + else + dispatched = true; +} diff --git a/modules/nickserv/main/identifyrequest.h b/modules/nickserv/main/identifyrequest.h new file mode 100644 index 000000000..e74d3129e --- /dev/null +++ b/modules/nickserv/main/identifyrequest.h @@ -0,0 +1,13 @@ +#include "modules/nickserv.h" + +class IdentifyRequestImpl : public NickServ::IdentifyRequest +{ + public: + IdentifyRequestImpl(NickServ::IdentifyRequestListener *, Module *o, const Anope::string &acc, const Anope::string &pass); + virtual ~IdentifyRequestImpl(); + + void Hold(Module *m) override; + void Release(Module *m) override; + void Success(Module *m) override; + void Dispatch() override; +}; diff --git a/modules/nickserv/main/mode.cpp b/modules/nickserv/main/mode.cpp new file mode 100644 index 000000000..61fe551c1 --- /dev/null +++ b/modules/nickserv/main/mode.cpp @@ -0,0 +1,23 @@ +#include "modules/nickserv.h" +#include "modetype.h" + +NickServ::Account *ModeImpl::GetAccount() +{ + return Get(&NSModeType::account); +} + +void ModeImpl::SetAccount(NickServ::Account *a) +{ + Set(&NSModeType::account, a); +} + +Anope::string ModeImpl::GetMode() +{ + return Get(&NSModeType::mode); +} + +void ModeImpl::SetMode(const Anope::string &m) +{ + Set(&NSModeType::mode, m); +} + diff --git a/modules/nickserv/main/mode.h b/modules/nickserv/main/mode.h new file mode 100644 index 000000000..9fc4c331e --- /dev/null +++ b/modules/nickserv/main/mode.h @@ -0,0 +1,14 @@ + +class ModeImpl : public NickServ::Mode +{ + public: + ModeImpl(Serialize::TypeBase *type) : NickServ::Mode(type) { } + ModeImpl(Serialize::TypeBase *type, Serialize::ID id) : NickServ::Mode(type, id) { } + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *) override; + + Anope::string GetMode() override; + void SetMode(const Anope::string &) override; +}; + diff --git a/modules/nickserv/main/modetype.h b/modules/nickserv/main/modetype.h new file mode 100644 index 000000000..0c4aefecc --- /dev/null +++ b/modules/nickserv/main/modetype.h @@ -0,0 +1,14 @@ +#include "mode.h" + +class NSModeType : public Serialize::Type +{ + public: + Serialize::ObjectField account; + Serialize::Field mode; + + NSModeType(Module *creator) : Serialize::Type(creator, "NSKeepMode") + , account(this, "account", true) + , mode(this, "mode") + { + } +}; diff --git a/modules/nickserv/main/nick.cpp b/modules/nickserv/main/nick.cpp new file mode 100644 index 000000000..0ac55ff93 --- /dev/null +++ b/modules/nickserv/main/nick.cpp @@ -0,0 +1,192 @@ +/* + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + * + */ + +#include "module.h" +#include "nicktype.h" + +NickImpl::~NickImpl() +{ + /* Remove us from the aliases list */ + NickServ::nickalias_map &map = NickServ::service->GetNickMap(); + map.erase(GetNick()); +} + +void NickImpl::Delete() +{ + Event::OnDelNick(&Event::DelNick::OnDelNick, this); + + if (this->GetAccount()) + { + /* Next: see if our core is still useful. */ + std::vector aliases = this->GetAccount()->GetRefs(NickServ::nick); + + auto it = std::find(aliases.begin(), aliases.end(), this); + if (it != aliases.end()) + aliases.erase(it); + + if (aliases.empty()) + { + /* just me */ + this->GetAccount()->Delete(); + } + else + { + /* Display updating stuff */ + if (GetNick().equals_ci(this->GetAccount()->GetDisplay())) + this->GetAccount()->SetDisplay(aliases[0]); + } + } + + return Serialize::Object::Delete(); +} + +Anope::string NickImpl::GetNick() +{ + return Get(&NickType::nick); +} + +void NickImpl::SetNick(const Anope::string &nick) +{ + Set(&NickType::nick, nick); +} + +Anope::string NickImpl::GetLastQuit() +{ + return Get(&NickType::last_quit); +} + +void NickImpl::SetLastQuit(const Anope::string &lq) +{ + Set(&NickType::last_quit, lq); +} + +Anope::string NickImpl::GetLastRealname() +{ + return Get(&NickType::last_realname); +} + +void NickImpl::SetLastRealname(const Anope::string &lr) +{ + Set(&NickType::last_realname, lr); +} + +Anope::string NickImpl::GetLastUsermask() +{ + return Get(&NickType::last_usermask); +} + +void NickImpl::SetLastUsermask(const Anope::string &lu) +{ + Set(&NickType::last_usermask, lu); +} + +Anope::string NickImpl::GetLastRealhost() +{ + return Get(&NickType::last_realhost); +} + +void NickImpl::SetLastRealhost(const Anope::string &lr) +{ + Set(&NickType::last_realhost, lr); +} + +time_t NickImpl::GetTimeRegistered() +{ + return Get(&NickType::time_registered); +} + +void NickImpl::SetTimeRegistered(const time_t &tr) +{ + Set(&NickType::time_registered, tr); +} + +time_t NickImpl::GetLastSeen() +{ + return Get(&NickType::last_seen); +} + +void NickImpl::SetLastSeen(const time_t &ls) +{ + Set(&NickType::last_seen, ls); +} + +NickServ::Account *NickImpl::GetAccount() +{ + return Get(&NickType::nc); +} + +void NickImpl::SetAccount(NickServ::Account *acc) +{ + Set(&NickType::nc, acc); +} + +void NickImpl::SetVhost(const Anope::string &ident, const Anope::string &host, const Anope::string &creator, time_t created) +{ + Set(&NickType::vhost_ident, ident); + Set(&NickType::vhost_host, host); + Set(&NickType::vhost_creator, creator); + Set(&NickType::vhost_created, created); +} + +void NickImpl::RemoveVhost() +{ + Anope::string e; + Set(&NickType::vhost_ident, e); + Set(&NickType::vhost_host, e); + Set(&NickType::vhost_creator, e); + Set(&NickType::vhost_created, 0); +} + +bool NickImpl::HasVhost() +{ + return !GetVhostHost().empty(); +} + +Anope::string NickImpl::GetVhostIdent() +{ + return Get(&NickType::vhost_ident); +} + +void NickImpl::SetVhostIdent(const Anope::string &i) +{ + Set(&NickType::vhost_ident, i); +} + +Anope::string NickImpl::GetVhostHost() +{ + return Get(&NickType::vhost_host); +} + +void NickImpl::SetVhostHost(const Anope::string &h) +{ + Set(&NickType::vhost_host, h); +} + +Anope::string NickImpl::GetVhostCreator() +{ + return Get(&NickType::vhost_creator); +} + +void NickImpl::SetVhostCreator(const Anope::string &c) +{ + Set(&NickType::vhost_creator, c); +} + +time_t NickImpl::GetVhostCreated() +{ + return Get(&NickType::vhost_created); +} + +void NickImpl::SetVhostCreated(const time_t &cr) +{ + Set(&NickType::vhost_created, cr); +} diff --git a/modules/nickserv/main/nick.h b/modules/nickserv/main/nick.h new file mode 100644 index 000000000..043d7c052 --- /dev/null +++ b/modules/nickserv/main/nick.h @@ -0,0 +1,46 @@ + +class NickImpl : public NickServ::Nick +{ + public: + NickImpl(Serialize::TypeBase *type) : NickServ::Nick(type) { } + NickImpl(Serialize::TypeBase *type, Serialize::ID id) : NickServ::Nick(type, id) { } + ~NickImpl(); + void Delete() override; + + Anope::string GetNick() override; + void SetNick(const Anope::string &) override; + + Anope::string GetLastQuit() override; + void SetLastQuit(const Anope::string &) override; + + Anope::string GetLastRealname() override; + void SetLastRealname(const Anope::string &) override; + + Anope::string GetLastUsermask() override; + void SetLastUsermask(const Anope::string &) override; + + Anope::string GetLastRealhost() override; + void SetLastRealhost(const Anope::string &) override; + + time_t GetTimeRegistered() override; + void SetTimeRegistered(const time_t &) override; + + time_t GetLastSeen() override; + void SetLastSeen(const time_t &) override; + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *acc) override; + + void SetVhost(const Anope::string &ident, const Anope::string &host, const Anope::string &creator, time_t created = Anope::CurTime) override; + void RemoveVhost() override; + bool HasVhost() override; + + Anope::string GetVhostIdent() override; + void SetVhostIdent(const Anope::string &) override; + Anope::string GetVhostHost() override; + void SetVhostHost(const Anope::string &) override; + Anope::string GetVhostCreator() override; + void SetVhostCreator(const Anope::string &) override; + time_t GetVhostCreated() override; + void SetVhostCreated(const time_t &) override; +}; diff --git a/modules/nickserv/main/nickserv.cpp b/modules/nickserv/main/nickserv.cpp new file mode 100644 index 000000000..26f1457cc --- /dev/null +++ b/modules/nickserv/main/nickserv.cpp @@ -0,0 +1,686 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_info.h" +#include "modules/ns_group.h" +#include "modules/ns_update.h" +#include "modules/help.h" +#include "modules/nickserv.h" +#include "identifyrequest.h" +#include "nicktype.h" +#include "accounttype.h" +#include "modetype.h" + +class NickServCollide; +static std::set collides; + +/** Timer for colliding nicks to force people off of nicknames + */ +class NickServCollide : public Timer +{ + NickServ::NickServService *service; + Reference u; + time_t ts; + Reference na; + + public: + NickServCollide(Module *me, NickServ::NickServService *nss, User *user, NickServ::Nick *nick, time_t delay) : Timer(me, delay), service(nss), u(user), ts(user->timestamp), na(nick) + { + collides.insert(this); + } + + ~NickServCollide() + { + collides.erase(this); + } + + NickServ::Nick *GetNick() + { + return na; + } + + User *GetUser() + { + return u; + } + + void Tick(time_t t) override + { + if (!u || !na) + return; + /* If they identified or don't exist anymore, don't kill them. */ + if (u->Account() == na->GetAccount() || u->timestamp > ts) + return; + + service->Collide(u, na); + } +}; + +/** Timer for removing HELD status from nicks. + */ +class NickServHeld : public Timer +{ + Reference na; + Anope::string nick; + public: + NickServHeld(Module *me, NickServ::Nick *n, long l) : Timer(me, l), na(n), nick(na->GetNick()) + { + n->SetS("HELD", true); + } + + void Tick(time_t) + { + if (na) + na->UnsetS("HELD"); + } +}; + +class NickServRelease; +static Anope::map NickServReleases; + +/** Timer for releasing nicks to be available for use + */ +class NickServRelease : public User, public Timer +{ + Anope::string nick; + + public: + NickServRelease(Module *me, NickServ::Nick *na, time_t delay) : User(na->GetNick(), Config->GetModule("nickserv")->Get("enforceruser", "user"), + Config->GetModule("nickserv")->Get("enforcerhost", "services.localhost.net"), "", "", Me, "Services Enforcer", Anope::CurTime, "", IRCD->UID_Retrieve(), NULL), Timer(me, delay), nick(na->GetNick()) + { + /* Erase the current release timer and use the new one */ + Anope::map::iterator nit = NickServReleases.find(this->nick); + if (nit != NickServReleases.end()) + { + IRCD->SendQuit(nit->second, ""); + delete nit->second; + } + + NickServReleases.insert(std::make_pair(this->nick, this)); + + IRCD->SendClientIntroduction(this); + } + + ~NickServRelease() + { + IRCD->SendQuit(this, ""); + NickServReleases.erase(this->nick); + } + + void Tick(time_t t) override { } +}; + +class NickServCore : public Module, public NickServ::NickServService + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + Reference NickServ; + std::vector defaults; + ExtensibleItem held, collided; + EventHandlers onprenickexpire; + EventHandlers onnickexpire; + EventHandlers onnickregister; + EventHandlers onnickvalidate; + std::set identifyrequests; + NickServ::nickalias_map NickList; + NickServ::nickcore_map AccountList; + NickType nick_type; + AccountType account_type; + NSModeType mode_type; + + void OnCancel(User *u, NickServ::Nick *na) + { + if (collided.HasExt(na)) + { + collided.Unset(na); + + new NickServHeld(this, na, Config->GetModule("nickserv")->Get("releasetimeout", "1m")); + + if (IRCD->CanSVSHold) + IRCD->SendSVSHold(na->GetNick(), Config->GetModule("nickserv")->Get("releasetimeout", "1m")); + else + new NickServRelease(this, na, Config->GetModule("nickserv")->Get("releasetimeout", "1m")); + } + } + + public: + NickServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) + , NickServ::NickServService(this) + , held(this, "HELD") + , collided(this, "COLLIDED") + , onprenickexpire(this) + , onnickexpire(this) + , onnickregister(this) + , onnickvalidate(this) + , nick_type(this) + , account_type(this) + , mode_type(this) + { + } + + ~NickServCore() + { + OnShutdown(); + } + + void OnShutdown() override + { + /* On shutdown, restart, or mod unload, remove all of our holds for nicks (svshold or qlines) + * because some IRCds do not allow us to have these automatically expire + */ + for (NickServ::Nick *na : nick_type.List()) + this->Release(na); + } + + void OnRestart() override + { + OnShutdown(); + } + + void Validate(User *u) override + { + NickServ::Nick *na = NickServ::FindNick(u->nick); + if (!na) + return; + + EventReturn MOD_RESULT = this->onnickvalidate(&NickServ::Event::NickValidate::OnNickValidate, u, na); + if (MOD_RESULT == EVENT_STOP) + { + this->Collide(u, na); + return; + } + else if (MOD_RESULT == EVENT_ALLOW) + return; + + if (!na->GetAccount()->HasFieldS("NS_SECURE") && u->IsRecognized()) + { + na->SetLastSeen(Anope::CurTime); + na->SetLastUsermask(u->GetIdent() + "@" + u->GetDisplayedHost()); + na->SetLastRealname(u->realname); + return; + } + + if (Config->GetModule("nickserv")->Get("nonicknameownership")) + return; + + bool on_access = u->IsRecognized(false); + + if (on_access || !na->GetAccount()->HasFieldS("KILL_IMMED")) + { + if (na->GetAccount()->HasFieldS("NS_SECURE")) + u->SendMessage(*NickServ, _("This nickname is registered and protected. If this is your nickname, type \002{0}{1} IDENTIFY \037password\037\002. Otherwise, please choose a different nickname."), Config->StrictPrivmsg, NickServ->nick); // XXX + else + u->SendMessage(*NickServ, _("This nickname is owned by someone else. If this is your nickname, type \002{0}{1} IDENTIFY \037password\037\002. Otherwise, please choose a different nickname."), Config->StrictPrivmsg, NickServ->nick); // XXX + } + if (na->GetAccount()->HasFieldS("KILLPROTECT") && !on_access) + { + if (na->GetAccount()->HasFieldS("KILL_IMMED")) + { + u->SendMessage(*NickServ, _("This nickname has been registered; you may not use it.")); + this->Collide(u, na); + } + else if (na->GetAccount()->HasFieldS("KILL_QUICK")) + { + time_t killquick = Config->GetModule("nickserv")->Get("killquick", "20s"); + u->SendMessage(*NickServ, _("If you do not change within %s, I will change your nick."), Anope::Duration(killquick, u->Account()).c_str()); + new NickServCollide(this, this, u, na, killquick); + } + else + { + time_t kill = Config->GetModule("nickserv")->Get("kill", "60s"); + u->SendMessage(*NickServ, _("If you do not change within %s, I will change your nick."), Anope::Duration(kill, u->Account()).c_str()); + new NickServCollide(this, this, u, na, kill); + } + } + + } + + void OnUserLogin(User *u) override + { + NickServ::Nick *na = NickServ::FindNick(u->nick); + if (na && na->GetAccount() == u->Account() && !Config->GetModule("nickserv")->Get("nonicknameownership") && !na->GetAccount()->HasFieldS("UNCONFIRMED")) + u->SetMode(NickServ, "REGISTERED"); + + const Anope::string &modesonid = Config->GetModule(this)->Get("modesonid"); + if (!modesonid.empty()) + u->SetModes(NickServ, "%s", modesonid.c_str()); + } + + void Collide(User *u, NickServ::Nick *na) override + { + if (na) + collided.Set(na, true); + + if (IRCD->CanSVSNick) + { + unsigned nicklen = Config->GetBlock("networkinfo")->Get("nicklen"); + const Anope::string &guestprefix = Config->GetModule("nickserv")->Get("guestnickprefix", "Guest"); + + Anope::string guestnick; + + int i = 0; + do + { + guestnick = guestprefix + stringify(static_cast(rand())); + if (guestnick.length() > nicklen) + guestnick = guestnick.substr(0, nicklen); + } + while (User::Find(guestnick) && i++ < 10); + + if (i == 11) + u->Kill(*NickServ, "Services nickname-enforcer kill"); + else + { + u->SendMessage(*NickServ, _("Your nickname is now being changed to \002%s\002"), guestnick.c_str()); + IRCD->SendForceNickChange(u, guestnick, Anope::CurTime); + } + } + else + u->Kill(*NickServ, "Services nickname-enforcer kill"); + } + + void Release(NickServ::Nick *na) override + { + if (held.HasExt(na)) + { + if (IRCD->CanSVSHold) + IRCD->SendSVSHoldDel(na->GetNick()); + else + { + User *u = User::Find(na->GetNick()); + if (u && u->server == Me) + { + u->Quit(); + } + } + + held.Unset(na); + } + collided.Unset(na); /* clear pending collide */ + } + + NickServ::IdentifyRequest *CreateIdentifyRequest(NickServ::IdentifyRequestListener *l, Module *o, const Anope::string &acc, const Anope::string &pass) override + { + return new IdentifyRequestImpl(l, o, acc, pass); + } + + std::set& GetIdentifyRequests() override + { + return identifyrequests; + } + + std::vector GetNickList() override + { + return nick_type.List(); + } + + NickServ::nickalias_map& GetNickMap() override + { + return NickList; + } + + std::vector GetAccountList() override + { + return account_type.List(); + } + + NickServ::nickcore_map& GetAccountMap() override + { + return AccountList; + } + + NickServ::Nick *FindNick(const Anope::string &nick) override + { + return nick_type.FindNick(nick); + } + + NickServ::Account *FindAccount(const Anope::string &acc) override + { + return account_type.FindAccount(acc); + } + + void OnReload(Configuration::Conf *conf) override + { + const Anope::string &nsnick = conf->GetModule(this)->Get("client"); + + if (nsnick.empty()) + throw ConfigException(Module::name + ": must be defined"); + + ServiceBot *bi = ServiceBot::Find(nsnick, true); + if (!bi) + throw ConfigException(Module::name + ": no bot named " + nsnick); + + NickServ = bi; + + spacesepstream(conf->GetModule(this)->Get("defaults", "ns_secure memo_signon memo_receive")).GetTokens(defaults); + if (defaults.empty()) + { + defaults.push_back("NS_SECURE"); + defaults.push_back("MEMO_SIGNON"); + defaults.push_back("MEMO_RECEIVE"); + } + else if (defaults[0].equals_ci("none")) + defaults.clear(); + } + + void OnDelNick(NickServ::Nick *na) override + { + User *u = User::Find(na->GetNick()); + if (u && u->Account() == na->GetAccount()) + { + IRCD->SendLogout(u); + u->RemoveMode(NickServ, "REGISTERED"); + u->Logout(); + } + } + + void OnDelCore(NickServ::Account *nc) override + { + Log(NickServ, "nick") << "Deleting nickname group " << nc->GetDisplay(); + + /* Clean up this nick core from any users online */ + for (unsigned int i = nc->users.size(); i > 0; --i) + { + User *user = nc->users[i - 1]; + IRCD->SendLogout(user); + user->RemoveMode(NickServ, "REGISTERED"); + user->Logout(); + Event::OnNickLogout(&Event::NickLogout::OnNickLogout, user); + } + } + + void OnChangeCoreDisplay(NickServ::Account *nc, const Anope::string &newdisplay) override + { + Log(LOG_NORMAL, "nick", NickServ) << "Changing " << nc->GetDisplay() << " nickname group display to " << newdisplay; + } + + void OnNickIdentify(User *u) override + { + Configuration::Block *block = Config->GetModule(this); + + if (block->Get("modeonid", "yes")) + + for (User::ChanUserList::iterator it = u->chans.begin(), it_end = u->chans.end(); it != it_end; ++it) + { + ChanUserContainer *cc = it->second; + Channel *c = cc->chan; + if (c) + c->SetCorrectModes(u, true); + } + + const Anope::string &modesonid = block->Get("modesonid"); + if (!modesonid.empty()) + u->SetModes(NickServ, "%s", modesonid.c_str()); + + if (block->Get("forceemail", "yes") && u->Account()->GetEmail().empty()) + { + u->SendMessage(*NickServ, _("You must now supply an e-mail for your nick.\n" + "This e-mail will allow you to retrieve your password in\n" + "case you forget it.")); + u->SendMessage(*NickServ, _("Type \002%s%s SET EMAIL \037e-mail\037\002 in order to set your e-mail.\n" + "Your privacy is respected; this e-mail won't be given to\n" + "any third-party person."), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str()); + } + + for (std::set::iterator it = collides.begin(); it != collides.end(); ++it) + { + NickServCollide *c = *it; + if (c->GetUser() == u && c->GetNick() && c->GetNick()->GetAccount() == u->Account()) + { + delete c; + break; + } + } + } + + void OnNickGroup(User *u, NickServ::Nick *target) override + { + if (!target->GetAccount()->HasFieldS("UNCONFIRMED")) + u->SetMode(NickServ, "REGISTERED"); + } + + void OnNickUpdate(User *u) override + { + for (User::ChanUserList::iterator it = u->chans.begin(), it_end = u->chans.end(); it != it_end; ++it) + { + ChanUserContainer *cc = it->second; + Channel *c = cc->chan; + if (c) + c->SetCorrectModes(u, true); + } + } + + void OnUserConnect(User *u, bool &exempt) override + { + if (u->Quitting() || !u->server->IsSynced() || u->server->IsULined()) + return; + + const NickServ::Nick *na = NickServ::FindNick(u->nick); + + const Anope::string &unregistered_notice = Config->GetModule(this)->Get("unregistered_notice"); + if (!Config->GetModule("nickserv")->Get("nonicknameownership") && !unregistered_notice.empty() && !na && !u->Account()) + u->SendMessage(*NickServ, unregistered_notice); + else if (na && !u->IsIdentified(true)) + this->Validate(u); + } + + void OnPostUserLogoff(User *u) override + { + NickServ::Nick *na = NickServ::FindNick(u->nick); + if (na) + OnCancel(u, na); + } + + void OnServerSync(Server *s) override + { + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + { + User *u = it->second; + + if (u->server == s) + { + if (u->HasMode("REGISTERED") && !u->IsIdentified(true)) + u->RemoveMode(NickServ, "REGISTERED"); + if (!u->IsIdentified()) + this->Validate(u); + } + } + } + + void OnUserNickChange(User *u, const Anope::string &oldnick) override + { + NickServ::Nick *old_na = NickServ::FindNick(oldnick), *na = NickServ::FindNick(u->nick); + /* If the new nick isn't registered or it's registered and not yours */ + if (!na || na->GetAccount() != u->Account()) + { + /* Remove +r, but keep an account associated with the user */ + u->RemoveMode(NickServ, "REGISTERED"); + + this->Validate(u); + } + else + { + /* Reset +r and re-send account (even though it really should be set at this point) */ + IRCD->SendLogin(u, na); + if (!Config->GetModule("nickserv")->Get("nonicknameownership") && na->GetAccount() == u->Account() && !na->GetAccount()->HasFieldS("UNCONFIRMED")) + u->SetMode(NickServ, "REGISTERED"); + Log(u, "", NickServ) << u->GetMask() << " automatically identified for group " << u->Account()->GetDisplay(); + } + + if (!u->nick.equals_ci(oldnick) && old_na) + OnCancel(u, old_na); + } + + void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override + { + if (u->server->IsSynced() && mname == "REGISTERED" && !u->IsIdentified(true)) + u->RemoveMode(NickServ, mname); + } + + EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override + { + if (!params.empty() || source.c || source.service != *NickServ) + return EVENT_CONTINUE; + if (!Config->GetModule("nickserv")->Get("nonicknameownership")) + source.Reply(_("\002%s\002 allows you to register a nickname and\n" + "prevent others from using it. The following\n" + "commands allow for registration and maintenance of\n" + "nicknames; to use them, type \002%s%s \037command\037\002.\n" + "For more information on a specific command, type\n" + "\002%s%s %s \037command\037\002.\n"), NickServ->nick.c_str(), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str(), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str(), source.command.c_str()); + else + source.Reply(_("\002%s\002 allows you to register an account.\n" + "The following commands allow for registration and maintenance of\n" + "accounts; to use them, type \002%s%s \037command\037\002.\n" + "For more information on a specific command, type\n" + "\002%s%s %s \037command\037\002.\n"), NickServ->nick.c_str(), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str(), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str(), source.command.c_str()); + return EVENT_CONTINUE; + } + + void OnPostHelp(CommandSource &source, const std::vector ¶ms) override + { + if (!params.empty() || source.c || source.service != *NickServ) + return; + if (source.IsServicesOper()) + source.Reply(_(" \n" + "Services Operators can also drop any nickname without needing\n" + "to identify for the nick, and may view the access list for\n" + "any nickname.")); + time_t nickserv_expire = Config->GetModule(this)->Get("expire", "21d"); + if (nickserv_expire >= 86400) + source.Reply(_(" \n" + "Accounts that are not used anymore are subject to\n" + "the automatic expiration, i.e. they will be deleted\n" + "after %d days if not used."), nickserv_expire / 86400); + source.Reply(_(" \n" + "\002NOTICE:\002 This service is intended to provide a way for\n" + "IRC users to ensure their identity is not compromised.\n" + "It is \002NOT\002 intended to facilitate \"stealing\" of\n" + "nicknames or other malicious actions. Abuse of %s\n" + "will result in, at minimum, loss of the abused\n" + "nickname(s)."), NickServ->nick.c_str()); + } + + void OnNickCoreCreate(NickServ::Account *nc) override + { + /* Set default flags */ + for (unsigned i = 0; i < defaults.size(); ++i) + nc->SetS(defaults[i].upper(), true); + } + + void OnUserQuit(User *u, const Anope::string &msg) override + { + if (u->server && !u->server->GetQuitReason().empty() && Config->GetModule(this)->Get("hidenetsplitquit")) + return; + + /* Update last quit and last seen for the user */ + NickServ::Nick *na = NickServ::FindNick(u->nick); + if (na && !na->GetAccount()->HasFieldS("NS_SUSPENDED") && (u->IsRecognized() || u->IsIdentified(true))) + { + na->SetLastSeen(Anope::CurTime); + na->SetLastQuit(msg); + } + } + + void OnExpireTick() override + { + if (Anope::NoExpire || Anope::ReadOnly) + return; + + time_t nickserv_expire = Config->GetModule(this)->Get("expire", "21d"); + + for (NickServ::Nick *na : nick_type.List()) + { + User *u = User::Find(na->GetNick()); + if (u && (u->IsIdentified(true) || u->IsRecognized())) + na->SetLastSeen(Anope::CurTime); + + bool expire = false; + + if (nickserv_expire && Anope::CurTime - na->GetLastSeen() >= nickserv_expire) + expire = true; + + this->onprenickexpire(&NickServ::Event::PreNickExpire::OnPreNickExpire, na, expire); + + if (expire) + { + Log(LOG_NORMAL, "nickserv/expire", NickServ) << "Expiring nickname " << na->GetNick() << " (group: " << na->GetAccount()->GetDisplay() << ") (e-mail: " << (na->GetAccount()->GetEmail().empty() ? "none" : na->GetAccount()->GetEmail()) << ")"; + this->onnickexpire(&NickServ::Event::NickExpire::OnNickExpire, na); + delete na; + } + } + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override + { + if (!na->GetAccount()->HasFieldS("UNCONFIRMED")) + { + time_t nickserv_expire = Config->GetModule(this)->Get("expire", "21d"); + if (!na->HasFieldS("NS_NO_EXPIRE") && nickserv_expire && !Anope::NoExpire && (source.HasPriv("nickserv/auspex") || na->GetLastSeen() != Anope::CurTime)) + info[_("Expires")] = Anope::strftime(na->GetLastSeen() + nickserv_expire, source.GetAccount()); + } + else + { + time_t unconfirmed_expire = Config->GetModule(this)->Get("unconfirmedexpire", "1d"); + info[_("Expires")] = Anope::strftime(na->GetTimeRegistered() + unconfirmed_expire, source.GetAccount()); + } + } + + void OnModuleUnload(User *u, Module *m) override + { + for (std::set::iterator it = identifyrequests.begin(), it_end = identifyrequests.end(); it != it_end;) + { + NickServ::IdentifyRequest *ir = *it; + ++it; + + ir->Release(m); +#if 0 + ir->holds.erase(m); + if (ir->holds.empty() && ir->dispatched) + { + if (!ir->success) + ir->OnFail(); + delete ir; + continue; + } + + if (ir->GetOwner() == m) + { + if (!ir->success) + ir->OnFail(); + delete ir; + } +#endif + } + } +}; + +MODULE_INIT(NickServCore) + diff --git a/modules/nickserv/main/nicktype.cpp b/modules/nickserv/main/nicktype.cpp new file mode 100644 index 000000000..9607aaa2f --- /dev/null +++ b/modules/nickserv/main/nicktype.cpp @@ -0,0 +1,45 @@ +#include "module.h" +#include "nicktype.h" + +NickType::NickType(Module *me) : Serialize::Type(me, "NickAlias") + , nick(this, "nick") + , last_quit(this, "last_quit") + , last_realname(this, "last_realname") + , last_usermask(this, "last_usermask") + , last_realhost(this, "last_realhost") + , time_registered(this, "time_registered") + , last_seen(this, "last_seen") + , vhost_ident(this, "vhost_ident") + , vhost_host(this, "vhost_host") + , vhost_creator(this, "vhost_creator") + , vhost_created(this, "vhost_created") + , nc(this, "nc") +{ + +} + +void NickType::Nick::SetField(NickImpl *na, const Anope::string &value) +{ + /* Remove us from the aliases list */ + NickServ::nickalias_map &map = NickServ::service->GetNickMap(); + map.erase(GetField(na)); + + Serialize::Field::SetField(na, value); + + map[value] = na; +} + +NickServ::Nick *NickType::FindNick(const Anope::string &n) +{ + Serialize::ID id; + EventReturn result = Event::OnSerialize(&Event::SerializeEvents::OnSerializeFind, this, &this->nick, n, id); + if (result == EVENT_ALLOW) + return RequireID(id); + + NickServ::nickalias_map &map = NickServ::service->GetNickMap(); + auto it = map.find(n); + if (it != map.end()) + return it->second; + return nullptr; +} + diff --git a/modules/nickserv/main/nicktype.h b/modules/nickserv/main/nicktype.h new file mode 100644 index 000000000..d205845e9 --- /dev/null +++ b/modules/nickserv/main/nicktype.h @@ -0,0 +1,32 @@ +#include "nick.h" + +class NickType : public Serialize::Type +{ + public: + struct Nick : Serialize::Field + { + using Serialize::Field::Field; + + void SetField(NickImpl *s, const Anope::string &value) override; + } nick; + Serialize::Field last_quit; + Serialize::Field last_realname; + /* Last usermask this nick was seen on, eg user@host */ + Serialize::Field last_usermask; + /* Last uncloaked usermask, requires nickserv/auspex to see */ + Serialize::Field last_realhost; + Serialize::Field time_registered; + Serialize::Field last_seen; + + Serialize::Field vhost_ident; + Serialize::Field vhost_host; + Serialize::Field vhost_creator; + Serialize::Field vhost_created; + + /* Account this nick is tied to. Multiple nicks can be tied to a single account. */ + Serialize::ObjectField nc; + + NickType(Module *); + + NickServ::Nick *FindNick(const Anope::string &nick); +}; diff --git a/modules/nickserv/maxemail.cpp b/modules/nickserv/maxemail.cpp new file mode 100644 index 000000000..38054f819 --- /dev/null +++ b/modules/nickserv/maxemail.cpp @@ -0,0 +1,80 @@ +/* ns_maxemail.cpp - Limit the amount of times an email address + * can be used for a NickServ account. + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Included in the Anope module pack since Anope 1.7.9 + * Anope Coder: GeniusDex + * + * Please read COPYING and README for further details. + */ + +#include "module.h" + +class NSMaxEmail : public Module + , public EventHook +{ + bool CheckLimitReached(CommandSource &source, const Anope::string &email) + { + int NSEmailMax = Config->GetModule(this)->Get("maxemails"); + + if (NSEmailMax < 1 || email.empty()) + return false; + + if (this->CountEmail(email, source.nc) < NSEmailMax) + return false; + + if (NSEmailMax == 1) + source.Reply(_("The email address \002{0}\002 has reached its usage limit of \0021\002 user."), email); + else + source.Reply(_("The email address \002{0}\002 has reached its usage limit of \002{1}\002 users."), email, NSEmailMax); + + return true; + } + + int CountEmail(const Anope::string &email, NickServ::Account *unc) + { + int count = 0; + + if (email.empty()) + return 0; + + for (NickServ::Account *nc : NickServ::service->GetAccountList()) + if (unc != nc && !nc->GetEmail().empty() && nc->GetEmail().equals_ci(email)) + ++count; + + return count; + } + + public: + NSMaxEmail(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + { + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override + { + if (source.IsOper()) + return EVENT_CONTINUE; + + if (command->name == "nickserv/register") + { + if (this->CheckLimitReached(source, params.size() > 1 ? params[1] : "")) + return EVENT_STOP; + } + else if (command->name == "nickserv/set/email") + { + if (this->CheckLimitReached(source, params.size() > 0 ? params[0] : "")) + return EVENT_STOP; + } + else if (command->name == "nickserv/ungroup" && source.GetAccount()) + { + if (this->CheckLimitReached(source, source.GetAccount()->GetEmail())) + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(NSMaxEmail) diff --git a/modules/nickserv/recover.cpp b/modules/nickserv/recover.cpp new file mode 100644 index 000000000..65033bab9 --- /dev/null +++ b/modules/nickserv/recover.cpp @@ -0,0 +1,262 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_cert.h" +#include "modules/nickserv.h" + +typedef std::map NSRecoverInfo; + +class NSRecoverRequestListener : public NickServ::IdentifyRequestListener +{ + CommandSource source; + Command *cmd; + Anope::string user; + Anope::string pass; + + public: + NSRecoverRequestListener(CommandSource &src, Command *c, const Anope::string &nick, const Anope::string &p) : source(src), cmd(c), user(nick), pass(p) { } + + void OnSuccess(NickServ::IdentifyRequest *) override + { + User *u = User::Find(user, true); + if (!source.GetUser() || !source.service) + return; + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + return; + + Log(LOG_COMMAND, source, cmd) << "for " << na->GetNick(); + + /* Nick is being held by us, release it */ + if (na->HasFieldS("HELD")) + { + NickServ::service->Release(na); + source.Reply(_("Service's hold on \002{0}\002 has been released."), na->GetNick()); + } + else if (!u) + { + source.Reply(_("No one is using your nick, and services are not holding it.")); + } + // If the user being recovered is identified for the account of the nick then the user is the + // same person that is executing the command, so kill them off (old GHOST command). + else if (u->Account() == na->GetAccount()) + { + if (!source.GetAccount() && na->GetAccount()->HasFieldS("NS_SECURE")) + { + source.GetUser()->Login(u->Account()); + Log(LOG_COMMAND, source, cmd) << "and was automatically identified to " << u->Account()->GetDisplay(); + } + + if (Config->GetModule("ns_recover")->Get("restoreonrecover")) + { + if (!u->chans.empty()) + { + NSRecoverInfo i; + for (User::ChanUserList::iterator it = u->chans.begin(), it_end = u->chans.end(); it != it_end; ++it) + i[it->first->name] = it->second->status; + source.GetUser()->Extend("recover", i); + } + } + + u->SendMessage(*source.service, _("This nickname has been recovered by \002{0}\002. If you did not do this, then \002{0}\002 may have your password, and you should change it."), + source.GetNick()); + + Anope::string buf = source.command.upper() + " command used by " + source.GetNick(); + u->Kill(*source.service, buf); + + source.Reply(_("Ghost with your nick has been killed.")); + + if (IRCD->CanSVSNick) + IRCD->SendForceNickChange(source.GetUser(), user, Anope::CurTime); + } + /* User is not identified or not identified to the same account as the person using this command */ + else + { + if (!source.GetAccount() && na->GetAccount()->HasFieldS("NS_SECURE")) + { + source.GetUser()->Login(na->GetAccount()); // Identify the user using the command if they arent identified + Log(LOG_COMMAND, source, cmd) << "and was automatically identified to " << na->GetNick() << " (" << na->GetAccount()->GetDisplay() << ")"; + } + + u->SendMessage(*source.service, _("This nickname has been recovered by \002{0}\002."), source.GetNick()); + if (NickServ::service) + NickServ::service->Collide(u, na); + + if (IRCD->CanSVSNick) + { + /* If we can svsnick then release our hold and svsnick the user using the command */ + if (NickServ::service) + NickServ::service->Release(na); + IRCD->SendForceNickChange(source.GetUser(), user, Anope::CurTime); + source.Reply(_("You have regained control of \002%s\002 and are now identified as \002%s\002."), user, na->GetAccount()->GetDisplay().c_str()); + } + else + source.Reply(_("The user with your nick has been removed. Use this command again to release services's hold on your nick.")); + } + } + + void OnFail(NickServ::IdentifyRequest *) override + { + if (NickServ::FindNick(user) != NULL) + { + source.Reply(_("Access denied.")); + if (!pass.empty()) + { + Log(LOG_COMMAND, source, cmd) << "with an invalid password for " << user; + if (source.GetUser()) + source.GetUser()->BadPassword(); + } + } + else + source.Reply(_("\002{0}\002 isn't registered."), user); + } +}; + +class CommandNSRecover : public Command +{ + public: + CommandNSRecover(Module *creator) : Command(creator, "nickserv/recover", 1, 2) + { + this->SetDesc(_("Regains control of your nick")); + this->SetSyntax(_("\037nickname\037 [\037password\037]")); + this->AllowUnregistered(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &nick = params[0]; + const Anope::string &pass = params.size() > 1 ? params[1] : ""; + + User *user = User::Find(nick, true); + + if (user && source.GetUser() == user) + { + source.Reply(_("You can't %s yourself!"), source.command.lower().c_str()); + return; + } + + NickServ::Nick *na = NickServ::FindNick(nick); + + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + if (na->GetAccount()->HasFieldS("NS_SUSPENDED")) + { + source.Reply(_("\002{0}\002 is suspended."), na->GetNick()); + return; + } + + bool ok = false; + if (source.GetAccount() == na->GetAccount()) + ok = true; + else if (!na->GetAccount()->HasFieldS("NS_SECURE") && source.GetUser() && na->GetAccount()->IsOnAccess(source.GetUser())) + ok = true; + + if (certservice && source.GetUser() && certservice->Matches(source.GetUser(), na->GetAccount())) + ok = true; + + if (ok == false && !pass.empty()) + { + NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new NSRecoverRequestListener(source, this, na->GetNick(), pass), owner, na->GetNick(), pass); + Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, source.GetUser(), req); + req->Dispatch(); + } + else + { + NSRecoverRequestListener req(source, this, na->GetNick(), pass); + + if (ok) + req.OnSuccess(nullptr); + else + req.OnFail(nullptr); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Recovers your nickname from another user or from services." + "If services are currently holding your nickname, the hold will be released." + " If another user is holding your nickname and is identified they will be killed." + " If they are not identified they will be forced off of the nickname.")); + return true; + } +}; + +class NSRecover : public Module + , public EventHook + , public EventHook +{ + CommandNSRecover commandnsrecover; + ExtensibleItem recover; + + public: + NSRecover(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsrecover(this) + , recover(this, "recover") + { + + if (Config->GetModule("nickserv")->Get("nonicknameownership")) + throw ModuleException(modname + " can not be used with options:nonicknameownership enabled"); + + } + + void OnUserNickChange(User *u, const Anope::string &oldnick) override + { + if (Config->GetModule(this)->Get("restoreonrecover")) + { + NSRecoverInfo *ei = recover.Get(u); + ServiceBot *NickServ = Config->GetClient("NickServ"); + + if (ei != NULL && NickServ != NULL) + for (NSRecoverInfo::iterator it = ei->begin(), it_end = ei->end(); it != it_end;) + { + Channel *c = Channel::Find(it->first); + const Anope::string &cname = it->first; + ++it; + + /* User might already be on the channel */ + if (u->FindChannel(c)) + this->OnJoinChannel(u, c); + else if (IRCD->CanSVSJoin) + IRCD->SendSVSJoin(NickServ, u, cname, ""); + } + } + } + + void OnJoinChannel(User *u, Channel *c) override + { + if (Config->GetModule(this)->Get("restoreonrecover")) + { + NSRecoverInfo *ei = recover.Get(u); + + if (ei != NULL) + { + NSRecoverInfo::iterator it = ei->find(c->name); + if (it != ei->end()) + { + for (size_t i = 0; i < it->second.Modes().length(); ++i) + c->SetMode(c->ci->WhoSends(), ModeManager::FindChannelModeByChar(it->second.Modes()[i]), u->GetUID()); + + ei->erase(it); + if (ei->empty()) + recover.Unset(u); + } + } + } + } +}; + +MODULE_INIT(NSRecover) diff --git a/modules/nickserv/register.cpp b/modules/nickserv/register.cpp new file mode 100644 index 000000000..c7e6e5af2 --- /dev/null +++ b/modules/nickserv/register.cpp @@ -0,0 +1,426 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/nickserv.h" + +static bool SendRegmail(User *u, NickServ::Nick *na, ServiceBot *bi); + +class CommandNSConfirm : public Command +{ + public: + CommandNSConfirm(Module *creator) : Command(creator, "nickserv/confirm", 1, 2) + { + this->SetDesc(_("Confirm a passcode")); + this->SetSyntax(_("\037passcode\037")); + this->AllowUnregistered(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &passcode = params[0]; + + if (source.nc && !source.nc->HasFieldS("UNCONFIRMED") && source.HasPriv("nickserv/confirm")) + { + NickServ::Nick *na = NickServ::FindNick(passcode); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), passcode); + return; + } + + if (na->GetAccount()->HasFieldS("UNCONFIRMED") == false) + { + source.Reply(_("\002{0}\002 is already confirmed."), na->GetNick()); + return; + } + + na->GetAccount()->UnsetS("UNCONFIRMED"); + if (NickServ::Event::OnNickConfirm) + NickServ::Event::OnNickConfirm(&NickServ::Event::NickConfirm::OnNickConfirm, source.GetUser(), na->GetAccount()); + Log(LOG_ADMIN, source, this) << "to confirm nick " << na->GetNick() << " (" << na->GetAccount()->GetDisplay() << ")"; + source.Reply(_("\002{0}\002 has been confirmed."), na->GetNick()); + } + else if (source.nc) + { + Anope::string *code = source.nc->GetExt("passcode"); + if (code == nullptr || *code != passcode) + { + source.Reply(_("Invalid passcode.")); + return; + } + + NickServ::Account *nc = source.nc; + nc->ShrinkOK("passcode"); + Log(LOG_COMMAND, source, this) << "to confirm their email"; + source.Reply(_("Your email address of \002{0}\002 has been confirmed."), source.nc->GetEmail()); + nc->UnsetS("UNCONFIRMED"); + if (NickServ::Event::OnNickConfirm) + NickServ::Event::OnNickConfirm(&NickServ::Event::NickConfirm::OnNickConfirm, source.GetUser(), nc); + + if (source.GetUser()) + { + NickServ::Nick *na = NickServ::FindNick(source.GetNick()); + if (na) + { + IRCD->SendLogin(source.GetUser(), na); + if (!Config->GetModule("nickserv")->Get("nonicknameownership") && na->GetAccount() == source.GetAccount() && !na->GetAccount()->HasFieldS("UNCONFIRMED")) + source.GetUser()->SetMode(source.service, "REGISTERED"); + } + } + } + else + source.Reply(_("Invalid passcode.")); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command is used by several commands as a way to confirm changes made to your account.\n" + "\n" + "This is most commonly used to confirm your email address once you register or change it.\n" + "\n" + "This is also used after when resetting your password to force identify you to your account so you may change your password.")); + if (source.HasPriv("nickserv/confirm")) + source.Reply(_("Additionally, Services Operators with the \037nickserv/confirm\037 permission can\n" + "replace \037passcode\037 with a users nick to force validate them.")); + return true; + } +}; + +class CommandNSRegister : public Command +{ + public: + CommandNSRegister(Module *creator) : Command(creator, "nickserv/register", 1, 2) + { + this->SetDesc(_("Register a nickname")); + if (Config->GetModule("nickserv")->Get("forceemail", "yes")) + this->SetSyntax(_("\037password\037 \037email\037")); + else + this->SetSyntax(_("\037password\037 \037[email]\037")); + this->AllowUnregistered(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + User *u = source.GetUser(); + Anope::string u_nick = source.GetNick(); + size_t nicklen = u_nick.length(); + Anope::string pass = params[0]; + Anope::string email = params.size() > 1 ? params[1] : ""; + const Anope::string &nsregister = Config->GetModule(this->owner)->Get("registration"); + + if (Anope::ReadOnly) + { + source.Reply(_("Sorry, nickname registration is temporarily disabled.")); + return; + } + + if (nsregister.equals_ci("disable")) + { + source.Reply(_("Registration is currently disabled.")); + return; + } + + time_t nickregdelay = Config->GetModule(this->owner)->Get("nickregdelay"); + time_t reg_delay = Config->GetModule("nickserv")->Get("regdelay"); + if (u && !u->HasMode("OPER") && nickregdelay && Anope::CurTime - u->timestamp < nickregdelay) + { + source.Reply(_("You must have been using this nickname for at least {0} seconds to register."), nickregdelay); + return; + } + + /* Prevent "Guest" nicks from being registered. -TheShadow */ + + /* Guest nick can now have a series of between 1 and 7 digits. + * --lara + */ + const Anope::string &guestnick = Config->GetModule("nickserv")->Get("guestnickprefix", "Guest"); + if (nicklen <= guestnick.length() + 7 && nicklen >= guestnick.length() + 1 && !u_nick.find_ci(guestnick) && u_nick.substr(guestnick.length()).find_first_not_of("1234567890") == Anope::string::npos) + { + source.Reply(_("\002{0}\002 may not be registered."), u_nick); + return; + } + + if (!IRCD->IsNickValid(u_nick)) + { + source.Reply(_("\002{0}\002 may not be registered."), u_nick); + return; + } + + if (ServiceBot::Find(u_nick, true)) + { + source.Reply(_("\002{0}\002 may not be registered."), u_nick); + return; + } + + if (Config->GetModule("nickserv")->Get("restrictopernicks")) + for (Oper *o : Serialize::GetObjects(operblock)) + { + if (!source.IsOper() && u_nick.find_ci(o->GetName()) != Anope::string::npos) + { + source.Reply(_("\002{0}\002 may not be registered."), u_nick); + return; + } + } + + unsigned int passlen = Config->GetModule("nickserv")->Get("passlen", "32"); + + if (Config->GetModule("nickserv")->Get("forceemail", "yes") && email.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + if (u && Anope::CurTime < u->lastnickreg + reg_delay) + { + source.Reply(_("Please wait \002{0}\002 seconds before using the {1} command again."), (u->lastnickreg + reg_delay) - Anope::CurTime, source.command); + return; + } + + if (NickServ::FindNick(u_nick) != NULL) + { + source.Reply(_("\002{0}\002 is already registered."), u_nick); + return; + } + + if (pass.equals_ci(u_nick) || (Config->GetBlock("options")->Get("strictpasswords") && pass.length() < 5)) + { + source.Reply(_("Please try again with a more obscure password. Passwords should be at least five characters long, should not be something easily guessed" + " (e.g. your real name or your nickname), and cannot contain the space or tab characters.")); + return; + } + + if (pass.length() > Config->GetModule("nickserv")->Get("passlen", "32")) + { + source.Reply(_("Your password is too long, it can not contain more than \002{0}\002 characters."), Config->GetModule("nickserv")->Get("passlen", "32")); + return; + } + + if (!email.empty() && !Mail::Validate(email)) + { + source.Reply(_("\002{0}\002 is not a valid e-mail address."), email); + return; + } + + NickServ::Account *nc = NickServ::account.Create(); + nc->SetDisplay(u_nick); + + NickServ::Nick *na = NickServ::nick.Create(); + na->SetNick(u_nick); + na->SetAccount(nc); + Anope::string epass; + Anope::Encrypt(pass, epass); + nc->SetPassword(epass); + if (!email.empty()) + nc->SetEmail(email); + + if (u) + { + na->SetLastUsermask(u->GetIdent() + "@" + u->GetDisplayedHost()); + na->SetLastRealname(u->realname); + } + else + na->SetLastRealname(source.GetNick()); + + Log(LOG_COMMAND, source, this) << "to register " << na->GetNick() << " (email: " << (!na->GetAccount()->GetEmail().empty() ? na->GetAccount()->GetEmail() : "none") << ")"; + + source.Reply(_("\002{0}\002 has been registered."), u_nick); + + Anope::string tmp_pass; + if (Anope::Decrypt(na->GetAccount()->GetPassword(), tmp_pass)) + source.Reply(_("Your password is \002{0}\002 - remember this for later use."), tmp_pass); + + if (nsregister.equals_ci("admin")) + { + nc->SetS("UNCONFIRMED", true); + // User::Identify() called below will notify the user that their registration is pending + } + else if (nsregister.equals_ci("mail")) + { + if (!email.empty()) + { + nc->SetS("UNCONFIRMED", true); + SendRegmail(NULL, na, source.service); + } + } + + if (NickServ::Event::OnNickRegister) + NickServ::Event::OnNickRegister(&NickServ::Event::NickRegister::OnNickRegister, source.GetUser(), na, pass); + + if (u) + { + u->Identify(na); + u->lastnickreg = Anope::CurTime; + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Registers your nickname. Once your nickname is registered, you will be able to use most features of services, including owning and managing channels." + "Make sure you remember the password - you'll need it to identify yourself later. Your email address will only be used if you forget your password.")); + + if (!Config->GetModule("nickserv")->Get("forceemail", "yes")) + { + source.Reply(" "); + source.Reply(_("The \037email\037 parameter is optional and will set the email\n" + "for your nick immediately.\n" + "Your privacy is respected; this e-mail won't be given to\n" + "any third-party person. You may also wish to \002SET HIDE\002 it\n" + "after registering if it isn't the default setting already.")); + } + + if (!Config->GetModule("nickserv")->Get("nonicknameownership")) + { + source.Reply(" "); + source.Reply(_("This command also creates a new group for your nickname, which will allow you to group other nicknames later, which share the same configuration, the same set of memos and the same channel privileges.")); + } + return true; + } +}; + +class CommandNSResend : public Command +{ + public: + CommandNSResend(Module *creator) : Command(creator, "nickserv/resend", 0, 0) + { + this->SetDesc(_("Resend registration confirmation email")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (!Config->GetModule(this->owner)->Get("registration").equals_ci("mail")) + { + source.Reply(_("Access denied.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(source.GetNick()); + + if (na == NULL) + { + source.Reply(_("Your nickname isn't registered.")); + return; + } + + if (na->GetAccount() != source.GetAccount() || !source.nc->HasFieldS("UNCONFIRMED")) + { + source.Reply(_("Your account is already confirmed.")); + return; + } + + if (Anope::CurTime < source.nc->lastmail + Config->GetModule(this->owner)->Get("resenddelay")) + { + source.Reply(_("Cannot send mail now; please retry a little later.")); + return; + } + + if (!SendRegmail(source.GetUser(), na, source.service)) + { + Log(this->owner) << "Unable to resend registration verification code for " << source.GetNick(); + return; + } + + na->GetAccount()->lastmail = Anope::CurTime; + source.Reply(_("Your passcode has been re-sent to \002{0}\002."), na->GetAccount()->GetEmail()); + Log(LOG_COMMAND, source, this) << "to resend registration verification code"; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (!Config->GetModule(this->owner)->Get("registration").equals_ci("mail")) + return false; + + source.Reply(_("This command will resend you the registration confirmation email.")); + return true; + } + + void OnServHelp(CommandSource &source) override + { + if (Config->GetModule(this->owner)->Get("registration").equals_ci("mail")) + Command::OnServHelp(source); + } +}; + +class NSRegister : public Module + , public EventHook + , public EventHook +{ + CommandNSRegister commandnsregister; + CommandNSConfirm commandnsconfirm; + CommandNSResend commandnsrsend; + + Serialize::Field unconfirmed; + Serialize::Field passcode; + + public: + NSRegister(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsregister(this) + , commandnsconfirm(this) + , commandnsrsend(this) + , unconfirmed(this, NickServ::account, "UNCONFIRMED") + , passcode(this, NickServ::account, "passcode") + { + if (Config->GetModule(this)->Get("registration").equals_ci("disable")) + throw ModuleException("Module " + this->name + " will not load with registration disabled."); + } + + void OnNickIdentify(User *u) override + { + ServiceBot *NickServ; + if (unconfirmed.HasExt(u->Account()) && (NickServ = Config->GetClient("NickServ"))) + { + const Anope::string &nsregister = Config->GetModule(this)->Get("registration"); + if (nsregister.equals_ci("admin")) + u->SendMessage(NickServ, _("All new accounts must be validated by an administrator. Please wait for your registration to be confirmed.")); + else + u->SendMessage(NickServ, _("Your email address is not confirmed. To confirm it, follow the instructions that were emailed to you.")); + NickServ::Nick *this_na = NickServ::FindNick(u->Account()->GetDisplay()); + time_t time_registered = Anope::CurTime - this_na->GetTimeRegistered(); + time_t unconfirmed_expire = Config->GetModule(this)->Get("unconfirmedexpire", "1d"); + if (unconfirmed_expire > time_registered) + u->SendMessage(NickServ, _("Your account will expire, if not confirmed, in %s."), Anope::Duration(unconfirmed_expire - time_registered, u->Account()).c_str()); + } + } + + void OnPreNickExpire(NickServ::Nick *na, bool &expire) override + { + if (unconfirmed.HasExt(na->GetAccount())) + { + time_t unconfirmed_expire = Config->GetModule(this)->Get("unconfirmedexpire", "1d"); + if (unconfirmed_expire && Anope::CurTime - na->GetTimeRegistered() >= unconfirmed_expire) + expire = true; + } + } +}; + +static bool SendRegmail(User *u, NickServ::Nick *na, ServiceBot *bi) +{ + NickServ::Account *nc = na->GetAccount(); + + Anope::string *code = na->GetAccount()->GetExt("passcode"); + if (code == NULL) + code = na->GetAccount()->Extend("passcode", Anope::Random(9)); + + Anope::string subject = Language::Translate(na->GetAccount(), Config->GetBlock("mail")->Get("registration_subject").c_str()), + message = Language::Translate(na->GetAccount(), Config->GetBlock("mail")->Get("registration_message").c_str()); + + subject = subject.replace_all_cs("%n", na->GetNick()); + subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); + subject = subject.replace_all_cs("%c", *code); + + message = message.replace_all_cs("%n", na->GetNick()); + message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); + message = message.replace_all_cs("%c", *code); + + return Mail::Send(u, nc, bi, subject, message); +} + +MODULE_INIT(NSRegister) diff --git a/modules/nickserv/resetpass.cpp b/modules/nickserv/resetpass.cpp new file mode 100644 index 000000000..3be5a4c06 --- /dev/null +++ b/modules/nickserv/resetpass.cpp @@ -0,0 +1,141 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +static bool SendResetEmail(User *u, NickServ::Nick *na, ServiceBot *bi); + +class CommandNSResetPass : public Command +{ + public: + CommandNSResetPass(Module *creator) : Command(creator, "nickserv/resetpass", 2, 2) + { + this->SetDesc(_("Helps you reset lost passwords")); + this->SetSyntax(_("\037account\037 \037email\037")); + this->AllowUnregistered(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + NickServ::Nick *na = NickServ::FindNick(params[0]); + + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), params[0]); + return; + } + + if (!na->GetAccount()->GetEmail().equals_ci(params[1])) + { + source.Reply(_("Incorrect email address.")); + return; + } + + if (SendResetEmail(source.GetUser(), na, source.service)) + { + Log(LOG_COMMAND, source, this) << "for " << na->GetNick() << " (group: " << na->GetAccount()->GetDisplay() << ")"; + source.Reply(_("Password reset email for \002{0}\002 has been sent."), na->GetNick()); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sends a passcode to the email address of \037account\037 with instructions on how to reset their password. \037email\037 must be the email address associated to \037account\037.")); + return true; + } +}; + +struct ResetInfo +{ + Anope::string code; + time_t time; +}; + +class NSResetPass : public Module + , public EventHook +{ + CommandNSResetPass commandnsresetpass; + ExtensibleItem reset; + + public: + NSResetPass(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsresetpass(this), reset(this, "reset") + { + if (!Config->GetBlock("mail")->Get("usemail")) + throw ModuleException("Not using mail."); + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override + { + if (command->name == "nickserv/confirm" && params.size() > 1) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return EVENT_STOP; + } + + NickServ::Nick *na = NickServ::FindNick(params[0]); + + ResetInfo *ri = na ? reset.Get(na->GetAccount()) : NULL; + if (na && ri) + { + NickServ::Account *nc = na->GetAccount(); + const Anope::string &passcode = params[1]; + if (ri->time < Anope::CurTime - 3600) + { + reset.Unset(nc); + source.Reply(_("Your password reset request has expired.")); + } + else if (passcode.equals_cs(ri->code)) + { + reset.Unset(nc); + nc->UnsetS("UNCONFIRMED"); + + Log(LOG_COMMAND, source, &commandnsresetpass) << "confirmed RESETPASS to forcefully identify as " << na->GetNick(); + + if (source.GetUser()) + { + source.GetUser()->Identify(na); + source.Reply(_("You are now identified for \002{0}\002. Change your password now."), na->GetAccount()->GetDisplay()); + } + } + else + return EVENT_CONTINUE; + + return EVENT_STOP; + } + } + + return EVENT_CONTINUE; + } +}; + +static bool SendResetEmail(User *u, NickServ::Nick *na, ServiceBot *bi) +{ + Anope::string subject = Language::Translate(na->GetAccount(), Config->GetBlock("mail")->Get("reset_subject").c_str()), + message = Language::Translate(na->GetAccount(), Config->GetBlock("mail")->Get("reset_message").c_str()), + passcode = Anope::Random(20); + + subject = subject.replace_all_cs("%n", na->GetNick()); + subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); + subject = subject.replace_all_cs("%c", passcode); + + message = message.replace_all_cs("%n", na->GetNick()); + message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); + message = message.replace_all_cs("%c", passcode); + + na->GetAccount()->Extend("reset", ResetInfo{passcode, Anope::CurTime}); + + return Mail::Send(u, na->GetAccount(), bi, subject, message); +} + +MODULE_INIT(NSResetPass) diff --git a/modules/nickserv/set.cpp b/modules/nickserv/set.cpp new file mode 100644 index 000000000..a75386743 --- /dev/null +++ b/modules/nickserv/set.cpp @@ -0,0 +1,1244 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_info.h" +#include "modules/ns_set.h" +#include "modules/nickserv.h" + +class CommandNSSet : public Command +{ + public: + CommandNSSet(Module *creator) : Command(creator, "nickserv/set", 1, 3) + { + this->SetDesc(_("Set options, including kill protection")); + this->SetSyntax(_("\037option\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets various options on your account\n" + "\n" + "Available options:")); + + Anope::string this_name = source.command; + bool hide_privileged_commands = Config->GetBlock("options")->Get("hideprivilegedcommands"), + hide_registered_commands = Config->GetBlock("options")->Get("hideregisteredcommands"); + for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + const CommandInfo &info = it->second; + + if (c_name.find_ci(this_name + " ") == 0) + { + ServiceReference c("Command", info.name); + // XXX dup + if (!c) + continue; + else if (hide_registered_commands && !c->AllowUnregistered() && !source.GetAccount()) + continue; + else if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission)) + continue; + + source.command = c_name; + c->OnServHelp(source); + } + } + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("Type \002{0}{1} {2} {3} \037option\037\002 for more information on a particular option."), + Config->StrictPrivmsg, source.service->nick, help->cname, this_name); + + return true; + } +}; + +class CommandNSSASet : public Command +{ + public: + CommandNSSASet(Module *creator) : Command(creator, "nickserv/saset", 2, 4) + { + this->SetDesc(_("Set SET-options on another nickname")); + this->SetSyntax(_("\037option\037 \037nickname\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->OnSyntaxError(source, ""); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Sets various options on other users accounts\n" + "\n" + "Available options:")); + + Anope::string this_name = source.command; + for (CommandInfo::map::const_iterator it = source.service->commands.begin(), it_end = source.service->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + const CommandInfo &info = it->second; + + if (c_name.find_ci(this_name + " ") == 0) + { + ServiceReference command("Command", info.name); + if (command) + { + source.command = c_name; + command->OnServHelp(source); + } + } + } + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("Type \002{0}{1} {2} {3} \037option\037\002 for more information on a particular option."), + Config->StrictPrivmsg, source.service->nick, help->cname, this_name); + + return true; + } +}; + +class CommandNSSetPassword : public Command +{ + public: + CommandNSSetPassword(Module *creator) : Command(creator, "nickserv/set/password", 1) + { + this->SetDesc(_("Changes your password")); + this->SetSyntax(_("\037new-password\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string ¶m = params[0]; + unsigned len = param.length(); + + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + if (source.GetNick().equals_ci(param) || (Config->GetBlock("options")->Get("strictpasswords") && len < 5)) + { + source.Reply(_("Please try again with a more obscure password. Passwords should be at least five characters long, should not be something easily guessed (e.g. your real name or your nick), and cannot contain the space or tab characters.")); + return; + } + + if (len > Config->GetModule("nickserv")->Get("passlen", "32")) + { + source.Reply(_("Your password is too long, it can not contain more than \002{0}\002 characters."), Config->GetModule("nickserv")->Get("passlen", "32")); + return; + } + + Log(LOG_COMMAND, source, this) << "to change their password"; + + Anope::string tmp_pass; + Anope::Encrypt(param, tmp_pass); + source.nc->SetPassword(tmp_pass); + + if (Anope::Decrypt(source.nc->GetPassword(), tmp_pass)) + source.Reply(_("Password for \002{0}\002 changed to \002{1]\002."), source.nc->GetDisplay(), tmp_pass); + else + source.Reply(_("Password for \002{0}\002 changed."), source.nc->GetDisplay()); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes your password to \037new-password\037.")); + return true; + } +}; + +class CommandNSSASetPassword : public Command +{ + public: + CommandNSSASetPassword(Module *creator) : Command(creator, "nickserv/saset/password", 2, 2) + { + this->SetDesc(_("Changes the password of another user")); + this->SetSyntax(_("\037account\037 \037new-password\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *setter_na = NickServ::FindNick(params[0]); + if (setter_na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), params[0]); + return; + } + NickServ::Account *nc = setter_na->GetAccount(); + + size_t len = params[1].length(); + + if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && source.nc != nc && nc->IsServicesOper()) + { + source.Reply(_("You may not change the password of other Services Operators.")); + return; + } + + if (nc->GetDisplay().equals_ci(params[1]) || (Config->GetBlock("options")->Get("strictpasswords") && len < 5)) + { + source.Reply(_("Please try again with a more obscure password. Passwords should be at least five characters long, should not be something easily guessed (e.g. your real name or your nick), and cannot contain the space or tab characters.")); + return; + } + + if (len > Config->GetModule("nickserv")->Get("passlen", "32")) + { + source.Reply(_("Your password is too long, it can not contain more than \002{0}\002 characters."), Config->GetModule("nickserv")->Get("passlen", "32")); + return; + } + + Log(LOG_ADMIN, source, this) << "to change the password of " << nc->GetDisplay(); + + Anope::string tmp_pass; + Anope::Encrypt(params[1], tmp_pass); + nc->SetPassword(tmp_pass); + if (Anope::Decrypt(nc->GetPassword(), tmp_pass) == 1) + source.Reply(_("Password for \002{0}\002 changed to \002{1}\002."), nc->GetDisplay(), tmp_pass); + else + source.Reply(_("Password for \002{0}\002 changed."), nc->GetDisplay()); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the password of \037account\037 to \037new-password\037.")); + return true; + } +}; + +class CommandNSSetAutoOp : public Command +{ + public: + CommandNSSetAutoOp(Module *creator, const Anope::string &sname = "nickserv/set/autoop", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Sets whether services should set channel status modes on you automatically.")); + this->SetSyntax("{ON | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable autoop for " << na->GetAccount()->GetDisplay(); + nc->SetS("AUTOOP", true); + source.Reply(_("Services will from now on set status modes on \002{0}\002 in channels."), nc->GetDisplay()); + } + else if (param.equals_ci("OFF")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable autoop for " << na->GetAccount()->GetDisplay(); + nc->UnsetS("AUTOOP"); + source.Reply(_("Services will no longer set status modes on \002{0}\002 in channels."), nc->GetDisplay()); + } + else + this->OnSyntaxError(source, "AUTOOP"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Sets whether you will be given your channel status modes automatically when you join a channel." + " Note that depending on channel settings some modes may not get set automatically.")); + return true; + } +}; + +class CommandNSSASetAutoOp : public CommandNSSetAutoOp +{ + public: + CommandNSSASetAutoOp(Module *creator) : CommandNSSetAutoOp(creator, "nickserv/saset/autoop", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Sets whether the given nickname will be given their status modes automatically when they join a channel." + " Note that depending on channel settings some modes may not get set automatically.")); + return true; + } +}; + +class CommandNSSetDisplay : public Command +{ + public: + CommandNSSetDisplay(Module *creator, const Anope::string &sname = "nickserv/set/display", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Set the display of your group in Services")); + this->SetSyntax(_("\037new-display\037")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *user_na = NickServ::FindNick(user), *na = NickServ::FindNick(param); + + if (Config->GetModule("nickserv")->Get("nonicknameownership")) + { + source.Reply(_("This command may not be used on this network because nickname ownership is disabled.")); + return; + } + + if (user_na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + + if (!na || na->GetAccount() != user_na->GetAccount()) + { + source.Reply(_("The new display must be a nickname of the nickname group \002{0}\02."), user_na->GetAccount()->GetDisplay()); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, user_na->GetAccount(), param); + if (MOD_RESULT == EVENT_STOP) + return; + + Log(user_na->GetAccount() == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the display of " << user_na->GetAccount()->GetDisplay() << " to " << na->GetNick(); + + user_na->GetAccount()->SetDisplay(na); + if (source.GetUser()) + IRCD->SendLogin(source.GetUser(), na); + source.Reply(_("The new display is now \002{0}\002."), user_na->GetAccount()->GetDisplay()); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the display used to refer to your nickname group in services. The new display nickname must be a nickname of your group.")); + return true; + } +}; + +class CommandNSSASetDisplay : public CommandNSSetDisplay +{ + public: + CommandNSSASetDisplay(Module *creator) : CommandNSSetDisplay(creator, "nickserv/saset/display", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 \037new-display\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the display used to refer to the nickname group \037account\037 in services. The new display nickname must be a nickname in the group of \037account\037.")); + return true; + } +}; + +class CommandNSSetEmail : public Command +{ + static bool SendConfirmMail(User *u, ServiceBot *bi, const Anope::string &new_email) + { + Anope::string code = Anope::Random(9); + + u->Account()->Extend >("ns_set_email", std::make_pair(new_email, code)); + + Anope::string subject = Config->GetBlock("mail")->Get("emailchange_subject"), + message = Config->GetBlock("mail")->Get("emailchange_message"); + + subject = subject.replace_all_cs("%e", u->Account()->GetEmail()); + subject = subject.replace_all_cs("%E", new_email); + subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); + subject = subject.replace_all_cs("%c", code); + + message = message.replace_all_cs("%e", u->Account()->GetEmail()); + message = message.replace_all_cs("%E", new_email); + message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); + message = message.replace_all_cs("%c", code); + + Anope::string old = u->Account()->GetEmail(); + u->Account()->SetEmail(new_email); + bool b = Mail::Send(u, u->Account(), bi, subject, message); + u->Account()->SetEmail(old); + return b; + } + + public: + CommandNSSetEmail(Module *creator, const Anope::string &cname = "nickserv/set/email", size_t min = 0) : Command(creator, cname, min, min + 1) + { + this->SetDesc(_("Associate an E-mail address with your nickname")); + this->SetSyntax(_("\037address\037")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + if (nc->HasFieldS("UNCONFIRMED")) + { + source.Reply(_("You may not change the email of an unconfirmed account.")); + return; + } + + if (param.empty() && Config->GetModule("nickserv")->Get("forceemail", "yes")) + { + source.Reply(_("You cannot unset the e-mail on this network.")); + return; + } + + if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && source.nc != nc && nc->IsServicesOper()) + { + source.Reply(_("You may not change the e-mail of other Services Operators.")); + return; + } + + if (!param.empty() && !Mail::Validate(param)) + { + source.Reply(_("\002{0}\002 is not a valid e-mail address."), param); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.empty()) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to unset the email of " << nc->GetDisplay(); + nc->SetEmail(""); + source.Reply(_("E-mail address for \002{0}\002 unset."), nc->GetDisplay()); + } + else if (Config->GetModule("nickserv")->Get("confirmemailchanges") && !source.IsServicesOper()) + { + if (SendConfirmMail(source.GetUser(), source.service, param)) + source.Reply(_("A confirmation e-mail has been sent to \002{0}\002. Follow the instructions in it to change your e-mail address."), param); + } + else + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the email of " << nc->GetDisplay() << " to " << param; + nc->SetEmail(param); + source.Reply(_("E-mail address for \002{0}\002 changed to \002{1}\002."), nc->GetDisplay(), param); + } + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params.size() ? params[0] : ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes your email address to \037address\037.")); + return true; + } +}; + +class CommandNSSASetEmail : public CommandNSSetEmail +{ + public: + CommandNSSASetEmail(Module *creator) : CommandNSSetEmail(creator, "nickserv/saset/email", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 \037address\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params.size() > 1 ? params[1] : ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the email address of \037account\037 to \037address\037.")); + return true; + } +}; + +class CommandNSSetKeepModes : public Command +{ + public: + CommandNSSetKeepModes(Module *creator, const Anope::string &sname = "nickserv/set/keepmodes", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Enable or disable keep modes")); + this->SetSyntax("{ON | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable keepmodes for " << nc->GetDisplay(); + nc->SetS("NS_KEEP_MODES", true); + source.Reply(_("Keep modes for \002{0}\002 is now \002on\002."), nc->GetDisplay()); + } + else if (param.equals_ci("OFF")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable keepmodes for " << nc->GetDisplay(); + nc->UnsetS("NS_KEEP_MODES"); + source.Reply(_("Keep modes for \002{0}\002 is now \002off\002."), nc->GetDisplay()); + } + else + this->OnSyntaxError(source, ""); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables keepmodes for your account. If keepmodes is enabled, services will remember your usermodes and attempt to re-set them the next time you log on.")); + return true; + } +}; + +class CommandNSSASetKeepModes : public CommandNSSetKeepModes +{ + public: + CommandNSSASetKeepModes(Module *creator) : CommandNSSetKeepModes(creator, "nickserv/saset/keepmodes", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Enables or disables keepmodes for \037account\037. If keep modes is enabled, services will remember users' usermodes and attempt to re-set them the next time they log pn.")); + return true; + } +}; + +class CommandNSSetKill : public Command +{ + public: + CommandNSSetKill(Module *creator, const Anope::string &sname = "nickserv/set/kill", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Turn protection on or off")); + this->SetSyntax("{ON | QUICK | IMMED | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + if (Config->GetModule("nickserv")->Get("nonicknameownership")) + { + source.Reply(_("This command may not be used on this network because nickname ownership is disabled.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + nc->SetS("KILLPROTECT", true); + nc->UnsetS("KILL_QUICK"); + nc->UnsetS("KILL_IMMED"); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to set kill on for " << nc->GetDisplay(); + source.Reply(_("Protection is now \002on\002 for \002{0}\002."), nc->GetDisplay()); + } + else if (param.equals_ci("QUICK")) + { + nc->SetS("KILLPROTECT", true); + nc->SetS("KILL_QUICK", true); + nc->UnsetS("KILL_IMMED"); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to set kill quick for " << nc->GetDisplay(); + source.Reply(_("Protection is now \002on\002 for \002{0}\002, with a reduced delay."), nc->GetDisplay()); + } + else if (param.equals_ci("IMMED")) + { + if (Config->GetModule(this->owner)->Get("allowkillimmed")) + { + nc->SetS("KILLPROTECT",true); + nc->UnsetS("KILL_QUICK"); + nc->SetS("KILL_IMMED", true); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to set kill immed for " << nc->GetDisplay(); + source.Reply(_("Protection is now \002on\002 for \002{0}\002, with no delay."), nc->GetDisplay()); + } + else + source.Reply(_("The \002IMMED\002 option is not available on this network.")); + } + else if (param.equals_ci("OFF")) + { + nc->UnsetS("KILLPROTECT"); + nc->UnsetS("KILL_QUICK"); + nc->UnsetS("KILL_IMMED"); + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable kill for " << nc->GetDisplay(); + source.Reply(_("Protection is now \002off\002 for \002{0}\002."), nc->GetDisplay()); + } + else + this->OnSyntaxError(source, "KILL"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Turns the automatic protection option for your account on or off." + " With protection on, if another user tries to use one of your nicknames, they will be given {0} to change to another their nickname, after which {1} will forcibly change their nickname.\n" + "\n" + "If you select \002QUICK\002, the user will be given only {1} to change their nick instead {0}." + " If you select \002IMMED\002, the user's nickname will be changed immediately \037without\037 being warned first or given a chance to change their nick." + " With this set, the only way to use the nickname is to match an entry on the account's access list.")); + return true; + } +}; + +class CommandNSSASetKill : public CommandNSSetKill +{ + public: + CommandNSSASetKill(Module *creator) : CommandNSSetKill(creator, "nickserv/saset/kill", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 {ON | QUICK | IMMED | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Turns the automatic protection option for \037account\037 on or off." + " With protection on, if another user tries to use one of the nicknames in the group, they will be given {0} to change to another their nickname, after which {1} will forcibly change their nickname.\n" + "\n" + "If you select \002QUICK\002, the user will be given only {1} to change their nick instead {0}." + " If you select \002IMMED\002, the user's nickname will be changed immediately \037without\037 being warned first or given a chance to change their nick." + " With this set, the only way to use the nickname is to match an entry on the account's access list.")); + return true; + } +}; + +class CommandNSSetLanguage : public Command +{ + public: + CommandNSSetLanguage(Module *creator, const Anope::string &sname = "nickserv/set/language", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Set the language Services will use when messaging you")); + this->SetSyntax(_("\037language\037")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param != "en_US") + for (unsigned j = 0; j < Language::Languages.size(); ++j) + { + if (Language::Languages[j] == param) + break; + else if (j + 1 == Language::Languages.size()) + { + this->OnSyntaxError(source, ""); + return; + } + } + + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the language of " << nc->GetDisplay() << " to " << param; + + nc->SetLanguage(param); + source.Reply(_("Language changed to \002English\002.")); + } + + void Execute(CommandSource &source, const std::vector ¶m) override + { + this->Run(source, source.nc->GetDisplay(), param[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the language services will use when sending messages to you (for example, when responding to a command you send). \037language\037 should be chosen from the following list of supported languages:")); + + source.Reply(" en_US (English)"); + for (unsigned j = 0; j < Language::Languages.size(); ++j) + { + const Anope::string &langname = Language::Translate(Language::Languages[j].c_str(), _("English")); + if (langname == "English") + continue; + source.Reply(" %s (%s)", Language::Languages[j].c_str(), langname.c_str()); + } + + return true; + } +}; + +class CommandNSSASetLanguage : public CommandNSSetLanguage +{ + public: + CommandNSSASetLanguage(Module *creator) : CommandNSSetLanguage(creator, "nickserv/saset/language", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 \037language\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Changes the language services will use when sending messages to the given user (for example, when responding to a command they send). \037language\037 should be chosen from the following list of supported languages:")); + source.Reply(" en (English)"); + for (unsigned j = 0; j < Language::Languages.size(); ++j) + { + const Anope::string &langname = Language::Translate(Language::Languages[j].c_str(), _("English")); + if (langname == "English") + continue; + source.Reply(" %s (%s)", Language::Languages[j].c_str(), langname.c_str()); + } + return true; + } +}; + +class CommandNSSetMessage : public Command +{ + public: + CommandNSSetMessage(Module *creator, const Anope::string &sname = "nickserv/set/message", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Change the communication method of Services")); + this->SetSyntax("{ON | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + if (!Config->GetBlock("options")->Get("useprivmsg")) + { + source.Reply(_("You cannot %s on this network."), source.command); + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable " << source.command << " for " << nc->GetDisplay(); + nc->SetS("MSG", true); + source.Reply(_("Services will now reply to \002{0}\002 with \002messages\002."), nc->GetDisplay()); + } + else if (param.equals_ci("OFF")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable " << source.command << " for " << nc->GetDisplay(); + nc->UnsetS("MSG"); + source.Reply(_("Services will now reply to \002{0}\002 with \002notices\002."), nc->GetDisplay()); + } + else + this->OnSyntaxError(source, "MSG"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + Anope::string cmd = source.command; + size_t i = cmd.find_last_of(' '); + if (i != Anope::string::npos) + cmd = cmd.substr(i + 1); + + source.Reply(_("Allows you to choose the way services communicate with you. With \002{0}\002 set, services will use messages instead of notices."), cmd); + return true; + } + + void OnServHelp(CommandSource &source) override + { + if (!Config->GetBlock("options")->Get("useprivmsg")) + Command::OnServHelp(source); + } +}; + +class CommandNSSASetMessage : public CommandNSSetMessage +{ + public: + CommandNSSASetMessage(Module *creator) : CommandNSSetMessage(creator, "nickserv/saset/message", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 {ON | OFF}")); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + Anope::string cmd = source.command; + size_t i = cmd.find_last_of(' '); + if (i != Anope::string::npos) + cmd = cmd.substr(i + 1); + + source.Reply(_("Allows you to choose the way services communicate with the given user. With \002{0}\002 set, services will use messages instead of notices."), cmd); + return true; + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params[1]); + } +}; + +class CommandNSSetSecure : public Command +{ + public: + CommandNSSetSecure(Module *creator, const Anope::string &sname = "nickserv/set/secure", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Turn nickname security on or off")); + this->SetSyntax("{ON | OFF}"); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + if (param.equals_ci("ON")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to enable secure for " << nc->GetDisplay(); + nc->SetS("NS_SECURE", true); + source.Reply(_("Secure option is now \002on\002 for \002{0}\002."), nc->GetDisplay()); + } + else if (param.equals_ci("OFF")) + { + Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to disable secure for " << nc->GetDisplay(); + nc->UnsetS("NS_SECURE"); + source.Reply(_("Secure option is now \002off\002 for \002{0}\002."), nc->GetDisplay()); + } + else + this->OnSyntaxError(source, "SECURE"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Turns the security feature on or off for your account." + " With \002SECURE\002 set, you must enter your password before you will be recognized as the owner of the account," + " regardless of whether your address is on the access list or not." + " However, if you are on the access list, services will not force you to change your nickname, regardless of the setting of the \002kill\002 option.")); + return true; + } +}; + +class CommandNSSASetSecure : public CommandNSSetSecure +{ + public: + CommandNSSASetSecure(Module *creator) : CommandNSSetSecure(creator, "nickserv/saset/secure", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037account\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Turns the security feature on or off for \037account\037." + " With \002SECURE\002 set, you the user must enter their password before they will be recognized as the owner of the account," + " regardless of whether their address address is on the access list or not." + " However, if they are on the access list, services will not force them to change your nickname, regardless of the setting of the \002kill\002 option.")); + return true; + } +}; + +class CommandNSSASetNoexpire : public Command +{ + public: + CommandNSSASetNoexpire(Module *creator) : Command(creator, "nickserv/saset/noexpire", 1, 2) + { + this->SetDesc(_("Prevent the nickname from expiring")); + this->SetSyntax(_("\037nickname\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + const Anope::string &user = params[0]; + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + + Anope::string param = params.size() > 1 ? params[1] : ""; + + if (param.equals_ci("ON")) + { + Log(LOG_ADMIN, source, this) << "to enable noexpire for " << na->GetAccount()->GetDisplay(); + na->SetS("NS_NO_EXPIRE", true); + source.Reply(_("\002{0}\002 \002will not\002 expire."), na->GetNick()); + } + else if (param.equals_ci("OFF")) + { + Log(LOG_ADMIN, source, this) << "to disable noexpire for " << na->GetAccount()->GetDisplay(); + na->UnsetS("NS_NO_EXPIRE"); + source.Reply(_("\002{0}\002 \002will\002 expire."), na->GetNick()); + } + else + this->OnSyntaxError(source, "NOEXPIRE"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + source.Reply(_("Sets whether the given nickname will expire. Setting this to \002ON\002 prevents the nickname from expiring.")); + return true; + } +}; + +class NSSet : public Module + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + CommandNSSet commandnsset; + CommandNSSASet commandnssaset; + + CommandNSSetAutoOp commandnssetautoop; + CommandNSSASetAutoOp commandnssasetautoop; + + CommandNSSetDisplay commandnssetdisplay; + CommandNSSASetDisplay commandnssasetdisplay; + + CommandNSSetEmail commandnssetemail; + CommandNSSASetEmail commandnssasetemail; + + CommandNSSetKeepModes commandnssetkeepmodes; + CommandNSSASetKeepModes commandnssasetkeepmodes; + + CommandNSSetKill commandnssetkill; + CommandNSSASetKill commandnssasetkill; + + CommandNSSetLanguage commandnssetlanguage; + CommandNSSASetLanguage commandnssasetlanguage; + + CommandNSSetMessage commandnssetmessage; + CommandNSSASetMessage commandnssasetmessage; + + CommandNSSetPassword commandnssetpassword; + CommandNSSASetPassword commandnssasetpassword; + + CommandNSSetSecure commandnssetsecure; + CommandNSSASetSecure commandnssasetsecure; + + CommandNSSASetNoexpire commandnssasetnoexpire; + + Serialize::Field autoop, keep_modes, killprotect, kill_quick, kill_immed, message, secure; + Serialize::Field noexpire; + + /* email, passcode */ + ExtensibleItem > ns_set_email; + + EventHandlers onsetnickoption; + + public: + NSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsset(this) + , commandnssaset(this) + , commandnssetautoop(this) + , commandnssasetautoop(this) + , commandnssetdisplay(this) + , commandnssasetdisplay(this) + , commandnssetemail(this) + , commandnssasetemail(this) + , commandnssetkeepmodes(this) + , commandnssasetkeepmodes(this) + , commandnssetkill(this) + , commandnssasetkill(this) + , commandnssetlanguage(this) + , commandnssasetlanguage(this) + , commandnssetmessage(this) + , commandnssasetmessage(this) + , commandnssetpassword(this) + , commandnssasetpassword(this) + , commandnssetsecure(this) + , commandnssasetsecure(this) + , commandnssasetnoexpire(this) + + , autoop(this, NickServ::account, "AUTOOP") + , keep_modes(this, NickServ::account, "NS_KEEP_MODES") + , killprotect(this, NickServ::account, "KILLPROTECT") + , kill_quick(this, NickServ::account, "KILL_QUICK") + , kill_immed(this, NickServ::account, "KILL_IMMED") + , message(this, NickServ::account, "MSG") + , secure(this, NickServ::account, "NS_SECURE") + , noexpire(this, NickServ::nick, "NS_NO_EXPIRE") + + , ns_set_email(this, "ns_set_email") + + , onsetnickoption(this) + { + + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override + { + NickServ::Account *uac = source.nc; + + if (command->name == "nickserv/confirm" && !params.empty() && uac) + { + std::pair *n = ns_set_email.Get(uac); + if (n) + { + if (params[0] == n->second) + { + uac->SetEmail(n->first); + Log(LOG_COMMAND, source, command) << "to confirm their email address change to " << uac->GetEmail(); + source.Reply(_("Your email address has been changed to \002%s\002."), uac->GetEmail().c_str()); + ns_set_email.Unset(uac); + return EVENT_STOP; + } + } + } + + return EVENT_CONTINUE; + } + + void OnSetCorrectModes(User *user, Channel *chan, ChanServ::AccessGroup &access, bool &give_modes, bool &take_modes) override + { + if (chan->ci) + { + /* Only give modes if autoop is set */ + give_modes &= !user->Account() || autoop.HasExt(user->Account()); + } + } + + void OnPreNickExpire(NickServ::Nick *na, bool &expire) override + { + if (noexpire.HasExt(na)) + expire = false; + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override + { + if (!show_hidden) + return; + + if (kill_immed.HasExt(na->GetAccount())) + info.AddOption(_("Immediate protection")); + else if (kill_quick.HasExt(na->GetAccount())) + info.AddOption(_("Quick protection")); + else if (killprotect.HasExt(na->GetAccount())) + info.AddOption(_("Protection")); + if (secure.HasExt(na->GetAccount())) + info.AddOption(_("Security")); + if (message.HasExt(na->GetAccount())) + info.AddOption(_("Message mode")); + if (autoop.HasExt(na->GetAccount())) + info.AddOption(_("Auto-op")); + if (noexpire.HasExt(na)) + info.AddOption(_("No expire")); + if (keep_modes.HasExt(na->GetAccount())) + info.AddOption(_("Keep modes")); + } + + void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override + { + if (u->Account() && setter.GetUser() == u && NickServ::mode) + { + NickServ::Mode *m = NickServ::mode.Create(); + m->SetAccount(u->Account()); + m->SetMode(mname); + } + } + + void OnUserModeUnset(const MessageSource &setter, User *u, const Anope::string &mname) override + { + if (u->Account() && setter.GetUser() == u) + { + for (NickServ::Mode *m : u->Account()->GetRefs(NickServ::mode)) + if (m->GetMode() == mname) + m->Delete(); + } + } + + void OnUserLogin(User *u) override + { + if (keep_modes.HasExt(u->Account())) + for (NickServ::Mode *mode : u->Account()->GetRefs(NickServ::mode)) + { + UserMode *um = ModeManager::FindUserModeByName(mode->GetMode()); + /* if the null user can set the mode, then it's probably safe */ + if (um && um->CanSet(NULL)) + u->SetMode(NULL, mode->GetMode()); + } + } +}; + +MODULE_INIT(NSSet) diff --git a/modules/nickserv/set_misc.cpp b/modules/nickserv/set_misc.cpp new file mode 100644 index 000000000..5d03797b1 --- /dev/null +++ b/modules/nickserv/set_misc.cpp @@ -0,0 +1,225 @@ +/* + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_set_misc.h" +#include "modules/ns_info.h" +#include "modules/ns_set.h" + +static Anope::map descriptions; + +class NSMiscDataImpl : public NSMiscData +{ + public: + NSMiscDataImpl(Serialize::TypeBase *type) : NSMiscData(type) { } + NSMiscDataImpl(Serialize::TypeBase *type, Serialize::ID id) : NSMiscData(type, id) { } + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *s) override; + + Anope::string GetName() override; + void SetName(const Anope::string &n) override; + + Anope::string GetData() override; + void SetData(const Anope::string &d) override; +}; + +class NSMiscDataType : public Serialize::Type +{ + public: + Serialize::ObjectField owner; + Serialize::Field name, data; + + NSMiscDataType(Module *me) : Serialize::Type(me, "NSMiscData") + , owner(this, "nc", true) + , name(this, "name") + , data(this, "data") + { + } +}; + +NickServ::Account *NSMiscDataImpl::GetAccount() +{ + return Get(&NSMiscDataType::owner); +} + +void NSMiscDataImpl::SetAccount(NickServ::Account *s) +{ + Set(&NSMiscDataType::owner, s); +} + +Anope::string NSMiscDataImpl::GetName() +{ + return Get(&NSMiscDataType::name); +} + +void NSMiscDataImpl::SetName(const Anope::string &n) +{ + Set(&NSMiscDataType::name, n); +} + +Anope::string NSMiscDataImpl::GetData() +{ + return Get(&NSMiscDataType::data); +} + +void NSMiscDataImpl::SetData(const Anope::string &d) +{ + Set(&NSMiscDataType::data, d); +} + +class CommandNSSetMisc : public Command +{ + Anope::string GetAttribute(const Anope::string &command) + { + size_t sp = command.rfind(' '); + if (sp != Anope::string::npos) + return command.substr(sp + 1); + return command; + } + + public: + CommandNSSetMisc(Module *creator, const Anope::string &cname = "nickserv/set/misc", size_t min = 0) : Command(creator, cname, min, min + 1) + { + this->SetSyntax(_("[\037parameter\037]")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + if (Anope::ReadOnly) + { + source.Reply(_("Services are in read-only mode.")); + return; + } + + NickServ::Nick *na = NickServ::FindNick(user); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), user); + return; + } + NickServ::Account *nc = na->GetAccount(); + + EventReturn MOD_RESULT = Event::OnSetNickOption(&Event::SetNickOption::OnSetNickOption, source, this, nc, param); + if (MOD_RESULT == EVENT_STOP) + return; + + Anope::string scommand = GetAttribute(source.command); + + /* remove existing */ + for (NSMiscData *data : nc->GetRefs(nsmiscdata)) + if (data->GetName() == scommand) + { + data->Delete(); + break; + } + + if (!param.empty()) + { + NSMiscData *data = nsmiscdata.Create(); + data->SetAccount(nc); + data->SetName(scommand); + data->SetData(param); + + source.Reply(_("\002{0}\002 for \002{1}\002 set to \002{2}\002."), scommand, nc->GetDisplay(), param); + } + else + { + source.Reply(_("\002{0}\002 for \002{1}\002 unset."), scommand, nc->GetDisplay()); + } + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, source.nc->GetDisplay(), !params.empty() ? params[0] : ""); + } + + void OnServHelp(CommandSource &source) override + { + if (descriptions.count(source.command)) + { + this->SetDesc(descriptions[source.command]); + Command::OnServHelp(source); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (descriptions.count(source.command)) + { + source.Reply(Language::Translate(source.nc, descriptions[source.command].c_str())); + return true; + } + return false; + } +}; + +class CommandNSSASetMisc : public CommandNSSetMisc +{ + public: + CommandNSSASetMisc(Module *creator) : CommandNSSetMisc(creator, "nickserv/saset/misc", 1) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 [\037parameter\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + this->Run(source, params[0], params.size() > 1 ? params[1] : ""); + } +}; + +class NSSetMisc : public Module + , public EventHook +{ + CommandNSSetMisc commandnssetmisc; + CommandNSSASetMisc commandnssasetmisc; + NSMiscDataType type; + + public: + NSSetMisc(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnssetmisc(this) + , commandnssasetmisc(this) + , type(this) + { + } + + void OnReload(Configuration::Conf *conf) override + { + descriptions.clear(); + + for (int i = 0; i < conf->CountBlock("command"); ++i) + { + Configuration::Block *block = conf->GetBlock("command", i); + + const Anope::string &cmd = block->Get("command"); + + if (cmd != "nickserv/set/misc" && cmd != "nickserv/saset/misc") + continue; + + Anope::string cname = block->Get("name"); + Anope::string desc = block->Get("misc_description"); + + if (cname.empty() || desc.empty()) + continue; + + descriptions[cname] = desc; + } + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool) override + { + for (NSMiscData *data : na->GetAccount()->GetRefs(nsmiscdata)) + info[data->GetName()] = data->GetData(); + } +}; + +MODULE_INIT(NSSetMisc) diff --git a/modules/nickserv/status.cpp b/modules/nickserv/status.cpp new file mode 100644 index 000000000..7dc751d8b --- /dev/null +++ b/modules/nickserv/status.cpp @@ -0,0 +1,88 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandNSStatus : public Command +{ + public: + CommandNSStatus(Module *creator) : Command(creator, "nickserv/status", 0, 16) + { + this->SetDesc(_("Returns the owner status of the given nickname")); + this->SetSyntax(_("[\037nickname\037]")); + this->AllowUnregistered(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &nick = !params.empty() ? params[0] : source.GetNick(); + const NickServ::Nick *na = NickServ::FindNick(nick); + spacesepstream sep(nick); + Anope::string nickbuf; + + while (sep.GetToken(nickbuf)) + { + #if 0 + User *u2 = User::Find(nickbuf, true); + if (!u2) /* Nick is not online */ + source.Reply("STATUS %s %d %s", nickbuf.c_str(), 0, ""); + else if (u2->IsIdentified() && na && na->GetAccount() == u2->Account()) /* Nick is identified */ + source.Reply("STATUS %s %d %s", nickbuf.c_str(), 3, u2->Account()->GetDisplay().c_str()); + else if (u2->IsRecognized()) /* Nick is recognised, but NOT identified */ + source.Reply("STATUS %s %d %s", nickbuf.c_str(), 2, u2->Account() ? u2->Account()->GetDisplay().c_str() : ""); + else if (!na) /* Nick is online, but NOT a registered */ + source.Reply("STATUS %s %d %s", nickbuf.c_str(), 0, ""); + else + /* Nick is not identified for the nick, but they could be logged into an account, + * so we tell the user about it + */ + source.Reply("STATUS %s %d %s", nickbuf.c_str(), 1, u2->Account() ? u2->Account()->GetDisplay().c_str() : ""); + #endif + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Returns whether the user using the given nickname is\n" + "recognized as the owner of the nickname. The response has\n" + "this format:\n" + " \n" + " \037nickname\037 \037status-code\037 \037account\037\n" + " \n" + "where \037nickname\037 is the nickname sent with the command,\n" + "\037status-code\037 is one of the following, and \037account\037\n" + "is the account they are logged in as.\n" + " \n" + " 0 - no such user online \002or\002 nickname not registered\n" + " 1 - user not recognized as nickname's owner\n" + " 2 - user recognized as owner via access list only\n" + " 3 - user recognized as owner via password identification\n" + " \n" + "If no nickname is given, your status will be returned.")); + return true; + } +}; + +class NSStatus : public Module +{ + CommandNSStatus commandnsstatus; + + public: + NSStatus(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsstatus(this) + { + throw ModuleException("Remind Adam to fix this"); + } +}; + +MODULE_INIT(NSStatus) diff --git a/modules/nickserv/suspend.cpp b/modules/nickserv/suspend.cpp new file mode 100644 index 000000000..2859eacc1 --- /dev/null +++ b/modules/nickserv/suspend.cpp @@ -0,0 +1,339 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_suspend.h" +#include "modules/ns_info.h" +#include "modules/nickserv.h" + +class NSSuspendInfoImpl : public NSSuspendInfo +{ + public: + NSSuspendInfoImpl(Serialize::TypeBase *type) : NSSuspendInfo(type) { } + NSSuspendInfoImpl(Serialize::TypeBase *type, Serialize::ID id) : NSSuspendInfo(type, id) { } + + NickServ::Account *GetAccount() override; + void SetAccount(NickServ::Account *) override; + + Anope::string GetBy() override; + void SetBy(const Anope::string &by) override; + + Anope::string GetReason() override; + void SetReason(const Anope::string &reason) override; + + time_t GetWhen() override; + void SetWhen(const time_t &w) override; + + time_t GetExpires() override; + void SetExpires(const time_t &e) override; +}; + +class NSSuspendType : public Serialize::Type +{ + public: + Serialize::ObjectField account; + Serialize::Field by, reason; + Serialize::Field when, expires; + + NSSuspendType(Module *me) : Serialize::Type(me, "NSSuspendInfo") + , account(this, "nick", true) + , by(this, "by") + , reason(this, "reason") + , when(this, "time") + , expires(this, "expires") + { + } +}; + +NickServ::Account *NSSuspendInfoImpl::GetAccount() +{ + return Get(&NSSuspendType::account); +} + +void NSSuspendInfoImpl::SetAccount(NickServ::Account *s) +{ + Set(&NSSuspendType::account, s); +} + +Anope::string NSSuspendInfoImpl::GetBy() +{ + return Get(&NSSuspendType::by); +} + +void NSSuspendInfoImpl::SetBy(const Anope::string &by) +{ + Set(&NSSuspendType::by, by); +} + +Anope::string NSSuspendInfoImpl::GetReason() +{ + return Get(&NSSuspendType::reason); +} + +void NSSuspendInfoImpl::SetReason(const Anope::string &reason) +{ + Set(&NSSuspendType::reason, reason); +} + +time_t NSSuspendInfoImpl::GetWhen() +{ + return Get(&NSSuspendType::when); +} + +void NSSuspendInfoImpl::SetWhen(const time_t &w) +{ + Set(&NSSuspendType::when, w); +} + +time_t NSSuspendInfoImpl::GetExpires() +{ + return Get(&NSSuspendType::expires); +} + +void NSSuspendInfoImpl::SetExpires(const time_t &e) +{ + Set(&NSSuspendType::expires, e); +} + +class CommandNSSuspend : public Command +{ + EventHandlers &onnicksuspend; + + public: + CommandNSSuspend(Module *creator, EventHandlers &event) : Command(creator, "nickserv/suspend", 2, 3), onnicksuspend(event) + { + this->SetDesc(_("Suspend a given nick")); + this->SetSyntax(_("\037account\037 [+\037expiry\037] [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + + const Anope::string &nick = params[0]; + Anope::string expiry = params[1]; + Anope::string reason = params.size() > 2 ? params[2] : ""; + time_t expiry_secs = Config->GetModule(this->owner)->Get("suspendexpire"); + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + if (expiry[0] != '+') + { + reason = expiry + " " + reason; + reason.trim(); + expiry.clear(); + } + else + { + expiry_secs = Anope::DoTime(expiry); + if (expiry_secs == -1) + { + source.Reply(_("Invalid expiry time \002{0}\002."), expiry); + return; + } + } + + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + if (Config->GetModule("nickserv")->Get("secureadmins", "yes") && na->GetAccount()->IsServicesOper()) + { + source.Reply(_("You may not suspend other Services Operators' nicknames.")); + return; + } + + NSSuspendInfo *si = na->GetAccount()->GetRef(nssuspendinfo); + if (!si) + { + source.Reply(_("\002%s\002 is already suspended."), na->GetAccount()->GetDisplay().c_str()); + return; + } + + NickServ::Account *nc = na->GetAccount(); + + si = nssuspendinfo.Create(); + si->SetAccount(nc); + si->SetBy(source.GetNick()); + si->SetReason(reason); + si->SetWhen(Anope::CurTime); + si->SetExpires(expiry_secs ? expiry_secs + Anope::CurTime : 0); + + for (NickServ::Nick *na2 : nc->GetRefs(NickServ::nick)) + { + na2->SetLastQuit(reason); + + User *u2 = User::Find(na2->GetNick(), true); + if (u2) + { + u2->Logout(); + if (NickServ::service) + NickServ::service->Collide(u2, na2); + } + } + + Log(LOG_ADMIN, source, this) << "for " << nick << " (" << (!reason.empty() ? reason : "No reason") << "), expires on " << (expiry_secs ? Anope::strftime(Anope::CurTime + expiry_secs) : "never"); + source.Reply(_("\002{0}\002 is now suspended."), na->GetNick()); + + this->onnicksuspend(&Event::NickSuspend::OnNickSuspend, na); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Suspends \037account\037, which prevents it from being used while keeping all the data for it." + " If an expiry is given the account will be unsuspended after that period of time, otherwise the default expiry from the configuration is used.")); // XXX + return true; + } +}; + +class CommandNSUnSuspend : public Command +{ + EventHandlers &onnickunsuspend; + + public: + CommandNSUnSuspend(Module *creator, EventHandlers &event) : Command(creator, "nickserv/unsuspend", 1, 1), onnickunsuspend(event) + { + this->SetDesc(_("Unsuspend a given nick")); + this->SetSyntax(_("\037account\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &nick = params[0]; + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + NickServ::Nick *na = NickServ::FindNick(nick); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), nick); + return; + } + + NSSuspendInfo *si = na->GetAccount()->GetRef(nssuspendinfo); + if (!si) + { + source.Reply(_("\002{0}\002 is not suspended."), na->GetNick()); + return; + } + + Log(LOG_ADMIN, source, this) << "for " << na->GetNick() << " which was suspended by " << (!si->GetBy().empty() ? si->GetBy() : "(none)") << " for: " << (!si->GetReason().empty() ? si->GetReason() : "No reason"); + + si->Delete(); + + source.Reply(_("\002{0}\002 is now released."), na->GetNick()); + + this->onnickunsuspend(&Event::NickUnsuspend::OnNickUnsuspend, na); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Unsuspends \037account\037, which allows it to be used again.")); + return true; + } +}; + +class NSSuspend : public Module + , public EventHook + , public EventHook + , public EventHook +{ + CommandNSSuspend commandnssuspend; + CommandNSUnSuspend commandnsunsuspend; + EventHandlers onnicksuspend; + EventHandlers onnickunsuspend; + std::vector show; + NSSuspendType nst; + + struct trim + { + Anope::string operator()(Anope::string s) const + { + return s.trim(); + } + }; + + bool Show(CommandSource &source, const Anope::string &what) const + { + return source.IsOper() || std::find(show.begin(), show.end(), what) != show.end(); + } + + public: + NSSuspend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnssuspend(this, onnicksuspend) + , commandnsunsuspend(this, onnickunsuspend) + , onnicksuspend(this) + , onnickunsuspend(this) + , nst(this) + { + } + + void OnReload(Configuration::Conf *conf) override + { + Anope::string s = conf->GetModule(this)->Get("show"); + commasepstream(s).GetTokens(show); + std::transform(show.begin(), show.end(), show.begin(), trim()); + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override + { + NSSuspendInfo *s = na->GetAccount()->GetRef(nssuspendinfo); + if (!s) + return; + + if (show_hidden || Show(source, "suspended")) + info[_("Suspended")] = _("This nickname is \002suspended\002."); + if (!s->GetBy().empty() && (show_hidden || Show(source, "by"))) + info[_("Suspended by")] = s->GetBy(); + if (!s->GetReason().empty() && (show_hidden || Show(source, "reason"))) + info[_("Suspend reason")] = s->GetReason(); + if (s->GetWhen() && (show_hidden || Show(source, "on"))) + info[_("Suspended on")] = Anope::strftime(s->GetWhen(), source.GetAccount()); + if (s->GetExpires() && (show_hidden || Show(source, "expires"))) + info[_("Suspension expires")] = Anope::strftime(s->GetExpires(), source.GetAccount()); + } + + void OnPreNickExpire(NickServ::Nick *na, bool &expire) override + { + NSSuspendInfo *s = na->GetAccount()->GetRef(nssuspendinfo); + if (!s) + return; + + expire = false; + + if (!s->GetExpires()) + return; + + if (s->GetExpires() < Anope::CurTime) + { + na->SetLastSeen(Anope::CurTime); + s->Delete(); + + Log(LOG_NORMAL, "nickserv/expire", Config->GetClient("NickServ")) << "Expiring suspend for " << na->GetNick(); + } + } + + EventReturn OnNickValidate(User *u, NickServ::Nick *na) override + { + NSSuspendInfo *s = na->GetAccount()->GetRef(nssuspendinfo); + if (!s) + return EVENT_CONTINUE; + + u->SendMessage(Config->GetClient("NickServ"), _("\002{0}\002 is suspended."), u->nick); + return EVENT_STOP; + } +}; + +MODULE_INIT(NSSuspend) diff --git a/modules/nickserv/update.cpp b/modules/nickserv/update.cpp new file mode 100644 index 000000000..0022f389d --- /dev/null +++ b/modules/nickserv/update.cpp @@ -0,0 +1,67 @@ +/* NickServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/ns_update.h" + +class CommandNSUpdate : public Command +{ + EventHandlers &onnickupdate; + + public: + CommandNSUpdate(Module *creator, EventHandlers &event) : Command(creator, "nickserv/update", 0, 0), onnickupdate(event) + { + this->SetDesc(_("Updates your current status, i.e. it checks for new memos")); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + User *u = source.GetUser(); + NickServ::Nick *na = NickServ::FindNick(u->nick); + + if (na && na->GetAccount() == source.GetAccount()) + { + na->SetLastRealname(u->realname); + na->SetLastSeen(Anope::CurTime); + } + + this->onnickupdate(&Event::NickUpdate::OnNickUpdate, u); + + source.Reply(_("Status updated (memos, vhost, chmodes, flags).")); + } + + bool OnHelp(CommandSource &source, const Anope::string &) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Updates your current status, i.e. it checks for new memos,\n" + "sets needed channel modes and updates your vhost and\n" + "your userflags (lastseentime, etc).")); + return true; + } +}; + +class NSUpdate : public Module +{ + CommandNSUpdate commandnsupdate; + EventHandlers onnickupdate; + + public: + NSUpdate(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandnsupdate(this, onnickupdate) + , onnickupdate(this) + { + + } +}; + +MODULE_INIT(NSUpdate) diff --git a/modules/ns_maxemail.cpp b/modules/ns_maxemail.cpp deleted file mode 100644 index 38054f819..000000000 --- a/modules/ns_maxemail.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* ns_maxemail.cpp - Limit the amount of times an email address - * can be used for a NickServ account. - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Included in the Anope module pack since Anope 1.7.9 - * Anope Coder: GeniusDex - * - * Please read COPYING and README for further details. - */ - -#include "module.h" - -class NSMaxEmail : public Module - , public EventHook -{ - bool CheckLimitReached(CommandSource &source, const Anope::string &email) - { - int NSEmailMax = Config->GetModule(this)->Get("maxemails"); - - if (NSEmailMax < 1 || email.empty()) - return false; - - if (this->CountEmail(email, source.nc) < NSEmailMax) - return false; - - if (NSEmailMax == 1) - source.Reply(_("The email address \002{0}\002 has reached its usage limit of \0021\002 user."), email); - else - source.Reply(_("The email address \002{0}\002 has reached its usage limit of \002{1}\002 users."), email, NSEmailMax); - - return true; - } - - int CountEmail(const Anope::string &email, NickServ::Account *unc) - { - int count = 0; - - if (email.empty()) - return 0; - - for (NickServ::Account *nc : NickServ::service->GetAccountList()) - if (unc != nc && !nc->GetEmail().empty() && nc->GetEmail().equals_ci(email)) - ++count; - - return count; - } - - public: - NSMaxEmail(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - { - } - - EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override - { - if (source.IsOper()) - return EVENT_CONTINUE; - - if (command->name == "nickserv/register") - { - if (this->CheckLimitReached(source, params.size() > 1 ? params[1] : "")) - return EVENT_STOP; - } - else if (command->name == "nickserv/set/email") - { - if (this->CheckLimitReached(source, params.size() > 0 ? params[0] : "")) - return EVENT_STOP; - } - else if (command->name == "nickserv/ungroup" && source.GetAccount()) - { - if (this->CheckLimitReached(source, source.GetAccount()->GetEmail())) - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } -}; - -MODULE_INIT(NSMaxEmail) diff --git a/modules/operserv/CMakeLists.txt b/modules/operserv/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/operserv/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/operserv/akill.cpp b/modules/operserv/akill.cpp new file mode 100644 index 000000000..7f366e0c9 --- /dev/null +++ b/modules/operserv/akill.cpp @@ -0,0 +1,437 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +static ServiceReference akills("XLineManager", "xlinemanager/sgline"); + +class CommandOSAKill : public Command +{ + private: + void DoAdd(CommandSource &source, const std::vector ¶ms) + { + Anope::string expiry, mask; + + if (params.size() < 2) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + spacesepstream sep(params[1]); + sep.GetToken(mask); + + if (mask[0] == '+') + { + expiry = mask; + sep.GetToken(mask); + } + + time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get("autokillexpiry", "30d"); + /* If the expiry given does not contain a final letter, it's in days, + * said the doc. Ah well. + */ + if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) + expires *= 86400; + /* Do not allow less than a minute expiry time */ + if (expires && expires < 60) + { + source.Reply(_("Invalid expiry time \002{0}\002."), expiry); + return; + } + else if (expires > 0) + expires += Anope::CurTime; + + if (sep.StreamEnd()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + Anope::string reason; + if (mask.find('#') != Anope::string::npos) + { + Anope::string remaining = sep.GetRemaining(); + + size_t co = remaining[0] == ':' ? 0 : remaining.rfind(" :"); + if (co == Anope::string::npos) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + if (co != 0) + ++co; + + reason = remaining.substr(co + 1); + mask += " " + remaining.substr(0, co); + mask.trim(); + } + else + reason = sep.GetRemaining(); + + if (mask[0] == '/' && mask[mask.length() - 1] == '/') + { + if (!Config->regex_flags) + { + source.Reply(_("Regex is disabled.")); + return; + } + + Anope::string stripped_mask = mask.substr(1, mask.length() - 2); + try + { + std::regex(stripped_mask.str(), Config->regex_flags); + } + catch (const std::regex_error &ex) + { + source.Reply("%s", ex.what()); + return; + } + } + + User *targ = User::Find(mask, true); + if (targ) + mask = "*@" + targ->host; + + if (Config->GetModule("operserv")->Get("addakiller", "yes") && !source.GetNick().empty()) + reason = "[" + source.GetNick() + "] " + reason; + + if (!akills->CanAdd(source, mask, expires, reason)) + return; + + if (mask.find_first_not_of("/~@.*?") == Anope::string::npos) + { + source.Reply(_("\002{0}\002 coverage is too wide; Please use a more specific mask."), mask); + return; + } + + if (mask.find('@') == Anope::string::npos) + { + source.Reply(_("Mask must be in the form \037user\037@\037host\037.")); + return; + } + + XLine *x = new XLine(mask, source.GetNick(), expires, reason); + if (Config->GetModule("operserv")->Get("akillids")) + x->SetID(XLineManager::GenerateUID()); + + unsigned int affected = 0; + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + if (akills->Check(it->second, x)) + ++affected; + float percent = static_cast(affected) / static_cast(UserListByNick.size()) * 100.0; + + if (percent > 95) + { + source.Reply(_("\002{0}\002 coverage is too wide; Please use a more specific mask."), mask); + Log(LOG_ADMIN, source, this) << "tried to akill " << percent << "% of the network (" << affected << " users)"; + delete x; + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnAddXLine(&Event::AddXLine::OnAddXLine, source, x, akills); + if (MOD_RESULT == EVENT_STOP) + { + delete x; + return; + } + + akills->AddXLine(x); + if (Config->GetModule("operserv")->Get("akillonadd")) + akills->Send(NULL, x); + + source.Reply(_("\002{0}\002 added to the akill list."), mask); + + Log(LOG_ADMIN, source, this) << "on " << mask << " (" << x->GetReason() << "), expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]"; + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + + void DoDel(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (akills->GetXLines().empty()) + { + source.Reply(_("The akill list is empty.")); + return; + } + + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + unsigned int deleted = 0; + + NumberList(mask, true, + [&](unsigned int number) + { + XLine *x = akills->GetEntry(number - 1); + + if (!x) + return; + + Log(LOG_ADMIN, source, this) << "to remove " << x->GetMask() << " from the list"; + + ++deleted; + x->Delete(); + }, + [&]() + { + if (!deleted) + source.Reply(_("No matching entries on the akill list.")); + else if (deleted == 1) + source.Reply(_("Deleted \0021\002 entry from the akill list.")); + else + source.Reply(_("Deleted \002{0}\002 entries from the akill list."), deleted); + }); + } + else + { + XLine *x = akills->HasEntry(mask); + + if (!x) + { + source.Reply(_("\002{0}\002 was not found on the akill list."), mask); + return; + } + + do + { + Event::OnDelXLine(&Event::DelXLine::OnDelXLine, source, x, akills); + + Log(LOG_ADMIN, source, this) << "to remove " << x->GetMask() << " from the list"; + source.Reply(_("\002{0}\002 deleted from the akill list."), x->GetMask()); + x->Delete(); + } + while ((x = akills->HasEntry(mask))); + + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + + void ProcessList(CommandSource &source, const std::vector ¶ms, ListFormatter &list) + { + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + NumberList(mask, false, + [&](unsigned int number) + { + XLine *x = akills->GetEntry(number - 1); + + if (!x) + return; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(number); + entry["Mask"] = x->GetMask(); + entry["Creator"] = x->GetBy(); + entry["Created"] = Anope::strftime(x->GetCreated(), NULL, true); + entry["Expires"] = Anope::Expires(x->GetExpires(), source.nc); + entry["Reason"] = x->GetReason(); + list.AddEntry(entry); + }, + [&]{}); + } + else + { + unsigned int i = 0; + for (XLine *x : akills->GetXLines()) + { + if (mask.empty() || mask.equals_ci(x->GetMask()) || mask == x->id || Anope::Match(x->GetMask(), mask, false, true)) + { + ListFormatter::ListEntry entry; + entry["Number"] = stringify(++i); + entry["Mask"] = x->GetMask(); + entry["Creator"] = x->GetBy(); + entry["Created"] = Anope::strftime(x->GetCreated(), NULL, true); + entry["Expires"] = Anope::Expires(x->GetExpires(), source.nc); + entry["Reason"] = x->GetReason(); + list.AddEntry(entry); + } + } + } + + if (list.IsEmpty()) + { + source.Reply(_("There are no matching entries on the akill list.")); + return; + } + + source.Reply(_("Current akill list:")); + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of akill list.")); + } + + void DoList(CommandSource &source, const std::vector ¶ms) + { + if (akills->GetXLines().empty()) + { + source.Reply(_("The akill list is empty.")); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Reason")); + + this->ProcessList(source, params, list); + } + + void DoView(CommandSource &source, const std::vector ¶ms) + { + if (akills->GetXLines().empty()) + { + source.Reply(_("The akill list is empty.")); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Expires")).AddColumn(_("Reason")); + + this->ProcessList(source, params, list); + } + + void DoClear(CommandSource &source) + { + for (XLine *x : akills->GetXLines()) + { + Event::OnDelXLine(&Event::DelXLine::OnDelXLine, source, x, akills); + x->Delete(); + } + + Log(LOG_ADMIN, source, this) << "to CLEAR the list"; + source.Reply(_("The akill list has been cleared.")); + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + public: + CommandOSAKill(Module *creator) : Command(creator, "operserv/akill", 1, 2) + { + this->SetDesc(_("Manipulate the AKILL list")); + this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037 \037reason\037")); + this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}")); + this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]")); + this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]")); + this->SetSyntax("CLEAR"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + + if (!akills) + return; + + if (cmd.equals_ci("ADD")) + return this->DoAdd(source, params); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, params); + else if (cmd.equals_ci("LIST")) + return this->DoList(source, params); + else if (cmd.equals_ci("VIEW")) + return this->DoView(source, params); + else if (cmd.equals_ci("CLEAR")) + return this->DoClear(source); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + { + source.Reply(_("The \002{0} ADD\002 command adds \037mask\037 to the auto kill list with the given \037reason\037." + "\037mask\037 can be in the format of nickname!username@hostname#realname." + " If a real name is specified, the reason must be prefixed with a :." + " \037expiry\037 is optional, and if specified must be an integer followed by one of \037d\037 (days), \037h\037 (hours), or \037m\037 (minutes)." + " If a unit specifier is not included, the default is days, so \037+30\037 means 30 days." + " To add an auto kill which does not expire, use \037+0\037. " + " The default auto kill expiry time is \002{1}\002"), + source.command, Anope::Duration(Config->GetModule("operserv")->Get("autokillexpiry", "30d"), source.GetAccount())); + + const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); + if (!regexengine.empty()) + { + source.Reply(" "); + source.Reply(_("Regex matches are also supported using the %s engine. Enclose your mask in // if this is desired."), regexengine); + } + } + else if (subcommand.equals_ci("DEL")) + source.Reply(_("The \002{0} DEL\002 command removes the given \037mask\037 from the auto kill list, if present." + " If a list of entry numbers is given, those entries are deleted."), + source.command); + else if (subcommand.equals_ci("LIST") || subcommand.equals_ci("VIEW")) + source.Reply(_("The \002{0} LIST\002 and \002{0} VIEW\002 commands display the auto kill list." + " If a wildcard \037mask\037 is given, only those entries matching the mask are displayed." + " If a list of entry numbers is given, only those entries are shown." + " \002VIEW\002 is similar to \002LIST\002 but also shows who created the auto kill, when it was created, and when it expires.\n" + "\n" + "Example:\n" + "\n" + " {0} LIST 2-5,7-9\n" + " Lists auto kill entries numbered 2 through 5 and 7 through 9."), + source.command); + else if (subcommand.equals_ci("CLEAR")) + source.Reply(_("The \002{0} CLEAR\002 command removes all auto kills from the auto kill list."), + source.command); + else + { + CommandInfo *help = source.service->FindCommand("generic/help"); + source.Reply(_("Allows manipulating the AKILL list." + " If a user matching an AKILL mask connects, services will issue a kill the user and prevent them from reconnecting.\n" + "\n" + "The \002ADD\002 command adds \037mask\037 to the auto kill list.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "The \002DEL\002 command removes \037mask\037 from the auto kill list.\n" + "\002{msg}{service} {help} {command} DEL\002 for more information.\n" + "\n" + "The \002LIST\002 and \002VIEW\002 commands both show the auto kill list, but \002VIEW\002 also shows who created the auto kill entry, when it was created, and when it expires.\n" + "\002{msg}{service} {help} {command} [LIST | VIEW]\002 for more information.\n" + "\n" + "The \002CLEAR\002 command clears th auto kill list." + "\002{msg}{service} {help} {command} CLEAR\002 for more information.\n"), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "help"_kw = help->cname, "command"_kw = source.command); + } + return true; + } +}; + +class OSAKill : public Module +{ + CommandOSAKill commandosakill; + + public: + OSAKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosakill(this) + { + + } +}; + +MODULE_INIT(OSAKill) diff --git a/modules/operserv/chankill.cpp b/modules/operserv/chankill.cpp new file mode 100644 index 000000000..8d3773e9d --- /dev/null +++ b/modules/operserv/chankill.cpp @@ -0,0 +1,113 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +static ServiceReference akills("XLineManager", "xlinemanager/sgline"); + +class CommandOSChanKill : public Command +{ + public: + CommandOSChanKill(Module *creator) : Command(creator, "operserv/chankill", 2, 3) + { + this->SetDesc(_("AKILL all users on a specific channel")); + this->SetSyntax(_("[+\037expiry\037] \037channel\037 \037reason\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (!akills) + return; + + Anope::string expiry, channel; + unsigned last_param = 1; + + channel = params[0]; + if (!channel.empty() && channel[0] == '+') + { + expiry = channel; + channel = params[1]; + last_param = 2; + } + + time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get("autokillexpiry", "30d"); + if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) + expires *= 86400; + + if (expires) + { + if (expires < 60) + { + source.Reply(_("Invalid expiry time \002{0}\002."), expiry); + return; + } + + expires += Anope::CurTime; + } + + if (params.size() <= last_param) + { + this->OnSyntaxError(source, ""); + return; + } + + Anope::string reason = params[last_param]; + if (params.size() > last_param + 1) + reason += params[last_param + 1]; + + Anope::string realreason; + if (Config->GetModule("operserv")->Get("addakiller") && !source.GetNick().empty()) + realreason = "[" + source.GetNick() + "] " + reason; + else + realreason = reason; + + Channel *c = Channel::Find(channel); + if (!c) + { + source.Reply(_("Channel \002{0}\002 doesn't exist."), channel); + return; + } + + for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ++it) + { + ChanUserContainer *uc = it->second; + + if (uc->user->server == Me || uc->user->HasMode("OPER")) + continue; + + XLine *x = new XLine("*@" + uc->user->host, source.GetNick(), expires, realreason, XLineManager::GenerateUID()); + akills->AddXLine(x); + akills->OnMatch(uc->user, x); + } + + Log(LOG_ADMIN, source, this) << "on " << c->name << " (" << realreason << ")"; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Adds an auto kill for every user on \037channel\037, except for IRC operators.")); + return true; + } +}; + +class OSChanKill : public Module +{ + CommandOSChanKill commandoschankill; + + public: + OSChanKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandoschankill(this) + { + + } +}; + +MODULE_INIT(OSChanKill) diff --git a/modules/operserv/config.cpp b/modules/operserv/config.cpp new file mode 100644 index 000000000..257f8fee4 --- /dev/null +++ b/modules/operserv/config.cpp @@ -0,0 +1,147 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSConfig : public Command +{ + public: + CommandOSConfig(Module *creator) : Command(creator, "operserv/config", 1, 4) + { + this->SetDesc(_("View and change configuration file settings")); + this->SetSyntax(_("{\037MODIFY\037|\037VIEW\037} [\037block name\037 \037item name\037 \037item value\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &what = params[0]; + + if (what.equals_ci("MODIFY") && params.size() > 3) + { + if (!source.HasPriv("operserv/config")) + { + source.Reply(_("Access denied. You do not have the operator privilege \002{0}\002."), "operserv/config"); + return; + } + + Configuration::Block *block = Config->GetBlock(params[1]); + if (!block) + block = Config->GetModule(params[1]); + + if (!block) + { + source.Reply(_("There is no such configuration block \002{0}\002."), params[1]); + return; + } + + block->Set(params[2], params[3]); + + Log(LOG_ADMIN, source, this) << "to change the configuration value of " << params[1] << ":" << params[2] << " to " << params[3]; + source.Reply(_("Value of \002{0}:{1}\002 changed to \002{2}\002."), params[1], params[2], params[3]); + } + else if (what.equals_ci("VIEW")) + { + /* Blocks we should show */ + const Anope::string show_blocks[] = { "serverinfo", "networkinfo", "options", "" }; + + Log(LOG_ADMIN, source, this) << "VIEW"; + + for (unsigned i = 0; !show_blocks[i].empty(); ++i) + { + Configuration::Block *block = Config->GetBlock(show_blocks[i]); + const Configuration::Block::item_map *items = block->GetItems(); + + if (!items) + continue; + + ListFormatter lflist(source.GetAccount()); + lflist.AddColumn(_("Name")).AddColumn(_("Value")); + + for (Configuration::Block::item_map::const_iterator it = items->begin(), it_end = items->end(); it != it_end; ++it) + { + ListFormatter::ListEntry entry; + entry["Name"] = it->first; + entry["Value"] = it->second; + lflist.AddEntry(entry); + } + + std::vector replies; + lflist.Process(replies); + + source.Reply(_("%s settings:"), block->GetName()); + + for (unsigned j = 0; j < replies.size(); ++j) + source.Reply(replies[j]); + + source.Reply(" "); + } + + ListFormatter lflist(source.GetAccount()); + lflist.AddColumn(_("Module Name")).AddColumn(_("Name")).AddColumn(_("Value")); + + for (int i = 0; i < Config->CountBlock("module"); ++i) + { + Configuration::Block *block = Config->GetBlock("module", i); + const Configuration::Block::item_map *items = block->GetItems(); + + if (!items || items->size() <= 1) + continue; + + ListFormatter::ListEntry entry; + entry["Module Name"] = block->Get("name"); + + for (Configuration::Block::item_map::const_iterator it = items->begin(), it_end = items->end(); it != it_end; ++it) + { + entry["Name"] = it->first; + entry["Value"] = it->second; + lflist.AddEntry(entry); + } + } + + std::vector replies; + lflist.Process(replies); + + source.Reply(_("Module settings:")); + + for (unsigned j = 0; j < replies.size(); ++j) + source.Reply(replies[j]); + + source.Reply(_("End of configuration.")); + } + else + this->OnSyntaxError(source, what); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows you to change and view configuration settings. Settings changed by this command are temporary and will not be reflected back into the configuration file, and will be lost if Anope is shut down, restarted, or the configuration is reloaded.\n" + "Use of the \002MODIFY\002 command requires the \002{0}\002 operator privilege.\n" + "\n" + "Example:\n" + " {0} MODIFY nickserv forcemail no" + " Changes the \"forceemail\" setting of the module configuration for \"nickserv\" to \"no\"")); + return true; + } +}; + +class OSConfig : public Module +{ + CommandOSConfig commandosconfig; + + public: + OSConfig(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosconfig(this) + { + + } +}; + +MODULE_INIT(OSConfig) diff --git a/modules/operserv/defcon.cpp b/modules/operserv/defcon.cpp new file mode 100644 index 000000000..804b66c27 --- /dev/null +++ b/modules/operserv/defcon.cpp @@ -0,0 +1,619 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/os_session.h" +#include "modules/os_defcon.h" +#include "modules/global.h" + +enum DefconLevel +{ + DEFCON_NO_NEW_CHANNELS, + DEFCON_NO_NEW_NICKS, + DEFCON_NO_MLOCK_CHANGE, + DEFCON_FORCE_CHAN_MODES, + DEFCON_REDUCE_SESSION, + DEFCON_NO_NEW_CLIENTS, + DEFCON_OPER_ONLY, + DEFCON_SILENT_OPER_ONLY, + DEFCON_AKILL_NEW_CLIENTS, + DEFCON_NO_NEW_MEMOS +}; + +bool DefConModesSet = false; + +struct DefconConfig +{ + std::vector > DefCon; + std::set DefConModesOn, DefConModesOff; + std::map DefConModesOnParams; + + int defaultlevel, sessionlimit; + Anope::string chanmodes, message, offmessage, akillreason; + std::vector defcons; + time_t akillexpire, timeout; + bool globalondefcon; + + unsigned max_session_kill; + time_t session_autokill_expiry; + Anope::string sle_reason, sle_detailsloc; + + DefconConfig() + { + this->DefCon.resize(6); + this->defcons.resize(5); + } + + bool Check(DefconLevel level) + { + return this->Check(this->defaultlevel, level); + } + + bool Check(int dlevel, DefconLevel level) + { + return this->DefCon[dlevel].test(level); + } + + void Add(int dlevel, DefconLevel level) + { + this->DefCon[dlevel][level] = true; + } + + void Del(int dlevel, DefconLevel level) + { + this->DefCon[dlevel][level] = false; + } + + bool SetDefConParam(const Anope::string &name, const Anope::string &buf) + { + return DefConModesOnParams.insert(std::make_pair(name, buf)).second; + } + + void UnsetDefConParam(const Anope::string &name) + { + DefConModesOnParams.erase(name); + } + + bool GetDefConParam(const Anope::string &name, Anope::string &buf) + { + std::map::iterator it = DefConModesOnParams.find(name); + + buf.clear(); + + if (it != DefConModesOnParams.end()) + { + buf = it->second; + return true; + } + + return false; + } +}; + +static DefconConfig DConfig; + +static void runDefCon(); +static Anope::string defconReverseModes(const Anope::string &modes); + +static Timer *timeout; + +class DefConTimeout : public Timer +{ + EventHandlers &eventdefcon; + int level; + + public: + DefConTimeout(EventHandlers &ev, Module *mod, int newlevel) : Timer(mod, DConfig.timeout), eventdefcon(ev), level(newlevel) + { + timeout = this; + } + + ~DefConTimeout() + { + timeout = NULL; + } + + void Tick(time_t) override + { + if (DConfig.defaultlevel != level) + { + DConfig.defaultlevel = level; + this->eventdefcon(&Event::DefconLevel::OnDefconLevel, level); + Log(Config->GetClient("OperServ"), "operserv/defcon") << "Defcon level timeout, returning to level " << level; + + if (DConfig.globalondefcon && Global::service) + { + if (!DConfig.offmessage.empty()) + Global::service->SendGlobal(NULL, "", DConfig.offmessage); + else + Global::service->SendGlobal(NULL, "", Anope::printf(Language::Translate(_("The Defcon level is now at: \002%d\002")), DConfig.defaultlevel)); + + if (!DConfig.message.empty()) + Global::service->SendGlobal(NULL, "", DConfig.message); + } + + runDefCon(); + } + } +}; + +class CommandOSDefcon : public Command +{ + void SendLevels(CommandSource &source) + { + if (DConfig.Check(DEFCON_NO_NEW_CHANNELS)) + source.Reply(_("* No new channel registrations")); + if (DConfig.Check(DEFCON_NO_NEW_NICKS)) + source.Reply(_("* No new nick registrations")); + if (DConfig.Check(DEFCON_NO_MLOCK_CHANGE)) + source.Reply(_("* No mode lock changes")); + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && !DConfig.chanmodes.empty()) + source.Reply(_("* Force channel modes (\002{0}\002) to be set on all channels"), DConfig.chanmodes); + if (DConfig.Check(DEFCON_REDUCE_SESSION)) + source.Reply(_("* Use the reduced session limit of \002{0}\002"), DConfig.sessionlimit); + if (DConfig.Check(DEFCON_NO_NEW_CLIENTS)) + source.Reply(_("* Kill any new clients connecting")); + if (DConfig.Check(DEFCON_OPER_ONLY)) + source.Reply(_("* Ignore non-opers with a message")); + if (DConfig.Check(DEFCON_SILENT_OPER_ONLY)) + source.Reply(_("* Silently ignore non-opers")); + if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) + source.Reply(_("* AKILL any new clients connecting")); + if (DConfig.Check(DEFCON_NO_NEW_MEMOS)) + source.Reply(_("* No new memos sent")); + } + + EventHandlers &ondefconlevel; + + public: + CommandOSDefcon(Module *creator, EventHandlers &event) : Command(creator, "operserv/defcon", 1, 1), ondefconlevel(event) + { + this->SetDesc(_("Manipulate the DefCon system")); + this->SetSyntax(_("[\0021\002|\0022\002|\0023\002|\0024\002|\0025\002]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &lvl = params[0]; + + if (lvl.empty()) + { + source.Reply(_("Services are now at defcon \002{0}\002."), DConfig.defaultlevel); + this->SendLevels(source); + return; + } + + int newLevel = 0; + try + { + newLevel = convertTo(lvl); + } + catch (const ConvertException &) { } + + if (newLevel < 1 || newLevel > 5) + { + this->OnSyntaxError(source, ""); + return; + } + + DConfig.defaultlevel = newLevel; + + this->ondefconlevel(&Event::DefconLevel::OnDefconLevel, newLevel); + + delete timeout; + + if (DConfig.timeout) + timeout = new DefConTimeout(this->ondefconlevel, this->module, 5); + + source.Reply(_("Services are now at defcon \002{0}\002."), DConfig.defaultlevel); + this->SendLevels(source); + Log(LOG_ADMIN, source, this) << "to change defcon level to " << newLevel; + + /* Global notice the user what is happening. Also any Message that + the Admin would like to add. Set in config file. */ + if (DConfig.globalondefcon && Global::service) + { + if (DConfig.defaultlevel == 5 && !DConfig.offmessage.empty()) + Global::service->SendGlobal(NULL, "", DConfig.offmessage); + else if (DConfig.defaultlevel != 5) + { + Global::service->SendGlobal(NULL, "", Anope::printf(_("The defcon level is now at \002%d\002"), DConfig.defaultlevel)); + if (!DConfig.message.empty()) + Global::service->SendGlobal(NULL, "", DConfig.message); + } + } + + /* Run any defcon functions, e.g. FORCE CHAN MODE */ + runDefCon(); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("The defcon system can be used to implement a pre-defined set of restrictions to services useful during an attempted attack on the network.")); + // XXX actually explain what this does + return true; + } +}; + +class OSDefcon : public Module + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + ServiceReference session_service; + ServiceReference akills; + CommandOSDefcon commandosdefcon; + EventHandlers ondefconlevel; + + void ParseModeString() + { + int add = -1; /* 1 if adding, 0 if deleting, -1 if neither */ + unsigned char mode; + ChannelMode *cm; + ChannelModeParam *cmp; + Anope::string modes, param; + + spacesepstream ss(DConfig.chanmodes); + + DConfig.DefConModesOn.clear(); + DConfig.DefConModesOff.clear(); + ss.GetToken(modes); + + /* Loop while there are modes to set */ + for (unsigned i = 0, end = modes.length(); i < end; ++i) + { + mode = modes[i]; + + switch (mode) + { + case '+': + add = 1; + continue; + case '-': + add = 0; + continue; + default: + if (add < 0) + continue; + } + + if ((cm = ModeManager::FindChannelModeByChar(mode))) + { + if (cm->type == MODE_STATUS || cm->type == MODE_LIST) + { + Log(this) << "DefConChanModes mode character '" << mode << "' cannot be locked"; + continue; + } + else if (add) + { + DConfig.DefConModesOn.insert(cm->name); + DConfig.DefConModesOff.erase(cm->name); + + if (cm->type == MODE_PARAM) + { + cmp = anope_dynamic_static_cast(cm); + + if (!ss.GetToken(param)) + { + Log(this) << "DefConChanModes mode character '" << mode << "' has no parameter while one is expected"; + continue; + } + + if (!cmp->IsValid(param)) + continue; + + DConfig.SetDefConParam(cmp->name, param); + } + } + else if (DConfig.DefConModesOn.count(cm->name)) + { + DConfig.DefConModesOn.erase(cm->name); + + if (cm->type == MODE_PARAM) + DConfig.UnsetDefConParam(cm->name); + } + } + } + + /* We can't mlock +L if +l is not mlocked as well. */ + if ((cm = ModeManager::FindChannelModeByName("REDIRECT")) && DConfig.DefConModesOn.count(cm->name) && !DConfig.DefConModesOn.count("LIMIT")) + { + DConfig.DefConModesOn.erase("REDIRECT"); + + Log(this) << "DefConChanModes must lock mode +l as well to lock mode +L"; + } + } + + public: + OSDefcon(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , session_service("SessionService", "session") + , akills("XLineManager", "xlinemanager/sgline") + , commandosdefcon(this, ondefconlevel) + , ondefconlevel(this) + { + + } + + ~OSDefcon() + { + delete timeout; + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + DefconConfig dconfig; + + dconfig.defaultlevel = block->Get("defaultlevel"); + dconfig.defcons[4] = block->Get("level4"); + dconfig.defcons[3] = block->Get("level3"); + dconfig.defcons[2] = block->Get("level2"); + dconfig.defcons[1] = block->Get("level1"); + dconfig.sessionlimit = block->Get("sessionlimit"); + dconfig.akillreason = block->Get("akillreason"); + dconfig.akillexpire = block->Get("akillexpire"); + dconfig.chanmodes = block->Get("chanmodes"); + dconfig.timeout = block->Get("timeout"); + dconfig.globalondefcon = block->Get("globalondefcon"); + dconfig.message = block->Get("message"); + dconfig.offmessage = block->Get("offmessage"); + + Module *session = ModuleManager::FindModule("os_session"); + block = conf->GetModule(session); + + dconfig.max_session_kill = block->Get("maxsessionkill"); + dconfig.session_autokill_expiry = block->Get("sessionautokillexpiry"); + dconfig.sle_reason = block->Get("sessionlimitexceeded"); + dconfig.sle_detailsloc = block->Get("sessionlimitdetailsloc"); + + if (dconfig.defaultlevel < 1 || dconfig.defaultlevel > 5) + throw ConfigException("The value for must be between 1 and 5"); + else if (dconfig.akillexpire <= 0) + throw ConfigException("The value for must be greater than zero!"); + + for (unsigned level = 1; level < 5; ++level) + { + spacesepstream operations(dconfig.defcons[level]); + Anope::string operation; + while (operations.GetToken(operation)) + { + if (operation.equals_ci("nonewchannels")) + dconfig.Add(level, DEFCON_NO_NEW_CHANNELS); + else if (operation.equals_ci("nonewnicks")) + dconfig.Add(level, DEFCON_NO_NEW_NICKS); + else if (operation.equals_ci("nomlockchanges")) + dconfig.Add(level, DEFCON_NO_MLOCK_CHANGE); + else if (operation.equals_ci("forcechanmodes")) + dconfig.Add(level, DEFCON_FORCE_CHAN_MODES); + else if (operation.equals_ci("reducedsessions")) + dconfig.Add(level, DEFCON_REDUCE_SESSION); + else if (operation.equals_ci("nonewclients")) + dconfig.Add(level, DEFCON_NO_NEW_CLIENTS); + else if (operation.equals_ci("operonly")) + dconfig.Add(level, DEFCON_OPER_ONLY); + else if (operation.equals_ci("silentoperonly")) + dconfig.Add(level, DEFCON_SILENT_OPER_ONLY); + else if (operation.equals_ci("akillnewclients")) + dconfig.Add(level, DEFCON_AKILL_NEW_CLIENTS); + else if (operation.equals_ci("nonewmemos")) + dconfig.Add(level, DEFCON_NO_NEW_MEMOS); + } + + if (dconfig.Check(level, DEFCON_REDUCE_SESSION) && dconfig.sessionlimit <= 0) + throw ConfigException("The value for must be greater than zero!"); + else if (dconfig.Check(level, DEFCON_AKILL_NEW_CLIENTS) && dconfig.akillreason.empty()) + throw ConfigException("The value for must not be empty!"); + else if (dconfig.Check(level, DEFCON_FORCE_CHAN_MODES) && dconfig.chanmodes.empty()) + throw ConfigException("The value for must not be empty!"); + } + + DConfig = dconfig; + this->ParseModeString(); + } + + EventReturn OnChannelModeSet(Channel *c, const MessageSource &source, ChannelMode *mode, const Anope::string ¶m) override + { + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && DConfig.DefConModesOff.count(mode->name) && source.GetUser() && !source.GetBot()) + { + c->RemoveMode(Config->GetClient("OperServ"), mode, param); + + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + EventReturn OnChannelModeUnset(Channel *c, const MessageSource &source, ChannelMode *mode, const Anope::string &) override + { + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && DConfig.DefConModesOn.count(mode->name) && source.GetUser() && !source.GetBot()) + { + Anope::string param; + + if (DConfig.GetDefConParam(mode->name, param)) + c->SetMode(Config->GetClient("OperServ"), mode, param); + else + c->SetMode(Config->GetClient("OperServ"), mode); + + return EVENT_STOP; + + } + + return EVENT_CONTINUE; + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override + { + if (DConfig.Check(DEFCON_OPER_ONLY) && !source.IsOper()) + { + source.Reply(_("Services are in DefCon mode, please try again later.")); + return EVENT_STOP; + } + else if (DConfig.Check(DEFCON_SILENT_OPER_ONLY) && !source.IsOper()) + { + return EVENT_STOP; + } + else if (command->name == "nickserv/register" || command->name == "nickserv/group") + { + if (DConfig.Check(DEFCON_NO_NEW_NICKS)) + { + source.Reply(_("Services are in defcon mode, please try again later.")); + return EVENT_STOP; + } + } + else if (command->name == "chanserv/mode" && params.size() > 1 && params[1].equals_ci("LOCK")) + { + if (DConfig.Check(DEFCON_NO_MLOCK_CHANGE)) + { + source.Reply(_("Services are in defcon mode, please try again later.")); + return EVENT_STOP; + } + } + else if (command->name == "chanserv/register") + { + if (DConfig.Check(DEFCON_NO_NEW_CHANNELS)) + { + source.Reply(_("Services are in defcon mode, please try again later.")); + return EVENT_STOP; + } + } + else if (command->name == "memoserv/send") + { + if (DConfig.Check(DEFCON_NO_NEW_MEMOS)) + { + source.Reply(_("Services are in defcon mode, please try again later.")); + return EVENT_STOP; + } + } + + return EVENT_CONTINUE; + } + + void OnUserConnect(User *u, bool &exempt) override + { + if (exempt || u->Quitting() || !u->server->IsSynced() || u->server->IsULined()) + return; + + ServiceBot *OperServ = Config->GetClient("OperServ"); + if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS) && akills) + { + Log(OperServ, "operserv/defcon") << "DEFCON: adding akill for *@" << u->host; + XLine x("*@" + u->host, OperServ ? OperServ->nick : "defcon", Anope::CurTime + DConfig.akillexpire, DConfig.akillreason, XLineManager::GenerateUID()); + akills->Send(NULL, &x); + } + + if (DConfig.Check(DEFCON_NO_NEW_CLIENTS) || DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) + { + u->Kill(OperServ, DConfig.akillreason); + return; + } + + if (DConfig.sessionlimit <= 0 || !session_service) + return; + + Session *session = session_service->FindSession(u->ip.addr()); + Exception *e = session_service->FindException(u); + + if (DConfig.Check(DEFCON_REDUCE_SESSION) && !e) + { + if (session && session->count > static_cast(DConfig.sessionlimit)) + { + if (!DConfig.sle_reason.empty()) + { + Anope::string message = DConfig.sle_reason.replace_all_cs("%IP%", u->ip.addr()); + u->SendMessage(OperServ, message); + } + if (!DConfig.sle_detailsloc.empty()) + u->SendMessage(OperServ, DConfig.sle_detailsloc); + + ++session->hits; + if (akills && DConfig.max_session_kill && session->hits >= DConfig.max_session_kill) + { + XLine x("*@" + session->addr.mask(), OperServ ? OperServ->nick : "", Anope::CurTime + DConfig.session_autokill_expiry, "Defcon session limit exceeded", XLineManager::GenerateUID()); + akills->Send(NULL, &x); + Log(OperServ, "akill/defcon") << "[DEFCON] Added a temporary AKILL for \002*@" << session->addr.mask() << "\002 due to excessive connections"; + } + else + { + u->Kill(OperServ, "Defcon session limit exceeded"); + } + } + } + } + + void OnChannelModeAdd(ChannelMode *cm) override + { + if (DConfig.chanmodes.find(cm->mchar) != Anope::string::npos) + this->ParseModeString(); + } + + void OnChannelSync(Channel *c) override + { + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES)) + c->SetModes(Config->GetClient("OperServ"), false, "%s", DConfig.chanmodes.c_str()); + } +}; + +static void runDefCon() +{ + ServiceBot *OperServ = Config->GetClient("OperServ"); + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES)) + { + if (!DConfig.chanmodes.empty() && !DefConModesSet) + { + if (DConfig.chanmodes[0] == '+' || DConfig.chanmodes[0] == '-') + { + Log(OperServ, "operserv/defcon") << "DEFCON: setting " << DConfig.chanmodes << " on all channels"; + DefConModesSet = true; + for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) + it->second->SetModes(OperServ, false, "%s", DConfig.chanmodes.c_str()); + } + } + } + else + { + if (!DConfig.chanmodes.empty() && DefConModesSet) + { + if (DConfig.chanmodes[0] == '+' || DConfig.chanmodes[0] == '-') + { + DefConModesSet = false; + Anope::string newmodes = defconReverseModes(DConfig.chanmodes); + if (!newmodes.empty()) + { + Log(OperServ, "operserv/defcon") << "DEFCON: setting " << newmodes << " on all channels"; + for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) + it->second->SetModes(OperServ, true, "%s", newmodes.c_str()); + } + } + } + } +} + +static Anope::string defconReverseModes(const Anope::string &modes) +{ + if (modes.empty()) + return ""; + Anope::string newmodes; + for (unsigned i = 0, end = modes.length(); i < end; ++i) + { + if (modes[i] == '+') + newmodes += '-'; + else if (modes[i] == '-') + newmodes += '+'; + else + newmodes += modes[i]; + } + return newmodes; +} + +MODULE_INIT(OSDefcon) diff --git a/modules/operserv/dns.cpp b/modules/operserv/dns.cpp new file mode 100644 index 000000000..a231a1e74 --- /dev/null +++ b/modules/operserv/dns.cpp @@ -0,0 +1,1012 @@ +/* + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" +#include "modules/dns.h" +#include "modules/os_dns.h" + +static std::map > server_quit_times; + +class DNSZoneImpl : public DNSZone +{ + public: + DNSZoneImpl(Serialize::TypeBase *type) : DNSZone(type) { } + DNSZoneImpl(Serialize::TypeBase *type, Serialize::ID id) : DNSZone(type, id) { } + + Anope::string GetName() override; + void SetName(const Anope::string &) override; + + static DNSZone *Find(const Anope::string &name) + { + for (DNSZone *zone : Serialize::GetObjects(dnszone)) + if (zone->GetName().equals_ci(name)) + return zone; + return nullptr; + } +}; + +class DNSZoneType : public Serialize::Type +{ + public: + Serialize::Field name; + + DNSZoneType(Module *creator) : Serialize::Type(creator, "DNSZone") + , name(this, "name") + { + } +}; + +Anope::string DNSZoneImpl::GetName() +{ + return Get(&DNSZoneType::name); +} + +void DNSZoneImpl::SetName(const Anope::string &name) +{ + Set(&DNSZoneType::name, name); +} + +class DNSServerImpl : public DNSServer +{ + /* is actually in the pool */ + bool active = false; + + public: + std::set zones; + time_t repool = 0; + + DNSServerImpl(Serialize::TypeBase *type) : DNSServer(type) { } + DNSServerImpl(Serialize::TypeBase *type, Serialize::ID id) : DNSServer(type, id) { } + + DNSZone *GetZone() override; + void SetZone(DNSZone *) override; + + Anope::string GetName() override; + void SetName(const Anope::string &) override; + + unsigned int GetLimit() override; + void SetLimit(const unsigned int &) override; + + bool GetPooled() override; + void SetPool(const bool &) override; + + bool Active() + { + return GetPooled() && active; + } + + void SetActive(bool p) + { + if (p) + this->SetPool(p); + active = p; + + if (DNS::manager) + { + DNS::manager->UpdateSerial(); + for (std::set::iterator it = zones.begin(), it_end = zones.end(); it != it_end; ++it) + DNS::manager->Notify(*it); + } + } + + static DNSServerImpl *Find(const Anope::string &s) + { + for (DNSServerImpl *server : Serialize::GetObjects(dnsserver)) + if (server->GetName().equals_ci(s)) + return server; + return nullptr; + } +}; + +class DNSServerType : public Serialize::Type +{ + public: + Serialize::ObjectField zone; + Serialize::Field name; + Serialize::Field limit; + Serialize::Field pooled; + + DNSServerType(Module *creator) : Serialize::Type(creator, "DNSServer") + , zone(this, "zone") + , name(this, "name") + , limit(this, "limit") + , pooled(this, "pooled") + { + } +}; + +DNSZone *DNSServerImpl::GetZone() +{ + return Get(&DNSServerType::zone); +} + +void DNSServerImpl::SetZone(DNSZone *z) +{ + Set(&DNSServerType::zone, z); +} + +Anope::string DNSServerImpl::GetName() +{ + return Get(&DNSServerType::name); +} + +void DNSServerImpl::SetName(const Anope::string &n) +{ + Set(&DNSServerType::name, n); +} + +unsigned int DNSServerImpl::GetLimit() +{ + return Get(&DNSServerType::limit); +} + +void DNSServerImpl::SetLimit(const unsigned int &l) +{ + Set(&DNSServerType::limit, l); +} + +bool DNSServerImpl::GetPooled() +{ + return Get(&DNSServerType::pooled); +} + +void DNSServerImpl::SetPool(const bool &p) +{ + Set(&DNSServerType::pooled, p); +} + +class DNSZoneMembershipImpl : public DNSZoneMembership +{ + public: + DNSZoneMembershipImpl(Serialize::TypeBase *type) : DNSZoneMembership(type) { } + DNSZoneMembershipImpl(Serialize::TypeBase *type, Serialize::ID id) : DNSZoneMembership(type, id) { } + + DNSServer *GetServer() override; + void SetServer(DNSServer *) override; + + DNSZone *GetZone() override; + void SetZone(DNSZone *) override; + + static DNSZoneMembership *Find(DNSServer *server, DNSZone *zone) + { + for (DNSZoneMembership *mem : Serialize::GetObjects(dnszonemembership)) + if (mem->GetServer() == server && mem->GetZone() == zone) + return mem; + return nullptr; + } +}; + +class DNSZoneMembershipType : public Serialize::Type +{ + public: + Serialize::ObjectField server; + Serialize::ObjectField zone; + + DNSZoneMembershipType(Module *creator) : Serialize::Type(creator, "DNSZoneMembership") + , server(this, "server") + , zone(this, "zone") + { + } +}; + +DNSServer *DNSZoneMembershipImpl::GetServer() +{ + return Get(&DNSZoneMembershipType::server); +} + +void DNSZoneMembershipImpl::SetServer(DNSServer *s) +{ + Set(&DNSZoneMembershipType::server, s); +} + +DNSZone *DNSZoneMembershipImpl::GetZone() +{ + return Get(&DNSZoneMembershipType::zone); +} + +void DNSZoneMembershipImpl::SetZone(DNSZone *z) +{ + Set(&DNSZoneMembershipType::zone, z); +} + +class DNSIPImpl : public DNSIP +{ + public: + DNSIPImpl(Serialize::TypeBase *type) : DNSIP(type) { } + DNSIPImpl(Serialize::TypeBase *type, Serialize::ID id) : DNSIP(type, id) { } + + DNSServer *GetServer() override; + void SetServer(DNSServer *) override; + + Anope::string GetIP() override; + void SetIP(const Anope::string &) override; +}; + +class DNSIPType : public Serialize::Type +{ + public: + Serialize::ObjectField server; + Serialize::Field ip; + + DNSIPType(Module *creator) : Serialize::Type(creator, "DNSIP") + , server(this, "server") + , ip(this, "ip") + { + } +}; + +DNSServer *DNSIPImpl::GetServer() +{ + return Get(&DNSIPType::server); +} + +void DNSIPImpl::SetServer(DNSServer *s) +{ + Set(&DNSIPType::server, s); +} + +Anope::string DNSIPImpl::GetIP() +{ + return Get(&DNSIPType::ip); +} + +void DNSIPImpl::SetIP(const Anope::string &ip) +{ + Set(&DNSIPType::ip, ip); +} + +class CommandOSDNS : public Command +{ + void DisplayPoolState(CommandSource &source) + { + std::vector servers = Serialize::GetObjects(dnsserver); + + if (servers.empty()) + { + source.Reply(_("There are no configured servers.")); + return; + } + + ListFormatter lf(source.GetAccount()); + lf.AddColumn(_("Server")).AddColumn(_("IP")).AddColumn(_("Limit")).AddColumn(_("State")); + for (DNSServerImpl *s : servers) + { + Server *srv = Server::Find(s->GetName(), true); + + ListFormatter::ListEntry entry; + entry["Server"] = s->GetName(); + entry["Limit"] = s->GetLimit() ? stringify(s->GetLimit()) : Language::Translate(source.GetAccount(), _("None")); + + Anope::string ip_str; + for (DNSIP *ip : s->GetRefs(dnsip)) + ip_str += ip->GetIP() + " "; + ip_str.trim(); + if (ip_str.empty()) + ip_str = "None"; + entry["IP"] = ip_str; + + if (s->Active()) + entry["State"] = Language::Translate(source.GetAccount(), _("Pooled/Active")); + else if (s->GetPooled()) + entry["State"] = Language::Translate(source.GetAccount(), _("Pooled/Not Active")); + else + entry["State"] = Language::Translate(source.GetAccount(), _("Unpooled")); + + if (!srv) + entry["State"] += Anope::string(" ") + Language::Translate(source.GetAccount(), _("(Split)")); + + lf.AddEntry(entry); + } + + std::vector replies; + lf.Process(replies); + + std::vector zones = Serialize::GetObjects(dnszone); + if (!zones.empty()) + { + ListFormatter lf2(source.GetAccount()); + lf2.AddColumn(_("Zone")).AddColumn(_("Servers")); + + for (DNSZone *z : zones) + { + ListFormatter::ListEntry entry; + entry["Zone"] = z->GetName(); + + Anope::string server_str; + for (DNSServer *s : z->GetRefs(dnsserver)) + server_str += s->GetName() + " "; + server_str.trim(); + + if (server_str.empty()) + server_str = "None"; + + entry["Servers"] = server_str; + + lf2.AddEntry(entry); + } + + lf2.Process(replies); + } + + for (const Anope::string &r : replies) + source.Reply(r); + } + + void AddZone(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &zone = params[1]; + + if (DNSZoneImpl::Find(zone)) + { + source.Reply(_("Zone \002{0}\002 already exists."), zone); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to add zone " << zone; + + DNSZone *z = dnszone.Create(); + z->SetName(zone); + source.Reply(_("Added zone \002{0}\002."), zone); + } + + void DelZone(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &zone = params[1]; + + DNSZone *z = DNSZoneImpl::Find(zone); + if (!z) + { + source.Reply(_("Zone \002{0}\002 does not exist."), zone); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to delete zone " << z->GetName(); + + for (DNSZoneMembership *mem : z->GetRefs(dnszonemembership)) + mem->Delete(); + + if (DNS::manager) + { + DNS::manager->UpdateSerial(); + DNS::manager->Notify(z->GetName()); + } + + source.Reply(_("Zone \002{0}\002 removed."), z->GetName()); + z->Delete(); + } + + void AddServer(CommandSource &source, const std::vector ¶ms) + { + DNSServer *s = DNSServerImpl::Find(params[1]); + const Anope::string &zone = params.size() > 2 ? params[2] : ""; + + if (s) + { + if (zone.empty()) + { + source.Reply(_("Server \002{0}\002 already exists."), s->GetName()); + } + else + { + DNSZone *z = DNSZoneImpl::Find(zone); + if (!z) + { + source.Reply(_("Zone \002{0}\002 does not exist."), zone); + return; + } + + DNSZoneMembership *mem = DNSZoneMembershipImpl::Find(s, z); + if (mem) + { + source.Reply(_("Server \002{0}\002 is already in zone \002{1}\002."), s->GetName(), z->GetName()); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + mem = dnszonemembership.Create(); + mem->SetZone(z); + mem->SetServer(s); + + if (DNS::manager) + { + DNS::manager->UpdateSerial(); + DNS::manager->Notify(zone); + } + + Log(LOG_ADMIN, source, this) << "to add server " << s->GetName() << " to zone " << z->GetName(); + + source.Reply(_("Server \002{0}\002 added to zone \002{1}\002."), s->GetName(), z->GetName()); + } + + return; + } + + Server *serv = Server::Find(params[1], true); + if (!serv || serv == Me || serv->IsJuped()) + { + source.Reply(_("Server \002{0}\002 is not linked to the network."), params[1]); + return; + } + + s = dnsserver.Create(); + s->SetName(params[1]); + if (zone.empty()) + { + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to add server " << s->GetName(); + source.Reply(_("Added server \002{0}\002."), s->GetName()); + } + else + { + DNSZone *z = DNSZoneImpl::Find(zone); + if (!z) + { + source.Reply(_("Zone \002{0}\002 does not exist."), zone); + s->Delete(); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to add server " << s->GetName() << " to zone " << zone; + + DNSZoneMembership *mem = dnszonemembership.Create(); + mem->SetServer(s); + mem->SetZone(z); + + if (DNS::manager) + { + DNS::manager->UpdateSerial(); + DNS::manager->Notify(z->GetName()); + } + } + } + + void DelServer(CommandSource &source, const std::vector ¶ms) + { + DNSServer *s = DNSServerImpl::Find(params[1]); + const Anope::string &zone = params.size() > 2 ? params[2] : ""; + + if (!s) + { + source.Reply(_("Server \002{0}\002 does not exist."), params[1]); + return; + } + + if (!zone.empty()) + { + DNSZone *z = DNSZoneImpl::Find(zone); + if (!z) + { + source.Reply(_("Zone \002{0}\002 does not exist."), zone); + return; + } + + DNSZoneMembership *mem = DNSZoneMembershipImpl::Find(s, z); + if (!mem) + { + source.Reply(_("Server \002{0}\002 is not in zone \002{1}\002."), s->GetName(), z->GetName()); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to remove server " << s->GetName() << " from zone " << z->GetName(); + + if (DNS::manager) + { + DNS::manager->UpdateSerial(); + DNS::manager->Notify(z->GetName()); + } + + mem->Delete(); + source.Reply(_("Removed server \002{0}\002 from zone \002{1}\002."), s->GetName(), z->GetName()); + return; + } + + if (Server::Find(s->GetName(), true)) + { + source.Reply(_("Server \002{0}\002 must be quit before it can be deleted."), s->GetName()); + return; + } + + for (DNSZoneMembership *mem : s->GetRefs(dnszonemembership)) + mem->Delete(); + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + if (DNS::manager) + DNS::manager->UpdateSerial(); + + Log(LOG_ADMIN, source, this) << "to delete server " << s->GetName(); + source.Reply(_("Removed server \002{0}\002."), s->GetName()); + s->Delete(); + } + + void AddIP(CommandSource &source, const std::vector ¶ms) + { + DNSServerImpl *s = DNSServerImpl::Find(params[1]); + + if (!s) + { + source.Reply(_("Server \002{0}\002 does not exist."), params[1]); + return; + } + + for (DNSIP *ip : s->GetRefs(dnsip)) + if (params[2].equals_ci(ip->GetIP())) + { + source.Reply(_("IP \002{0}\002 already exists for \002{1}\002."), ip->GetIP(), s->GetName()); + return; + } + + sockaddrs addr(params[2]); + if (!addr.valid()) + { + source.Reply(_("\002{0}\002 is not a valid IP address."), params[2]); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + DNSIP *ip = dnsip.Create(); + ip->SetServer(s); + ip->SetIP(params[2]); + + source.Reply(_("Added IP \002{0}\002 to \002{1}\002."), params[2], s->GetName()); + Log(LOG_ADMIN, source, this) << "to add IP " << params[2] << " to " << s->GetName(); + + if (s->Active() && DNS::manager) + { + DNS::manager->UpdateSerial(); + for (DNSZone *zone : s->GetRefs(dnszone)) + DNS::manager->Notify(zone->GetName()); + } + } + + void DelIP(CommandSource &source, const std::vector ¶ms) + { + DNSServerImpl *s = DNSServerImpl::Find(params[1]); + + if (!s) + { + source.Reply(_("Server \002{0}\002 does not exist."), params[1]); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + for (DNSIP *ip : s->GetRefs(dnsip)) + if (params[2].equals_ci(ip->GetIP())) + { + ip->Delete(); + + source.Reply(_("Removed IP \002{0}\002 from \002{1}\002."), params[2], s->GetName()); + Log(LOG_ADMIN, source, this) << "to remove IP " << params[2] << " from " << s->GetName(); + + if (s->GetRefs(dnsip).empty()) + { + s->repool = 0; + s->SetPool(false); + } + + if (s->Active() && DNS::manager) + { + DNS::manager->UpdateSerial(); + for (DNSZone *zone : s->GetRefs(dnszone)) + DNS::manager->Notify(zone->GetName()); + } + + return; + } + + source.Reply(_("IP \002{0}\002 does not exist for \002{1}\002."), params[2], s->GetName()); + } + + void OnSet(CommandSource &source, const std::vector ¶ms) + { + DNSServer *s = DNSServerImpl::Find(params[1]); + + if (!s) + { + source.Reply(_("Server \002{0}\002 does not exist."), params[1]); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + if (params[2].equals_ci("LIMIT")) + { + try + { + unsigned l = convertTo(params[3]); + s->SetLimit(l); + if (l) + source.Reply(_("User limit for \002{0}\002 set to \002{1}\002."), s->GetName(), l); + else + source.Reply(_("User limit for \002{0}\002 removed."), s->GetName()); + } + catch (const ConvertException &ex) + { + source.Reply(_("Invalid value for limit. Must be numerical.")); + } + } + else + source.Reply(_("Unknown SET option.")); + } + + void OnPool(CommandSource &source, const std::vector ¶ms) + { + DNSServerImpl *s = DNSServerImpl::Find(params[1]); + + if (!s) + { + source.Reply(_("Server \002{0}\002 does not exist."), params[1]); + return; + } + + if (!Server::Find(s->GetName(), true)) + { + source.Reply(_("Server \002{0}\002 is not currently linked."), s->GetName()); + return; + } + + if (s->GetPooled()) + { + source.Reply(_("Server \002{0}\002 is already pooled."), s->GetName()); + return; + } + + if (s->GetRefs(dnsip).empty()) + { + source.Reply(_("Server \002{0}\002 has no configured IPs."), s->GetName()); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + s->SetActive(true); + + source.Reply(_("Pooled \002{0}\002."), s->GetName()); + Log(LOG_ADMIN, source, this) << "to pool " << s->GetName(); + } + + + void OnDepool(CommandSource &source, const std::vector ¶ms) + { + DNSServer *s = DNSServerImpl::Find(params[1]); + + if (!s) + { + source.Reply(_("Server \002{0}\002 does not exist."), params[1]); + return; + } + + if (!s->GetPooled()) + { + source.Reply(_("Server \002{0}\002 is not pooled."), s->GetName()); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + s->SetPool(false); + + source.Reply(_("Depooled \002{0}\002."), s->GetName()); + Log(LOG_ADMIN, source, this) << "to depool " << s->GetName(); + } + + public: + CommandOSDNS(Module *creator) : Command(creator, "operserv/dns", 0, 4) + { + this->SetDesc(_("Manage DNS zones for this network")); + this->SetSyntax(_("ADDZONE \037zone.name\037")); + this->SetSyntax(_("DELZONE \037zone.name\037")); + this->SetSyntax(_("ADDSERVER \037server.name\037 [\037zone.name\037]")); + this->SetSyntax(_("DELSERVER \037server.name\037 [\037zone.name\037]")); + this->SetSyntax(_("ADDIP \037server.name\037 \037ip\037")); + this->SetSyntax(_("DELIP \037server.name\037 \037ip\037")); + this->SetSyntax(_("SET \037server.name\037 \037option\037 \037value\037")); + this->SetSyntax(_("POOL \037server.name\037")); + this->SetSyntax(_("DEPOOL \037server.name\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (params.empty()) + this->DisplayPoolState(source); + else if (params[0].equals_ci("ADDZONE") && params.size() > 1) + this->AddZone(source, params); + else if (params[0].equals_ci("DELZONE") && params.size() > 1) + this->DelZone(source, params); + else if (params[0].equals_ci("ADDSERVER") && params.size() > 1) + this->AddServer(source, params); + else if (params[0].equals_ci("DELSERVER") && params.size() > 1) + this->DelServer(source, params); + else if (params[0].equals_ci("ADDIP") && params.size() > 2) + this->AddIP(source, params); + else if (params[0].equals_ci("DELIP") && params.size() > 2) + this->DelIP(source, params); + else if (params[0].equals_ci("SET") && params.size() > 3) + this->OnSet(source, params); + else if (params[0].equals_ci("POOL") && params.size() > 1) + this->OnPool(source, params); + else if (params[0].equals_ci("DEPOOL") && params.size() > 1) + this->OnDepool(source, params); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command allows managing DNS zones used for controlling what servers users are directed to when connecting." + " Omitting all parameters prints out the status of the DNS zone.\n" + "\n" + "\002{0} ADDZONE\002 adds a zone, eg. us.example.com. Servers can then be added to this zone with the \002ADDSERVER\002 command.\n" + "\n" + "The \002{0} ADDSERVER\002 command adds a server to the given zone." + " When a DNS query is done, the zone in question is served if it exists, otherwise all servers in all zones are served." + " A server may be in more than one zone.\n" + "\n" + "The \002{0} ADDIP\002 command associates an IP with a server.\n" + " \n" + "The \002{0} POOL\002 and \002{0} DEPOOL\002 commands actually add and remove servers to their given zones."), + source.command); + return true; + } +}; + +class ModuleDNS : public Module + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + DNSZoneType zonetype; + DNSServerType servertype; + DNSZoneMembershipType zonemembtype; + DNSIPType iptype; + CommandOSDNS commandosdns; + + time_t ttl; + int user_drop_mark; + time_t user_drop_time; + time_t user_drop_readd_time; + bool remove_split_servers; + bool readd_connected_servers; + + time_t last_warn; + + public: + ModuleDNS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + , zonetype(this) + , servertype(this) + , zonemembtype(this) + , iptype(this) + , commandosdns(this) + , last_warn(0) + { +#if 0 + for (unsigned j = 0; j < dns_servers->size(); ++j) + { + DNSServer *s = dns_servers->at(j); + if (s->Pooled() && Server::Find(s->GetName(), true)) + s->SetActive(true); + } +#endif + } + + ~ModuleDNS() + { + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + this->ttl = block->Get("ttl"); + this->user_drop_mark = block->Get("user_drop_mark"); + this->user_drop_time = block->Get("user_drop_time"); + this->user_drop_readd_time = block->Get("user_drop_readd_time"); + this->remove_split_servers = block->Get("remove_split_servers"); + this->readd_connected_servers = block->Get("readd_connected_servers"); + } + + void OnNewServer(Server *s) override + { + if (s == Me || s->IsJuped()) + return; + if (!Me->IsSynced() || this->readd_connected_servers) + { + DNSServerImpl *dns = DNSServerImpl::Find(s->GetName()); + if (dns && dns->GetPooled() && !dns->Active() && !dns->GetRefs(dnsip).empty()) + { + dns->SetActive(true); + Log(this) << "Pooling server " << s->GetName(); + } + } + } + + void OnServerQuit(Server *s) override + { + DNSServerImpl *dns = DNSServerImpl::Find(s->GetName()); + if (remove_split_servers && dns && dns->GetPooled() && dns->Active()) + { + if (readd_connected_servers) + dns->SetActive(false); // Will be reactivated when it comes back + else + dns->SetPool(false); // Otherwise permanently pull this + Log(this) << "Depooling delinked server " << s->GetName(); + } + } + + void OnUserConnect(User *u, bool &exempt) override + { + if (!u->Quitting() && u->server) + { + DNSServerImpl *s = DNSServerImpl::Find(u->server->GetName()); + /* Check for user limit reached */ + if (s && s->GetPooled() && s->Active() && s->GetLimit() && u->server->users >= s->GetLimit()) + { + Log(this) << "Depooling full server " << s->GetName() << ": " << u->server->users << " users"; + s->SetActive(false); + } + } + } + + void OnPreUserLogoff(User *u) override + { + if (u && u->server) + { + DNSServerImpl *s = DNSServerImpl::Find(u->server->GetName()); + if (!s || !s->GetPooled()) + return; + + /* Check for dropping under userlimit */ + if (s->GetLimit() && !s->Active() && s->GetLimit() > u->server->users) + { + Log(this) << "Pooling server " << s->GetName(); + s->SetActive(true); + } + + if (this->user_drop_mark > 0) + { + std::list& times = server_quit_times[u->server->GetName()]; + times.push_back(Anope::CurTime); + if (times.size() > static_cast(this->user_drop_mark)) + times.pop_front(); + + if (times.size() == static_cast(this->user_drop_mark)) + { + time_t diff = Anope::CurTime - *times.begin(); + + /* Check for very fast user drops */ + if (s->Active() && diff <= this->user_drop_time) + { + Log(this) << "Depooling server " << s->GetName() << ": dropped " << this->user_drop_mark << " users in " << diff << " seconds"; + s->repool = Anope::CurTime + this->user_drop_readd_time; + s->SetActive(false); + } + /* Check for needing to re-pool a server that dropped users */ + else if (!s->Active() && s->repool && s->repool <= Anope::CurTime) + { + s->SetActive(true); + s->repool = 0; + Log(this) << "Pooling server " << s->GetName(); + } + } + } + } + } + + void OnDnsRequest(DNS::Query &req, DNS::Query *packet) override + { + if (req.questions.empty()) + return; + /* Currently we reply to any QR for A/AAAA */ + const DNS::Question& q = req.questions[0]; + if (q.type != DNS::QUERY_A && q.type != DNS::QUERY_AAAA && q.type != DNS::QUERY_AXFR && q.type != DNS::QUERY_ANY) + return; + + DNSZone *zone = DNSZoneImpl::Find(q.name); + size_t answer_size = packet->answers.size(); + if (zone) + { + for (DNSServerImpl *s : zone->GetRefs(dnsserver)) + { + if (!s->Active()) + continue; + + for (DNSIP *ip : s->GetRefs(dnsip)) + { + DNS::QueryType q_type = ip->GetIP().find(':') != Anope::string::npos ? DNS::QUERY_AAAA : DNS::QUERY_A; + + if (q.type == DNS::QUERY_AXFR || q.type == DNS::QUERY_ANY || q_type == q.type) + { + DNS::ResourceRecord rr(q.name, q_type); + rr.ttl = this->ttl; + rr.rdata = ip->GetIP(); + packet->answers.push_back(rr); + } + } + } + } + + if (packet->answers.size() == answer_size) + { + /* Default zone */ + for (DNSServerImpl *s : Serialize::GetObjects(dnsserver)) + { + if (!s->Active()) + continue; + + for (DNSIP *ip : s->GetRefs(dnsip)) + { + DNS::QueryType q_type = ip->GetIP().find(':') != Anope::string::npos ? DNS::QUERY_AAAA : DNS::QUERY_A; + + if (q.type == DNS::QUERY_AXFR || q.type == DNS::QUERY_ANY || q_type == q.type) + { + DNS::ResourceRecord rr(q.name, q_type); + rr.ttl = this->ttl; + rr.rdata = ip->GetIP(); + packet->answers.push_back(rr); + } + } + } + } + + if (packet->answers.size() == answer_size) + { + if (last_warn + 60 < Anope::CurTime) + { + last_warn = Anope::CurTime; + Log(this) << "Warning! There are no pooled servers!"; + } + + /* Something messed up, just return them all and hope one is available */ + for (DNSServer *s : Serialize::GetObjects(dnsserver)) + for (DNSIP *ip : s->GetRefs(dnsip)) + { + DNS::QueryType q_type = ip->GetIP().find(':') != Anope::string::npos ? DNS::QUERY_AAAA : DNS::QUERY_A; + + if (q.type == DNS::QUERY_AXFR || q.type == DNS::QUERY_ANY || q_type == q.type) + { + DNS::ResourceRecord rr(q.name, q_type); + rr.ttl = this->ttl; + rr.rdata = ip->GetIP(); + packet->answers.push_back(rr); + } + } + + if (packet->answers.size() == answer_size) + { + Log(this) << "Error! There are no servers with any IPs of type " << q.type; + /* Send back an empty answer anyway */ + } + } + } +}; + +MODULE_INIT(ModuleDNS) diff --git a/modules/operserv/forbid.cpp b/modules/operserv/forbid.cpp new file mode 100644 index 000000000..0a7b1c209 --- /dev/null +++ b/modules/operserv/forbid.cpp @@ -0,0 +1,577 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/os_forbid.h" +#include "modules/nickserv.h" +#include "modules/chanserv.h" + +class ForbidDataImpl : public ForbidData +{ + public: + ForbidDataImpl(Serialize::TypeBase *type) : ForbidData(type) { } + ForbidDataImpl(Serialize::TypeBase *type, Serialize::ID id) : ForbidData(type, id) { } + + Anope::string GetMask() override; + void SetMask(const Anope::string &) override; + + Anope::string GetCreator() override; + void SetCreator(const Anope::string &) override; + + Anope::string GetReason() override; + void SetReason(const Anope::string &) override; + + time_t GetCreated() override; + void SetCreated(const time_t &) override; + + time_t GetExpires() override; + void SetExpires(const time_t &) override; + + ForbidType GetType() override; + void SetType(const ForbidType &) override; +}; + +class ForbidDataType : public Serialize::Type +{ + public: + Serialize::Field mask, creator, reason; + Serialize::Field created, expires; + Serialize::Field type; + + ForbidDataType(Module *me) : Serialize::Type(me, "ForbidData") + , mask(this, "mask") + , creator(this, "creator") + , reason(this, "reason") + , created(this, "created") + , expires(this, "expires") + , type(this, "type") + { + } +}; + +Anope::string ForbidDataImpl::GetMask() +{ + return Get(&ForbidDataType::mask); +} + +void ForbidDataImpl::SetMask(const Anope::string &m) +{ + Set(&ForbidDataType::mask, m); +} + +Anope::string ForbidDataImpl::GetCreator() +{ + return Get(&ForbidDataType::creator); +} + +void ForbidDataImpl::SetCreator(const Anope::string &c) +{ + Set(&ForbidDataType::creator, c); +} + +Anope::string ForbidDataImpl::GetReason() +{ + return Get(&ForbidDataType::reason); +} + +void ForbidDataImpl::SetReason(const Anope::string &r) +{ + Set(&ForbidDataType::reason, r); +} + +time_t ForbidDataImpl::GetCreated() +{ + return Get(&ForbidDataType::created); +} + +void ForbidDataImpl::SetCreated(const time_t &t) +{ + Set(&ForbidDataType::created, t); +} + +time_t ForbidDataImpl::GetExpires() +{ + return Get(&ForbidDataType::expires); +} + +void ForbidDataImpl::SetExpires(const time_t &t) +{ + Set(&ForbidDataType::expires, t); +} + +ForbidType ForbidDataImpl::GetType() +{ + return Get(&ForbidDataType::type); +} + +void ForbidDataImpl::SetType(const ForbidType &t) +{ + Set(&ForbidDataType::type, t); +} + +class MyForbidService : public ForbidService +{ + public: + MyForbidService(Module *m) : ForbidService(m) + { + } + + ForbidData *FindForbid(const Anope::string &mask, ForbidType ftype) override + { + for (ForbidData *d : GetForbids()) + if (d->GetType() == ftype && Anope::Match(mask, d->GetMask(), false, true)) + return d; + return NULL; + } + + std::vector GetForbids() override + { + for (ForbidData *d : Serialize::GetObjects(forbiddata)) + if (d->GetExpires() && !Anope::NoExpire && Anope::CurTime >= d->GetExpires()) + { + Anope::string ftype = "none"; + if (d->GetType() == FT_NICK) + ftype = "nick"; + else if (d->GetType() == FT_CHAN) + ftype = "chan"; + else if (d->GetType() == FT_EMAIL) + ftype = "email"; + + Log(LOG_NORMAL, "expire/forbid", Config->GetClient("OperServ")) << "Expiring forbid for " << d->GetMask() << " type " << ftype; + d->Delete(); + } + return Serialize::GetObjects(forbiddata); + } +}; + +class CommandOSForbid : public Command +{ + ServiceReference fs; + public: + CommandOSForbid(Module *creator) : Command(creator, "operserv/forbid", 1, 5), fs("ForbidService", "forbid") + { + this->SetDesc(_("Forbid usage of nicknames, channels, and emails")); + this->SetSyntax(_("ADD {NICK|CHAN|EMAIL|REGISTER} [+\037expiry\037] \037entry\037 \037reason\037")); + this->SetSyntax(_("DEL {NICK|CHAN|EMAIL|REGISTER} \037entry\037")); + this->SetSyntax("LIST [NICK|CHAN|EMAIL|REGISTER]"); + } + + void OnUserNickChange(User *u) + { + if (u->HasMode("OPER")) + return; + + ForbidData *d = fs->FindForbid(u->nick, FT_NICK); + if (d != NULL) + { + ServiceBot *bi = Config->GetClient("NickServ"); + if (!bi) + bi = Config->GetClient("OperServ"); + if (bi) + u->SendMessage(bi, _("This nickname has been forbidden: \002{0}\002"), d->GetReason()); + if (NickServ::service) + NickServ::service->Collide(u, NULL); + } + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (!this->fs) + return; + + const Anope::string &command = params[0]; + const Anope::string &subcommand = params.size() > 1 ? params[1] : ""; + + ForbidType ftype = FT_SIZE; + if (subcommand.equals_ci("NICK")) + ftype = FT_NICK; + else if (subcommand.equals_ci("CHAN")) + ftype = FT_CHAN; + else if (subcommand.equals_ci("EMAIL")) + ftype = FT_EMAIL; + else if (subcommand.equals_ci("REGISTER")) + ftype = FT_REGISTER; + + if (command.equals_ci("ADD") && params.size() > 3 && ftype != FT_SIZE) + { + const Anope::string &expiry = params[2][0] == '+' ? params[2] : ""; + const Anope::string &entry = !expiry.empty() ? params[3] : params[2]; + Anope::string reason; + if (expiry.empty()) + reason = params[3] + " "; + if (params.size() > 4) + reason += params[4]; + reason.trim(); + + if (entry.replace_all_cs("?*", "").empty()) + { + source.Reply(_("The mask must contain at least one non wildcard character.")); + return; + } + + time_t expiryt = 0; + + if (!expiry.empty()) + { + expiryt = Anope::DoTime(expiry); + if (expiryt == -1) + { + source.Reply(_("Invalid expiry time \002{0}\002."), expiry); + return; + } + else if (expiryt) + expiryt += Anope::CurTime; + } + + NickServ::Nick *target = NickServ::FindNick(entry); + if (target != NULL && Config->GetModule("nickserv")->Get("secureadmins", "yes") && target->GetAccount()->IsServicesOper()) + { + source.Reply(_("Access denied.")); + return; + } + + ForbidData *d = this->fs->FindForbid(entry, ftype); + bool created = false; + if (d == NULL) + { + d = forbiddata.Create(); + created = true; + } + + d->SetMask(entry); + d->SetCreator(source.GetNick()); + d->SetReason(reason); + d->SetCreated(Anope::CurTime); + d->SetExpires(expiryt); + d->SetType(ftype); + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to add a forbid on " << entry << " of type " << subcommand; + source.Reply(_("Added a forbid on \002{0}\002 of type \002{1}\002 to expire on \002{2}\002."), entry, subcommand.lower(), expiryt ? Anope::strftime(expiryt, source.GetAccount()) : "never"); + + /* apply forbid */ + switch (ftype) + { + case FT_NICK: + { + int na_matches = 0; + + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + this->OnUserNickChange(it->second); + + for (auto it = NickServ::service->GetNickList().begin(); it != NickServ::service->GetNickList().end();) + { + NickServ::Nick *na = *it; + ++it; + + d = this->fs->FindForbid(na->GetNick(), FT_NICK); + if (d == NULL) + continue; + + ++na_matches; + + delete na; + } + + source.Reply(_("\002{0}\002 nickname(s) dropped."), na_matches); + break; + } + case FT_CHAN: + { + int chan_matches = 0, ci_matches = 0; + + for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end;) + { + Channel *c = it->second; + ++it; + + d = this->fs->FindForbid(c->name, FT_CHAN); + if (d == NULL) + continue; + + ServiceBot *OperServ = Config->GetClient("OperServ"); + if (IRCD->CanSQLineChannel && OperServ) + { + time_t inhabit = Config->GetModule("chanserv")->Get("inhabit", "15s"); + XLine x(c->name, OperServ->nick, Anope::CurTime + inhabit, d->GetReason()); + IRCD->SendSQLine(NULL, &x); + } + else if (ChanServ::service) + { + ChanServ::service->Hold(c); + } + + ++chan_matches; + + for (Channel::ChanUserList::const_iterator cit = c->users.begin(), cit_end = c->users.end(); cit != cit_end;) + { + User *u = cit->first; + ++cit; + + if (u->server == Me || u->HasMode("OPER")) + continue; + + reason = Anope::printf(Language::Translate(u, _("This channel has been forbidden: \002%s\002")), d->GetReason().c_str()); + + c->Kick(source.service, u, "%s", reason.c_str()); + } + } + + for (auto it = ChanServ::service->GetChannels().begin(); it != ChanServ::service->GetChannels().end();) + { + ChanServ::Channel *ci = it->second; + ++it; + + d = this->fs->FindForbid(ci->GetName(), FT_CHAN); + if (d == NULL) + continue; + + ++ci_matches; + + delete ci; + } + + source.Reply(_("\002{0}\002 channel(s) cleared, and \002{1}\002 channel(s) dropped."), chan_matches, ci_matches); + + break; + } + default: + break; + } + + } + else if (command.equals_ci("DEL") && params.size() > 2 && ftype != FT_SIZE) + { + const Anope::string &entry = params[2]; + + ForbidData *d = this->fs->FindForbid(entry, ftype); + if (d == nullptr) + { + source.Reply(_("Forbid on \002{0}\002 was not found."), entry); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to remove forbid on " << d->GetMask() << " of type " << subcommand; + source.Reply(_("\002{0}\002 deleted from the \002{1}\002 forbid list."), d->GetMask(), subcommand); + d->Delete(); + } + else if (command.equals_ci("LIST")) + { + const std::vector &forbids = this->fs->GetForbids(); + if (forbids.empty()) + { + source.Reply(_("Forbid list is empty.")); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Mask")).AddColumn(_("Type")).AddColumn(_("Creator")).AddColumn(_("Expires")).AddColumn(_("Reason")); + + unsigned shown = 0; + for (unsigned i = 0; i < forbids.size(); ++i) + { + ForbidData *d = forbids[i]; + + if (ftype != FT_SIZE && ftype != d->GetType()) + continue; + + Anope::string stype; + if (d->GetType() == FT_NICK) + stype = "NICK"; + else if (d->GetType() == FT_CHAN) + stype = "CHAN"; + else if (d->GetType() == FT_EMAIL) + stype = "EMAIL"; + else if (d->GetType() == FT_REGISTER) + stype = "REGISTER"; + else + continue; + + ListFormatter::ListEntry entry; + entry["Mask"] = d->GetMask(); + entry["Type"] = stype; + entry["Creator"] = d->GetCreator(); + entry["Expires"] = d->GetExpires() ? Anope::strftime(d->GetExpires(), NULL, true).c_str() : Language::Translate(source.GetAccount(), _("Never")); + entry["Reason"] = d->GetReason(); + list.AddEntry(entry); + ++shown; + } + + if (!shown) + { + source.Reply(_("There are no forbids of type \002{0}\002."), subcommand.upper()); + return; + } + + source.Reply(_("Forbid list:")); + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + if (shown >= forbids.size()) + source.Reply(_("End of forbid list.")); + else + source.Reply(_("End of forbid list - \002{0}\002/\002{1}\002 entries shown."), shown, forbids.size()); + } + else + this->OnSyntaxError(source, command); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Forbid allows you to forbid usage of certain nicknames, channels, and email addresses. Wildcards are accepted for all entries.")); + + const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); + if (!regexengine.empty()) + { + source.Reply(" "); + source.Reply(_("Regex matches are also supported using the %s engine. Enclose your pattern in // if this is desired."), regexengine); + } + + return true; + } +}; + +class OSForbid : public Module + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + MyForbidService forbid_service; + ForbidDataType type; + CommandOSForbid commandosforbid; + + public: + OSForbid(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , forbid_service(this) + , type(this) + , commandosforbid(this) + { + } + + void OnUserConnect(User *u, bool &exempt) override + { + if (u->Quitting() || exempt) + return; + + commandosforbid.OnUserNickChange(u); + } + + void OnUserNickChange(User *u, const Anope::string &) override + { + commandosforbid.OnUserNickChange(u); + } + + EventReturn OnCheckKick(User *u, Channel *c, Anope::string &mask, Anope::string &reason) override + { + ServiceBot *OperServ = Config->GetClient("OperServ"); + if (u->HasMode("OPER") || !OperServ) + return EVENT_CONTINUE; + + ForbidData *d = this->forbid_service.FindForbid(c->name, FT_CHAN); + if (d != NULL) + { + if (IRCD->CanSQLineChannel) + { + time_t inhabit = Config->GetModule("chanserv")->Get("inhabit", "15s"); + XLine x(c->name, OperServ->nick, Anope::CurTime + inhabit, d->GetReason()); + IRCD->SendSQLine(NULL, &x); + } + else if (ChanServ::service) + { + ChanServ::service->Hold(c); + } + + reason = Anope::printf(Language::Translate(u, _("This channel has been forbidden: %s")), d->GetReason().c_str()); + + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) override + { + if (command->name == "nickserv/info" && params.size() > 0) + { + ForbidData *d = this->forbid_service.FindForbid(params[0], FT_NICK); + if (d != NULL) + { + if (source.IsOper()) + source.Reply(_("Nick \002%s\002 is forbidden by %s: %s"), params[0], d->GetCreator(), d->GetReason()); + else + source.Reply(_("Nick \002%s\002 is forbidden."), params[0]); + return EVENT_STOP; + } + } + else if (command->name == "chanserv/info" && params.size() > 0) + { + ForbidData *d = this->forbid_service.FindForbid(params[0], FT_CHAN); + if (d != NULL) + { + if (source.IsOper()) + source.Reply(_("Channel \002%s\002 is forbidden by %s: %s"), params[0], d->GetCreator(), d->GetReason()); + else + source.Reply(_("Channel \002%s\002 is forbidden."), params[0]); + return EVENT_STOP; + } + } + else if (source.IsOper()) + return EVENT_CONTINUE; + else if (command->name == "nickserv/register" && params.size() > 1) + { + ForbidData *d = this->forbid_service.FindForbid(source.GetNick(), FT_REGISTER); + if (d != NULL) + { + source.Reply(_("\002{0}\002 may not be registered."), source.GetNick()); + return EVENT_STOP; + } + + d = this->forbid_service.FindForbid(params[1], FT_EMAIL); + if (d != NULL) + { + source.Reply(_("Your email address is not allowed, choose a different one.")); + return EVENT_STOP; + } + } + else if (command->name == "nickserv/set/email" && params.size() > 0) + { + ForbidData *d = this->forbid_service.FindForbid(params[0], FT_EMAIL); + if (d != NULL) + { + source.Reply(_("Your email address is not allowed, choose a different one.")); + return EVENT_STOP; + } + } + else if (command->name == "chanserv/register" && !params.empty()) + { + ForbidData *d = this->forbid_service.FindForbid(params[0], FT_REGISTER); + if (d != NULL) + { + source.Reply(_("Channel \002{0}\002 is currently suspended."), params[0]); + return EVENT_STOP; + } + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(OSForbid) diff --git a/modules/operserv/ignore.cpp b/modules/operserv/ignore.cpp new file mode 100644 index 000000000..d9563cc29 --- /dev/null +++ b/modules/operserv/ignore.cpp @@ -0,0 +1,393 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/os_ignore.h" + +class IgnoreImpl : public Ignore +{ + public: + IgnoreImpl(Serialize::TypeBase *type) : Ignore(type) { } + IgnoreImpl(Serialize::TypeBase *type, Serialize::ID id) : Ignore(type, id) { } + + Anope::string GetMask(); + void SetMask(const Anope::string &); + + Anope::string GetCreator(); + void SetCreator(const Anope::string &); + + Anope::string GetReason(); + void SetReason(const Anope::string &); + + time_t GetTime(); + void SetTime(const time_t &); +}; + +class IgnoreType : public Serialize::Type +{ + public: + Serialize::Field mask, creator, reason; + Serialize::Field time; + + IgnoreType(Module *me) : Serialize::Type(me, "IgnoreData") + , mask(this, "mask") + , creator(this, "creator") + , reason(this, "reason") + , time(this, "time") + { + } +}; + +Anope::string IgnoreImpl::GetMask() +{ + return Get(&IgnoreType::mask); +} + +void IgnoreImpl::SetMask(const Anope::string &m) +{ + Set(&IgnoreType::mask, m); +} + +Anope::string IgnoreImpl::GetCreator() +{ + return Get(&IgnoreType::creator); +} + +void IgnoreImpl::SetCreator(const Anope::string &c) +{ + Set(&IgnoreType::creator, c); +} + +Anope::string IgnoreImpl::GetReason() +{ + return Get(&IgnoreType::reason); +} + +void IgnoreImpl::SetReason(const Anope::string &r) +{ + Set(&IgnoreType::reason, r); +} + +time_t IgnoreImpl::GetTime() +{ + return Get(&IgnoreType::time); +} + +void IgnoreImpl::SetTime(const time_t &t) +{ + Set(&IgnoreType::time, t); +} + +class OSIgnoreService : public IgnoreService +{ + public: + OSIgnoreService(Module *o) : IgnoreService(o) + { + } + + Ignore *Find(const Anope::string &mask) override + { + User *u = User::Find(mask, true); + std::vector ignores = Serialize::GetObjects(ignoretype); + std::vector::iterator ign = ignores.begin(), ign_end = ignores.end(); + + if (u) + { + for (; ign != ign_end; ++ign) + { + Entry ignore_mask("", (*ign)->GetMask()); + if (ignore_mask.Matches(u, true)) + break; + } + } + else + { + size_t user, host; + Anope::string tmp; + /* We didn't get a user.. generate a valid mask. */ + if ((host = mask.find('@')) != Anope::string::npos) + { + if ((user = mask.find('!')) != Anope::string::npos) + { + /* this should never happen */ + if (user > host) + return NULL; + tmp = mask; + } + else + /* We have user@host. Add nick wildcard. */ + tmp = "*!" + mask; + } + /* We only got a nick.. */ + else + tmp = mask + "!*@*"; + + for (; ign != ign_end; ++ign) + if (Anope::Match(tmp, (*ign)->GetMask(), false, true)) + break; + } + + /* Check whether the entry has timed out */ + if (ign != ign_end) + { + Ignore *id = *ign; + + if (id->GetTime() && !Anope::NoExpire && id->GetTime() <= Anope::CurTime) + { + Log(LOG_NORMAL, "expire/ignore", Config->GetClient("OperServ")) << "Expiring ignore entry " << id->GetMask(); + id->Delete(); + } + else + return id; + } + + return NULL; + } +}; + +class CommandOSIgnore : public Command +{ + private: + Anope::string RealMask(const Anope::string &mask) + { + /* If it s an existing user, we ignore the hostmask. */ + User *u = User::Find(mask, true); + if (u) + return "*!*@" + u->host; + + size_t host = mask.find('@'); + /* Determine whether we get a nick or a mask. */ + if (host != Anope::string::npos) + { + size_t user = mask.find('!'); + /* Check whether we have a nick too.. */ + if (user != Anope::string::npos) + { + if (user > host) + /* this should never happen */ + return ""; + else + return mask; + } + else + /* We have user@host. Add nick wildcard. */ + return "*!" + mask; + } + + /* We only got a nick.. */ + return mask + "!*@*"; + } + + void DoAdd(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &time = params.size() > 1 ? params[1] : ""; + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + const Anope::string &reason = params.size() > 3 ? params[3] : ""; + + if (time.empty() || nick.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + time_t t = Anope::DoTime(time); + + if (t <= -1) + { + source.Reply(_("Invalid expiry time \002{0}\002."), time); + return; + } + + Anope::string mask = RealMask(nick); + if (mask.empty()) + { + source.Reply(_("Mask must be in the form \037user\037@\037host\037.")); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Ignore *ign = ignoretype.Create(); + ign->SetMask(mask); + ign->SetCreator(source.GetNick()); + ign->SetReason(reason); + ign->SetTime(t ? Anope::CurTime + t : 0); + + if (!t) + { + source.Reply(_("\002{0}\002 will now permanently be ignored."), mask); + Log(LOG_ADMIN, source, this) << "to add a permanent ignore for " << mask; + } + else + { + source.Reply(_("\002{0}\002 will now be ignored for \002{1}\002."), mask, Anope::Duration(t, source.GetAccount())); + Log(LOG_ADMIN, source, this) << "to add an ignore on " << mask << " for " << Anope::Duration(t); + } + } + + void DoList(CommandSource &source) + { + std::vector ignores = Serialize::GetObjects(ignoretype); + + for (Ignore *id : ignores) + { + if (id->GetTime() && !Anope::NoExpire && id->GetTime() <= Anope::CurTime) + { + Log(LOG_NORMAL, "expire/ignore", Config->GetClient("OperServ")) << "Expiring ignore entry " << id->GetMask(); + id->Delete(); + } + } + + ignores = Serialize::GetObjects(ignoretype); + if (ignores.empty()) + { + source.Reply(_("Ignore list is empty.")); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Mask")).AddColumn(_("Creator")).AddColumn(_("Reason")).AddColumn(_("Expires")); + + for (Ignore *ignore : ignores) + { + ListFormatter::ListEntry entry; + entry["Mask"] = ignore->GetMask(); + entry["Creator"] = ignore->GetCreator(); + entry["Reason"] = ignore->GetReason(); + entry["Expires"] = Anope::Expires(ignore->GetTime(), source.GetAccount()); + list.AddEntry(entry); + } + + source.Reply(_("Services ignore list:")); + + std::vector replies; + list.Process(replies); + + for (const Anope::string &r : replies) + source.Reply(r); + } + + void DoDel(CommandSource &source, const std::vector ¶ms) + { + const Anope::string nick = params.size() > 1 ? params[1] : ""; + if (nick.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + Anope::string mask = RealMask(nick); + if (mask.empty()) + { + source.Reply(_("Mask must be in the form \037user\037@\037host\037.")); + return; + } + + Ignore *ign = ignore_service->Find(mask); + if (!ign) + { + source.Reply(_("\002{0}\002 not found on ignore list."), mask); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "to remove an ignore on " << mask; + source.Reply(_("\002{0}\002 will no longer be ignored."), mask); + ign->Delete(); + } + + void DoClear(CommandSource &source) + { + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + for (Ignore *ign : Serialize::GetObjects(ignoretype)) + ign->Delete(); + + Log(LOG_ADMIN, source, this) << "to CLEAR the list"; + source.Reply(_("Ignore list has been cleared.")); + } + + public: + CommandOSIgnore(Module *creator) : Command(creator, "operserv/ignore", 1, 4) + { + this->SetDesc(_("Modify the Services ignore list")); + this->SetSyntax(_("ADD \037expiry\037 {\037nick\037|\037mask\037} [\037reason\037]")); + this->SetSyntax(_("DEL {\037nick\037|\037mask\037}")); + this->SetSyntax("LIST"); + this->SetSyntax("CLEAR"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + + if (cmd.equals_ci("ADD")) + return this->DoAdd(source, params); + else if (cmd.equals_ci("LIST")) + return this->DoList(source); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, params); + else if (cmd.equals_ci("CLEAR")) + return this->DoClear(source); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("The \002{0}\002 command allows you to make services ignore a user or hostmask." + " \037expiry\037 must be an integer and can be followed by one of \037d\037 (days), \037h\037 (hours), or \037m\037 (minutes)." + " If a unit specifier is not included, the default is seconds." + " To make services permanently ignore the user, use 0 as the expiry time." + " When adding a \037mask\037, it should be in the format nick!user@host, everything else will be considered a nicknames. Wildcards are permitted."), + source.command); + + const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); + if (!regexengine.empty()) + { + source.Reply(" "); + source.Reply(_("Regex matches are also supported using the \002{0}\002 engine. Enclose your pattern in // if this is desired."), regexengine); + } + + return true; + } +}; + +class OSIgnore : public Module + , public EventHook +{ + IgnoreType ignoretype; + OSIgnoreService osignoreservice; + CommandOSIgnore commandosignore; + + public: + OSIgnore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , ignoretype(this) + , osignoreservice(this) + , commandosignore(this) + { + + } + + EventReturn OnBotPrivmsg(User *u, ServiceBot *bi, Anope::string &message) override + { + if (!u->HasMode("OPER") && this->osignoreservice.Find(u->nick)) + return EVENT_STOP; + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(OSIgnore) diff --git a/modules/operserv/info.cpp b/modules/operserv/info.cpp new file mode 100644 index 000000000..7b292d4b2 --- /dev/null +++ b/modules/operserv/info.cpp @@ -0,0 +1,260 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" +#include "modules/ns_info.h" +#include "modules/cs_info.h" +#include "modules/os_info.h" + +class OperInfoImpl : public OperInfo +{ + public: + OperInfoImpl(Serialize::TypeBase *type) : OperInfo(type) { } + OperInfoImpl(Serialize::TypeBase *type, Serialize::ID id) : OperInfo(type, id) { } + + Serialize::Object *GetTarget(); + void SetTarget(Serialize::Object *); + + Anope::string GetInfo(); + void SetInfo(const Anope::string &); + + Anope::string GetCreator(); + void SetCreator(const Anope::string &); + + time_t GetCreated(); + void SetCreated(const time_t &); +}; + +class OperInfoType : public Serialize::Type +{ + public: + Serialize::ObjectField target; + Serialize::Field info, creator; + Serialize::Field created; + + OperInfoType(Module *c) : Serialize::Type(c, "OperInfo") + , target(this, "target", true) + , info(this, "info") + , creator(this, "adder") + , created(this, "created") + { + } +}; + +Serialize::Object *OperInfoImpl::GetTarget() +{ + return Get(&OperInfoType::target); +} + +void OperInfoImpl::SetTarget(Serialize::Object *t) +{ + Set(&OperInfoType::target, t); +} + +Anope::string OperInfoImpl::GetInfo() +{ + return Get(&OperInfoType::info); +} + +void OperInfoImpl::SetInfo(const Anope::string &i) +{ + Set(&OperInfoType::info, i); +} + +Anope::string OperInfoImpl::GetCreator() +{ + return Get(&OperInfoType::creator); +} + +void OperInfoImpl::SetCreator(const Anope::string &c) +{ + Set(&OperInfoType::creator, c); +} + +time_t OperInfoImpl::GetCreated() +{ + return Get(&OperInfoType::created); +} + +void OperInfoImpl::SetCreated(const time_t &t) +{ + Set(&OperInfoType::created, t); +} + +class CommandOSInfo : public Command +{ + public: + CommandOSInfo(Module *creator) : Command(creator, "operserv/info", 2, 3) + { + this->SetDesc(_("Associate oper info with a nick or channel")); + this->SetSyntax(_("ADD \037target\037 \037info\037")); + this->SetSyntax(_("DEL \037target\037 \037info\037")); + this->SetSyntax(_("CLEAR \037target\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0], target = params[1], info = params.size() > 2 ? params[2] : ""; + + Serialize::Object *e; + if (IRCD->IsChannelValid(target)) + { + ChanServ::Channel *ci = ChanServ::Find(target); + if (!ci) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), target); + return; + } + + e = ci; + } + else + { + NickServ::Nick *na = NickServ::FindNick(target); + if (!na) + { + source.Reply(_("\002{0}\002 isn't registered."), target); + return; + } + + e = na->GetAccount(); + } + + if (cmd.equals_ci("ADD")) + { + if (info.empty()) + { + this->OnSyntaxError(source, cmd); + return; + } + + std::vector oinfos = e->GetRefs(operinfo); + if (oinfos.size() >= Config->GetModule(this->module)->Get("max", "10")) + { + source.Reply(_("The oper info list for \002{0}\002 is full."), target); + return; + } + + for (OperInfo *o : oinfos) + if (o->GetInfo().equals_ci(info)) + { + source.Reply(_("The oper info already exists on \002{0}\002."), target); + return; + } + + OperInfo *o = operinfo.Create(); + o->SetTarget(e); + o->SetInfo(info); + o->SetCreator(source.GetNick()); + o->SetCreated(Anope::CurTime); + + source.Reply(_("Added info to \002{0}\002."), target); + Log(LOG_ADMIN, source, this) << "to add information to " << target; + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + else if (cmd.equals_ci("DEL")) + { + if (info.empty()) + { + this->OnSyntaxError(source, cmd); + return; + } + + std::vector oinfos = e->GetRefs(operinfo); + if (oinfos.empty()) + { + source.Reply(_("Oper info list for \002{0}\002 is empty."), target); + return; + } + + for (OperInfo *o : oinfos) + { + if (o->GetInfo().equals_ci(info)) + { + o->Delete(); + + source.Reply(_("Deleted info from \002{0}\002."), target); + Log(LOG_ADMIN, source, this) << "to remove information from " << target; + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + return; + } + } + + source.Reply(_("No such info \002{0}\002 on \002{1}\002."), info, target); + } + else if (cmd.equals_ci("CLEAR")) + { + std::vector oinfos = e->GetRefs(operinfo); + + if (oinfos.empty()) + { + source.Reply(_("Oper info list for \002{0}\002 is empty."), target); + return; + } + + for (OperInfo *o : oinfos) + o->Delete(); + + source.Reply(_("Cleared info from \002{0}\002."), target); + Log(LOG_ADMIN, source, this) << "to clear information for " << target; + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + else + { + this->OnSyntaxError(source, cmd); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Add or delete oper information for a given accpint or channel. This informaition will show to opers in the respective info command for the account or channel.")); + return true; + } +}; + +class OSInfo : public Module + , public EventHook + , public EventHook +{ + CommandOSInfo commandosinfo; + OperInfoType oinfotype; + + void OnInfo(CommandSource &source, Serialize::Object *e, InfoFormatter &info) + { + if (!source.IsOper()) + return; + + for (OperInfo *o : e->GetRefs(operinfo)) + info[_("Oper Info")] = Anope::printf(_("(by %s on %s) %s"), o->GetCreator().c_str(), Anope::strftime(o->GetCreated(), source.GetAccount(), true).c_str(), o->GetInfo().c_str()); + } + + public: + OSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosinfo(this) + , oinfotype(this) + { + } + + void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override + { + OnInfo(source, na->GetAccount(), info); + } + + void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_hidden) override + { + OnInfo(source, ci, info); + } +}; + +MODULE_INIT(OSInfo) diff --git a/modules/operserv/jupe.cpp b/modules/operserv/jupe.cpp new file mode 100644 index 000000000..e2dd30b3a --- /dev/null +++ b/modules/operserv/jupe.cpp @@ -0,0 +1,72 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSJupe : public Command +{ + public: + CommandOSJupe(Module *creator) : Command(creator, "operserv/jupe", 1, 2) + { + this->SetDesc(_("\"Jupiter\" a server")); + this->SetSyntax(_("\037server\037 [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &jserver = params[0]; + const Anope::string &reason = params.size() > 1 ? params[1] : ""; + Server *server = Server::Find(jserver, true); + + if (!IRCD->IsHostValid(jserver) || jserver.find('.') == Anope::string::npos) + source.Reply(_("\002{0}\002 is not a valid server name."), jserver); + else if (server == Me || server == Servers::GetUplink() || server->IsULined()) + source.Reply(_("You can not jupe Servoces or its uplink server.")); + else if (server && server->IsJuped()) + source.Reply(_("You can not jupe an already juped server.")); + else + { + Anope::string rbuf = "Juped by " + source.GetNick() + (!reason.empty() ? ": " + reason : ""); + /* Generate the new sid before quitting the old server, so they can't collide */ + Anope::string sid = IRCD->SID_Retrieve(); + if (server) + { + IRCD->SendSquit(server, rbuf); + server->Delete(rbuf); + } + Server *juped_server = new Server(Me, jserver, 1, rbuf, sid, true); + IRCD->SendServer(juped_server); + + Log(LOG_ADMIN, source, this) << "on " << jserver << " (" << rbuf << ")"; + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Tells services to jupiter a server -- that is, to create a fake \"server\" connected to Services which prevents the real server of that name from connecting." + " The jupe may be removed using a standard \002SQUIT\002. If a reason is given, it is placed in the server information field; otherwise, the server information field will contain the text \"Juped by \", showing the nickname of the person who jupitered the server.")); + return true; + } +}; + +class OSJupe : public Module +{ + CommandOSJupe commandosjupe; + + public: + OSJupe(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosjupe(this) + { + + } +}; + +MODULE_INIT(OSJupe) diff --git a/modules/operserv/kick.cpp b/modules/operserv/kick.cpp new file mode 100644 index 000000000..d97e21602 --- /dev/null +++ b/modules/operserv/kick.cpp @@ -0,0 +1,81 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSKick : public Command +{ + public: + CommandOSKick(Module *creator) : Command(creator, "operserv/kick", 3, 3) + { + this->SetDesc(_("Kick a user from a channel")); + this->SetSyntax(_("\037channel\037 \037user\037 \037reason\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &nick = params[1]; + const Anope::string &s = params[2]; + Channel *c; + User *u2; + + if (!(c = Channel::Find(chan))) + { + source.Reply(_("Channel \002%s\002 doesn't exist."), chan); + return; + } + + if (c->bouncy_modes) + { + source.Reply(_("Services is unable to change modes. Are your servers' U:lines configured correctly?")); + return; + } + + if (!(u2 = User::Find(nick, true))) + { + source.Reply(_("\002{0}\002 isn't currently online."), nick); + return; + } + + if (!c->Kick(source.service, u2, "%s (%s)", source.GetNick().c_str(), s.c_str())) + { + source.Reply(_("Access denied.")); + return; + } + + Log(LOG_ADMIN, source, this) << "on " << u2->nick << " in " << c->name << " (" << s << ")"; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows you to kick a user from any channel. Parameters are the same as for the standard /KICK command." + " The kick message will have the nickname of the IRCop sending the KICK command prepended; for example:\n" + "\n" + "*** SpamMan has been kicked off channel #my_channel by {0} (Alcan (Flood))"), + source.service->nick); + return true; + } +}; + +class OSKick : public Module +{ + CommandOSKick commandoskick; + + public: + OSKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandoskick(this) + { + + } +}; + +MODULE_INIT(OSKick) diff --git a/modules/operserv/kill.cpp b/modules/operserv/kill.cpp new file mode 100644 index 000000000..ef6f11ebe --- /dev/null +++ b/modules/operserv/kill.cpp @@ -0,0 +1,63 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSKill : public Command +{ + public: + CommandOSKill(Module *creator) : Command(creator, "operserv/kill", 1, 2) + { + this->SetDesc(_("Kill a user")); + this->SetSyntax(_("\037user\037 [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &nick = params[0]; + Anope::string reason = params.size() > 1 ? params[1] : ""; + + User *u2 = User::Find(nick, true); + if (u2 == NULL) + source.Reply(_("\002{0}\002 isn't currently online."), nick); + else if (u2->IsProtected() || u2->server == Me) + source.Reply(_("\002{0}\002 is protected and cannot be killed."), u2->nick); + else + { + if (reason.empty()) + reason = "No reason specified"; + if (Config->GetModule("operserv")->Get("addakiller")) + reason = "(" + source.GetNick() + ") " + reason; + Log(LOG_ADMIN, source, this) << "on " << u2->nick << " for " << reason; + u2->Kill(*source.service, reason); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows you to kill a user from the network. Parameters are the same as for the standard /KILL command.")); + return true; + } +}; + +class OSKill : public Module +{ + CommandOSKill commandoskill; + + public: + OSKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandoskill(this) + { + + } +}; + +MODULE_INIT(OSKill) diff --git a/modules/operserv/list.cpp b/modules/operserv/list.cpp new file mode 100644 index 000000000..ed825f373 --- /dev/null +++ b/modules/operserv/list.cpp @@ -0,0 +1,238 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSChanList : public Command +{ + public: + CommandOSChanList(Module *creator) : Command(creator, "operserv/chanlist", 0, 2) + { + this->SetDesc(_("Lists all channel records")); + this->SetSyntax(_("[{\037pattern\037 | \037nick\037} [\037SECRET\037]]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &pattern = !params.empty() ? params[0] : ""; + const Anope::string &opt = params.size() > 1 ? params[1] : ""; + std::set modes; + User *u2; + + if (!pattern.empty()) + Log(LOG_ADMIN, source, this) << "for " << pattern; + else + Log(LOG_ADMIN, source, this); + + if (!opt.empty() && opt.equals_ci("SECRET")) + { + modes.insert("SECRET"); + modes.insert("PRIVATE"); + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Name")).AddColumn(_("Users")).AddColumn(_("Modes")).AddColumn(_("Topic")); + + if (!pattern.empty() && (u2 = User::Find(pattern, true))) + { + source.Reply(_("Channel list for \002{0}\002:"), u2->nick); + + for (User::ChanUserList::iterator uit = u2->chans.begin(), uit_end = u2->chans.end(); uit != uit_end; ++uit) + { + ChanUserContainer *cc = uit->second; + + if (!modes.empty()) + for (std::set::iterator it = modes.begin(), it_end = modes.end(); it != it_end; ++it) + if (!cc->chan->HasMode(*it)) + continue; + + ListFormatter::ListEntry entry; + entry["Name"] = cc->chan->name; + entry["Users"] = stringify(cc->chan->users.size()); + entry["Modes"] = cc->chan->GetModes(true, true); + entry["Topic"] = cc->chan->topic; + list.AddEntry(entry); + } + } + else + { + source.Reply(_("Channel list:")); + + for (channel_map::const_iterator cit = ChannelList.begin(), cit_end = ChannelList.end(); cit != cit_end; ++cit) + { + Channel *c = cit->second; + + if (!pattern.empty() && !Anope::Match(c->name, pattern, false, true)) + continue; + if (!modes.empty()) + for (std::set::iterator it = modes.begin(), it_end = modes.end(); it != it_end; ++it) + if (!c->HasMode(*it)) + continue; + + ListFormatter::ListEntry entry; + entry["Name"] = c->name; + entry["Users"] = stringify(c->users.size()); + entry["Modes"] = c->GetModes(true, true); + entry["Topic"] = c->topic; + list.AddEntry(entry); + } + } + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of channel list.")); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Lists all channels currently in use on the IRC network, whether they are registered or not.\n" + "\n" + "If \002pattern\002 is given, lists only channels that match it. If a nicknam is given, lists only the channels the user using it is on." + " If SECRET is specified, lists only channels matching \002pattern\002 that have the +s or +p mode.")); + + const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); + if (!regexengine.empty()) + { + source.Reply(" "); + source.Reply(_("Regex matches are also supported using the {0} engine. Enclose your pattern in // if this is desired."), regexengine); + } + + return true; + } +}; + +class CommandOSUserList : public Command +{ + public: + CommandOSUserList(Module *creator) : Command(creator, "operserv/userlist", 0, 2) + { + this->SetDesc(_("Lists all user records")); + this->SetSyntax(_("[{\037pattern\037 | \037channel\037} [\037INVISIBLE\037]]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &pattern = !params.empty() ? params[0] : ""; + const Anope::string &opt = params.size() > 1 ? params[1] : ""; + Channel *c; + std::set modes; + + if (!pattern.empty()) + Log(LOG_ADMIN, source, this) << "for " << pattern; + else + Log(LOG_ADMIN, source, this); + + if (!opt.empty() && opt.equals_ci("INVISIBLE")) + modes.insert("INVIS"); + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Name")).AddColumn(_("Mask")); + + if (!pattern.empty() && (c = Channel::Find(pattern))) + { + source.Reply(_("User list for \002{0}\002:"), pattern); + + for (Channel::ChanUserList::iterator cuit = c->users.begin(), cuit_end = c->users.end(); cuit != cuit_end; ++cuit) + { + ChanUserContainer *uc = cuit->second; + + if (!modes.empty()) + for (std::set::iterator it = modes.begin(), it_end = modes.end(); it != it_end; ++it) + if (!uc->user->HasMode(*it)) + continue; + + ListFormatter::ListEntry entry; + entry["Name"] = uc->user->nick; + entry["Mask"] = uc->user->GetIdent() + "@" + uc->user->GetDisplayedHost(); + list.AddEntry(entry); + } + } + else + { + /* Historically this has been ordered, so... */ + Anope::map ordered_map; + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + ordered_map[it->first] = it->second; + + source.Reply(_("Users list:")); + + for (Anope::map::const_iterator it = ordered_map.begin(); it != ordered_map.end(); ++it) + { + User *u2 = it->second; + + if (!pattern.empty()) + { + Anope::string mask = u2->nick + "!" + u2->GetIdent() + "@" + u2->GetDisplayedHost(), mask2 = u2->nick + "!" + u2->GetIdent() + "@" + u2->host, mask3 = u2->nick + "!" + u2->GetIdent() + "@" + u2->ip.addr(); + if (!Anope::Match(mask, pattern) && !Anope::Match(mask2, pattern) && !Anope::Match(mask3, pattern)) + continue; + if (!modes.empty()) + for (std::set::iterator mit = modes.begin(), mit_end = modes.end(); mit != mit_end; ++mit) + if (!u2->HasMode(*mit)) + continue; + } + + ListFormatter::ListEntry entry; + entry["Name"] = u2->nick; + entry["Mask"] = u2->GetIdent() + "@" + u2->GetDisplayedHost(); + list.AddEntry(entry); + } + } + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of users list.")); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Lists all users currently online on the IRC network, whether their nickname is registered or not.\n" + "\n" + "If \002pattern\002 is given, lists only users that match it (it must be in the format nick!user@host)." + " If \002channel\002 is given, lists only users that are on the given channel. If INVISIBLE is specified, only users with the +i flag will be listed.")); + + const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); + if (!regexengine.empty()) + { + source.Reply(" "); + source.Reply(_("Regex matches are also supported using the %s engine.\n" + "Enclose your pattern in // if this is desired."), regexengine.c_str()); + } + + return true; + } +}; + +class OSList : public Module +{ + CommandOSChanList commandoschanlist; + CommandOSUserList commandosuserlist; + + public: + OSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandoschanlist(this) + , commandosuserlist(this) + { + + } +}; + +MODULE_INIT(OSList) diff --git a/modules/operserv/login.cpp b/modules/operserv/login.cpp new file mode 100644 index 000000000..650c48e3d --- /dev/null +++ b/modules/operserv/login.cpp @@ -0,0 +1,148 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + * + * + */ +#include "module.h" + +class CommandOSLogin : public Command +{ + public: + CommandOSLogin(Module *creator) : Command(creator, "operserv/login", 1, 1) + { + this->SetSyntax(_("\037password\037")); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &password = params[0]; + + User *u = source.GetUser(); + Oper *o = source.nc->o; + if (o == NULL) + { + source.Reply(_("No oper block for your nickname.")); + return; + } + + if (o->GetPassword().empty()) + { + source.Reply(_("Your oper block doesn't require logging in.")); + return; + } + + if (u->HasExtOK("os_login")) + { + source.Reply(_("You are already logged in.")); + return; + } + + if (o->GetPassword() != password) + { + source.Reply(_("Password incorrect.")); + u->BadPassword(); + return; + } + + Log(LOG_ADMIN, source, this) << "and successfully identified to " << source.service->nick; + u->Extend("os_login", true); + source.Reply(_("Password accepted.")); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Logs you in to {0} so you gain Services Operator privileges. This command is unnecessary if your oper block is configured without a password."), source.service->nick); + return true; + } + + const Anope::string GetDesc(CommandSource &source) const override + { + return Anope::printf(Language::Translate(source.GetAccount(), _("Login to %s")), source.service->nick.c_str()); + } +}; + +class CommandOSLogout : public Command +{ + public: + CommandOSLogout(Module *creator) : Command(creator, "operserv/logout", 0, 0) + { + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + User *u = source.GetUser(); + Oper *o = source.nc->o; + if (o == NULL) + { + source.Reply(_("No oper block for your nick.")); + return; + } + + if (o->GetPassword().empty()) + { + source.Reply(_("Your oper block doesn't require logging in.")); + return; + } + + if (!u->HasExtOK("os_login")) + { + source.Reply(_("You are not identified.")); + return; + } + + Log(LOG_ADMIN, source, this); + u->ShrinkOK("os_login"); + source.Reply(_("You have been logged out.")); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Logs you out from %s so you lose Services Operator privileges. This command is only useful if your oper block is configured with a password."), source.service->nick); + return true; + } + + const Anope::string GetDesc(CommandSource &source) const override + { + return Anope::printf(Language::Translate(source.GetAccount(), _("Logout from %s")), source.service->nick.c_str()); + } +}; + +class OSLogin : public Module + , public EventHook +{ + CommandOSLogin commandoslogin; + CommandOSLogout commandoslogout; + ExtensibleItem os_login; + + public: + OSLogin(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandoslogin(this) + , commandoslogout(this) + , os_login(this, "os_login") + { + + } + + EventReturn IsServicesOper(User *u) override + { + if (!u->Account()->o->GetPassword().empty()) + { + if (os_login.HasExt(u)) + return EVENT_ALLOW; + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(OSLogin) diff --git a/modules/operserv/logsearch.cpp b/modules/operserv/logsearch.cpp new file mode 100644 index 000000000..0fa425aba --- /dev/null +++ b/modules/operserv/logsearch.cpp @@ -0,0 +1,151 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSLogSearch : public Command +{ + static inline Anope::string CreateLogName(const Anope::string &file, time_t t = Anope::CurTime) + { + char timestamp[32]; + + tm *tm = localtime(&t); + + strftime(timestamp, sizeof(timestamp), "%Y%m%d", tm); + + return Anope::LogDir + "/" + file + "." + timestamp; + } + + public: + CommandOSLogSearch(Module *creator) : Command(creator, "operserv/logsearch", 1, 3) + { + this->SetDesc(_("Searches logs for a matching pattern")); + this->SetSyntax(_("[+\037days\037d] [+\037limit\037l] \037pattern\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + int days = 7, replies = 50; + + unsigned i; + for (i = 0; i < params.size() && params[i][0] == '+'; ++i) + { + switch (params[i][params[i].length() - 1]) + { + case 'd': + if (params[i].length() > 2) + { + Anope::string dur = params[i].substr(1, params[i].length() - 2); + try + { + days = convertTo(dur); + if (days <= 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + source.Reply(_("Invalid duration \002{0}\002, using \002{1}\002 days."), dur, days); + } + } + break; + case 'l': + if (params[i].length() > 2) + { + Anope::string dur = params[i].substr(1, params[i].length() - 2); + try + { + replies = convertTo(dur); + if (replies <= 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + source.Reply(_("Invalid limit \002{0}\002, using \002{1}\002."), dur, replies); + } + } + break; + default: + source.Reply(_("Unknown parameter: {0}"), params[i]); + } + } + + if (i >= params.size()) + { + this->OnSyntaxError(source, ""); + return; + } + + Anope::string search_string = params[i++]; + for (; i < params.size(); ++i) + search_string += " " + params[i]; + + Log(LOG_ADMIN, source, this) << "for " << search_string; + + const Anope::string &logfile_name = Config->GetModule(this->owner)->Get("logname"); + std::list matches; + for (int d = days - 1; d >= 0; --d) + { + Anope::string lf_name = CreateLogName(logfile_name, Anope::CurTime - (d * 86400)); + Log(LOG_DEBUG) << "Searching " << lf_name; + std::fstream fd(lf_name.c_str(), std::ios_base::in); + if (!fd.is_open()) + continue; + + for (Anope::string buf, token; std::getline(fd, buf.str());) + if (Anope::Match(buf, "*" + search_string + "*")) + matches.push_back(buf); + + fd.close(); + } + + unsigned found = matches.size(); + if (!found) + { + source.Reply(_("No matches for \002{0}\002 found."), search_string); + return; + } + + while (matches.size() > static_cast(replies)) + matches.pop_front(); + + source.Reply(_("Matches for \002{0}\002:"), search_string); + unsigned count = 0; + for (std::list::iterator it = matches.begin(), it_end = matches.end(); it != it_end; ++it) + source.Reply("#%d: %s", ++count, it->c_str()); + source.Reply(_("Showed \002{0}/{1}\002 matches for \002{2}\002."), matches.size(), found, search_string); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command searches the Services logfiles for messages that match the given pattern." + " The day and limit argument may be used to specify how many days of logs to search and the number of replies to limit to." + " By default this command searches one week of logs, and limits replies to 50.\n" + "\n" + "Example:\n" + " {0} +21d +500l Anope\n" + " Searches the last 21 days worth of logs for messages containing \"Anope\" and lists the most recent 500 of them."), + source.command); + return true; + } +}; + +class OSLogSearch : public Module +{ + CommandOSLogSearch commandoslogsearch; + + public: + OSLogSearch(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandoslogsearch(this) + { + } +}; + +MODULE_INIT(OSLogSearch) diff --git a/modules/operserv/main/CMakeLists.txt b/modules/operserv/main/CMakeLists.txt new file mode 100644 index 000000000..781f0ef1f --- /dev/null +++ b/modules/operserv/main/CMakeLists.txt @@ -0,0 +1 @@ +build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/operserv/main/operserv.cpp b/modules/operserv/main/operserv.cpp new file mode 100644 index 000000000..0a4f1b692 --- /dev/null +++ b/modules/operserv/main/operserv.cpp @@ -0,0 +1,337 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/help.h" +#include "modules/nickserv.h" + +class SGlineType : public XLineType +{ + public: + SGlineType(Module *creator) : XLineType(creator, "SGLine") + { + SetParent(xline); + } +}; + +class SGLineManager : public XLineManager +{ + public: + SGLineManager(Module *creator) : XLineManager(creator, "xlinemanager/sgline", 'G') { } + + void OnMatch(User *u, XLine *x) override + { + this->Send(u, x); + } + + void OnExpire(XLine *x) override + { + ::Log(Config->GetClient("OperServ"), "expire/akill") << "AKILL on \002" << x->GetMask() << "\002 has expired"; + } + + void Send(User *u, XLine *x) override + { + IRCD->SendAkill(u, x); + } + + void SendDel(XLine *x) override + { + IRCD->SendAkillDel(x); + } + + bool Check(User *u, XLine *x) override + { + if (x->regex) + { + Anope::string uh = u->GetIdent() + "@" + u->host, nuhr = u->nick + "!" + uh + "#" + u->realname; + return std::regex_match(uh.str(), *x->regex) || std::regex_match(nuhr.str(), *x->regex); + } + + if (!x->GetNick().empty() && !Anope::Match(u->nick, x->GetNick())) + return false; + + if (!x->GetUser().empty() && !Anope::Match(u->GetIdent(), x->GetUser())) + return false; + + if (!x->GetReal().empty() && !Anope::Match(u->realname, x->GetReal())) + return false; + + if (x->c && x->c->match(u->ip)) + return true; + + if (x->GetHost().empty() || Anope::Match(u->host, x->GetHost()) || Anope::Match(u->ip.addr(), x->GetHost())) + return true; + + return false; + } +}; + +class SQlineType : public XLineType +{ + public: + SQlineType(Module *creator) : XLineType(creator, "SQLine") + { + SetParent(xline); + } +}; + +class SQLineManager : public XLineManager +{ + public: + SQLineManager(Module *creator) : XLineManager(creator, "xlinemanager/sqline", 'Q') { } + + void OnMatch(User *u, XLine *x) override + { + this->Send(u, x); + } + + void OnExpire(XLine *x) override + { + ::Log(Config->GetClient("OperServ"), "expire/sqline") << "SQLINE on \002" << x->GetMask() << "\002 has expired"; + } + + void Send(User *u, XLine *x) override + { + if (!IRCD->CanSQLine) + { + if (!u) + ; + else if (NickServ::service) + NickServ::service->Collide(u, NULL); + else + u->Kill(Config->GetClient("OperServ"), "Q-Lined: " + x->GetReason()); + } + else if (x->IsRegex()) + ; + else if (x->GetMask()[0] != '#' || IRCD->CanSQLineChannel) + IRCD->SendSQLine(u, x); + } + + void SendDel(XLine *x) override + { + if (!IRCD->CanSQLine || x->IsRegex()) + ; + else if (x->GetMask()[0] != '#' || IRCD->CanSQLineChannel) + IRCD->SendSQLineDel(x); + } + + bool Check(User *u, XLine *x) override + { + if (x->regex) + return std::regex_match(u->nick.c_str(), *x->regex); + return Anope::Match(u->nick, x->GetMask()); + } + + XLine *CheckChannel(Channel *c) + { + for (XLine *x : this->GetXLines()) + { + if (x->regex) + { + if (std::regex_match(c->name.str(), *x->regex)) + return x; + } + else + { + if (x->GetMask().empty() || x->GetMask()[0] != '#') + continue; + + if (Anope::Match(c->name, x->GetMask(), false, true)) + return x; + } + } + return nullptr; + } +}; + +class SNlineType : public XLineType +{ + public: + SNlineType(Module *creator) : XLineType(creator, "SNLine") + { + SetParent(xline); + } +}; + +class SNLineManager : public XLineManager +{ + public: + SNLineManager(Module *creator) : XLineManager(creator, "xlinemanager/snline", 'N') { } + + void OnMatch(User *u, XLine *x) override + { + this->Send(u, x); + } + + void OnExpire(XLine *x) override + { + ::Log(Config->GetClient("OperServ"), "expire/snline") << "SNLINE on \002" << x->GetMask() << "\002 has expired"; + } + + void Send(User *u, XLine *x) override + { + if (IRCD->CanSNLine && !x->IsRegex()) + IRCD->SendSGLine(u, x); + + if (u) + u->Kill(Config->GetClient("OperServ"), "SNLined: " + x->GetReason()); + } + + void SendDel(XLine *x) override + { + if (IRCD->CanSNLine && !x->IsRegex()) + IRCD->SendSGLineDel(x); + } + + bool Check(User *u, XLine *x) override + { + if (x->regex) + return std::regex_match(u->realname.str(), *x->regex); + return Anope::Match(u->realname, x->GetMask(), false, true); + } +}; + +class OperServCore : public Module + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook + , public EventHook +{ + Reference OperServ; + SGlineType sgtype; + SGLineManager sglines; + SQlineType sqtype; + SQLineManager sqlines; + SNlineType sntype; + SNLineManager snlines; + + public: + OperServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) + , sgtype(this) + , sglines(this) + , sqtype(this) + , sqlines(this) + , sntype(this) + , snlines(this) + { + + /* Yes, these are in this order for a reason. Most violent->least violent. */ + XLineManager::RegisterXLineManager(&sglines); + XLineManager::RegisterXLineManager(&sqlines); + XLineManager::RegisterXLineManager(&snlines); + } + + ~OperServCore() + { + this->sglines.Clear(); + this->sqlines.Clear(); + this->snlines.Clear(); + + XLineManager::UnregisterXLineManager(&sglines); + XLineManager::UnregisterXLineManager(&sqlines); + XLineManager::UnregisterXLineManager(&snlines); + } + + void OnReload(Configuration::Conf *conf) override + { + const Anope::string &osnick = conf->GetModule(this)->Get("client"); + + if (osnick.empty()) + throw ConfigException(this->name + ": must be defined"); + + ServiceBot *bi = ServiceBot::Find(osnick, true); + if (!bi) + throw ConfigException(this->name + ": no bot named " + osnick); + + OperServ = bi; + } + + EventReturn OnBotPrivmsg(User *u, ServiceBot *bi, Anope::string &message) override + { + if (bi == OperServ && !u->HasMode("OPER") && Config->GetModule(this)->Get("opersonly")) + { + u->SendMessage(bi, "Access denied."); + ::Log(bi, "bados") << "Denied access to " << bi->nick << " from " << u->GetMask() << " (non-oper)"; + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + void OnServerQuit(Server *server) override + { + if (server->IsJuped()) + ::Log(server, "squit", OperServ) << "Received SQUIT for juped server " << server->GetName(); + } + + void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override + { + if (mname == "OPER") + ::Log(u, "oper", OperServ) << "is now an IRC operator."; + } + + void OnUserModeUnset(const MessageSource &setter, User *u, const Anope::string &mname) override + { + if (mname == "OPER") + ::Log(u, "oper", OperServ) << "is no longer an IRC operator"; + } + + void OnUserConnect(User *u, bool &exempt) override + { + if (!u->Quitting() && !exempt) + XLineManager::CheckAll(u); + } + + void OnUserNickChange(User *u, const Anope::string &oldnick) override + { + if (!u->HasMode("OPER")) + this->sqlines.CheckAllXLines(u); + } + + EventReturn OnCheckKick(User *u, Channel *c, Anope::string &mask, Anope::string &reason) override + { + XLine *x = this->sqlines.CheckChannel(c); + if (x) + { + this->sqlines.OnMatch(u, x); + reason = x->GetReason(); + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override + { + if (!params.empty() || source.c || source.service != *OperServ) + return EVENT_CONTINUE; + source.Reply(_("%s commands:"), OperServ->nick.c_str()); + return EVENT_CONTINUE; + } + + void OnPostHelp(CommandSource &source, const std::vector ¶ms) override + { + } + + void OnLog(::Log *l) override + { + if (l->type == LOG_SERVER) + l->bi = OperServ; + } +}; + +MODULE_INIT(OperServCore) + diff --git a/modules/operserv/mode.cpp b/modules/operserv/mode.cpp new file mode 100644 index 000000000..30e1d502b --- /dev/null +++ b/modules/operserv/mode.cpp @@ -0,0 +1,187 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSMode : public Command +{ + public: + CommandOSMode(Module *creator) : Command(creator, "operserv/mode", 2, 3) + { + this->SetDesc(_("Change channel modes")); + this->SetSyntax(_("\037channel\037 \037modes\037")); + this->SetSyntax(_("\037channel\037 CLEAR [ALL]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &target = params[0]; + const Anope::string &modes = params[1]; + + Reference c = Channel::Find(target); + if (!c) + source.Reply(_("Channel \002{0}\002 doesn't exist."), target); + else if (c->bouncy_modes) + source.Reply(_("Services is unable to change modes. Are your servers' U:lines configured correctly?")); + else if (modes.equals_ci("CLEAR")) + { + bool all = params.size() > 2 && params[2].equals_ci("ALL"); + + const Channel::ModeList chmodes = c->GetModes(); + for (Channel::ModeList::const_iterator it = chmodes.begin(), it_end = chmodes.end(); it != it_end && c; ++it) + c->RemoveMode(c->ci->WhoSends(), it->first, it->second, false); + + if (!c) + { + source.Reply(_("Modes cleared on %s and the channel destroyed."), target.c_str()); + return; + } + + if (all) + { + for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ++it) + { + ChanUserContainer *uc = it->second; + + if (uc->user->HasMode("OPER")) + continue; + + for (size_t i = uc->status.Modes().length(); i > 0; --i) + c->RemoveMode(c->ci->WhoSends(), ModeManager::FindChannelModeByChar(uc->status.Modes()[i - 1]), uc->user->GetUID(), false); + } + + source.Reply(_("All modes cleared on \002{0}\002."), c->name); + } + else + source.Reply(_("Non-status modes cleared on \002{0}\002."), c->name); + } + else + { + spacesepstream sep(modes + (params.size() > 2 ? " " + params[2] : "")); + Anope::string mode; + int add = 1; + Anope::string log_modes, log_params; + + sep.GetToken(mode); + for (unsigned i = 0; i < mode.length() && c; ++i) + { + char ch = mode[i]; + + if (ch == '+') + { + add = 1; + log_modes += "+"; + continue; + } + else if (ch == '-') + { + add = 0; + log_modes += "-"; + continue; + } + + ChannelMode *cm = ModeManager::FindChannelModeByChar(ch); + if (!cm) + continue; + + Anope::string param, param_log; + if (cm->type != MODE_REGULAR) + { + if (cm->type == MODE_PARAM && !add && anope_dynamic_static_cast(cm)->minus_no_arg) + ; + else if (!sep.GetToken(param)) + continue; + + param_log = param; + + if (cm->type == MODE_STATUS) + { + User *targ = User::Find(param, true); + if (targ == NULL || c->FindUser(targ) == NULL) + continue; + param = targ->GetUID(); + } + } + + log_modes += cm->mchar; + if (!param.empty()) + log_params += " " + param_log; + + if (add) + c->SetMode(source.service, cm, param, false); + else + c->RemoveMode(source.service, cm, param, false); + } + + if (!log_modes.replace_all_cs("+", "").replace_all_cs("-", "").empty()) + Log(LOG_ADMIN, source, this) << log_modes << log_params << " on " << (c ? c->name : target); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows Services Operators to change modes for any channel. Parameters are the same as for the standard /MODE command.\n" + "Alternatively, CLEAR may be given to clear all modes on the channel.\n" + "If CLEAR ALL is given then all modes, including user status, is removed.")); + return true; + } +}; + +class CommandOSUMode : public Command +{ + public: + CommandOSUMode(Module *creator) : Command(creator, "operserv/umode", 2, 2) + { + this->SetDesc(_("Change user modes")); + this->SetSyntax(_("\037user\037 \037modes\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &target = params[0]; + const Anope::string &modes = params[1]; + + User *u2 = User::Find(target, true); + if (!u2) + source.Reply(_("\002{0}\002 isn't currently online."), target); + else + { + u2->SetModes(source.service, "%s", modes.c_str()); + source.Reply(_("Changed usermodes of \002{0}\002 to \002{1}\002."), u2->nick.c_str(), modes.c_str()); + + u2->SendMessage(*source.service, _("\002{0}\002 changed your usermodes to \002{1}\002."), source.GetNick(), modes); + + Log(LOG_ADMIN, source, this) << modes << " on " << target; + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows Services Operators to change modes for any user. Parameters are the same as for the standard /MODE command.")); + return true; + } +}; + +class OSMode : public Module +{ + CommandOSMode commandosmode; + CommandOSUMode commandosumode; + + public: + OSMode(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosmode(this) + , commandosumode(this) + { + + } +}; + +MODULE_INIT(OSMode) diff --git a/modules/operserv/modinfo.cpp b/modules/operserv/modinfo.cpp new file mode 100644 index 000000000..f3c6db00f --- /dev/null +++ b/modules/operserv/modinfo.cpp @@ -0,0 +1,214 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSModInfo : public Command +{ + public: + CommandOSModInfo(Module *creator) : Command(creator, "operserv/modinfo", 1, 1) + { + this->SetDesc(_("Info about a loaded module")); + this->SetSyntax(_("\037modname\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &file = params[0]; + + Log(LOG_ADMIN, source, this) << "on " << file; + + Module *m = ModuleManager::FindModule(file); + if (m) + { + source.Reply(_("Module: \002{0}\002 Version: \002{1}\002 Author: \002{2}\002 Loaded: \002{3}\002"), m->name, !m->version.empty() ? m->version : "?", !m->author.empty() ? m->author : "Unknown", Anope::strftime(m->created, source.GetAccount())); + if (Anope::Debug) + source.Reply(_(" Loaded at: {0}"), Anope::printf("%o", m->handle)); + + std::vector servicekeys = Service::GetServiceKeys("Command"); + for (unsigned i = 0; i < servicekeys.size(); ++i) + { + ServiceReference c("Command", servicekeys[i]); + if (!c || c->owner != m) + continue; + + source.Reply(_(" Providing service: \002{0}\002"), c->name); + + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + { + User *u = it->second; + + if (u->type != UserType::BOT) + continue; + + ServiceBot *bi = anope_dynamic_static_cast(u); + + for (CommandInfo::map::const_iterator cit = bi->commands.begin(), cit_end = bi->commands.end(); cit != cit_end; ++cit) + { + const Anope::string &c_name = cit->first; + const CommandInfo &info = cit->second; + if (info.name != c->name) + continue; + source.Reply(_(" Command \002{0}\002 on \002{1}\002 is linked to \002{2}\002"), c_name, bi->nick, c->name); + } + } + } + } + else + source.Reply(_("No information about module \002{0}\002 is available."), file); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command lists information about the specified loaded module.")); + return true; + } +}; + +class CommandOSModList : public Command +{ + public: + CommandOSModList(Module *creator) : Command(creator, "operserv/modlist", 0, 1) + { + this->SetDesc(_("List loaded modules")); + this->SetSyntax("[all|third|vendor|extra|database|encryption|pseudoclient|protocol]"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string ¶m = !params.empty() ? params[0] : ""; + + if (!param.empty()) + Log(LOG_ADMIN, source, this) << "for " << param; + else + Log(LOG_ADMIN, source, this); + + bool third = false, vendor = false, extra = false, database = false, encryption = false, pseudoclient = false, protocol = false; + + if (param.equals_ci("all")) + third = vendor = extra = database = encryption = pseudoclient = protocol = true; + else if (param.equals_ci("third")) + third = true; + else if (param.equals_ci("vendor")) + vendor = true; + else if (param.equals_ci("extra")) + extra = true; + else if (param.equals_ci("database")) + database = true; + else if (param.equals_ci("encryption")) + encryption = true; + else if (param.equals_ci("pseudoclient")) + pseudoclient = true; + else if (param.equals_ci("protocol")) + protocol = true; + else + third = extra = database = encryption = protocol = true; + + Module *protomod = ModuleManager::FindFirstOf(PROTOCOL); + + source.Reply(_("Current module list:")); + + int count = 0; + for (std::list::iterator it = ModuleManager::Modules.begin(), it_end = ModuleManager::Modules.end(); it != it_end; ++it) + { + Module *m = *it; + + bool show = false; + Anope::string mtype; + + if (m->type & PROTOCOL) + { + show |= protocol; + if (!mtype.empty()) + mtype += ", "; + mtype += "Protocol"; + } + if (m->type & PSEUDOCLIENT) + { + show |= pseudoclient; + if (!mtype.empty()) + mtype += ", "; + mtype += "Pseudoclient"; + } + if (m->type & ENCRYPTION) + { + show |= encryption; + if (!mtype.empty()) + mtype += ", "; + mtype += "Encryption"; + } + if (m->type & DATABASE) + { + show |= database; + if (!mtype.empty()) + mtype += ", "; + mtype += "Database"; + } + if (m->type & EXTRA) + { + show |= extra; + if (!mtype.empty()) + mtype += ", "; + mtype += "Extra"; + } + if (m->type & VENDOR) + { + show |= vendor; + if (!mtype.empty()) + mtype += ", "; + mtype += "Vendor"; + } + if (m->type & THIRD) + { + show |= third; + if (!mtype.empty()) + mtype += ", "; + mtype += "Third"; + } + + if (!show) + continue; + else if (m->type & PROTOCOL && param.empty() && m != protomod) + continue; + + ++count; + + source.Reply(_("Module: \002{0}\002 [{1}] [{2}]"), m->name, m->version, mtype); + } + + if (!count) + source.Reply(_("No modules currently loaded matching that criteria.")); + else + source.Reply(_("\002{0}\002 modules loaded."), count); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Lists currently loaded modules.")); + return true; + } +}; + +class OSModInfo : public Module +{ + CommandOSModInfo commandosmodinfo; + CommandOSModList commandosmodlist; + + public: + OSModInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosmodinfo(this) + , commandosmodlist(this) + { + + } +}; + +MODULE_INIT(OSModInfo) diff --git a/modules/operserv/module.cpp b/modules/operserv/module.cpp new file mode 100644 index 000000000..14bc897e3 --- /dev/null +++ b/modules/operserv/module.cpp @@ -0,0 +1,179 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSModLoad : public Command +{ + public: + CommandOSModLoad(Module *creator) : Command(creator, "operserv/modload", 1, 1) + { + this->SetDesc(_("Load a module")); + this->SetSyntax(_("\037modname\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &mname = params[0]; + + ModuleReturn status = ModuleManager::LoadModule(mname, source.GetUser()); + if (status == MOD_ERR_OK) + { + Log(LOG_ADMIN, source, this) << "to load module " << mname; + source.Reply(_("Module \002{0}\002 loaded."), mname); + } + else if (status == MOD_ERR_EXISTS) + source.Reply(_("Module \002{0}\002 is already loaded."), mname); + else + source.Reply(_("Unable to load module \002{0}\002."), mname); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command loads the module named \037modname\037.")); + return true; + } +}; + +class CommandOSModReLoad : public Command +{ + public: + CommandOSModReLoad(Module *creator) : Command(creator, "operserv/modreload", 1, 1) + { + this->SetDesc(_("Reload a module")); + this->SetSyntax(_("\037modname\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &mname = params[0]; + + Module *m = ModuleManager::FindModule(mname); + if (!m) + { + source.Reply(_("Module \002{0}\002 isn't loaded."), mname); + return; + } + + if (!m->handle || m->GetPermanent()) + { + source.Reply(_("Unable to remove module \002{0}\002."), m->name); + return; + } + + Module *protocol = ModuleManager::FindFirstOf(PROTOCOL); + if (m->type == PROTOCOL && m != protocol) + { + source.Reply(_("You can not reload this module directly, instead reload {0}."), protocol ? protocol->name : "(unknown)"); + return; + } + + /* Unrecoverable */ + bool fatal = m->type == PROTOCOL; + ModuleReturn status = ModuleManager::UnloadModule(m, source.GetUser()); + + if (status != MOD_ERR_OK) + { + source.Reply(_("Unable to remove module \002{0}\002."), mname); + return; + } + + status = ModuleManager::LoadModule(mname, source.GetUser()); + if (status == MOD_ERR_OK) + { + Log(LOG_ADMIN, source, this) << "to reload module " << mname; + source.Reply(_("Module \002{0}\002 reloaded."), mname); + } + else + { + if (fatal) + { + Anope::QuitReason = "Unable to reload module " + mname; + Anope::Quitting = true; + } + else + source.Reply(_("Unable to load module \002{0}\002."), mname); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command reloads the module named \037modname\037.")); + return true; + } +}; + +class CommandOSModUnLoad : public Command +{ + public: + CommandOSModUnLoad(Module *creator) : Command(creator, "operserv/modunload", 1, 1) + { + this->SetDesc(_("Un-Load a module")); + this->SetSyntax(_("\037modname\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &mname = params[0]; + + Module *m = ModuleManager::FindModule(mname); + if (!m) + { + source.Reply(_("Module \002{0}\002 isn't loaded."), mname); + return; + } + + if (!m->handle || m->GetPermanent() || m->type == PROTOCOL) + { + source.Reply(_("Unable to remove module \002{0}\002."), m->name); + return; + } + + Log(this->owner) << "Trying to unload module [" << mname << "]"; + + ModuleReturn status = ModuleManager::UnloadModule(m, source.GetUser()); + + if (status == MOD_ERR_OK) + { + Log(LOG_ADMIN, source, this) << "to unload module " << mname; + source.Reply(_("Module \002{0}\002 unloaded."), mname); + } + else + source.Reply(_("Unable to remove module \002{0}\002."), mname); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("This command unloads the module named \037modname\037.")); + return true; + } +}; + +class OSModule : public Module +{ + CommandOSModLoad commandosmodload; + CommandOSModReLoad commandosmodreload; + CommandOSModUnLoad commandosmodunload; + + public: + OSModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosmodload(this) + , commandosmodreload(this) + , commandosmodunload(this) + { + this->SetPermanent(true); + + } +}; + +MODULE_INIT(OSModule) diff --git a/modules/operserv/news.cpp b/modules/operserv/news.cpp new file mode 100644 index 000000000..03a1e3a57 --- /dev/null +++ b/modules/operserv/news.cpp @@ -0,0 +1,459 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at info@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/os_news.h" + +/* List of messages for each news type. This simplifies message sending. */ + +enum +{ + MSG_SYNTAX, + MSG_LIST_HEADER, + MSG_LIST_NONE, + MSG_ADDED, + MSG_DEL_NOT_FOUND, + MSG_DELETED, + MSG_DEL_NONE, + MSG_DELETED_ALL, + MSG_SIZE +}; + +static struct NewsMessages +{ + NewsType type; + Anope::string name; + const char *msgs[MSG_SIZE]; +} msgarray[] = { + {NEWS_LOGON, "LOGON", + {_("LOGONNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"), + _("Logon news items:"), + _("There is no logon news."), + _("Added new logon news item."), + _("Logon news item #{0} not found!"), + _("Logon news item #{0} deleted."), + _("No logon news items to delete!"), + _("All logon news items deleted.")} + }, + {NEWS_OPER, "OPER", + {_("OPERNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"), + _("Oper news items:"), + _("There is no oper news."), + _("Added new oper news item."), + _("Oper news item #{0} not found!"), + _("Oper news item #{0} deleted."), + _("No oper news items to delete!"), + _("All oper news items deleted.")} + }, + {NEWS_RANDOM, "RANDOM", + {_("RANDOMNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"), + _("Random news items:"), + _("There is no random news."), + _("Added new random news item."), + _("Random news item #{0} not found!"), + _("Random news item #{0} deleted."), + _("No random news items to delete!"), + _("All random news items deleted.")} + } +}; + +class NewsItemImpl : public NewsItem +{ + public: + NewsItemImpl(Serialize::TypeBase *type) : NewsItem(type) { } + NewsItemImpl(Serialize::TypeBase *type, Serialize::ID id) : NewsItem(type, id) { } + + NewsType GetNewsType() override; + void SetNewsType(const NewsType &) override; + + Anope::string GetText() override; + void SetText(const Anope::string &) override; + + Anope::string GetWho() override; + void SetWho(const Anope::string &) override; + + time_t GetTime() override; + void SetTime(const time_t &) override; +}; + +class NewsItemType : public Serialize::Type +{ + public: + Serialize::Field type; + Serialize::Field text; + Serialize::Field who; + Serialize::Field time; + + NewsItemType(Module *me) : Serialize::Type(me, "NewsItem") + , type(this, "type") + , text(this, "text") + , who(this, "who") + , time(this, "time") + { + } +}; + +NewsType NewsItemImpl::GetNewsType() +{ + return Get(&NewsItemType::type); +} + +void NewsItemImpl::SetNewsType(const NewsType &t) +{ + Set(&NewsItemType::type, t); +} + +Anope::string NewsItemImpl::GetText() +{ + return Get(&NewsItemType::text); +} + +void NewsItemImpl::SetText(const Anope::string &t) +{ + Set(&NewsItemType::text, t); +} + +Anope::string NewsItemImpl::GetWho() +{ + return Get(&NewsItemType::who); +} + +void NewsItemImpl::SetWho(const Anope::string &w) +{ + Set(&NewsItemType::who, w); +} + +time_t NewsItemImpl::GetTime() +{ + return Get(&NewsItemType::time); +} + +void NewsItemImpl::SetTime(const time_t &t) +{ + Set(&NewsItemType::time, t); +} + +static const char **findmsgs(NewsType type) +{ + for (unsigned i = 0; i < sizeof(msgarray) / sizeof(*msgarray); ++i) + if (msgarray[i].type == type) + return msgarray[i].msgs; + return NULL; +} + +class NewsBase : public Command +{ + protected: + void DoList(CommandSource &source, NewsType ntype, const char **msgs) + { + std::vector list = Serialize::GetObjects(newsitem); + + if (list.empty()) + { + source.Reply(msgs[MSG_LIST_NONE]); + return; + } + + ListFormatter lflist(source.GetAccount()); + lflist.AddColumn(_("Number")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Text")); + + unsigned int i = 1; + for (NewsItem *n : list) + { + ListFormatter::ListEntry entry; + entry["Number"] = stringify(i++ + 1); + entry["Creator"] = n->GetWho(); + entry["Created"] = Anope::strftime(n->GetTime(), NULL, true); + entry["Text"] = n->GetText(); + lflist.AddEntry(entry); + } + + source.Reply(msgs[MSG_LIST_HEADER]); + + std::vector replies; + lflist.Process(replies); + + for (i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of news list.")); + } + + void DoAdd(CommandSource &source, const std::vector ¶ms, NewsType ntype, const char **msgs) + { + const Anope::string text = params.size() > 1 ? params[1] : ""; + + if (text.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + NewsItem *ni = newsitem.Create(); + ni->SetNewsType(ntype); + ni->SetText(text); + ni->SetTime(Anope::CurTime); + ni->SetWho(source.GetNick()); + + source.Reply(msgs[MSG_ADDED]); + Log(LOG_ADMIN, source, this) << "to add a news item"; + } + + void DoDel(CommandSource &source, const std::vector ¶ms, NewsType ntype, const char **msgs) + { + const Anope::string &text = params.size() > 1 ? params[1] : ""; + std::vector list = Serialize::GetObjects(newsitem); + + if (text.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (list.empty()) + { + source.Reply(msgs[MSG_LIST_NONE]); + return; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + if (!text.equals_ci("ALL")) + { + try + { + unsigned num = convertTo(text); + if (num > 0 && num <= list.size()) + { + list[num - 1]->Delete(); + source.Reply(msgs[MSG_DELETED], num); + Log(LOG_ADMIN, source, this) << "to delete a news item"; + return; + } + } + catch (const ConvertException &) { } + + source.Reply(msgs[MSG_DEL_NOT_FOUND], text.c_str()); + } + else + { + for (NewsItem *n : list) + n->Delete(); + source.Reply(msgs[MSG_DELETED_ALL]); + Log(LOG_ADMIN, source, this) << "to delete all news items"; + } + } + + void DoNews(CommandSource &source, const std::vector ¶ms, NewsType ntype) + { + const Anope::string &cmd = params[0]; + + const char **msgs = findmsgs(ntype); + if (!msgs) + throw CoreException("news: Invalid type to do_news()"); + + if (cmd.equals_ci("LIST")) + return this->DoList(source, ntype, msgs); + else if (cmd.equals_ci("ADD")) + return this->DoAdd(source, params, ntype, msgs); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, params, ntype, msgs); + else + this->OnSyntaxError(source, ""); + } + public: + NewsBase(Module *creator, const Anope::string &newstype) : Command(creator, newstype, 1, 2) + { + this->SetSyntax(_("ADD \037text\037")); + this->SetSyntax(_("DEL {\037num\037 | ALL}")); + this->SetSyntax("LIST"); + } + + virtual void Execute(CommandSource &source, const std::vector ¶ms) anope_abstract; + + virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_abstract; +}; + +class CommandOSLogonNews : public NewsBase +{ + public: + CommandOSLogonNews(Module *creator) : NewsBase(creator, "operserv/logonnews") + { + this->SetDesc(_("Define messages to be shown to users at logon")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + return this->DoNews(source, params, NEWS_LOGON); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Edits or displays the list of logon news messages. When a user connects to the network, these messages will be sent to them." + " However, no more than \002{0}\002 messages will be sent in order to avoid flooding the user." + " If there are more news messages, only the most recent will be sent."), + Config->GetModule(this->owner)->Get("newscount", "3")); + return true; + } +}; + +class CommandOSOperNews : public NewsBase +{ + public: + CommandOSOperNews(Module *creator) : NewsBase(creator, "operserv/opernews") + { + this->SetDesc(_("Define messages to be shown to users who oper")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + return this->DoNews(source, params, NEWS_OPER); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Edits or displays the list of oper news messages." + " When a user opers up (with the /OPER command), these messages will be sent to them." + " However, no more than \002{0}\002 messages will be sent in order to avoid flooding the user." + " If there are more news messages, only the most recent will be sent."), + Config->GetModule(this->owner)->Get("newscount", "3")); + return true; + } +}; + +class CommandOSRandomNews : public NewsBase +{ + public: + CommandOSRandomNews(Module *creator) : NewsBase(creator, "operserv/randomnews") + { + this->SetDesc(_("Define messages to be randomly shown to users at logon")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + return this->DoNews(source, params, NEWS_RANDOM); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Edits or displays the list of random news messages. When a user connects to the network, one (and only one) of the random news will be randomly chosen and sent to them.")); + return true; + } +}; + +class OSNews : public Module + , public EventHook + , public EventHook +{ + NewsItemType newsitemtype; + + CommandOSLogonNews commandoslogonnews; + CommandOSOperNews commandosopernews; + CommandOSRandomNews commandosrandomnews; + + Anope::string oper_announcer, announcer; + unsigned news_count; + unsigned cur_rand_news = 0; + + void DisplayNews(User *u, NewsType Type) + { + std::vector list = Serialize::GetObjects(newsitem); + if (list.empty()) + return; + + ServiceBot *bi = NULL; + if (Type == NEWS_OPER) + bi = ServiceBot::Find(Config->GetModule(this)->Get("oper_announcer", "OperServ"), true); + else + bi = ServiceBot::Find(Config->GetModule(this)->Get("announcer", "Global"), true); + if (bi == NULL) + return; + + Anope::string msg; + if (Type == NEWS_LOGON) + msg = _("[\002Logon News\002 - {0}] {1}"); + else if (Type == NEWS_OPER) + msg = _("[\002Oper News\002 - {0}] {1}"); + else if (Type == NEWS_RANDOM) + msg = _("[\002Random News\002 - {0}] {1]"); + else + return; + + unsigned int randnews = 0; + for (NewsItem *n : list) + if (n->GetNewsType() == NEWS_RANDOM) + ++randnews; + + /* Reset to head of list to get first random news value */ + if (Type == NEWS_RANDOM && cur_rand_news >= randnews) + cur_rand_news = 0; + + unsigned i = 0, displayed = 0; + for (NewsItem *n : list) + { + if (n->GetNewsType() != Type) + continue; + + if (Type == NEWS_RANDOM && i++ != cur_rand_news) + continue; + + u->SendMessage(bi, msg.c_str(), Anope::strftime(n->GetTime(), u->Account(), true), n->GetText()); + + ++displayed; + + if (Type == NEWS_RANDOM) + { + ++cur_rand_news; + break; + } + + if (displayed >= news_count) + break; + } + } + + public: + OSNews(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , newsitemtype(this) + , commandoslogonnews(this) + , commandosopernews(this) + , commandosrandomnews(this) + { + } + + void OnReload(Configuration::Conf *conf) override + { + oper_announcer = conf->GetModule(this)->Get("oper_announcer", "OperServ"); + announcer = conf->GetModule(this)->Get("announcer", "Global"); + news_count = conf->GetModule(this)->Get("newscount", "3"); + } + + void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override + { + if (mname == "OPER") + DisplayNews(u, NEWS_OPER); + } + + void OnUserConnect(User *user, bool &) override + { + if (user->Quitting() || !user->server->IsSynced()) + return; + + DisplayNews(user, NEWS_LOGON); + DisplayNews(user, NEWS_RANDOM); + } +}; + +MODULE_INIT(OSNews) diff --git a/modules/operserv/noop.cpp b/modules/operserv/noop.cpp new file mode 100644 index 000000000..a12bef6a2 --- /dev/null +++ b/modules/operserv/noop.cpp @@ -0,0 +1,98 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSNOOP : public Command +{ + public: + CommandOSNOOP(Module *creator) : Command(creator, "operserv/noop", 2, 2) + { + this->SetDesc(_("Remove all operators from a server remotely")); + this->SetSyntax(_("SET \037server\037")); + this->SetSyntax(_("REVOKE \037server\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + const Anope::string &server = params[1]; + + Server *s = Server::Find(server, true); + if (s == NULL) + source.Reply(_("Server \002{0}\002 does not exist."), server); + else if (s == Me || s->IsJuped() || s->IsULined()) + source.Reply(_("You can not NOOP Services.")); + else if (cmd.equals_ci("SET")) + { + /* Remove the O:lines */ + IRCD->SendSVSNOOP(s, true); + s->Extend("noop", source.GetNick()); + + Log(LOG_ADMIN, source, this) << "SET on " << s->GetName(); + source.Reply(_("All operators from \002{0}\002 have been removed."), s->GetName()); + + Anope::string reason = "NOOP command used by " + source.GetNick(); + /* Kill all the IRCops of the server */ + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + { + User *u2 = it->second; + + if (u2->server == s && u2->HasMode("OPER")) + u2->Kill(*source.service, reason); + } + } + else if (cmd.equals_ci("REVOKE")) + { + s->ShrinkOK("noop"); + IRCD->SendSVSNOOP(s, false); + Log(LOG_ADMIN, source, this) << "REVOKE on " << s->GetName(); + source.Reply(_("All O:lines of \002{0}\002 have been reset."), s->GetName()); + } + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("\002{0} SET\002 kills all operators from the given \002server\002 and prevents operators from opering up on the given server." + " \002{0} REVOKE\002 removes this restriction.")); + return true; + } +}; + +class OSNOOP : public Module + , public EventHook +{ + CommandOSNOOP commandosnoop; + ExtensibleItem noop; + + public: + OSNOOP(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosnoop(this) + , noop(this, "noop") + { + + } + + void OnUserModeSet(const MessageSource &, User *u, const Anope::string &mname) override + { + Anope::string *setter; + if (mname == "OPER" && (setter = noop.Get(u->server))) + { + Anope::string reason = "NOOP command used by " + *setter; + ServiceBot *OperServ = Config->GetClient("OperServ"); + u->Kill(OperServ, reason); + } + } +}; + +MODULE_INIT(OSNOOP) diff --git a/modules/operserv/oline.cpp b/modules/operserv/oline.cpp new file mode 100644 index 000000000..34c13a4a3 --- /dev/null +++ b/modules/operserv/oline.cpp @@ -0,0 +1,74 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSOLine : public Command +{ + public: + CommandOSOLine(Module *creator) : Command(creator, "operserv/oline", 2, 2) + { + this->SetDesc(_("Give Operflags to a certain user")); + this->SetSyntax(_("\037nick\037 \037flags\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &nick = params[0]; + const Anope::string &flag = params[1]; + User *u2 = NULL; + + /* let's check whether the user is online */ + if (!(u2 = User::Find(nick, true))) + source.Reply(_("\002{0}\002 isn't currently online."), nick); + else if (u2 && flag[0] == '+') + { + IRCD->SendSVSO(source.service, nick, flag); + u2->SetMode(source.service, "OPER"); + u2->SendMessage(*source.service, _("You are now an IRC Operator.")); + source.Reply(_("Operflags \002{0}\002 have been added for \002{1}\002."), flag, nick); + Log(LOG_ADMIN, source, this) << "for " << nick; + } + else if (u2 && flag[0] == '-') + { + IRCD->SendSVSO(source.service, nick, flag); + source.Reply(_("Operflags \002{0}\002 have been removed from \002{1}\002."), flag, nick); + Log(LOG_ADMIN, source, this) << "for " << nick; + } + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows Services Operators to give Operflags to any user. Flags have to be prefixed with a \"+\" or a \"-\". To remove all flags simply type a \"-\" instead of any flags.")); + return true; + } +}; + +class OSOLine : public Module +{ + CommandOSOLine commandosoline; + + public: + OSOLine(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosoline(this) + { + + if (!IRCD || !IRCD->CanSVSO) + throw ModuleException("Your IRCd does not support OMODE."); + + } +}; + +MODULE_INIT(OSOLine) diff --git a/modules/operserv/oper.cpp b/modules/operserv/oper.cpp new file mode 100644 index 000000000..923137013 --- /dev/null +++ b/modules/operserv/oper.cpp @@ -0,0 +1,241 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSOper : public Command +{ + bool HasPrivs(CommandSource &source, OperType *ot) const + { + std::list commands = ot->GetCommands(), privs = ot->GetPrivs(); + + for (std::list::iterator it = commands.begin(); it != commands.end(); ++it) + if (!source.HasCommand(*it)) + return false; + + for (std::list::iterator it = privs.begin(); it != privs.end(); ++it) + if (!source.HasPriv(*it)) + return false; + + return true; + } + + public: + CommandOSOper(Module *creator) : Command(creator, "operserv/oper", 1, 3) + { + this->SetDesc(_("View and change Services Operators")); + this->SetSyntax(_("ADD \037oper\037 \037type\037")); + this->SetSyntax(_("DEL \037oper\037")); + this->SetSyntax(_("INFO [\037type\037]")); + this->SetSyntax("LIST"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &subcommand = params[0]; + + if (subcommand.equals_ci("ADD") && params.size() > 2) + { + const Anope::string &oper = params[1]; + const Anope::string &otype = params[2]; + + if (!source.HasPriv("operserv/oper/modify")) + { + source.Reply(_("Access denied. You do not have the operator privilege \002{0}\002."), "operserv/oper/modify"); + return; + } + + NickServ::Nick *na = NickServ::FindNick(oper); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't currently online."), oper); + return; + } + + OperType *ot = OperType::Find(otype); + if (ot == NULL) + { + source.Reply(_("Oper type \002{0}\002 has not been configured."), otype); + return; + } + + if (!HasPrivs(source, ot)) + { + source.Reply(_("Access denied.")); + return; + } + + if (na->GetAccount()->o) + { + na->GetAccount()->o->Delete(); + na->GetAccount()->o = nullptr; + } + + Oper *o = operblock.Create(); + o->SetName(na->GetAccount()->GetDisplay()); + o->SetType(ot); + o->SetRequireOper(true); + + na->GetAccount()->o = o; + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "ADD " << na->GetNick() << " as type " << ot->GetName(); + source.Reply("\002{0}\002 (\002{1}\002) added to the \002{2}\002 list.", na->GetNick(), na->GetAccount()->GetDisplay(), ot->GetName()); + } + else if (subcommand.equals_ci("DEL") && params.size() > 1) + { + const Anope::string &oper = params[1]; + + if (!source.HasPriv("operserv/oper/modify")) + { + source.Reply(_("Access denied. You do not have the operator privilege \002{0}\002."), "operserv/oper/modify"); + return; + } + + NickServ::Nick *na = NickServ::FindNick(oper); + if (na == NULL) + { + source.Reply(_("\002{0}\002 isn't registered."), oper); + return; + } + + if (!na->GetAccount() || !na->GetAccount()->o) + { + source.Reply(_("Nick \002{0}\002 is not a Services Operator."), oper); + return; + } + + if (!HasPrivs(source, na->GetAccount()->o->GetType())) + { + source.Reply(_("Access denied.")); + return; + } + + na->GetAccount()->o->Delete(); + na->GetAccount()->o = NULL; + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + + Log(LOG_ADMIN, source, this) << "DEL " << na->GetNick(); + source.Reply(_("Oper privileges removed from \002{0}\002 (\002{1}\002)."), na->GetNick(), na->GetAccount()->GetDisplay()); + } + else if (subcommand.equals_ci("LIST")) + { + source.Reply(_("Name Type")); + for (NickServ::Account *nc : NickServ::service->GetAccountList()) + { + if (!nc->o) + continue; + + source.Reply(Anope::printf("%-8s %s", nc->o->GetName().c_str(), nc->o->GetType()->GetName().c_str())); + if (nc->o->conf) + source.Reply(_(" This oper is configured in the configuration file.")); + for (User *u : nc->users) + source.Reply(_(" \002{0}\002 is online using this oper block."), u->nick); + } + } + else if (subcommand.equals_ci("INFO")) + { + if (params.size() < 2) + { + source.Reply(_("Available opertypes:")); + for (unsigned i = 0; i < Config->MyOperTypes.size(); ++i) + { + OperType *ot = Config->MyOperTypes[i]; + source.Reply("%s", ot->GetName().c_str()); + } + return; + } + + Anope::string fulltype = params[1]; + if (params.size() > 2) + fulltype += " " + params[2]; + OperType *ot = OperType::Find(fulltype); + if (ot == NULL) + { + source.Reply(_("Oper type \002{0}\002 has not been configured."), fulltype); + return; + } + + if (ot->GetCommands().empty()) + source.Reply(_("Opertype \002{0}\002 has no allowed commands."), ot->GetName()); + else + { + source.Reply(_("Available commands for \002{0}\002:"), ot->GetName()); + Anope::string buf; + std::list cmds = ot->GetCommands(); + for (std::list::const_iterator it = cmds.begin(), it_end = cmds.end(); it != it_end; ++it) + { + buf += *it + " "; + if (buf.length() > 400) + { + source.Reply("%s", buf.c_str()); + buf.clear(); + } + } + if (!buf.empty()) + { + source.Reply("%s", buf.c_str()); + buf.clear(); + } + } + if (ot->GetPrivs().empty()) + source.Reply(_("Opertype \002{0}\002 has no allowed privileges."), ot->GetName()); + else + { + source.Reply(_("Available privileges for \002{0}\002:"), ot->GetName()); + Anope::string buf; + std::list privs = ot->GetPrivs(); + for (std::list::const_iterator it = privs.begin(), it_end = privs.end(); it != it_end; ++it) + { + buf += *it + " "; + if (buf.length() > 400) + { + source.Reply("%s", buf.c_str()); + buf.clear(); + } + } + if (!buf.empty()) + { + source.Reply("%s", buf.c_str()); + buf.clear(); + } + } + if (!ot->modes.empty()) + source.Reply(_("Opertype \002{0}\002 receives modes \002{1}\002 once identified."), ot->GetName(), ot->modes); + } + else + this->OnSyntaxError(source, subcommand); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows you to change and view Services Operators.\n" + " Note that operators removed by this command but are still set in the configuration file are not permanently affected by this.")); + return true; + } +}; + +class OSOper : public Module +{ + CommandOSOper commandosoper; + + public: + OSOper(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosoper(this) + { + } +}; + +MODULE_INIT(OSOper) diff --git a/modules/operserv/reload.cpp b/modules/operserv/reload.cpp new file mode 100644 index 000000000..217194e18 --- /dev/null +++ b/modules/operserv/reload.cpp @@ -0,0 +1,62 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSReload : public Command +{ + public: + CommandOSReload(Module *creator) : Command(creator, "operserv/reload", 0, 0) + { + this->SetDesc(_("Reload services' configuration file")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + try + { + Log(LOG_ADMIN, source, this); + + Configuration::Conf *new_config = new Configuration::Conf(); + Configuration::Conf *old = Config; + Config = new_config; + Config->Post(old); + delete old; + + source.Reply(_("Services' configuration has been reloaded.")); + } + catch (const ConfigException &ex) + { + Log(this->owner) << "Error reloading configuration file: " << ex.GetReason(); + source.Reply(_("Error reloading configuration file: {0}"), ex.GetReason()); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Causes Services to reload the configuration file.")); + return true; + } +}; + +class OSReload : public Module +{ + CommandOSReload commandosreload; + + public: + OSReload(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosreload(this) + { + + } +}; + +MODULE_INIT(OSReload) diff --git a/modules/operserv/session.cpp b/modules/operserv/session.cpp new file mode 100644 index 000000000..997914360 --- /dev/null +++ b/modules/operserv/session.cpp @@ -0,0 +1,768 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/os_session.h" + +namespace +{ + /* The default session limit */ + unsigned session_limit; + /* How many times to kill before adding an AKILL */ + unsigned max_session_kill; + /* How long session akills should last */ + time_t session_autokill_expiry; + /* Reason to use for session kills */ + Anope::string sle_reason; + /* Optional second reason */ + Anope::string sle_detailsloc; + + /* Max limit that can be used for exceptions */ + unsigned max_exception_limit; + /* How long before exceptions expire by default */ + time_t exception_expiry; + + /* Number of bits to use when comparing session IPs */ + unsigned ipv4_cidr; + unsigned ipv6_cidr; + + EventHandlers *events; +} + +class ExceptionImpl : public Exception +{ + public: + ExceptionImpl(Serialize::TypeBase *type) : Exception(type) { } + ExceptionImpl(Serialize::TypeBase *type, Serialize::ID id) : Exception(type, id) { } + + Anope::string GetMask() override; + void SetMask(const Anope::string &) override; + + unsigned int GetLimit() override; + void SetLimit(unsigned int) override; + + Anope::string GetWho() override; + void SetWho(const Anope::string &) override; + + Anope::string GetReason() override; + void SetReason(const Anope::string &) override; + + time_t GetTime() override; + void SetTime(const time_t &) override; + + time_t GetExpires() override; + void SetExpires(const time_t &) override; +}; + +class ExceptionType : public Serialize::Type +{ + public: + Serialize::Field mask, who, reason; + Serialize::Field limit; + Serialize::Field time, expires; + + ExceptionType(Module *me) : Serialize::Type(me, "Exception") + , mask(this, "mask") + , who(this, "who") + , reason(this, "reason") + , limit(this, "limit") + , time(this, "time") + , expires(this, "expires") + { + } +}; + +Anope::string ExceptionImpl::GetMask() +{ + return Get(&ExceptionType::mask); +} + +void ExceptionImpl::SetMask(const Anope::string &m) +{ + Set(&ExceptionType::mask, m); +} + +unsigned int ExceptionImpl::GetLimit() +{ + return Get(&ExceptionType::limit); +} + +void ExceptionImpl::SetLimit(unsigned int l) +{ + Set(&ExceptionType::limit, l); +} + +Anope::string ExceptionImpl::GetWho() +{ + return Get(&ExceptionType::who); +} + +void ExceptionImpl::SetWho(const Anope::string &w) +{ + Set(&ExceptionType::who, w); +} + +Anope::string ExceptionImpl::GetReason() +{ + return Get(&ExceptionType::reason); +} + +void ExceptionImpl::SetReason(const Anope::string &r) +{ + Set(&ExceptionType::reason, r); +} + +time_t ExceptionImpl::GetTime() +{ + return Get(&ExceptionType::time); +} + +void ExceptionImpl::SetTime(const time_t &t) +{ + Set(&ExceptionType::time, t); +} + +time_t ExceptionImpl::GetExpires() +{ + return Get(&ExceptionType::expires); +} + +void ExceptionImpl::SetExpires(const time_t &e) +{ + Set(&ExceptionType::expires, e); +} + +class MySessionService : public SessionService +{ + SessionMap Sessions; + + public: + MySessionService(Module *m) : SessionService(m) { } + + Exception *FindException(User *u) override + { + for (Exception *e : Serialize::GetObjects(exception)) + { + if (Anope::Match(u->host, e->GetMask()) || Anope::Match(u->ip.addr(), e->GetMask())) + return e; + + if (cidr(e->GetMask()).match(u->ip)) + return e; + } + return nullptr; + } + + Exception *FindException(const Anope::string &host) override + { + for (Exception *e : Serialize::GetObjects(exception)) + { + if (Anope::Match(host, e->GetMask())) + return e; + + if (cidr(e->GetMask()).match(sockaddrs(host))) + return e; + } + + return nullptr; + } + + void DelSession(Session *s) + { + this->Sessions.erase(s->addr); + } + + Session *FindSession(const Anope::string &ip) override + { + cidr c(ip, ip.find(':') != Anope::string::npos ? ipv6_cidr : ipv4_cidr); + if (!c.valid()) + return NULL; + SessionMap::iterator it = this->Sessions.find(c); + if (it != this->Sessions.end()) + return it->second; + return NULL; + } + + SessionMap::iterator FindSessionIterator(const sockaddrs &ip) + { + cidr c(ip, ip.ipv6() ? ipv6_cidr : ipv4_cidr); + if (!c.valid()) + return this->Sessions.end(); + return this->Sessions.find(c); + } + + Session* &FindOrCreateSession(const cidr &ip) + { + return this->Sessions[ip]; + } + + SessionMap &GetSessions() override + { + return this->Sessions; + } +}; + +class CommandOSSession : public Command +{ + private: + void DoList(CommandSource &source, const std::vector ¶ms) + { + Anope::string param = params[1]; + + unsigned mincount = 0; + try + { + mincount = convertTo(param); + } + catch (const ConvertException &) { } + + if (mincount <= 1) + { + source.Reply(_("Invalid threshold value. It must be a valid integer greater than 1.")); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Session")).AddColumn(_("Host")); + + for (SessionService::SessionMap::iterator it = session_service->GetSessions().begin(), it_end = session_service->GetSessions().end(); it != it_end; ++it) + { + Session *session = it->second; + + if (session->count >= mincount) + { + ListFormatter::ListEntry entry; + entry["Session"] = stringify(session->count); + entry["Host"] = session->addr.mask(); + list.AddEntry(entry); + } + } + + source.Reply(_("Hosts with at least \002{0}\002 sessions:"), mincount); + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + + void DoView(CommandSource &source, const std::vector ¶ms) + { + Anope::string param = params[1]; + Session *session = session_service->FindSession(param); + + Exception *e = session_service->FindException(param); + Anope::string entry = "no entry"; + unsigned limit = session_limit; + if (e) + { + if (!e->GetLimit()) + limit = 0; + else if (e->GetLimit() > limit) + limit = e->GetLimit(); + entry = e->GetMask(); + } + + if (!session) + source.Reply(_("\002{0}\002 not found on the session list, but has a limit of \002{1}\002 because it matches entry: \002{2}\002."), param, limit, entry); + else + source.Reply(_("The host \002{0}\002 currently has \002{1}\002 sessions with a limit of \002{2}\002 because it matches entry: \002{3}\002."), session->addr.mask(), session->count, limit, entry); + } + + public: + CommandOSSession(Module *creator) : Command(creator, "operserv/session", 2, 2) + { + this->SetDesc(_("View the list of host sessions")); + this->SetSyntax(_("LIST \037threshold\037")); + this->SetSyntax(_("VIEW \037host\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + + Log(LOG_ADMIN, source, this) << cmd << " " << params[1]; + + if (!session_limit) + source.Reply(_("Session limiting is disabled.")); + else if (cmd.equals_ci("LIST")) + this->DoList(source, params); + else if (cmd.equals_ci("VIEW")) + this->DoView(source, params); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Allows Services Operators to view the session list.\n" + "\n" + "\002{0} LIST\002 lists hosts with at least \037threshold\037 sessions. The threshold must be a number greater than 1.\n" + "\n" + "\002{0} VIEW\002 displays detailed information about a specific host - including the current session count and session limit.\n" + "\n" + "See the \002EXCEPTION\002 help for more information about session limiting and how to set session limits specific to certain hosts and groups thereof."), // XXX + source.command); + return true; + } +}; + +class CommandOSException : public Command +{ + static void DoDel(CommandSource &source, Exception *e) + { + (*events)(&Event::Exception::OnExceptionDel, source, e); + e->Delete(); + } + + void DoAdd(CommandSource &source, const std::vector ¶ms) + { + Anope::string mask, expiry, limitstr; + unsigned last_param = 3; + + mask = params.size() > 1 ? params[1] : ""; + if (!mask.empty() && mask[0] == '+') + { + expiry = mask; + mask = params.size() > 2 ? params[2] : ""; + last_param = 4; + } + + limitstr = params.size() > last_param - 1 ? params[last_param - 1] : ""; + + if (params.size() <= last_param) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + Anope::string reason = params[last_param]; + if (last_param == 3 && params.size() > 4) + reason += " " + params[4]; + if (reason.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : exception_expiry; + if (expires < 0) + { + source.Reply(_("Invalid expiry time \002{0}\002."), expiry); + return; + } + else if (expires > 0) + expires += Anope::CurTime; + + unsigned limit = -1; + try + { + limit = convertTo(limitstr); + } + catch (const ConvertException &) { } + + if (limit > max_exception_limit) + { + source.Reply(_("Invalid session limit. It must be a valid integer greater than or equal to zero and less than \002{0}\002."), max_exception_limit); + return; + } + else + { + if (mask.find('!') != Anope::string::npos || mask.find('@') != Anope::string::npos) + { + source.Reply(_("Invalid hostmask. Only real hostmasks are valid, as exceptions are not matched against nicks or usernames.")); + return; + } + + for (Exception *e : Serialize::GetObjects(exception)) + if (e->GetMask().equals_ci(mask)) + { + if (e->GetLimit() != limit) + { + e->SetLimit(limit); + source.Reply(_("Exception for \002{0}\002 has been updated to \002{1}\002."), mask, e->GetLimit()); + } + else + source.Reply(_("\002{0}\002 already exists on the session-limit exception list."), mask); + return; + } + + Exception *e = exception.Create(); + e->SetMask(mask); + e->SetLimit(limit); + e->SetReason(reason); + e->SetTime(Anope::CurTime); + e->SetWho(source.GetNick()); + e->SetExpires(expires); + + EventReturn MOD_RESULT; + MOD_RESULT = (*events)(&Event::Exception::OnExceptionAdd, e); + if (MOD_RESULT == EVENT_STOP) + return; + + Log(LOG_ADMIN, source, this) << "to set the session limit for " << mask << " to " << limit; + source.Reply(_("Session limit for \002{0}\002 set to \002{1}\002."), mask, limit); + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + } + + void DoDel(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + unsigned int deleted = 0; + + NumberList(mask, true, + [&](unsigned int number) + { + std::vector exceptions = Serialize::GetObjects(exception); + if (!number || number > exceptions.size()) + return; + + Exception *e = exceptions[number - 1]; + + Log(LOG_ADMIN, source, this) << "to remove the session limit exception for " << e->GetMask(); + + ++deleted; + DoDel(source, e); + }, + [&]() + { + if (!deleted) + source.Reply(_("No matching entries on session-limit exception list.")); + else if (deleted == 1) + source.Reply(_("Deleted \0021\002 entry from session-limit exception list.")); + else + source.Reply(_("Deleted \002{0}\002 entries from session-limit exception list."), deleted); + }); + } + else + { + bool found = false; + for (Exception *e : Serialize::GetObjects(exception)) + if (mask.equals_ci(e->GetMask())) + { + Log(LOG_ADMIN, source, this) << "to remove the session limit exception for " << mask; + DoDel(source, e); + source.Reply(_("\002{0}\002 deleted from session-limit exception list."), mask); + found = true; + break; + } + if (!found) + source.Reply(_("\002{0}\002 not found on session-limit exception list."), mask); + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + + void ProcessList(CommandSource &source, const std::vector ¶ms, ListFormatter &list) + { + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + std::vector exceptions = Serialize::GetObjects(exception); + + if (exceptions.empty()) + { + source.Reply(_("The session exception list is empty.")); + return; + } + + if (!mask.empty() && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + NumberList(mask, false, + [&](unsigned int number) + { + if (!number || number > exceptions.size()) + return; + + Exception *e = exceptions[number - 1]; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(number); + entry["Mask"] = e->GetMask(); + entry["By"] = e->GetWho(); + entry["Created"] = Anope::strftime(e->GetTime(), NULL, true); + entry["Expires"] = Anope::Expires(e->GetExpires(), source.GetAccount()); + entry["Limit"] = stringify(e->GetLimit()); + entry["Reason"] = e->GetReason(); + list.AddEntry(entry); + }, + []{}); + } + else + { + unsigned int i = 0; + for (Exception *e : exceptions) + { + if (mask.empty() || Anope::Match(e->GetMask(), mask)) + { + ListFormatter::ListEntry entry; + entry["Number"] = stringify(++i); + entry["Mask"] = e->GetMask(); + entry["By"] = e->GetWho(); + entry["Created"] = Anope::strftime(e->GetTime(), NULL, true); + entry["Expires"] = Anope::Expires(e->GetExpires(), source.GetAccount()); + entry["Limit"] = stringify(e->GetLimit()); + entry["Reason"] = e->GetReason(); + list.AddEntry(entry); + } + } + } + + if (list.IsEmpty()) + { + source.Reply(_("No matching entries on session-limit exception list.")); + return; + } + + source.Reply(_("Session-limit exception list:")); + + std::vector replies; + list.Process(replies); + + for (const Anope::string &r : replies) + source.Reply(r); + } + + void DoList(CommandSource &source, const std::vector ¶ms) + { + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Limit")).AddColumn(_("Mask")); + + this->ProcessList(source, params, list); + } + + void DoView(CommandSource &source, const std::vector ¶ms) + { + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("By")).AddColumn(_("Created")).AddColumn(_("Expires")).AddColumn(_("Limit")).AddColumn(_("Reason")); + + this->ProcessList(source, params, list); + } + + public: + CommandOSException(Module *creator) : Command(creator, "operserv/exception", 1, 5) + { + this->SetDesc(_("Modify the session-limit exception list")); + this->SetSyntax(_("ADD [\037+expiry\037] \037mask\037 \037limit\037 \037reason\037")); + this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037}")); + this->SetSyntax(_("LIST [\037mask\037 | \037list\037]")); + this->SetSyntax(_("VIEW [\037mask\037 | \037list\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + + if (!session_limit) + source.Reply(_("Session limiting is disabled.")); + else if (cmd.equals_ci("ADD")) + return this->DoAdd(source, params); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, params); + else if (cmd.equals_ci("LIST")) + return this->DoList(source, params); + else if (cmd.equals_ci("VIEW")) + return this->DoView(source, params); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + source.Reply(_("\002{0} ADD\002 adds the given hostmask to the exception list." + " Note that \002nick!user@host\002 and \002user@host\002 masks are invalid!\n" + " Only real host masks, such as \002box.host.dom\002 and \002*.host.dom\002, are allowed because sessions limiting does not take nickname or user names into account." + " \037limit\037 must be a number greater than or equal to zero." + " This determines how many sessions this host may carry at a time." + " A value of zero means the host has an unlimited session limit." + " If more than one entry matches a client, the first matching enty will be used."), + source.command); + else + source.Reply(_("Allows you to manipulate the list of hosts that have specific session limits - allowing certain machines, such as shell servers, to carry more than the default number of clients at a time." + " Once a host reaches its session limit, all clients attempting to connect from that host will be killed." + " Before the user is killed, they are notified, of a source of help regarding session limiting. The content of this notice is a config setting.\n" + "\n" + "\002{0} ADD\002 adds the given host mask to the exception list.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "\002{0} DEL\002 removes the given mask from the exception list.\n" + "\n" + "\002{0} LIST\002 and \002{0} VIEW\002 show all current sessions if the optional mask is given, the list is limited to those sessions matching the mask." + " The difference is that \002{0} VIEW\002 is more verbose, displaying the name of the person who added the exception, its session limit, reason, host mask and the expiry date and time.\n")); + return true; + } +}; + +class OSSession : public Module + , public EventHook + , public EventHook + , public EventHook +{ + MySessionService ss; + CommandOSSession commandossession; + CommandOSException commandosexception; + ServiceReference akills; + EventHandlers exceptionevents; + ExceptionType etype; + + public: + OSSession(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , EventHook(EventHook::Priority::FIRST) + , EventHook(EventHook::Priority::FIRST) + , EventHook(EventHook::Priority::FIRST) + , ss(this) + , commandossession(this) + , commandosexception(this) + , akills("XLineManager", "xlinemanager/sgline") + , exceptionevents(this) + , etype(this) + { + this->SetPermanent(true); + + events = &exceptionevents; + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = Config->GetModule(this); + + session_limit = block->Get("defaultsessionlimit"); + max_session_kill = block->Get("maxsessionkill"); + session_autokill_expiry = block->Get("sessionautokillexpiry"); + sle_reason = block->Get("sessionlimitexceeded"); + sle_detailsloc = block->Get("sessionlimitdetailsloc"); + + max_exception_limit = block->Get("maxsessionlimit"); + exception_expiry = block->Get("exceptionexpiry"); + + ipv4_cidr = block->Get("session_ipv4_cidr", "32"); + ipv6_cidr = block->Get("session_ipv6_cidr", "128"); + + if (ipv4_cidr > 32 || ipv6_cidr > 128) + throw ConfigException(this->name + ": session CIDR value out of range"); + } + + void OnUserConnect(User *u, bool &exempt) override + { + if (u->Quitting() || !session_limit || exempt || !u->server || u->server->IsULined()) + return; + + cidr u_ip(u->ip, u->ip.ipv6() ? ipv6_cidr : ipv4_cidr); + if (!u_ip.valid()) + return; + + Session* &session = this->ss.FindOrCreateSession(u_ip); + + if (session) + { + bool kill = false; + if (session->count >= session_limit) + { + kill = true; + Exception *e = this->ss.FindException(u); + if (e) + { + kill = false; + if (e->GetLimit() && session->count >= e->GetLimit()) + kill = true; + } + } + + ++session->count; + + if (kill && !exempt) + { + ServiceBot *OperServ = Config->GetClient("OperServ"); + if (OperServ) + { + if (!sle_reason.empty()) + { + Anope::string message = sle_reason.replace_all_cs("%IP%", u->ip.addr()); + u->SendMessage(OperServ, message); + } + if (!sle_detailsloc.empty()) + u->SendMessage(OperServ, sle_detailsloc); + } + + ++session->hits; + + const Anope::string &akillmask = "*@" + session->addr.mask(); + if (max_session_kill && session->hits >= max_session_kill && akills && !akills->HasEntry(akillmask)) + { + XLine *x = new XLine(akillmask, OperServ ? OperServ->nick : "", Anope::CurTime + session_autokill_expiry, "Session limit exceeded", XLineManager::GenerateUID()); + akills->AddXLine(x); + akills->Send(NULL, x); + Log(OperServ, "akill/session") << "Added a temporary AKILL for \002" << akillmask << "\002 due to excessive connections"; + } + else + { + u->Kill(OperServ, "Session limit exceeded"); + } + } + } + else + { + session = new Session(u->ip, u->ip.ipv6() ? ipv6_cidr : ipv4_cidr); + } + } + + void OnUserQuit(User *u, const Anope::string &msg) override + { + if (!session_limit || !u->server || u->server->IsULined()) + return; + + SessionService::SessionMap &sessions = this->ss.GetSessions(); + SessionService::SessionMap::iterator sit = this->ss.FindSessionIterator(u->ip); + + if (sit == sessions.end()) + return; + + Session *session = sit->second; + + if (session->count > 1) + { + --session->count; + return; + } + + delete session; + sessions.erase(sit); + } + + void OnExpireTick() override + { + if (Anope::NoExpire) + return; + + for (Exception *e : Serialize::GetObjects(exception)) + { + if (!e->GetExpires() || e->GetExpires() > Anope::CurTime) + continue; + + ServiceBot *OperServ = Config->GetClient("OperServ"); + Log(OperServ, "expire/exception") << "Session exception for " << e->GetMask() << " has expired."; + e->Delete(); + } + } +}; + +MODULE_INIT(OSSession) diff --git a/modules/operserv/set.cpp b/modules/operserv/set.cpp new file mode 100644 index 000000000..45515fa4b --- /dev/null +++ b/modules/operserv/set.cpp @@ -0,0 +1,243 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSSet : public Command +{ + private: + void DoList(CommandSource &source) + { + Log(LOG_ADMIN, source, this) << "LIST"; + + Anope::string index; + + index = Anope::ReadOnly ? _("%s is enabled") : _("%s is disabled"); + source.Reply(index.c_str(), "READONLY"); + index = Anope::Debug ? _("%s is enabled") : _("%s is disabled"); + source.Reply(index.c_str(), "DEBUG"); + index = Anope::NoExpire ? _("%s is enabled") : _("%s is disabled"); + source.Reply(index.c_str(), "NOEXPIRE"); + + return; + } + + void DoSetReadOnly(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &setting = params.size() > 1 ? params[1] : ""; + + if (setting.empty()) + { + this->OnSyntaxError(source, "READONLY"); + return; + } + + if (setting.equals_ci("ON")) + { + Anope::ReadOnly = true; + Log(LOG_ADMIN, source, this) << "READONLY ON"; + source.Reply(_("Services are now in \002read-only\002 mode.")); + } + else if (setting.equals_ci("OFF")) + { + Anope::ReadOnly = false; + Log(LOG_ADMIN, source, this) << "READONLY OFF"; + source.Reply(_("Services are now in \002read-write\002 mode.")); + } + else + source.Reply(_("Setting for READONLY must be \002ON\002 or \002OFF\002.")); + } + + void DoSetSuperAdmin(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &setting = params.size() > 1 ? params[1] : ""; + + if (!source.GetUser()) + return; + + if (setting.empty()) + { + this->OnSyntaxError(source, "SUPERADMIN"); + return; + } + + /** + * Allow the user to turn super admin on/off + * + * Rob + **/ + bool super_admin = Config->GetModule(this->owner)->Get("superadmin"); + if (!super_admin) + source.Reply(_("Super admin can not be set because it is not enabled in the configuration.")); + else if (setting.equals_ci("ON")) + { + source.GetUser()->super_admin = true; + source.Reply(_("You are now a super admin.")); + Log(LOG_ADMIN, source, this) << "SUPERADMIN ON"; + } + else if (setting.equals_ci("OFF")) + { + source.GetUser()->super_admin = false; + source.Reply(_("You are no longer a super admin.")); + Log(LOG_ADMIN, source, this) << "SUPERADMIN OFF"; + } + else + source.Reply(_("Setting for super admin must be \002ON\002 or \002OFF\002.")); + } + + void DoSetDebug(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &setting = params.size() > 1 ? params[1] : ""; + + if (setting.empty()) + { + this->OnSyntaxError(source, "DEBUG"); + return; + } + + if (setting.equals_ci("ON")) + { + Anope::Debug = 1; + Log(LOG_ADMIN, source, this) << "DEBUG ON"; + source.Reply(_("Services are now in \002debug\002 mode.")); + } + else if (setting.equals_ci("OFF") || setting == "0") + { + Log(LOG_ADMIN, source, this) << "DEBUG OFF"; + Anope::Debug = 0; + source.Reply(_("Services are now in \002non-debug\002 mode.")); + } + else + { + try + { + Anope::Debug = convertTo(setting); + Log(LOG_ADMIN, source, this) << "DEBUG " << Anope::Debug; + source.Reply(_("Services are now in \002debug\002 mode (level %d)."), Anope::Debug); + return; + } + catch (const ConvertException &) { } + + source.Reply(_("Setting for DEBUG must be \002ON\002, \002OFF\002, or a positive number.")); + } + + return; + } + + void DoSetNoExpire(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &setting = params.size() > 1 ? params[1] : ""; + + if (setting.empty()) + { + this->OnSyntaxError(source, "NOEXPIRE"); + return; + } + + if (setting.equals_ci("ON")) + { + Anope::NoExpire = true; + Log(LOG_ADMIN, source, this) << "NOEXPIRE ON"; + source.Reply(_("Services are now in \002no expire\002 mode.")); + } + else if (setting.equals_ci("OFF")) + { + Anope::NoExpire = false; + Log(LOG_ADMIN, source, this) << "NOEXPIRE OFF"; + source.Reply(_("Services are now in \002expire\002 mode.")); + } + else + source.Reply(_("Setting for NOEXPIRE must be \002ON\002 or \002OFF\002.")); + } + public: + CommandOSSet(Module *creator) : Command(creator, "operserv/set", 1, 2) + { + this->SetDesc(_("Set various global Services options")); + this->SetSyntax(_("\037option\037 \037setting\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &option = params[0]; + + if (option.equals_ci("LIST")) + return this->DoList(source); + else if (option.equals_ci("READONLY")) + return this->DoSetReadOnly(source, params); + else if (option.equals_ci("DEBUG")) + return this->DoSetDebug(source, params); + else if (option.equals_ci("NOEXPIRE")) + return this->DoSetNoExpire(source, params); + else if (option.equals_ci("SUPERADMIN")) + return this->DoSetSuperAdmin(source, params); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.empty()) + { + source.Reply(_("Sets various global Services options. Option names currently defined are:\n" + " READONLY Set read-only or read-write mode\n" + " DEBUG Activate or deactivate debug mode\n" + " NOEXPIRE Activate or deactivate no expire mode\n" + " SUPERADMIN Activate or deactivate super admin mode\n" + " LIST List the options")); + } + else if (subcommand.equals_ci("LIST")) + //source.Reply(_("Syntax: \002LIST\002\n" + // " \n" + source.Reply(("Display the various {0} settings."), source.service->nick); + else if (subcommand.equals_ci("READONLY")) + //source.Reply(_("Syntax: \002READONLY {ON | OFF}\002\n" + // " \n" + source.Reply(_("Sets read-only mode on or off. In read-only mode, norma users will not be allowed to modify any Services data, including channel and nickname access lists, etc." + "Services Operators will still be able to do most tasks, but should understand any changes they do may not be permanent.\n" + "\n" + "This option is equivalent to the command-line option \002--readonly\002.")); + else if (subcommand.equals_ci("DEBUG")) + //source.Reply(_("Syntax: \002DEBUG {ON | OFF}\002\n" + // " \n" + source.Reply(_("Sets debug mode on or off.\n" + "\n" + "This option is equivalent to the command-line option \002--debug\002.")); + else if (subcommand.equals_ci("NOEXPIRE")) + //source.Reply(_("Syntax: \002NOEXPIRE {ON | OFF}\002\n" + // " \n" + source.Reply(_("Sets no expire mode on or off. In no expire mode, nicks, channels, akills and exceptions won't expire until the option is unset.\n" + "\n" + "This option is equivalent to the command-line option \002--noexpire\002.")); + else if (subcommand.equals_ci("SUPERADMIN")) + //source.Reply(_("Syntax: \002SUPERADMIN {ON | OFF}\002\n" + // " \n" + source.Reply(_("Setting this will grant you extra privileges, such as the ability to be \"founder\" on all channels." + "\n" + "This option is \002not\002 persistent, and should only be used when needed, and set back to OFF when no longer needed.")); + else + return false; + + return true; + } +}; + +class OSSet : public Module +{ + CommandOSSet commandosset; + + public: + OSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosset(this) + { + } +}; + +MODULE_INIT(OSSet) diff --git a/modules/operserv/shutdown.cpp b/modules/operserv/shutdown.cpp new file mode 100644 index 000000000..642be5d84 --- /dev/null +++ b/modules/operserv/shutdown.cpp @@ -0,0 +1,101 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSQuit : public Command +{ + public: + CommandOSQuit(Module *creator) : Command(creator, "operserv/quit", 0, 0) + { + this->SetDesc(_("Terminate Services without saving")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + Log(LOG_ADMIN, source, this); + Anope::QuitReason = source.command + " command received from " + source.GetNick(); + Anope::Quitting = true; + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Causes Services to do an immediate shutdown; databases may \002not\002 saved." + " If you are using a real time database such as SQL or Redis, this command is not useful." + " This command should not be used unless damage to the in-memory copies of the databases is feared and they should not be saved.")); + return true; + } +}; + +class CommandOSRestart : public Command +{ + public: + CommandOSRestart(Module *creator) : Command(creator, "operserv/restart", 0, 0) + { + this->SetDesc(_("Save databases and restart Services")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + Log(LOG_ADMIN, source, this); + Anope::QuitReason = source.command + " command received from " + source.GetNick(); + Anope::Quitting = Anope::Restarting = true; + Anope::SaveDatabases(); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Causes Services to restart.")); + return true; + } +}; + +class CommandOSShutdown : public Command +{ + public: + CommandOSShutdown(Module *creator) : Command(creator, "operserv/shutdown", 0, 0) + { + this->SetDesc(_("Terminate services with save")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + Log(LOG_ADMIN, source, this); + Anope::QuitReason = source.command + " command received from " + source.GetNick(); + Anope::Quitting = true; + Anope::SaveDatabases(); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Causes Services to shut down")); + return true; + } +}; + +class OSShutdown : public Module +{ + CommandOSQuit commandosquit; + CommandOSRestart commandosrestart; + CommandOSShutdown commandosshutdown; + + public: + OSShutdown(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandosquit(this), commandosrestart(this), commandosshutdown(this) + { + + } +}; + +MODULE_INIT(OSShutdown) diff --git a/modules/operserv/stats.cpp b/modules/operserv/stats.cpp new file mode 100644 index 000000000..6e74303ce --- /dev/null +++ b/modules/operserv/stats.cpp @@ -0,0 +1,271 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/os_session.h" + +#if 0 +struct Stats : Serialize::Object +{ + static Stats *me; + + Stats() : Serialize::Object("Stats") + { + me = this; + } + + void Serialize(Serialize::Data &data) const override + { + data["maxusercnt"] << MaxUserCount; + data["maxusertime"] << MaxUserTime; + } + + static Serializable* Unserialize(Serializable *obj, Serialize::Data &data) + { + data["maxusercnt"] >> MaxUserCount; + data["maxusertime"] >> MaxUserTime; + return me; + } +}; + +Stats *Stats::me; + +/** + * Count servers connected to server s + * @param s The server to start counting from + * @return Amount of servers connected to server s + **/ +static int stats_count_servers(Server *s) +{ + if (!s) + return 0; + + int count = 1; + + if (!s->GetLinks().empty()) + for (unsigned i = 0, j = s->GetLinks().size(); i < j; ++i) + count += stats_count_servers(s->GetLinks()[i]); + + return count; +} + +class CommandOSStats : public Command +{ + ServiceReference akills, snlines, sqlines; + private: + void DoStatsAkill(CommandSource &source) + { + int timeout; + if (akills) + { + /* AKILLs */ + source.Reply(_("Current number of AKILLs: \002{0}\002"), akills->GetCount()); + timeout = Config->GetModule("operserv")->Get("autokillexpiry", "30d") + 59; + if (timeout >= 172800) + source.Reply(_("Default AKILL expiry time: \002{0} days\002"), timeout / 86400); + else if (timeout >= 86400) + source.Reply(_("Default AKILL expiry time: \0021 day\002")); + else if (timeout >= 7200) + source.Reply(_("Default AKILL expiry time: \002{0} hours\002"), timeout / 3600); + else if (timeout >= 3600) + source.Reply(_("Default AKILL expiry time: \0021 hour\002")); + else if (timeout >= 120) + source.Reply(_("Default AKILL expiry time: \002{0} minutes\002"), timeout / 60); + else if (timeout >= 60) + source.Reply(_("Default AKILL expiry time: \0021 minute\002")); + else + source.Reply(_("Default AKILL expiry time: \002No expiration\002")); + } + if (snlines) + { + /* SNLINEs */ + source.Reply(_("Current number of SNLINEs: \002{0}\002"), snlines->GetCount()); + timeout = Config->GetModule("operserv")->Get("snlineexpiry", "30d") + 59; + if (timeout >= 172800) + source.Reply(_("Default SNLINE expiry time: \002{0} days\002"), timeout / 86400); + else if (timeout >= 86400) + source.Reply(_("Default SNLINE expiry time: \0021 day\002")); + else if (timeout >= 7200) + source.Reply(_("Default SNLINE expiry time: \002{0} hours\002"), timeout / 3600); + else if (timeout >= 3600) + source.Reply(_("Default SNLINE expiry time: \0021 hour\002")); + else if (timeout >= 120) + source.Reply(_("Default SNLINE expiry time: \002{0} minutes\002"), timeout / 60); + else if (timeout >= 60) + source.Reply(_("Default SNLINE expiry time: \0021 minute\002")); + else + source.Reply(_("Default SNLINE expiry time: \002No expiration\002")); + } + if (sqlines) + { + /* SQLINEs */ + source.Reply(_("Current number of SQLINEs: \002{0}\002"), sqlines->GetCount()); + timeout = Config->GetModule("operserv")->Get("sglineexpiry", "30d") + 59; + if (timeout >= 172800) + source.Reply(_("Default SQLINE expiry time: \002{0} days\002"), timeout / 86400); + else if (timeout >= 86400) + source.Reply(_("Default SQLINE expiry time: \0021 day\002")); + else if (timeout >= 7200) + source.Reply(_("Default SQLINE expiry time: \002{0} hours\002"), timeout / 3600); + else if (timeout >= 3600) + source.Reply(_("Default SQLINE expiry time: \0021 hour\002")); + else if (timeout >= 120) + source.Reply(_("Default SQLINE expiry time: \002{0} minutes\002"), timeout / 60); + else if (timeout >= 60) + source.Reply(_("Default SQLINE expiry time: \0021 minute\002")); + else + source.Reply(_("Default SQLINE expiry time: \002No expiration\002")); + } + } + + void DoStatsReset(CommandSource &source) + { + MaxUserCount = UserListByNick.size(); + source.Reply(_("Statistics reset.")); + return; + } + + void DoStatsUptime(CommandSource &source) + { + time_t uptime = Anope::CurTime - Anope::StartTime; + source.Reply(_("Current users: \002{0}\002 (\002{1}\002 ops)"), UserListByNick.size(), OperCount); + source.Reply(_("Maximum users: \002{0}\002 ({1})"), MaxUserCount, Anope::strftime(MaxUserTime, source.GetAccount())); + source.Reply(_("Services up \002{0}\002."), Anope::Duration(uptime, source.GetAccount())); + } + + void DoStatsUplink(CommandSource &source) + { + Anope::string buf; + for (std::set::iterator it = Servers::Capab.begin(); it != Servers::Capab.end(); ++it) + buf += " " + *it; + if (!buf.empty()) + buf.erase(buf.begin()); + + source.Reply(_("Uplink server: \002{0}\002"), Me->GetLinks().front()->GetName()); + source.Reply(_("Uplink capab: {0}"), buf); + source.Reply(_("Servers found: {0}"), stats_count_servers(Me->GetLinks().front())); + } + + template void GetHashStats(const T& map, size_t& entries, size_t& buckets, size_t& max_chain) + { + entries = map.size(), buckets = map.bucket_count(), max_chain = 0; + for (size_t i = 0; i < buckets; ++i) + if (map.bucket_size(i) > max_chain) + max_chain = map.bucket_size(i); + } + + void DoStatsHash(CommandSource &source) + { + size_t entries, buckets, max_chain; + + GetHashStats(UserListByNick, entries, buckets, max_chain); + source.Reply(_("Users (nick): {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); + + if (!UserListByUID.empty()) + { + GetHashStats(UserListByUID, entries, buckets, max_chain); + source.Reply(_("Users (uid): {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); + } + + GetHashStats(ChannelList, entries, buckets, max_chain); + source.Reply(_("Channels: {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); + + GetHashStats(ChanServ::service->GetChannels(), entries, buckets, max_chain); + source.Reply(_("Registered channels: {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); + + GetHashStats(NickServ::service->GetNickMap(), entries, buckets, max_chain); + source.Reply(_("Registered nicknames: {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); + + GetHashStats(NickServ::service->GetAccountMap(), entries, buckets, max_chain); + source.Reply(_("Registered nick groups: {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); + + if (session_service) + { + GetHashStats(session_service->GetSessions(), entries, buckets, max_chain); + source.Reply(_("Sessions: {0} entries, {1} buckets, longest chain is {2}"), entries, buckets, max_chain); + } + } + + public: + CommandOSStats(Module *creator) : Command(creator, "operserv/stats", 0, 1), + akills("XLineManager", "xlinemanager/sgline"), snlines("XLineManager", "xlinemanager/snline"), sqlines("XLineManager", "xlinemanager/sqline") + { + this->SetDesc(_("Show status of Services and network")); + this->SetSyntax("[AKILL | HASH | UPLINK | UPTIME | ALL | RESET]"); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + Anope::string extra = !params.empty() ? params[0] : ""; + + Log(LOG_ADMIN, source, this) << extra; + + if (extra.equals_ci("RESET")) + return this->DoStatsReset(source); + + if (extra.equals_ci("ALL") || extra.equals_ci("AKILL")) + this->DoStatsAkill(source); + + if (extra.equals_ci("ALL") || extra.equals_ci("HASH")) + this->DoStatsHash(source); + + if (extra.equals_ci("ALL") || extra.equals_ci("UPLINK")) + this->DoStatsUplink(source); + + if (extra.empty() || extra.equals_ci("ALL") || extra.equals_ci("UPTIME")) + this->DoStatsUptime(source); + + if (!extra.empty() && !extra.equals_ci("ALL") && !extra.equals_ci("AKILL") && !extra.equals_ci("HASH") && !extra.equals_ci("UPLINK") && !extra.equals_ci("UPTIME")) + source.Reply(_("Unknown STATS option: \002{0}\002"), extra); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Without any option, shows the current number of users online, and the highest number of users online since Services was started, and the length of time Services has been running.\n" + "\n" + "With the \002AKILL\002 option, displays the current size of the AKILL list and the current default expiry time.\n" + "\n" + "The \002RESET\002 option currently resets the maximum user count to the number of users currently present on the network.\n" + "\n" + "The \002UPLINK\002 option displays information about the current server Anope uses as an uplink to the network.\n" + "\n" + "The \002HASH\002 option displays information about the hash maps.\n" + "\n" + "The \002ALL\002 option displays all of the above statistics.")); + return true; + } +}; + +class OSStats : public Module +{ + CommandOSStats commandosstats; + //Serialize::TypeBase stats_type; + Stats stats_saver; + + public: + OSStats(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosstats(this) +// , stats_type("Stats", Stats::Unserialize) + { + + } + +#if 0 + void OnUserConnect(User *u, bool &exempt) override + { + if (UserListByNick.size() == MaxUserCount && Anope::CurTime == MaxUserTime) + stats_saver.QueueUpdate(); + } +#endif +}; + +MODULE_INIT(OSStats) +#endif diff --git a/modules/operserv/svs.cpp b/modules/operserv/svs.cpp new file mode 100644 index 000000000..35c3136a8 --- /dev/null +++ b/modules/operserv/svs.cpp @@ -0,0 +1,171 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSSVSNick : public Command +{ + public: + CommandOSSVSNick(Module *creator) : Command(creator, "operserv/svsnick", 2, 2) + { + this->SetDesc(_("Forcefully change a user's nickname")); + this->SetSyntax(_("\037nick\037 \037newnick\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &nick = params[0]; + Anope::string newnick = params[1]; + User *u2; + + if (!IRCD->CanSVSNick) + { + source.Reply(_("Your IRCd does not support SVSNICK.")); + return; + } + + /* Truncate long nicknames to nicklen characters */ + unsigned nicklen = Config->GetBlock("networkinfo")->Get("nicklen"); + if (newnick.length() > nicklen) + { + source.Reply(_("Nick \002{0}\002 was truncated to \002{1}\002 characters."), newnick, nicklen); + newnick = params[1].substr(0, nicklen); + } + + /* Check for valid characters */ + if (!IRCD->IsNickValid(newnick)) + { + source.Reply(_("Nick \002{0}\002 is an illegal nickname and cannot be used."), newnick); + return; + } + + /* Check for a nick in use or a forbidden/suspended nick */ + if (!(u2 = User::Find(nick, true))) + source.Reply(_("\002{0}\002 isn't currently online."), nick); + else if (!nick.equals_ci(newnick) && User::Find(newnick)) + source.Reply(_("\002{0}\002 is currently in use."), newnick); + else + { + source.Reply(_("\002{0}\002 is now being changed to \002{1}\002."), nick, newnick); + Log(LOG_ADMIN, source, this) << "to change " << nick << " to " << newnick; + IRCD->SendForceNickChange(u2, newnick, Anope::CurTime); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Forcefully changes a user's nickname from \037nick\037 to \037newnick\037.")); + return true; + } +}; + +class CommandOSSVSJoin : public Command +{ + public: + CommandOSSVSJoin(Module *creator) : Command(creator, "operserv/svsjoin", 2, 2) + { + this->SetDesc(_("Forcefully join a user to a channel")); + this->SetSyntax(_("\037user\037 \037channel\037")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (!IRCD->CanSVSJoin) + { + source.Reply(_("Your IRCd does not support SVSJOIN.")); + return; + } + + User *target = User::Find(params[0], true); + Channel *c = Channel::Find(params[1]); + if (target == NULL) + source.Reply(_("\002{0}\002 isn't currently online."), params[0]); + else if (source.GetUser() != target && (target->IsProtected() || target->server == Me)) + source.Reply(_("Access denied.")); + else if (!c && !IRCD->IsChannelValid(params[1])) + source.Reply(_("\002{0}\002 isn't a valid channel."), params[1]); + else if (c && c->FindUser(target)) + source.Reply(_("\002{0}\002 is already in \002{1}\002."), target->nick, c->name); + else + { + IRCD->SendSVSJoin(*source.service, target, params[1], ""); + Log(LOG_ADMIN, source, this) << "to force " << target->nick << " to join " << params[1]; + source.Reply(_("\002{0}\002 has been joined to \002{1}\002."), target->nick, params[1]); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Forcefully join \037user\037 to \037channel\037.")); + return true; + } +}; + +class CommandOSSVSPart : public Command +{ + public: + CommandOSSVSPart(Module *creator) : Command(creator, "operserv/svspart", 2, 3) + { + this->SetDesc(_("Forcefully part a user from a channel")); + this->SetSyntax(_("\037nick\037 \037channel\037 [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + if (!IRCD->CanSVSJoin) + { + source.Reply(_("Your IRCd does not support SVSPART.")); + return; + } + + User *target = User::Find(params[0], true); + Channel *c = Channel::Find(params[1]); + const Anope::string &reason = params.size() > 2 ? params[2] : ""; + if (target == NULL) + source.Reply(_("\002{0}\002 isn't currently online."), params[0]); + else if (source.GetUser() != target && (target->IsProtected() || target->server == Me)) + source.Reply(_("Access denied.")); + else if (!c) + source.Reply(_("Channel \002{0}\002 doesn't exist."), params[1]); + else if (!c->FindUser(target)) + source.Reply(_("\002{0}\002 is not in \002{1}\002."), target->nick, c->name); + else + { + IRCD->SendSVSPart(*source.service, target, params[1], reason); + if (!reason.empty()) + Log(LOG_ADMIN, source, this) << "to force " << target->nick << " to part " << c->name << " with reason " << reason; + else + Log(LOG_ADMIN, source, this) << "to force " << target->nick << " to part " << c->name; + source.Reply(_("\002{0}\002 has been parted from \002{1}\002."), target->nick, c->name); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Forcefully part \037user\037 from \037channel\037.")); + return true; + } +}; + +class OSSVS : public Module +{ + CommandOSSVSNick commandossvsnick; + CommandOSSVSJoin commandossvsjoin; + CommandOSSVSPart commandossvspart; + + public: + OSSVS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandossvsnick(this), commandossvsjoin(this), commandossvspart(this) + { + } +}; + +MODULE_INIT(OSSVS) diff --git a/modules/operserv/sxline.cpp b/modules/operserv/sxline.cpp new file mode 100644 index 000000000..3465faae7 --- /dev/null +++ b/modules/operserv/sxline.cpp @@ -0,0 +1,673 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSSXLineBase : public Command +{ + private: + virtual XLineManager* xlm() anope_abstract; + + virtual void OnAdd(CommandSource &source, const std::vector ¶ms) anope_abstract; + + void OnDel(CommandSource &source, const std::vector ¶ms) + { + if (!this->xlm() || this->xlm()->GetXLines().empty()) + { + source.Reply(_("{0} list is empty."), source.command); + return; + } + + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + unsigned int deleted = 0; + + NumberList(mask, true, + [&](unsigned int number) + { + XLine *x = this->xlm()->GetEntry(number - 1); + + if (!x) + return; + + Log(LOG_ADMIN, source, this) << "to remove " << x->GetMask() << " from the list"; + + ++deleted; + x->Delete(); + }, + [&]() + { + if (!deleted) + source.Reply(_("No matching entries on the {0} list."), source.command); + else if (deleted == 1) + source.Reply(_("Deleted \0021\002 entry from the {0} list."), source.command); + else + source.Reply(_("Deleted \002{0}\002 entries from the {1} list."), deleted, source.command); + }); + } + else + { + XLine *x = this->xlm()->HasEntry(mask); + + if (!x) + { + source.Reply(_("\002{0}\002 not found on the {1] list."), mask, source.command); + return; + } + + Event::OnDelXLine(&Event::DelXLine::OnDelXLine, source, x, this->xlm()); + + x->Delete(); + source.Reply(_("\002{0}\002 deleted from the {1} list."), mask, source.command); + Log(LOG_ADMIN, source, this) << "to remove " << mask << " from the list"; + } + + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + + void ProcessList(CommandSource &source, const std::vector ¶ms, ListFormatter &list) + { + if (!this->xlm() || this->xlm()->GetXLines().empty()) + { + source.Reply(_("{0} list is empty."), source.command); + return; + } + + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + NumberList(mask, false, + [&](unsigned int number) + { + XLine *x = this->xlm()->GetEntry(number - 1); + + if (!x) + return; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(number); + entry["Mask"] = x->GetMask(); + entry["By"] = x->GetBy(); + entry["Created"] = Anope::strftime(x->GetCreated(), NULL, true); + entry["Expires"] = Anope::Expires(x->GetExpires(), source.nc); + entry["Reason"] = x->GetReason(); + list.AddEntry(entry); + }, + []{}); + } + else + { + unsigned int i = 0; + for (XLine *x : this->xlm()->GetXLines()) + { + if (mask.empty() || mask.equals_ci(x->GetMask()) || mask == x->id || Anope::Match(x->GetMask(), mask, false, true)) + { + ListFormatter::ListEntry entry; + entry["Number"] = stringify(i + 1); + entry["Mask"] = x->GetMask(); + entry["By"] = x->GetBy(); + entry["Created"] = Anope::strftime(x->GetCreated(), NULL, true); + entry["Expires"] = Anope::Expires(x->GetExpires(), source.nc); + entry["Reason"] = x->GetReason(); + list.AddEntry(entry); + } + } + } + + if (list.IsEmpty()) + source.Reply(_("No matching entries on the {0} list."), source.command); + else + { + source.Reply(_("{0} list:"), source.command); + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + } + + void OnList(CommandSource &source, const std::vector ¶ms) + { + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Reason")); + + this->ProcessList(source, params, list); + } + + void OnView(CommandSource &source, const std::vector ¶ms) + { + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("By")).AddColumn(_("Created")).AddColumn(_("Expires")).AddColumn(_("Reason")); + this->ProcessList(source, params, list); + } + + void OnClear(CommandSource &source) + { + Event::OnDelXLine(&Event::DelXLine::OnDelXLine, source, nullptr, this->xlm()); + + for (XLine *x : this->xlm()->GetXLines()) + x->Delete(); + + Log(LOG_ADMIN, source, this) << "to CLEAR the list"; + source.Reply(_("The {0} list has been cleared."), source.command); + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + public: + CommandOSSXLineBase(Module *creator, const Anope::string &cmd) : Command(creator, cmd, 1, 4) + { + } + + const Anope::string GetDesc(CommandSource &source) const override + { + return Anope::printf(Language::Translate(source.GetAccount(), _("Manipulate the %s list")), source.command.upper().c_str()); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &cmd = params[0]; + + if (cmd.equals_ci("ADD")) + return this->OnAdd(source, params); + else if (cmd.equals_ci("DEL")) + return this->OnDel(source, params); + else if (cmd.equals_ci("LIST")) + return this->OnList(source, params); + else if (cmd.equals_ci("VIEW")) + return this->OnView(source, params); + else if (cmd.equals_ci("CLEAR")) + return this->OnClear(source); + else + this->OnSyntaxError(source, ""); + } + + virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) = 0; +}; + +class CommandOSSNLine : public CommandOSSXLineBase +{ + XLineManager *xlm() + { + return this->snlines; + } + + void OnAdd(CommandSource &source, const std::vector ¶ms) override + { + if (!this->xlm()) + return; + + unsigned last_param = 2; + Anope::string param, expiry; + + param = params.size() > 1 ? params[1] : ""; + if (!param.empty() && param[0] == '+') + { + expiry = param; + param = params.size() > 2 ? params[2] : ""; + last_param = 3; + } + + time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get("snlineexpiry", "30d"); + /* If the expiry given does not contain a final letter, it's in days, + * said the doc. Ah well. + */ + if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) + expires *= 86400; + /* Do not allow less than a minute expiry time */ + if (expires && expires < 60) + { + source.Reply(_("Invalid expiry time \002{0}\002."), expiry); + return; + } + else if (expires > 0) + expires += Anope::CurTime; + + if (param.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + Anope::string rest = param; + if (params.size() > last_param) + rest += " " + params[last_param]; + + if (rest.find(':') == Anope::string::npos) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + sepstream sep(rest, ':'); + Anope::string mask; + sep.GetToken(mask); + Anope::string reason = sep.GetRemaining(); + + if (mask.empty() || reason.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + if (mask[0] == '/' && mask[mask.length() - 1] == '/') + { + if (!Config->regex_flags) + { + source.Reply(_("Regex is disabled.")); + return; + } + + Anope::string stripped_mask = mask.substr(1, mask.length() - 2); + try + { + std::regex(stripped_mask.str(), Config->regex_flags); + } + catch (const std::regex_error &ex) + { + source.Reply(ex.what()); + return; + } + } + + /* Clean up the last character of the mask if it is a space + * See bug #761 + */ + unsigned masklen = mask.length(); + if (mask[masklen - 1] == ' ') + mask.erase(masklen - 1); + + if (Config->GetModule("operserv")->Get("addakiller", "yes") && !source.GetNick().empty()) + reason = "[" + source.GetNick() + "] " + reason; + + if (!this->xlm()->CanAdd(source, mask, expires, reason)) + return; + else if (mask.find_first_not_of("/.*?") == Anope::string::npos) + { + source.Reply(_("\002{0}\002 coverage is too wide; please use a more specific mask."), mask); + return; + } + + XLine *x = new XLine(mask, source.GetNick(), expires, reason); + if (Config->GetModule("operserv")->Get("akillids")) + x->SetID(XLineManager::GenerateUID()); + + unsigned int affected = 0; + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + if (this->xlm()->Check(it->second, x)) + ++affected; + float percent = static_cast(affected) / static_cast(UserListByNick.size()) * 100.0; + + if (percent > 95) + { + source.Reply(_("\002{0}\002 coverage is too wide; please use a more specific mask."), mask); + Log(LOG_ADMIN, source, this) << "tried to " << source.command << " " << percent << "% of the network (" << affected << " users)"; + delete x; + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnAddXLine(&Event::AddXLine::OnAddXLine, source, x, this->xlm()); + if (MOD_RESULT == EVENT_STOP) + { + delete x; + return; + } + + this->xlm()->AddXLine(x); + + if (Config->GetModule("operserv")->Get("killonsnline", "yes")) + { + Anope::string rreason = "G-Lined: " + reason; + + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + { + User *user = it->second; + + if (!user->HasMode("OPER") && user->server != Me && this->xlm()->Check(user, x)) + user->Kill(Me, rreason); + } + + this->xlm()->Send(NULL, x); + } + + source.Reply(_("\002{0}\002 added to the {1} list."), mask, source.command); + Log(LOG_ADMIN, source, this) << "on " << mask << " (" << reason << "), expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]"; + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + + ServiceReference snlines; + public: + CommandOSSNLine(Module *creator) : CommandOSSXLineBase(creator, "operserv/snline"), snlines("XLineManager", "xlinemanager/snline") + { + this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037:\037reason\037")); + this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}")); + this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]")); + this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]")); + this->SetSyntax("CLEAR"); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + { + source.Reply(_("\002{0} ADD\002 adds the given \037mask\037 to the {0} list for the given \037reason\037." + " \037expiry\037 is specified as an integer followed by one of \037d\037 (days), \037h\037 (hours), or \037m\037 (minutes)." + " If a unit specifier is not included, the default is days, so \037+30\037 by itself means 30 days." + " To add a {0} which does not expire, use \037+0\037." + " The default {0} expiry time is \002{1}\002." + " Because the real name may contain spaces, the separator between it and the reason is a \002colon\002."), + source.command, Anope::Duration(Config->GetModule("operserv")->Get("snlineexpiry", "30d"), source.GetAccount())); + + const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); + if (!regexengine.empty()) + { + source.Reply(" "); + source.Reply(_("Regex matches are also supported using the {0} engine. Enclose your mask in // if this is desired."), regexengine); + } + } + else if (subcommand.equals_ci("DEL")) + source.Reply(_("The \002{0} DEL\002 command removes the given \037mask\037 from the {0} list if it is present." + " If a list of entry numbers is given, those entries are deleted.")); + else if (subcommand.equals_ci("LIST") || subcommand.equals_ci("VIEW")) + source.Reply(_("The \002{0} LIST\002 and \002{0} VIEW\002 commands displays the {0} list.\n" + "If a wildcard \037mask\037 is given, only those entries matching the \037mask\037 are displayed." + " If a list of entry numbers is given, only those entries are shown." + " \002VIEW\002 is similar to \002LIST\002 but also shows who created the {0}, when it was created, and when it expires.\n" + "\n" + "Example:\n" + "\n" + " {0} LIST 2-5,7-9\n" + " Lists {0} entries numbered 2 through 5 and 7 through 9.\n"), + source.command); + else if (subcommand.equals_ci("CLEAR")) + source.Reply(_("\002{0} CLEAR\002 removes all entries from the {0} list."), + source.command); + else + { + CommandInfo *help = source.service->FindCommand("generic/help"); + + source.Reply(_("Allows you to manipulate the {0} list." + " If a user attempts to use a realname that matches a {0} mask, services will kill the user." + "\n" + "The \002ADD\002 command adds \037mask\037 to the {0} list.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "The \002DEL\002 command removes \037mask\037 from the {0} list.\n" + "\002{msg}{service} {help} {command} DEL\002 for more information.\n" + "\n" + "The \002LIST\002 and \002VIEW\002 commands both show the {0} list, but \002VIEW\002 also shows who created the {0} entry, when it was created, and when it expires.\n" + "\002{msg}{service} {help} {command} [LIST | VIEW]\002 for more information.\n" + "\n" + "The \002CLEAR\002 command clears th auto kill list." + "\002{msg}{service} {help} {command} CLEAR\002 for more information.\n"), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "help"_kw = help->cname, "command"_kw = source.command); + } + return true; + } +}; + +class CommandOSSQLine : public CommandOSSXLineBase +{ + XLineManager *xlm() + { + return this->sqlines; + } + + void OnAdd(CommandSource &source, const std::vector ¶ms) override + { + if (!this->xlm()) + return; + + unsigned last_param = 2; + Anope::string expiry, mask; + + mask = params.size() > 1 ? params[1] : ""; + if (!mask.empty() && mask[0] == '+') + { + expiry = mask; + mask = params.size() > 2 ? params[2] : ""; + last_param = 3; + } + + time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get("sqlineexpiry", "30d"); + /* If the expiry given does not contain a final letter, it's in days, + * said the doc. Ah well. + */ + if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) + expires *= 86400; + /* Do not allow less than a minute expiry time */ + if (expires && expires < 60) + { + source.Reply(_("Invalid expiry time \002{0}\002."), expiry); + return; + } + else if (expires > 0) + expires += Anope::CurTime; + + if (params.size() <= last_param) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + Anope::string reason = params[last_param]; + if (last_param == 2 && params.size() > 3) + reason += " " + params[3]; + + if (mask.empty() || reason.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + if (mask[0] == '/' && mask[mask.length() - 1] == '/') + { + if (!Config->regex_flags) + { + source.Reply(_("Regex is disabled.")); + return; + } + + Anope::string stripped_mask = mask.substr(1, mask.length() - 2); + try + { + std::regex(stripped_mask.str(), Config->regex_flags); + } + catch (const std::regex_error &ex) + { + source.Reply(ex.what()); + return; + } + } + + if (Config->GetModule("operserv")->Get("addakiller", "yes") && !source.GetNick().empty()) + reason = "[" + source.GetNick() + "] " + reason; + + if (!this->sqlines->CanAdd(source, mask, expires, reason)) + return; + else if (mask.find_first_not_of("./?*") == Anope::string::npos) + { + source.Reply(_("\002{0}\002 coverage is too wide; please use a more specific mask."), mask); + return; + } + + XLine *x = new XLine(mask, source.GetNick(), expires, reason); + if (Config->GetModule("operserv")->Get("akillids")) + x->SetID(XLineManager::GenerateUID()); + + unsigned int affected = 0; + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + if (this->xlm()->Check(it->second, x)) + ++affected; + float percent = static_cast(affected) / static_cast(UserListByNick.size()) * 100.0; + + if (percent > 95) + { + source.Reply(_("\002{0}\002 coverage is too wide; please use a more specific mask."), mask); + Log(LOG_ADMIN, source, this) << "tried to SQLine " << percent << "% of the network (" << affected << " users)"; + delete x; + return; + } + + EventReturn MOD_RESULT; + MOD_RESULT = Event::OnAddXLine(&Event::AddXLine::OnAddXLine, source, x, this->xlm()); + if (MOD_RESULT == EVENT_STOP) + { + delete x; + return; + } + + this->xlm()->AddXLine(x); + + if (Config->GetModule("operserv")->Get("killonsqline", "yes")) + { + Anope::string rreason = "Q-Lined: " + reason; + + if (mask[0] == '#') + { + for (channel_map::const_iterator cit = ChannelList.begin(), cit_end = ChannelList.end(); cit != cit_end; ++cit) + { + Channel *c = cit->second; + + if (!Anope::Match(c->name, mask, false, true)) + continue; + + std::vector users; + for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ++it) + { + ChanUserContainer *uc = it->second; + User *user = uc->user; + + if (!user->HasMode("OPER") && user->server != Me) + users.push_back(user); + } + + for (unsigned i = 0; i < users.size(); ++i) + c->Kick(NULL, users[i], "%s", reason.c_str()); + } + } + else + { + for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + { + User *user = it->second; + + if (!user->HasMode("OPER") && user->server != Me && this->xlm()->Check(user, x)) + user->Kill(Me, rreason); + } + } + + this->xlm()->Send(NULL, x); + } + + source.Reply(_("\002{0}\002 added to the {1} list."), mask, source.command); + Log(LOG_ADMIN, source, this) << "on " << mask << " (" << reason << "), expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]"; + if (Anope::ReadOnly) + source.Reply(_("Services are in read-only mode. Any changes made may not persist.")); + } + + ServiceReference sqlines; + public: + CommandOSSQLine(Module *creator) : CommandOSSXLineBase(creator, "operserv/sqline"), sqlines("XLineManager", "xlinemanager/sqline") + { + this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037 \037reason\037")); + this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}")); + this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]")); + this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]")); + this->SetSyntax("CLEAR"); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + { + source.Reply(_("\002{0} ADD\002 adds the given \037mask\037 to the {0} list for the given \037reason\037." + " \037expiry\037 is specified as an integer followed by one of \037d\037 (days), \037h\037 (hours), or \037m\037 (minutes)." + " If a unit specifier is not included, the default is days, so \037+30\037 by itself means 30 days." + " To add a {0} which does not expire, use \037+0\037." + " The default {0} expiry time is \002{1}\002."), + source.command, Anope::Duration(Config->GetModule("operserv")->Get("sqlineexpiry", "30d"), source.GetAccount())); + + const Anope::string ®exengine = Config->GetBlock("options")->Get("regexengine"); + if (!regexengine.empty()) + { + source.Reply(" "); + source.Reply(_("Regex matches are also supported using the {0} engine. Enclose your mask in // if this is desired."), regexengine); + } + } + else if (subcommand.equals_ci("DEL")) + source.Reply(_("The \002{0} DEL\002 command removes the given \037mask\037 from the {0} list if it is present." + " If a list of entry numbers is given, those entries are deleted.")); + else if (subcommand.equals_ci("LIST") || subcommand.equals_ci("VIEW")) + source.Reply(_("The \002{0} LIST\002 and \002{0} VIEW\002 commands displays the {0} list.\n" + "If a wildcard \037mask\037 is given, only those entries matching the \037mask\037 are displayed." + " If a list of entry numbers is given, only those entries are shown." + " \002VIEW\002 is similar to \002LIST\002 but also shows who created the {0}, when it was created, and when it expires.\n" + "\n" + "Example:\n" + "\n" + " {0} LIST 2-5,7-9\n" + " Lists {0} entries numbered 2 through 5 and 7 through 9.\n"), + source.command); + else if (subcommand.equals_ci("CLEAR")) + source.Reply(_("\002{0} CLEAR\002 removes all entries from the {0} list."), + source.command); + else + { + CommandInfo *help = source.service->FindCommand("generic/help"); + + source.Reply(_("Allows you to manipulate the {0} list." + " If a user attempts to use a nickname that matches a {0} mask, services will force the user off of the nickname." + " If the first character of the mask is a #, services will prevent the use of matching channels." + "\n" + "The \002ADD\002 command adds \037mask\037 to the {0} list.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "The \002DEL\002 command removes \037mask\037 from the {0} list.\n" + "\002{msg}{service} {help} {command} DEL\002 for more information.\n" + "\n" + "The \002LIST\002 and \002VIEW\002 commands both show the {0} list, but \002VIEW\002 also shows who created the {0} entry, when it was created, and when it expires.\n" + "\002{msg}{service} {help} {command} [LIST | VIEW]\002 for more information.\n" + "\n" + "The \002CLEAR\002 command clears th auto kill list." + "\002{msg}{service} {help} {command} CLEAR\002 for more information.\n"), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "help"_kw = help->cname, "command"_kw = source.command); + } + return true; + } +}; + +class OSSXLine : public Module +{ + CommandOSSNLine commandossnline; + CommandOSSQLine commandossqline; + + public: + OSSXLine(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandossnline(this), commandossqline(this) + { + } +}; + +MODULE_INIT(OSSXLine) diff --git a/modules/operserv/update.cpp b/modules/operserv/update.cpp new file mode 100644 index 000000000..8ff39fb69 --- /dev/null +++ b/modules/operserv/update.cpp @@ -0,0 +1,49 @@ +/* OperServ core functions + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" + +class CommandOSUpdate : public Command +{ + public: + CommandOSUpdate(Module *creator) : Command(creator, "operserv/update", 0, 0) + { + this->SetDesc(_("Force the Services databases to be updated immediately")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + Log(LOG_ADMIN, source, this); + source.Reply(_("Updating databases.")); + Anope::SaveDatabases(); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + source.Reply(_("Causes Services to update all database files.")); + return true; + } +}; + +class OSUpdate : public Module +{ + CommandOSUpdate commandosupdate; + + public: + OSUpdate(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , commandosupdate(this) + { + + } +}; + +MODULE_INIT(OSUpdate) diff --git a/modules/protocol/CMakeLists.txt b/modules/protocol/CMakeLists.txt new file mode 100644 index 000000000..cd225a94d --- /dev/null +++ b/modules/protocol/CMakeLists.txt @@ -0,0 +1 @@ +build_modules(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/proxyscan.cpp b/modules/proxyscan.cpp new file mode 100644 index 000000000..398e021db --- /dev/null +++ b/modules/proxyscan.cpp @@ -0,0 +1,380 @@ +/* + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" + +struct ProxyCheck +{ + std::set types; + std::vector ports; + time_t duration; + Anope::string reason; +}; + +static Anope::string ProxyCheckString; +static Anope::string target_ip; +static unsigned short target_port; +static bool add_to_akill; + +class ProxyCallbackListener : public ListenSocket +{ + class ProxyCallbackClient : public ClientSocket, public BufferedSocket + { + public: + ProxyCallbackClient(ListenSocket *l, int f, const sockaddrs &a) : Socket(f, l->IsIPv6()), ClientSocket(l, a), BufferedSocket() + { + } + + void OnAccept() override + { + this->Write(ProxyCheckString); + } + + bool ProcessWrite() override + { + return !BufferedSocket::ProcessWrite() || this->write_buffer.empty() ? false : true; + } + }; + + public: + ProxyCallbackListener(const Anope::string &b, int p) : Socket(-1, b.find(':') != Anope::string::npos), ListenSocket(b, p, false) + { + } + + ClientSocket *OnAccept(int fd, const sockaddrs &addr) override + { + return new ProxyCallbackClient(this, fd, addr); + } +}; + +class ProxyConnect : public ConnectionSocket +{ + static ServiceReference akills; + + public: + static std::set proxies; + + ProxyCheck proxy; + unsigned short port; + time_t created; + + ProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ConnectionSocket(), proxy(p), + port(po), created(Anope::CurTime) + { + proxies.insert(this); + } + + ~ProxyConnect() + { + proxies.erase(this); + } + + virtual void OnConnect() override = 0; + virtual const Anope::string GetType() const = 0; + + protected: + void Ban() + { + Anope::string reason = this->proxy.reason; + + reason = reason.replace_all_cs("%t", this->GetType()); + reason = reason.replace_all_cs("%i", this->conaddr.addr()); + reason = reason.replace_all_cs("%p", stringify(this->conaddr.port())); + + ServiceBot *OperServ = Config->GetClient("OperServ"); + Log(OperServ) << "PROXYSCAN: Open " << this->GetType() << " proxy found on " << this->conaddr.addr() << ":" << this->conaddr.port() << " (" << reason << ")"; + + XLine *x = new XLine("*@" + this->conaddr.addr(), OperServ ? OperServ->nick : "", Anope::CurTime + this->proxy.duration, reason, XLineManager::GenerateUID()); + if (add_to_akill && akills) + { + akills->AddXLine(x); + akills->Send(NULL, x); + } + else + { + if (IRCD->CanSZLine) + IRCD->SendSZLine(NULL, x); + else + IRCD->SendAkill(NULL, x); + delete x; + } + } +}; +ServiceReference ProxyConnect::akills("XLineManager", "xlinemanager/sgline"); +std::set ProxyConnect::proxies; + +class HTTPProxyConnect : public ProxyConnect, public BufferedSocket +{ + public: + HTTPProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ProxyConnect(p, po), BufferedSocket() + { + } + + void OnConnect() override + { + this->Write("CONNECT %s:%d HTTP/1.0", target_ip.c_str(), target_port); + this->Write("Content-length: 0"); + this->Write("Connection: close"); + this->Write(""); + } + + const Anope::string GetType() const override + { + return "HTTP"; + } + + bool ProcessRead() override + { + bool b = BufferedSocket::ProcessRead(); + if (this->GetLine() == ProxyCheckString) + { + this->Ban(); + return false; + } + return b; + } +}; + +class SOCKS5ProxyConnect : public ProxyConnect, public BinarySocket +{ + public: + SOCKS5ProxyConnect(ProxyCheck &p, unsigned short po) : Socket(-1), ProxyConnect(p, po), BinarySocket() + { + } + + void OnConnect() override + { + sockaddrs target_addr; + char buf[4 + sizeof(target_addr.sa4.sin_addr.s_addr) + sizeof(target_addr.sa4.sin_port)]; + int ptr = 0; + target_addr.pton(AF_INET, target_ip, target_port); + if (!target_addr.valid()) + return; + + buf[ptr++] = 5; // Version + buf[ptr++] = 1; // # of methods + buf[ptr++] = 0; // No authentication + + this->Write(buf, ptr); + + ptr = 1; + buf[ptr++] = 1; // Connect + buf[ptr++] = 0; // Reserved + buf[ptr++] = 1; // IPv4 + memcpy(buf + ptr, &target_addr.sa4.sin_addr.s_addr, sizeof(target_addr.sa4.sin_addr.s_addr)); + ptr += sizeof(target_addr.sa4.sin_addr.s_addr); + memcpy(buf + ptr, &target_addr.sa4.sin_port, sizeof(target_addr.sa4.sin_port)); + ptr += sizeof(target_addr.sa4.sin_port); + + this->Write(buf, ptr); + } + + const Anope::string GetType() const override + { + return "SOCKS5"; + } + + bool Read(const char *buffer, size_t l) override + { + if (l >= ProxyCheckString.length() && !strncmp(buffer, ProxyCheckString.c_str(), ProxyCheckString.length())) + { + this->Ban(); + return false; + } + return true; + } +}; + +class ModuleProxyScan : public Module + , public EventHook +{ + Anope::string listen_ip; + unsigned short listen_port; + Anope::string con_notice, con_source; + std::vector proxyscans; + + ProxyCallbackListener *listener; + + class ConnectionTimeout : public Timer + { + public: + ConnectionTimeout(Module *c, long timeout) : Timer(c, timeout, Anope::CurTime, true) + { + } + + void Tick(time_t) override + { + for (std::set::iterator it = ProxyConnect::proxies.begin(), it_end = ProxyConnect::proxies.end(); it != it_end;) + { + ProxyConnect *p = *it; + ++it; + + if (p->created + this->GetSecs() < Anope::CurTime) + delete p; + } + } + } connectionTimeout; + + public: + ModuleProxyScan(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + , connectionTimeout(this, 5) + { + + + this->listener = NULL; + } + + ~ModuleProxyScan() + { + for (std::set::iterator it = ProxyConnect::proxies.begin(), it_end = ProxyConnect::proxies.end(); it != it_end;) + { + ProxyConnect *p = *it; + ++it; + delete p; + } + + for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) + { + Socket *s = it->second; + ++it; + + ClientSocket *cs = dynamic_cast(s); + if (cs != NULL && cs->ls == this->listener) + delete s; + } + + delete this->listener; + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *config = Config->GetModule(this); + + Anope::string s_target_ip = config->Get("target_ip"); + if (s_target_ip.empty()) + throw ConfigException(this->name + " target_ip may not be empty"); + + int s_target_port = config->Get("target_port", "-1"); + if (s_target_port <= 0) + throw ConfigException(this->name + " target_port may not be empty and must be a positive number"); + + Anope::string s_listen_ip = config->Get("listen_ip"); + if (s_listen_ip.empty()) + throw ConfigException(this->name + " listen_ip may not be empty"); + + int s_listen_port = config->Get("listen_port", "-1"); + if (s_listen_port <= 0) + throw ConfigException(this->name + " listen_port may not be empty and must be a positive number"); + + target_ip = s_target_ip; + target_port = s_target_port; + this->listen_ip = s_listen_ip; + this->listen_port = s_listen_port; + this->con_notice = config->Get("connect_notice"); + this->con_source = config->Get("connect_source"); + add_to_akill = config->Get("add_to_akill", "true"); + this->connectionTimeout.SetSecs(config->Get("timeout", "5s")); + + ProxyCheckString = Config->GetBlock("networkinfo")->Get("networkname") + " proxy check"; + delete this->listener; + this->listener = NULL; + try + { + this->listener = new ProxyCallbackListener(this->listen_ip, this->listen_port); + } + catch (const SocketException &ex) + { + throw ConfigException("m_proxyscan: " + ex.GetReason()); + } + + this->proxyscans.clear(); + for (int i = 0; i < config->CountBlock("proxyscan"); ++i) + { + Configuration::Block *block = config->GetBlock("proxyscan", i); + ProxyCheck p; + Anope::string token; + + commasepstream sep(block->Get("type")); + while (sep.GetToken(token)) + { + if (!token.equals_ci("HTTP") && !token.equals_ci("SOCKS5")) + continue; + p.types.insert(token); + } + if (p.types.empty()) + continue; + + commasepstream sep2(block->Get("port")); + while (sep2.GetToken(token)) + { + try + { + unsigned short port = convertTo(token); + p.ports.push_back(port); + } + catch (const ConvertException &) { } + } + if (p.ports.empty()) + continue; + + p.duration = block->Get("time", "4h"); + p.reason = block->Get("reason"); + if (p.reason.empty()) + continue; + + this->proxyscans.push_back(p); + } + } + + void OnUserConnect(User *user, bool &exempt) override + { + if (exempt || user->Quitting() || !Me->IsSynced() || !user->server->IsSynced()) + return; + + /* At this time we only support IPv4 */ + if (!user->ip.valid() || user->ip.sa.sa_family != AF_INET) + /* User doesn't have a valid IPv4 IP (ipv6/spoof/etc) */ + return; + + if (!this->con_notice.empty() && !this->con_source.empty()) + { + ServiceBot *bi = ServiceBot::Find(this->con_source, true); + if (bi) + user->SendMessage(bi, this->con_notice); + } + + for (unsigned i = this->proxyscans.size(); i > 0; --i) + { + ProxyCheck &p = this->proxyscans[i - 1]; + + for (std::set::iterator it = p.types.begin(), it_end = p.types.end(); it != it_end; ++it) + { + for (unsigned k = 0; k < p.ports.size(); ++k) + { + try + { + ProxyConnect *con = NULL; + if (it->equals_ci("HTTP")) + con = new HTTPProxyConnect(p, p.ports[k]); + else if (it->equals_ci("SOCKS5")) + con = new SOCKS5ProxyConnect(p, p.ports[k]); + else + continue; + con->Connect(user->ip.addr(), p.ports[k]); + } + catch (const SocketException &ex) + { + Log(LOG_DEBUG) << "m_proxyscan: " << ex.GetReason(); + } + } + } + } + } +}; + +MODULE_INIT(ModuleProxyScan) + diff --git a/modules/pseudoclients/botserv/CMakeLists.txt b/modules/pseudoclients/botserv/CMakeLists.txt deleted file mode 100644 index 781f0ef1f..000000000 --- a/modules/pseudoclients/botserv/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/pseudoclients/botserv/botserv.cpp b/modules/pseudoclients/botserv/botserv.cpp deleted file mode 100644 index 0b51691fc..000000000 --- a/modules/pseudoclients/botserv/botserv.cpp +++ /dev/null @@ -1,228 +0,0 @@ -/* BotServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/botserv.h" -#include "modules/help.h" - -class BotServCore : public Module, public BotServ::BotServService - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - Reference BotServ; - ExtensibleRef persist, inhabit;//XXX? - - public: - BotServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) - , BotServ::BotServService(this) - , persist("PERSIST") - , inhabit("inhabit") - { - } - - void OnReload(Configuration::Conf *conf) override - { - const Anope::string &bsnick = conf->GetModule(this)->Get("client"); - BotServ = ServiceBot::Find(bsnick, true); - } - - void OnSetCorrectModes(User *user, Channel *chan, ChanServ::AccessGroup &access, bool &give_modes, bool &take_modes) override - { - /* Do not allow removing bot modes on our service bots */ - if (chan->ci && chan->ci->GetBot() == user) - { - const Anope::string &botmodes = Config->GetModule(this)->Get("botmodes"); - for (unsigned i = 0; i < botmodes.length(); ++i) - chan->SetMode(chan->ci->GetBot(), ModeManager::FindChannelModeByChar(botmodes[i]), chan->ci->GetBot()->GetUID()); - } - } - - void OnBotAssign(User *sender, ChanServ::Channel *ci, ServiceBot *bi) override - { - if (ci->c && ci->c->users.size() >= Config->GetModule(this)->Get("minusers")) - { - ChannelStatus status(Config->GetModule(this)->Get("botmodes")); - bi->Join(ci->c, &status); - } - } - - void OnJoinChannel(User *user, Channel *c) override - { - if (!Config || !IRCD) - return; - - ServiceBot *bi = user->server == Me ? dynamic_cast(user) : NULL; - if (bi && Config->GetModule(this)->Get("smartjoin")) - { - std::vector bans = c->GetModeList("BAN"); - - /* We check for bans */ - for (unsigned int i = 0; i < bans.size(); ++i) - { - Entry ban("BAN", bans[i]); - if (ban.Matches(user)) - c->RemoveMode(NULL, "BAN", ban.GetMask()); - } - - Anope::string Limit; - unsigned limit = 0; - try - { - if (c->GetParam("LIMIT", Limit)) - limit = convertTo(Limit); - } - catch (const ConvertException &) { } - - /* Should we be invited? */ - if (c->HasMode("INVITE") || (limit && c->users.size() >= limit)) - { - ChannelMode *cm = ModeManager::FindChannelModeByName("OP"); - char symbol = cm ? anope_dynamic_static_cast(cm)->symbol : 0; - IRCD->SendNotice(bi, (symbol ? Anope::string(symbol) : "") + c->name, "%s invited %s into the channel.", user->nick.c_str(), user->nick.c_str()); - } - - ModeManager::ProcessModes(); - } - - if (user->server != Me && c->ci && c->ci->GetBot()) - { - /** - * We let the bot join even if it was an ignored user, as if we don't, - * and the ignored user doesn't just leave, the bot will never - * make it into the channel, leaving the channel botless even for - * legit users - Rob - **/ - /* This is before the user has joined the channel, so check usercount + 1 */ - if (c->users.size() + 1 >= Config->GetModule(this)->Get("minusers") && !c->FindUser(c->ci->GetBot())) - { - ChannelStatus status(Config->GetModule(this)->Get("botmodes")); - c->ci->GetBot()->Join(c, &status); - } - } - } - - void OnLeaveChannel(User *u, Channel *c) override - { - /* Channel is persistent, it shouldn't be deleted and the service bot should stay */ - if (c->ci && persist && persist->HasExt(c->ci)) - return; - - /* Channel is syncing from a netburst, don't destroy it as more users are probably wanting to join immediately - * We also don't part the bot here either, if necessary we will part it after the sync - */ - if (c->syncing) - return; - - /* Additionally, do not delete this channel if ChanServ/a BotServ bot is inhabiting it */ - if (inhabit && inhabit->HasExt(c)) - return; - - /* This is called prior to removing the user from the channnel, so c->users.size() - 1 should be safe */ - if (c->ci && c->ci->GetBot() && u != c->ci->GetBot() && c->users.size() - 1 <= Config->GetModule(this)->Get("minusers") && c->FindUser(c->ci->GetBot())) - c->ci->GetBot()->Part(c->ci->c); - } - - EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override - { - if (!params.empty()) - return EVENT_CONTINUE; - - if (source.c) - { - source.Reply(_("\002%s\002 allows you to execute \"fantasy\" commands in the channel.\n" - "Fantasy commands are commands that can be executed from messaging a\n" - "channel, and provide a more convenient way to execute commands. Commands that\n" - "require a channel as a parameter will automatically have that parameter\n" - "given.\n"), source.service->nick.c_str()); - const Anope::string &fantasycharacters = Config->GetModule("fantasy")->Get("fantasycharacter", "!"); - if (!fantasycharacters.empty()) - source.Reply(_(" \n" - "Fantasy commands may be prefixed with one of the following characters: %s\n"), fantasycharacters.c_str()); - source.Reply(_(" \n" - "Available commands are:")); - } - else if (*source.service == BotServ) - { - source.Reply(_("\002%s\002 allows you to have a bot on your own channel.\n" - "It has been created for users that can't host or\n" - "configure a bot, or for use on networks that don't\n" - "allow user bots. Available commands are listed\n" - "below; to use them, type \002%s%s \037command\037\002. For\n" - "more information on a specific command, type\n" - "\002%s%s %s \037command\037\002.\n"), - BotServ->nick.c_str(), Config->StrictPrivmsg.c_str(), BotServ->nick.c_str(), - Config->StrictPrivmsg.c_str(), BotServ->nick.c_str(), source.command.c_str()); - } - - return EVENT_CONTINUE; - } - - void OnPostHelp(CommandSource &source, const std::vector ¶ms) override - { - if (!params.empty() || source.c || source.service != *BotServ) - return; - - source.Reply(_(" \n" - "Bot will join a channel whenever there is at least\n" - "\002%d\002 user(s) on it."), Config->GetModule(this)->Get("minusers")); - const Anope::string &fantasycharacters = Config->GetModule("fantasy")->Get("fantasycharacter", "!"); - if (!fantasycharacters.empty()) - source.Reply(_("Additionally, if fantasy is enabled fantasy commands\n" - "can be executed by prefixing the command name with\n" - "one of the following characters: %s"), fantasycharacters.c_str()); - } - - EventReturn OnChannelModeSet(Channel *c, const MessageSource &source, ChannelMode *mode, const Anope::string ¶m) override - { - if (source.GetUser() && !source.GetBot() && Config->GetModule(this)->Get("smartjoin") && mode->name == "BAN" && c->ci && c->ci->GetBot() && c->FindUser(c->ci->GetBot())) - { - ServiceBot *bi = c->ci->GetBot(); - - Entry ban("BAN", param); - if (ban.Matches(bi)) - c->RemoveMode(bi, "BAN", param); - } - - return EVENT_CONTINUE; - } - - void OnCreateChan(ChanServ::Channel *ci) override - { - /* Set default bot flags */ - spacesepstream sep(Config->GetModule(this)->Get("defaults", "greet fantasy")); - for (Anope::string token; sep.GetToken(token);) - ci->SetS("BS_" + token.upper(), true); - } - - void OnUserKicked(const MessageSource &source, User *target, const Anope::string &channel, ChannelStatus &status, const Anope::string &kickmsg) override - { - ServiceBot *bi = ServiceBot::Find(target->GetUID()); - if (bi) - /* Bots get rejoined */ - bi->Join(channel, &status); - } - - void OnCreateBot(ServiceBot *bi) override - { - if (bi->botmodes.empty()) - bi->botmodes = Config->GetModule(this)->Get("botumodes"); - } -}; - -MODULE_INIT(BotServCore) - diff --git a/modules/pseudoclients/chanserv/CMakeLists.txt b/modules/pseudoclients/chanserv/CMakeLists.txt deleted file mode 100644 index 781f0ef1f..000000000 --- a/modules/pseudoclients/chanserv/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/pseudoclients/chanserv/chanaccess.cpp b/modules/pseudoclients/chanserv/chanaccess.cpp deleted file mode 100644 index 1601260c2..000000000 --- a/modules/pseudoclients/chanserv/chanaccess.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include "module.h" -#include "chanaccess.h" -#include "chanaccesstype.h" - -ChanServ::Channel *ChanAccessImpl::GetChannel() -{ - return Get(&ChanAccessType::ci); -} - -void ChanAccessImpl::SetChannel(ChanServ::Channel *ci) -{ - Object::Set(&ChanAccessType::ci, ci); -} - -Anope::string ChanAccessImpl::GetCreator() -{ - return Get(&ChanAccessType::creator); -} - -void ChanAccessImpl::SetCreator(const Anope::string &c) -{ - Object::Set(&ChanAccessType::creator, c); -} - -time_t ChanAccessImpl::GetLastSeen() -{ - return Get(&ChanAccessType::last_seen); -} - -void ChanAccessImpl::SetLastSeen(const time_t &t) -{ - Object::Set(&ChanAccessType::last_seen, t); -} - -time_t ChanAccessImpl::GetCreated() -{ - return Get(&ChanAccessType::created); -} - -void ChanAccessImpl::SetCreated(const time_t &t) -{ - Object::Set(&ChanAccessType::created, t); -} - -Anope::string ChanAccessImpl::GetMask() -{ - return Get(&ChanAccessType::mask); -} - -void ChanAccessImpl::SetMask(const Anope::string &n) -{ - Object::Set(&ChanAccessType::mask, n); -} - -Serialize::Object *ChanAccessImpl::GetObj() -{ - return Get(&ChanAccessType::obj); -} - -void ChanAccessImpl::SetObj(Serialize::Object *o) -{ - Object::Set(&ChanAccessType::obj, o); -} - -Anope::string ChanAccessImpl::Mask() -{ - if (NickServ::Account *acc = GetAccount()) - return acc->GetDisplay(); - - return GetMask(); -} - -NickServ::Account *ChanAccessImpl::GetAccount() -{ - if (!GetObj() || GetObj()->GetSerializableType() != NickServ::account) - return nullptr; - - return anope_dynamic_static_cast(GetObj()); -} - -bool ChanAccessImpl::Matches(const User *u, NickServ::Account *acc, Path &p) -{ - if (this->GetAccount()) - return this->GetAccount() == acc; - - if (u) - { - bool is_mask = this->Mask().find_first_of("!@?*") != Anope::string::npos; - if (is_mask && Anope::Match(u->nick, this->Mask())) - return true; - else if (Anope::Match(u->GetDisplayedMask(), this->Mask())) - return true; - } - - if (acc) - for (NickServ::Nick *na : acc->GetRefs(NickServ::nick)) - if (Anope::Match(na->GetNick(), this->Mask())) - return true; - - if (IRCD->IsChannelValid(this->Mask())) - { - ChanServ::Channel *tci = ChanServ::Find(this->Mask()); - if (tci) - { - for (unsigned i = 0; i < tci->GetAccessCount(); ++i) - { - ChanServ::ChanAccess *a = tci->GetAccess(i); - std::pair pair = std::make_pair(this, a); - - std::pair range = p.first.equal_range(this); - for (; range.first != range.second; ++range.first) - if (range.first->first == pair.first && range.first->second == pair.second) - goto cont; - - p.first.insert(pair); - if (a->Matches(u, acc, p)) - p.second.insert(pair); - - cont:; - } - - return p.second.count(this) > 0; - } - } - - return false; -} diff --git a/modules/pseudoclients/chanserv/chanaccess.h b/modules/pseudoclients/chanserv/chanaccess.h deleted file mode 100644 index bb65f79c5..000000000 --- a/modules/pseudoclients/chanserv/chanaccess.h +++ /dev/null @@ -1,30 +0,0 @@ - -class ChanAccessImpl : public ChanServ::ChanAccess -{ - public: - ChanAccessImpl(Serialize::TypeBase *type) : ChanServ::ChanAccess(type) { } - ChanAccessImpl(Serialize::TypeBase *type, Serialize::ID id) : ChanServ::ChanAccess(type, id) { } - - ChanServ::Channel *GetChannel() override; - void SetChannel(ChanServ::Channel *ci) override; - - Anope::string GetCreator() override; - void SetCreator(const Anope::string &c) override; - - time_t GetLastSeen() override; - void SetLastSeen(const time_t &t) override; - - time_t GetCreated() override; - void SetCreated(const time_t &t) override; - - Anope::string GetMask() override; - void SetMask(const Anope::string &) override; - - Serialize::Object *GetObj() override; - void SetObj(Serialize::Object *) override; - - Anope::string Mask() override; - NickServ::Account *GetAccount() override; - - bool Matches(const User *u, NickServ::Account *acc, Path &p) override; -}; diff --git a/modules/pseudoclients/chanserv/chanaccesstype.h b/modules/pseudoclients/chanserv/chanaccesstype.h deleted file mode 100644 index ca8dc1b79..000000000 --- a/modules/pseudoclients/chanserv/chanaccesstype.h +++ /dev/null @@ -1,21 +0,0 @@ - -class ChanAccessType : public Serialize::AbstractType -{ - public: - Serialize::ObjectField ci; - Serialize::Field mask; - Serialize::ObjectField obj; - Serialize::Field creator; - Serialize::Field last_seen; - Serialize::Field created; - - ChanAccessType(Module *me, const Anope::string &name) : Serialize::AbstractType(me, name) - , ci(this, "ci", true) - , mask(this, "mask") - , obj(this, "obj", true) - , creator(this, "creator") - , last_seen(this, "last_seen") - , created(this, "created") - { - } -}; diff --git a/modules/pseudoclients/chanserv/channel.cpp b/modules/pseudoclients/chanserv/channel.cpp deleted file mode 100644 index b24edcc0c..000000000 --- a/modules/pseudoclients/chanserv/channel.cpp +++ /dev/null @@ -1,429 +0,0 @@ -/* Registered channel functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - * - */ - -#include "module.h" -#include "channeltype.h" -#include "modules/cs_akick.h" - -ChannelImpl::ChannelImpl(Serialize::TypeBase *type, const Anope::string &chname) : ChanServ::Channel(type) -{ - SetName(chname); - SetTimeRegistered(Anope::CurTime); - SetLastUsed(Anope::CurTime); -} - -ChannelImpl::~ChannelImpl() -{ - ChanServ::registered_channel_map& map = ChanServ::service->GetChannels(); - map.erase(this->GetName()); -} - -void ChannelImpl::Delete() -{ - Event::OnDelChan(&Event::DelChan::OnDelChan, this); - - Log(LOG_DEBUG) << "Deleting channel " << this->GetName(); - - if (this->c) - { - if (this->GetBot() && this->c->FindUser(this->GetBot())) - this->GetBot()->Part(this->c); - - /* Parting the service bot can cause the channel to go away */ - - if (this->c) - { - if (this->c && this->c->CheckDelete()) - delete this->c; - - this->c = NULL; - } - } - - return Serialize::Object::Delete(); -} - -Anope::string ChannelImpl::GetName() -{ - return Get(&ChannelType::name); -} - -void ChannelImpl::SetName(const Anope::string &name) -{ - Set(&ChannelType::name, name); -} - -Anope::string ChannelImpl::GetDesc() -{ - return Get(&ChannelType::desc); -} - -void ChannelImpl::SetDesc(const Anope::string &desc) -{ - Set(&ChannelType::desc, desc); -} - -time_t ChannelImpl::GetTimeRegistered() -{ - return Get(&ChannelType::time_registered); -} - -void ChannelImpl::SetTimeRegistered(const time_t &t) -{ - Set(&ChannelType::time_registered, t); -} - -time_t ChannelImpl::GetLastUsed() -{ - return Get(&ChannelType::last_used); -} - -void ChannelImpl::SetLastUsed(const time_t &t) -{ - Set(&ChannelType::last_used, t); -} - -Anope::string ChannelImpl::GetLastTopic() -{ - return Get(&ChannelType::last_topic); -} - -void ChannelImpl::SetLastTopic(const Anope::string &topic) -{ - Set(&ChannelType::last_topic, topic); -} - -Anope::string ChannelImpl::GetLastTopicSetter() -{ - return Get(&ChannelType::last_topic_setter); -} - -void ChannelImpl::SetLastTopicSetter(const Anope::string &setter) -{ - Set(&ChannelType::last_topic_setter, setter); -} - -time_t ChannelImpl::GetLastTopicTime() -{ - return Get(&ChannelType::last_topic_time); -} - -void ChannelImpl::SetLastTopicTime(const time_t &t) -{ - Set(&ChannelType::last_topic_time, t); -} - -int16_t ChannelImpl::GetBanType() -{ - return Get(&ChannelType::bantype); -} - -void ChannelImpl::SetBanType(const int16_t &i) -{ - Set(&ChannelType::bantype, i); -} - -time_t ChannelImpl::GetBanExpire() -{ - return Get(&ChannelType::banexpire); -} - -void ChannelImpl::SetBanExpire(const time_t &t) -{ - Set(&ChannelType::banexpire, t); -} - -BotInfo *ChannelImpl::GetBI() -{ - return Get(&ChannelType::bi); -} - -void ChannelImpl::SetBI(BotInfo *bi) -{ - Set(&ChannelType::bi, bi); -} - -ServiceBot *ChannelImpl::GetBot() -{ - BotInfo *bi = GetBI(); - return bi ? bi->bot : nullptr; -} - -void ChannelImpl::SetBot(ServiceBot *bi) -{ - SetBI(bi->bi); -} - -MemoServ::MemoInfo *ChannelImpl::GetMemos() -{ - return GetRef(MemoServ::memoinfo); -} - -void ChannelImpl::SetFounder(NickServ::Account *nc) -{ - Set(&ChannelType::founder, nc); -} - -NickServ::Account *ChannelImpl::GetFounder() -{ - return Get(&ChannelType::founder); -} - -void ChannelImpl::SetSuccessor(NickServ::Account *nc) -{ - Set(&ChannelType::successor, nc); -} - -NickServ::Account *ChannelImpl::GetSuccessor() -{ - return Get(&ChannelType::successor); -} - -ChanServ::ChanAccess *ChannelImpl::GetAccess(unsigned index) -{ - std::vector a = GetRefs(ChanServ::chanaccess); - return a.size() > index ? a[index] : nullptr; -} - -ChanServ::AccessGroup ChannelImpl::AccessFor(const User *u, bool updateLastUsed) -{ - ChanServ::AccessGroup group; - - if (u == NULL) - return group; - - const NickServ::Account *nc = u->Account(); - if (nc == NULL && !this->HasFieldS("NS_SECURE") && u->IsRecognized()) - { - NickServ::Nick *na = NickServ::FindNick(u->nick); - if (na != NULL) - nc = na->GetAccount(); - } - - group.super_admin = u->super_admin; - group.founder = IsFounder(u); - group.ci = this; - group.nc = nc; - - for (unsigned i = 0, end = this->GetAccessCount(); i < end; ++i) - { - ChanServ::ChanAccess *a = this->GetAccess(i); - if (a->Matches(u, u->Account(), group.path)) - group.push_back(a); - } - - if (group.founder || !group.empty()) - { - if (updateLastUsed) - this->SetLastUsed(Anope::CurTime); - - for (unsigned i = 0; i < group.size(); ++i) - group[i]->SetLastSeen(Anope::CurTime); - } - - return group; -} - -ChanServ::AccessGroup ChannelImpl::AccessFor(NickServ::Account *nc, bool updateLastUsed) -{ - ChanServ::AccessGroup group; - - group.founder = GetFounder() && GetFounder() == nc; - group.ci = this; - group.nc = nc; - - for (unsigned i = 0, end = this->GetAccessCount(); i < end; ++i) - { - ChanServ::ChanAccess *a = this->GetAccess(i); - if (a->Matches(NULL, nc, group.path)) - group.push_back(a); - } - - if (group.founder || !group.empty()) - if (updateLastUsed) - this->SetLastUsed(Anope::CurTime); - - /* don't update access last seen here, this isn't the user requesting access */ - - return group; -} - -unsigned ChannelImpl::GetAccessCount() -{ - return GetRefs(ChanServ::chanaccess).size(); -} - -unsigned ChannelImpl::GetDeepAccessCount() const -{ - return 0; -#if 0 - ChanServ::ChanAccess::Path path; - for (unsigned i = 0, end = this->GetAccessCount(); i < end; ++i) - { - ChanServ::ChanAccess *a = this->GetAccess(i); - a->Matches(NULL, NULL, path); - } - - unsigned count = this->GetAccessCount(); - std::set channels; - channels.insert(this); - for (ChanServ::ChanAccess::Set::iterator it = path.first.begin(); it != path.first.end(); ++it) - { - const ChanServ::Channel *ci = it->first->GetChannel(); - if (!channels.count(ci)) - { - channels.count(ci); - count += ci->GetAccessCount(); - } - } - return count; -#endif -} - -void ChannelImpl::ClearAccess() -{ - for (ChanServ::ChanAccess *a : GetRefs(ChanServ::chanaccess)) - a->Delete(); -} - -AutoKick *ChannelImpl::AddAkick(const Anope::string &user, NickServ::Account *akicknc, const Anope::string &reason, time_t t, time_t lu) -{ - AutoKick *ak = ::autokick.Create(); - ak->SetChannel(this); - ak->SetAccount(akicknc); - ak->SetReason(reason); - ak->SetCreator(user); - ak->SetAddTime(t); - ak->SetLastUsed(lu); - - return ak; -} - -AutoKick *ChannelImpl::AddAkick(const Anope::string &user, const Anope::string &mask, const Anope::string &reason, time_t t, time_t lu) -{ - AutoKick *ak = ::autokick.Create(); - ak->SetChannel(this); - ak->SetMask(mask); - ak->SetReason(reason); - ak->SetCreator(user); - ak->SetAddTime(t); - ak->SetLastUsed(lu); - - return ak; -} - -AutoKick *ChannelImpl::GetAkick(unsigned index) -{ - std::vector a = GetRefs(autokick); - return a.size() > index ? a[index] : nullptr; -} - -unsigned ChannelImpl::GetAkickCount() -{ - std::vector t = GetRefs(autokick); - return t.size(); -} - -void ChannelImpl::ClearAkick() -{ - for (AutoKick *ak : GetRefs(autokick)) - ak->Delete(); -} - -int16_t ChannelImpl::GetLevel(const Anope::string &priv) -{ - for (ChanServ::Level *l : this->GetRefs(ChanServ::level)) - if (l->GetName() == priv) - return l->GetLevel(); - - ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(priv) : nullptr; - if (!p) - { - Log(LOG_DEBUG) << "Unknown privilege " + priv; - return ChanServ::ACCESS_INVALID; - } - - return p->level; -} - -void ChannelImpl::SetLevel(const Anope::string &priv, int16_t level) -{ - for (ChanServ::Level *l : this->GetRefs(ChanServ::level)) - if (l->GetName() == priv) - { - l->SetLevel(level); - return; - } - - ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(priv) : nullptr; - if (!p) - { - Log(LOG_DEBUG) << "Unknown privilege " + priv; - return; - } - - ChanServ::Level *l = ChanServ::level.Create(); - l->SetChannel(this); - l->SetName(priv); - l->SetLevel(level); -} - -void ChannelImpl::RemoveLevel(const Anope::string &priv) -{ - for (ChanServ::Level *l : this->GetRefs(ChanServ::level)) - if (l->GetName() == priv) - { - l->Delete(); - return; - } -} - -void ChannelImpl::ClearLevels() -{ - for (ChanServ::Level *l : this->GetRefs(ChanServ::level)) - l->Delete(); -} - -Anope::string ChannelImpl::GetIdealBan(User *u) -{ - int bt = this->GetBanType(); - switch (bt) - { - case 0: - return "*!" + u->GetVIdent() + "@" + u->GetDisplayedHost(); - case 1: - if (u->GetVIdent()[0] == '~') - return "*!*" + u->GetVIdent() + "@" + u->GetDisplayedHost(); - else - return "*!" + u->GetVIdent() + "@" + u->GetDisplayedHost(); - case 3: - return "*!" + u->Mask(); - case 2: - default: - return "*!*@" + u->GetDisplayedHost(); - } -} - -bool ChannelImpl::IsFounder(const User *user) -{ - if (!user) - return false; - - if (user->super_admin) - return true; - - if (user->Account() && user->Account() == this->GetFounder()) - return true; - - return false; -} - diff --git a/modules/pseudoclients/chanserv/channel.h b/modules/pseudoclients/chanserv/channel.h deleted file mode 100644 index 86e42a8f9..000000000 --- a/modules/pseudoclients/chanserv/channel.h +++ /dev/null @@ -1,68 +0,0 @@ - - -class ChannelImpl : public ChanServ::Channel -{ - public: - ChannelImpl(Serialize::TypeBase *type) : ChanServ::Channel(type) { } - ChannelImpl(Serialize::TypeBase *type, Serialize::ID id) : ChanServ::Channel(type, id) { } - ChannelImpl(Serialize::TypeBase *type, const Anope::string &chname); - ~ChannelImpl(); - void Delete() override; - - Anope::string GetName() override; - void SetName(const Anope::string &) override; - - Anope::string GetDesc() override; - void SetDesc(const Anope::string &) override; - - time_t GetTimeRegistered() override; - void SetTimeRegistered(const time_t &) override; - - time_t GetLastUsed() override; - void SetLastUsed(const time_t &) override; - - Anope::string GetLastTopic() override; - void SetLastTopic(const Anope::string &) override; - - Anope::string GetLastTopicSetter() override; - void SetLastTopicSetter(const Anope::string &) override; - - time_t GetLastTopicTime() override; - void SetLastTopicTime(const time_t &) override; - - int16_t GetBanType() override; - void SetBanType(const int16_t &) override; - - time_t GetBanExpire() override; - void SetBanExpire(const time_t &) override; - - BotInfo *GetBI() override; - void SetBI(BotInfo *) override; - - ServiceBot *GetBot() override; - void SetBot(ServiceBot *) override; - - MemoServ::MemoInfo *GetMemos() override; - - bool IsFounder(const User *user) override; - void SetFounder(NickServ::Account *nc) override; - NickServ::Account *GetFounder() override; - void SetSuccessor(NickServ::Account *nc) override; - NickServ::Account *GetSuccessor() override; - ChanServ::ChanAccess *GetAccess(unsigned index) /*const*/ override; - ChanServ::AccessGroup AccessFor(const User *u, bool = true) override; - ChanServ::AccessGroup AccessFor(NickServ::Account *nc, bool = true) override; - unsigned GetAccessCount()/* const*/ override; - unsigned GetDeepAccessCount() const override; - void ClearAccess() override; - AutoKick* AddAkick(const Anope::string &user, NickServ::Account *akicknc, const Anope::string &reason, time_t t = Anope::CurTime, time_t lu = 0) override; - AutoKick* AddAkick(const Anope::string &user, const Anope::string &mask, const Anope::string &reason, time_t t = Anope::CurTime, time_t lu = 0) override; - AutoKick* GetAkick(unsigned index) override; - unsigned GetAkickCount() override; - void ClearAkick() override; - int16_t GetLevel(const Anope::string &priv) override; - void SetLevel(const Anope::string &priv, int16_t level) override; - void RemoveLevel(const Anope::string &priv) override; - void ClearLevels() override; - Anope::string GetIdealBan(User *u) override; -}; diff --git a/modules/pseudoclients/chanserv/channeltype.cpp b/modules/pseudoclients/chanserv/channeltype.cpp deleted file mode 100644 index 4143285a8..000000000 --- a/modules/pseudoclients/chanserv/channeltype.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "module.h" -#include "channeltype.h" - -ChannelType::ChannelType(Module *me) : Serialize::Type(me, "ChannelInfo") - , name(this, "name") - , desc(this, "desc") - , time_registered(this, "time_registered") - , last_used(this, "last_used") - , last_topic(this, "last_topic") - , last_topic_setter(this, "last_topic_setter") - , last_topic_time(this, "last_topic_time") - , bantype(this, "bantype") - , banexpire(this, "banexpire") - , founder(this, "founder") - , successor(this, "successor") - , bi(this, "bi") -{ - -} - -void ChannelType::Name::SetField(ChanServ::Channel *c, const Anope::string &value) -{ - ChanServ::registered_channel_map& map = ChanServ::service->GetChannels(); - map.erase(GetField(c)); - - Serialize::Field::SetField(c, value); - - map[value] = c; -} - -ChanServ::Channel *ChannelType::FindChannel(const Anope::string &chan) -{ - Serialize::ID id; - EventReturn result = Event::OnSerialize(&Event::SerializeEvents::OnSerializeFind, this, &this->name, chan, id); - if (result == EVENT_ALLOW) - return RequireID(id); - - // fall back - ChanServ::registered_channel_map& map = ChanServ::service->GetChannels(); - auto it = map.find(chan); - if (it != map.end()) - return it->second; - return nullptr; -} - diff --git a/modules/pseudoclients/chanserv/channeltype.h b/modules/pseudoclients/chanserv/channeltype.h deleted file mode 100644 index 7bc22a8f6..000000000 --- a/modules/pseudoclients/chanserv/channeltype.h +++ /dev/null @@ -1,34 +0,0 @@ -#include "channel.h" - -class ChannelType : public Serialize::Type -{ - public: - /* Channel name */ - struct Name : Serialize::Field - { - using Serialize::Field::Field; - - void SetField(ChanServ::Channel *c, const Anope::string &value) override; - } name; - Serialize::Field desc; - Serialize::Field time_registered; - Serialize::Field last_used; - - Serialize::Field last_topic; - Serialize::Field last_topic_setter; - Serialize::Field last_topic_time; - - Serialize::Field bantype; - Serialize::Field banexpire; - - /* Channel founder */ - Serialize::ObjectField founder; - /* Who gets the channel if the founder nick is dropped or expires */ - Serialize::ObjectField successor; - - Serialize::ObjectField bi; - - ChannelType(Module *); - - ChanServ::Channel *FindChannel(const Anope::string &); -}; diff --git a/modules/pseudoclients/chanserv/chanserv.cpp b/modules/pseudoclients/chanserv/chanserv.cpp deleted file mode 100644 index 420051252..000000000 --- a/modules/pseudoclients/chanserv/chanserv.cpp +++ /dev/null @@ -1,587 +0,0 @@ -/* ChanServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/cs_mode.h" -#include "modules/help.h" -#include "modules/bs_bot.h" -#include "modules/chanserv.h" -#include "modules/cs_info.h" -#include "modules/cs_akick.h" -#include "channeltype.h" -#include "leveltype.h" -#include "modetype.h" -#include "chanaccesstype.h" -#include "chanaccess.h" - -class ChanServCore : public Module - , public ChanServ::ChanServService - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - Reference ChanServ; - std::vector defaults; - ExtensibleItem inhabit; - ExtensibleRef persist;//XXX? - bool always_lower; - EventHandlers OnPreChanExpire; - EventHandlers OnChanExpire; - std::vector Privileges; - ChanServ::registered_channel_map registered_channels; - ChannelType channel_type; - ChanAccessType chanaccess_type; - LevelType level_type; - CSModeType mode_type; - - public: - ChanServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) - , ChanServService(this) - , inhabit(this, "inhabit") - , persist("PERSIST") - , always_lower(false) - , OnPreChanExpire(this) - , OnChanExpire(this) - , channel_type(this) - , chanaccess_type(this, "ChanAccess") - , level_type(this) - , mode_type(this) - { - } - - ChanServ::Channel *Find(const Anope::string &name) override - { - return channel_type.FindChannel(name); - } - - ChanServ::registered_channel_map& GetChannels() override - { - return registered_channels; - } - - void Hold(Channel *c) override - { - /** A timer used to keep the BotServ bot/ChanServ in the channel - * after kicking the last user in a channel - */ - class ChanServTimer : public Timer - { - Reference &ChanServ; - ExtensibleItem &inhabit; - Reference c; - - public: - /** Constructor - * @param chan The channel - */ - ChanServTimer(Reference &cs, ExtensibleItem &i, Module *m, Channel *chan) : Timer(m, Config->GetModule(m)->Get("inhabit", "15s")), ChanServ(cs), inhabit(i), c(chan) - { - if (!ChanServ || !c) - return; - inhabit.Set(c, true); - if (!c->ci || !c->ci->GetBot()) - ChanServ->Join(c); - else if (!c->FindUser(c->ci->GetBot())) - c->ci->GetBot()->Join(c); - - /* Set +ntsi to prevent rejoin */ - c->SetMode(NULL, "NOEXTERNAL"); - c->SetMode(NULL, "TOPIC"); - c->SetMode(NULL, "SECRET"); - c->SetMode(NULL, "INVITE"); - } - - /** Called when the delay is up - * @param The current time - */ - void Tick(time_t) override - { - if (!c) - return; - - inhabit.Unset(c); - - /* In the event we don't part */ - c->RemoveMode(NULL, "SECRET"); - c->RemoveMode(NULL, "INVITE"); - - if (!c->ci || !c->ci->GetBot()) - { - if (ChanServ) - ChanServ->Part(c); - } - /* If someone has rejoined this channel in the meantime, don't part the bot */ - else if (c->users.size() <= 1) - c->ci->GetBot()->Part(c); - } - }; - - if (inhabit.HasExt(c)) - return; - - new ChanServTimer(ChanServ, inhabit, this->owner, c); - } - - void AddPrivilege(ChanServ::Privilege p) override - { - unsigned i; - for (i = 0; i < Privileges.size(); ++i) - { - ChanServ::Privilege &priv = Privileges[i]; - - if (priv.rank > p.rank) - break; - } - - Privileges.insert(Privileges.begin() + i, p); - } - - void RemovePrivilege(ChanServ::Privilege &p) override - { - std::vector::iterator it = std::find(Privileges.begin(), Privileges.end(), p); - if (it != Privileges.end()) - Privileges.erase(it); - - for (auto& cit : GetChannels()) - { - ChanServ::Channel *ci = cit.second; - ci->RemoveLevel(p.name); - } - } - - ChanServ::Privilege *FindPrivilege(const Anope::string &name) override - { - for (unsigned i = Privileges.size(); i > 0; --i) - if (Privileges[i - 1].name.equals_ci(name)) - return &Privileges[i - 1]; - return NULL; - } - - std::vector &GetPrivileges() override - { - return Privileges; - } - - void ClearPrivileges() override - { - Privileges.clear(); - } - - void OnReload(Configuration::Conf *conf) override - { - const Anope::string &channick = conf->GetModule(this)->Get("client"); - - if (channick.empty()) - throw ConfigException(Module::name + ": must be defined"); - - ServiceBot *bi = ServiceBot::Find(channick, true); - if (!bi) - throw ConfigException(Module::name + ": no bot named " + channick); - - ChanServ = bi; - - ClearPrivileges(); - for (int i = 0; i < conf->CountBlock("privilege"); ++i) - { - Configuration::Block *privilege = conf->GetBlock("privilege", i); - - const Anope::string &nname = privilege->Get("name"), - &desc = privilege->Get("desc"); - int rank = privilege->Get("rank"); - Anope::string value = privilege->Get("level"); - int level; - if (value.equals_ci("founder")) - level = ChanServ::ACCESS_FOUNDER; - else if (value.equals_ci("disabled")) - level = ChanServ::ACCESS_INVALID; - else - level = privilege->Get("level"); - - AddPrivilege(ChanServ::Privilege(nname, desc, rank, level)); - } - - spacesepstream(conf->GetModule(this)->Get("defaults", "greet fantasy")).GetTokens(defaults); - if (defaults.empty()) - { - defaults.push_back("KEEPTOPIC"); - defaults.push_back("CS_SECURE"); - defaults.push_back("SECUREFOUNDER"); - defaults.push_back("SIGNKICK"); - } - else if (defaults[0].equals_ci("none")) - defaults.clear(); - - always_lower = conf->GetModule(this)->Get("always_lower_ts"); - } - - void OnChannelCreate(Channel *c) override - { - c->ci = Find(c->name); - if (c->ci) - c->ci->c = c; - } - - void OnBotDelete(ServiceBot *bi) override - { - if (bi == ChanServ) - ChanServ = NULL; - } - - EventReturn OnBotPrivmsg(User *u, ServiceBot *bi, Anope::string &message) override - { - if (bi == ChanServ && Config->GetModule(this)->Get("opersonly") && !u->HasMode("OPER")) - { - u->SendMessage(bi, _("Access denied.")); - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } - - void OnDelCore(NickServ::Account *nc) override - { - unsigned int max_reg = Config->GetModule(this)->Get("maxregistered"); - for (ChanServ::Channel *ci : nc->GetRefs(ChanServ::channel)) - { - if (ci->GetFounder() == nc) - { - NickServ::Account *newowner = NULL; - if (ci->GetSuccessor() && ci->GetSuccessor() != nc && (ci->GetSuccessor()->IsServicesOper() || !max_reg || ci->GetSuccessor()->GetChannelCount() < max_reg)) - newowner = ci->GetSuccessor(); - else - { - ChanServ::ChanAccess *highest = NULL; - for (unsigned j = 0; j < ci->GetAccessCount(); ++j) - { - ChanServ::ChanAccess *ca = ci->GetAccess(j); - NickServ::Account *anc = ca->GetAccount(); - - if (!anc || (!anc->IsServicesOper() && max_reg && anc->GetChannelCount() >= max_reg) || (anc == nc)) - continue; - if (!highest || *ca > *highest) - highest = ca; - } - if (highest) - newowner = highest->GetAccount(); - } - - if (newowner) - { - ::Log(LOG_NORMAL, "chanserv/drop", ChanServ) << "Transferring foundership of " << ci->GetName() << " from deleted nick " << nc->GetDisplay() << " to " << newowner->GetDisplay(); - ci->SetFounder(newowner); - ci->SetSuccessor(NULL); - } - else - { - ::Log(LOG_NORMAL, "chanserv/drop", ChanServ) << "Deleting channel " << ci->GetName() << " owned by deleted nick " << nc->GetDisplay(); - - delete ci; - continue; - } - } - - if (ci->GetSuccessor() == nc) - ci->SetSuccessor(NULL); - - /* are these necessary? */ - for (unsigned j = 0; j < ci->GetAccessCount(); ++j) - { - ChanServ::ChanAccess *ca = ci->GetAccess(j); - NickServ::Account *anc = ca->GetAccount(); - - if (anc && anc == nc) - { - delete ca; - break; - } - } - - for (unsigned j = 0; j < ci->GetAkickCount(); ++j) - { - AutoKick *ak = ci->GetAkick(j); - if (ak->GetAccount() == nc) - { - delete ak; - break; - } - } - } - } - - void OnDelChan(ChanServ::Channel *ci) override - { - /* remove access entries that are this channel */ - - for (ChanServ::Channel *c : ci->GetRefs(ChanServ::channel)) - { - for (unsigned j = 0; j < c->GetAccessCount(); ++j) - { - ChanServ::ChanAccess *a = c->GetAccess(j); - - if (a->Mask().equals_ci(ci->GetName())) - { - delete a; - break; - } - } - } - - if (ci->c) - { - ci->c->RemoveMode(ci->WhoSends(), "REGISTERED", "", false); - - const Anope::string &require = Config->GetModule(this)->Get("require"); - if (!require.empty()) - ci->c->SetModes(ci->WhoSends(), false, "-%s", require.c_str()); - } - } - - EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override - { - if (!params.empty() || source.c || source.service != *ChanServ) - return EVENT_CONTINUE; - source.Reply(_("\002%s\002 allows you to register and control various\n" - "aspects of channels. %s can often prevent\n" - "malicious users from \"taking over\" channels by limiting\n" - "who is allowed channel operator privileges. Available\n" - "commands are listed below; to use them, type\n" - "\002%s%s \037command\037\002. For more information on a\n" - "specific command, type \002%s%s HELP \037command\037\002.\n"), - ChanServ->nick.c_str(), ChanServ->nick.c_str(), Config->StrictPrivmsg.c_str(), ChanServ->nick.c_str(), Config->StrictPrivmsg.c_str(), ChanServ->nick.c_str(), ChanServ->nick.c_str(), source.command.c_str()); - return EVENT_CONTINUE; - } - - void OnPostHelp(CommandSource &source, const std::vector ¶ms) override - { - if (!params.empty() || source.c || source.service != *ChanServ) - return; - time_t chanserv_expire = Config->GetModule(this)->Get("expire", "14d"); - if (chanserv_expire >= 86400) - source.Reply(_(" \n" - "Note that any channel which is not used for %d days\n" - "(i.e. which no user on the channel's access list enters\n" - "for that period of time) will be automatically dropped."), chanserv_expire / 86400); - if (source.IsServicesOper()) - source.Reply(_(" \n" - "Services Operators can also, depending on their access drop\n" - "any channel, view (and modify) the access, levels and akick\n" - "lists and settings for any channel.")); - } - - void OnCheckModes(Reference &c) override - { - if (!c) - return; - - if (c->ci) - c->SetMode(c->ci->WhoSends(), "REGISTERED", "", false); - else - c->RemoveMode(c->ci->WhoSends(), "REGISTERED", "", false); - - const Anope::string &require = Config->GetModule(this)->Get("require"); - if (!require.empty()) - { - if (c->ci) - c->SetModes(c->ci->WhoSends(), false, "+%s", require.c_str()); - else - c->SetModes(c->ci->WhoSends(), false, "-%s", require.c_str()); - } - } - - void OnCreateChan(ChanServ::Channel *ci) override - { - /* Set default chan flags */ - for (unsigned i = 0; i < defaults.size(); ++i) - ci->SetS(defaults[i].upper(), true); - } - - EventReturn OnCanSet(User *u, const ChannelMode *cm) override - { - if (Config->GetModule(this)->Get("nomlock").find(cm->mchar) != Anope::string::npos - || Config->GetModule(this)->Get("require").find(cm->mchar) != Anope::string::npos) - return EVENT_STOP; - return EVENT_CONTINUE; - } - - void OnChannelSync(Channel *c) override - { - bool perm = c->HasMode("PERM") || (c->ci && persist && persist->HasExt(c->ci)); - if (!perm && !c->botchannel && (c->users.empty() || (c->users.size() == 1 && c->users.begin()->second->user->server == Me))) - { - this->Hold(c); - } - } - - void OnLog(::Log *l) override - { - if (l->type == LOG_CHANNEL) - l->bi = ChanServ; - } - - void OnExpireTick() override - { - time_t chanserv_expire = Config->GetModule(this)->Get("expire", "14d"); - - if (!chanserv_expire || Anope::NoExpire || Anope::ReadOnly) - return; - - for (ChanServ::Channel *ci : channel_type.List()) - { - bool expire = false; - - if (Anope::CurTime - ci->GetLastUsed() >= chanserv_expire) - { - if (ci->c) - { - time_t last_used = ci->GetLastUsed(); - for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end && last_used == ci->GetLastUsed(); ++cit) - ci->AccessFor(cit->second->user); - expire = last_used == ci->GetLastUsed(); - } - else - expire = true; - } - - this->OnPreChanExpire(&ChanServ::Event::PreChanExpire::OnPreChanExpire, ci, expire); - - if (expire) - { - ::Log(LOG_NORMAL, "chanserv/expire", ChanServ) << "Expiring channel " << ci->GetName() << " (founder: " << (ci->GetFounder() ? ci->GetFounder()->GetDisplay() : "(none)") << ")"; - this->OnChanExpire(&ChanServ::Event::ChanExpire::OnChanExpire, ci); - delete ci; - } - } - } - - EventReturn OnCheckDelete(Channel *c) override - { - /* Do not delete this channel if ChanServ/a BotServ bot is inhabiting it */ - if (inhabit.HasExt(c)) - return EVENT_STOP; - - return EVENT_CONTINUE; - } - - void OnPreUplinkSync(Server *serv) override - { - if (!persist) - return; - /* Find all persistent channels and create them, as we are about to finish burst to our uplink */ - for (ChanServ::Channel *ci : channel_type.List()) - { - if (persist->HasExt(ci)) - { - bool c; - ci->c = Channel::FindOrCreate(ci->GetName(), c, ci->GetTimeRegistered()); - - if (ModeManager::FindChannelModeByName("PERM") != NULL) - { - if (c) - IRCD->SendChannel(ci->c); - ci->c->SetMode(NULL, "PERM"); - } - else - { - if (!ci->GetBot()) - ci->WhoSends()->Assign(NULL, ci); - if (ci->c->FindUser(ci->GetBot()) == NULL) - { - ChannelStatus status(Config->GetModule("botserv")->Get("botmodes")); - ci->GetBot()->Join(ci->c, &status); - } - } - } - } - - } - - void OnChanRegistered(ChanServ::Channel *ci) override - { - if (!persist || !ci->c) - return; - /* Mark the channel as persistent */ - if (ci->c->HasMode("PERM")) - persist->Set(ci, true); - /* Persist may be in def cflags, set it here */ - else if (persist->HasExt(ci)) - ci->c->SetMode(NULL, "PERM"); - } - - void OnJoinChannel(User *u, Channel *c) override - { - if (always_lower && c->ci && c->creation_time > c->ci->GetTimeRegistered()) - { - ::Log(LOG_DEBUG) << "Changing TS of " << c->name << " from " << c->creation_time << " to " << c->ci->GetTimeRegistered(); - c->creation_time = c->ci->GetTimeRegistered(); - IRCD->SendChannel(c); - c->Reset(); - } - } - - EventReturn OnChannelModeSet(Channel *c, const MessageSource &setter, ChannelMode *mode, const Anope::string ¶m) override - { - if (!always_lower && Anope::CurTime == c->creation_time && c->ci && setter.GetUser() && !setter.GetUser()->server->IsULined()) - { - ChanUserContainer *cu = c->FindUser(setter.GetUser()); - ChannelMode *cm = ModeManager::FindChannelModeByName("OP"); - if (cu && cm && !cu->status.HasMode(cm->mchar)) - { - /* Our -o and their mode change crossing, bounce their mode */ - c->RemoveMode(c->ci->WhoSends(), mode, param); - /* We don't set mlocks until after the join has finished processing, it will stack with this change, - * so there isn't much for the user to remove except -nt etc which is likely locked anyway. - */ - } - } - - return EVENT_CONTINUE; - } - - void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override - { - if (!show_all) - return; - - time_t chanserv_expire = Config->GetModule(this)->Get("expire", "14d"); - if (!ci->HasFieldS("CS_NO_EXPIRE") && chanserv_expire && !Anope::NoExpire && ci->GetLastUsed() != Anope::CurTime) - info[_("Expires")] = Anope::strftime(ci->GetLastUsed() + chanserv_expire, source.GetAccount()); - } - - void OnSetCorrectModes(User *user, Channel *chan, ChanServ::AccessGroup &access, bool &give_modes, bool &take_modes) override - { - if (always_lower) - // Since we always lower the TS, the other side will remove the modes if the channel ts lowers, so we don't - // have to worry about it - take_modes = false; - else if (ModeManager::FindChannelModeByName("REGISTERED")) - // Otherwise if the registered channel mode exists, we should remove modes if the channel is not +r - take_modes = !chan->HasMode("REGISTERED"); - } -}; - -MODULE_INIT(ChanServCore) - diff --git a/modules/pseudoclients/chanserv/level.cpp b/modules/pseudoclients/chanserv/level.cpp deleted file mode 100644 index d31c98d64..000000000 --- a/modules/pseudoclients/chanserv/level.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "modules/chanserv.h" -#include "leveltype.h" - -ChanServ::Channel *LevelImpl::GetChannel() -{ - return Get(&LevelType::channel); -} - -void LevelImpl::SetChannel(ChanServ::Channel *c) -{ - Set(&LevelType::channel, c); -} - -Anope::string LevelImpl::GetName() -{ - return Get(&LevelType::name); -} - -void LevelImpl::SetName(const Anope::string &n) -{ - Set(&LevelType::name, n); -} - -int LevelImpl::GetLevel() -{ - return Get(&LevelType::level); -} - -void LevelImpl::SetLevel(const int &i) -{ - Set(&LevelType::level, i); -} - diff --git a/modules/pseudoclients/chanserv/level.h b/modules/pseudoclients/chanserv/level.h deleted file mode 100644 index c9ffd0dfb..000000000 --- a/modules/pseudoclients/chanserv/level.h +++ /dev/null @@ -1,16 +0,0 @@ - -class LevelImpl : public ChanServ::Level -{ - public: - LevelImpl(Serialize::TypeBase *type) : ChanServ::Level(type) { } - LevelImpl(Serialize::TypeBase *type, Serialize::ID id) : ChanServ::Level(type, id) { } - - ChanServ::Channel *GetChannel(); - void SetChannel(ChanServ::Channel *); - - Anope::string GetName(); - void SetName(const Anope::string &); - - int GetLevel(); - void SetLevel(const int &); -}; diff --git a/modules/pseudoclients/chanserv/leveltype.h b/modules/pseudoclients/chanserv/leveltype.h deleted file mode 100644 index 12e4c132f..000000000 --- a/modules/pseudoclients/chanserv/leveltype.h +++ /dev/null @@ -1,16 +0,0 @@ -#include "level.h" - -class LevelType : public Serialize::Type -{ - public: - Serialize::ObjectField channel; - Serialize::Field name; - Serialize::Field level; - - LevelType(Module *creator) : Serialize::Type(creator, "Level") - , channel(this, "channel", true) - , name(this, "name") - , level(this, "level") - { - } -}; diff --git a/modules/pseudoclients/chanserv/mode.cpp b/modules/pseudoclients/chanserv/mode.cpp deleted file mode 100644 index 066e2cfd5..000000000 --- a/modules/pseudoclients/chanserv/mode.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "modules/chanserv.h" -#include "modetype.h" - -ChanServ::Channel *ModeImpl::GetChannel() -{ - return Get(&CSModeType::channel); -} - -void ModeImpl::SetChannel(ChanServ::Channel *c) -{ - Set(&CSModeType::channel, c); -} - -Anope::string ModeImpl::GetMode() -{ - return Get(&CSModeType::mode); -} - -void ModeImpl::SetMode(const Anope::string &m) -{ - Set(&CSModeType::mode, m); -} - -Anope::string ModeImpl::GetParam() -{ - return Get(&CSModeType::param); -} - -void ModeImpl::SetParam(const Anope::string &p) -{ - Set(&CSModeType::param, p); -} - diff --git a/modules/pseudoclients/chanserv/mode.h b/modules/pseudoclients/chanserv/mode.h deleted file mode 100644 index cd94e5ad1..000000000 --- a/modules/pseudoclients/chanserv/mode.h +++ /dev/null @@ -1,17 +0,0 @@ - -class ModeImpl : public ChanServ::Mode -{ - public: - ModeImpl(Serialize::TypeBase *type) : ChanServ::Mode(type) { } - ModeImpl(Serialize::TypeBase *type, Serialize::ID id) : ChanServ::Mode(type, id) { } - - ChanServ::Channel *GetChannel() override; - void SetChannel(ChanServ::Channel *) override; - - Anope::string GetMode() override; - void SetMode(const Anope::string &) override; - - Anope::string GetParam() override; - void SetParam(const Anope::string &) override; -}; - diff --git a/modules/pseudoclients/chanserv/modetype.h b/modules/pseudoclients/chanserv/modetype.h deleted file mode 100644 index b30c0491f..000000000 --- a/modules/pseudoclients/chanserv/modetype.h +++ /dev/null @@ -1,15 +0,0 @@ -#include "mode.h" - -class CSModeType : public Serialize::Type -{ - public: - Serialize::ObjectField channel; - Serialize::Field mode, param; - - CSModeType(Module *creator) : Serialize::Type(creator, "CSKeepMode") - , channel(this, "channel", true) - , mode(this, "mode") - , param(this, "param") - { - } -}; diff --git a/modules/pseudoclients/global.cpp b/modules/pseudoclients/global.cpp deleted file mode 100644 index f31605acb..000000000 --- a/modules/pseudoclients/global.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* Global core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/help.h" -#include "modules/global.h" - -class GlobalCore : public Module - , public Global::GlobalService - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - Reference Global; - - void ServerGlobal(ServiceBot *sender, Server *s, const Anope::string &message) - { - if (s != Me && !s->IsJuped()) - s->Notice(sender, message); - for (unsigned i = 0, j = s->GetLinks().size(); i < j; ++i) - this->ServerGlobal(sender, s->GetLinks()[i], message); - } - - public: - GlobalCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) - , GlobalService(this) - { - } - - void SendGlobal(ServiceBot *sender, const Anope::string &source, const Anope::string &message) override - { - if (Me->GetLinks().empty()) - return; - if (!sender) - sender = Global; - if (!sender) - return; - - Anope::string rmessage; - - if (!source.empty() && !Config->GetModule("global")->Get("anonymousglobal")) - rmessage = "[" + source + "] " + message; - else - rmessage = message; - - this->ServerGlobal(sender, Servers::GetUplink(), rmessage); - } - - void OnReload(Configuration::Conf *conf) override - { - const Anope::string &glnick = conf->GetModule(this)->Get("client"); - - if (glnick.empty()) - throw ConfigException(Module::name + ": must be defined"); - - ServiceBot *bi = ServiceBot::Find(glnick, true); - if (!bi) - throw ConfigException(Module::name + ": no bot named " + glnick); - - Global = bi; - } - - void OnRestart() override - { - const Anope::string &gl = Config->GetModule(this)->Get("globaloncycledown"); - if (!gl.empty()) - this->SendGlobal(Global, "", gl); - } - - void OnShutdown() override - { - const Anope::string &gl = Config->GetModule(this)->Get("globaloncycledown"); - if (!gl.empty()) - this->SendGlobal(Global, "", gl); - } - - void OnNewServer(Server *s) override - { - const Anope::string &gl = Config->GetModule(this)->Get("globaloncycleup"); - if (!gl.empty() && !Me->IsSynced()) - s->Notice(Global, gl); - } - - EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override - { - if (!params.empty() || source.c || source.service != *Global) - return EVENT_CONTINUE; - source.Reply(_("%s commands:"), Global->nick.c_str()); - return EVENT_CONTINUE; - } - - void OnPostHelp(CommandSource &source, const std::vector ¶ms) override - { - } -}; - -MODULE_INIT(GlobalCore) - diff --git a/modules/pseudoclients/hostserv.cpp b/modules/pseudoclients/hostserv.cpp deleted file mode 100644 index 285ea893b..000000000 --- a/modules/pseudoclients/hostserv.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* HostServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_update.h" -#include "modules/hs_del.h" -#include "modules/help.h" - -class HostServCore : public Module - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - Reference HostServ; - - public: - HostServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) - { - if (!IRCD || !IRCD->CanSetVHost) - throw ModuleException("Your IRCd does not support vhosts"); - } - - void OnReload(Configuration::Conf *conf) override - { - const Anope::string &hsnick = conf->GetModule(this)->Get("client"); - - if (hsnick.empty()) - throw ConfigException(Module::name + ": must be defined"); - - ServiceBot *bi = ServiceBot::Find(hsnick, true); - if (!bi) - throw ConfigException(Module::name + ": no bot named " + hsnick); - - HostServ = bi; - } - - void OnUserLogin(User *u) override - { - if (!IRCD->CanSetVHost) - return; - - NickServ::Nick *na = NickServ::FindNick(u->nick); - if (!na || na->GetAccount() != u->Account() || !na->HasVhost()) - na = NickServ::FindNick(u->Account()->GetDisplay()); - if (!na || !na->HasVhost()) - return; - - if (u->vhost.empty() || !u->vhost.equals_cs(na->GetVhostHost()) || (!na->GetVhostIdent().empty() && !u->GetVIdent().equals_cs(na->GetVhostIdent()))) - { - IRCD->SendVhost(u, na->GetVhostIdent(), na->GetVhostHost()); - - u->vhost = na->GetVhostHost(); - u->UpdateHost(); - - if (IRCD->CanSetVIdent && !na->GetVhostIdent().empty()) - u->SetVIdent(na->GetVhostIdent()); - - if (HostServ) - { - if (!na->GetVhostIdent().empty()) - u->SendMessage(*HostServ, _("Your vhost of \002%s\002@\002%s\002 is now activated."), na->GetVhostIdent().c_str(), na->GetVhostHost().c_str()); - else - u->SendMessage(*HostServ, _("Your vhost of \002%s\002 is now activated."), na->GetVhostHost().c_str()); - } - } - } - - void OnNickUpdate(User *u) override - { - this->OnUserLogin(u); - } - - EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override - { - if (!params.empty() || source.c || source.service != *HostServ) - return EVENT_CONTINUE; - source.Reply(_("%s commands:"), HostServ->nick.c_str()); - return EVENT_CONTINUE; - } - - void OnPostHelp(CommandSource &source, const std::vector ¶ms) override - { - } - - void OnSetVhost(NickServ::Nick *na) override - { - if (Config->GetModule(this)->Get("activate_on_set")) - { - User *u = User::Find(na->GetNick()); - - if (u && u->Account() == na->GetAccount()) - { - IRCD->SendVhost(u, na->GetVhostIdent(), na->GetVhostHost()); - - u->vhost = na->GetVhostHost(); - u->UpdateHost(); - - if (IRCD->CanSetVIdent && !na->GetVhostIdent().empty()) - u->SetVIdent(na->GetVhostIdent()); - - if (HostServ) - { - if (!na->GetVhostIdent().empty()) - u->SendMessage(*HostServ, _("Your vhost of \002%s\002@\002%s\002 is now activated."), na->GetVhostIdent().c_str(), na->GetVhostHost().c_str()); - else - u->SendMessage(*HostServ, _("Your vhost of \002%s\002 is now activated."), na->GetVhostHost().c_str()); - } - } - } - } - - void OnDeleteVhost(NickServ::Nick *na) override - { - if (Config->GetModule(this)->Get("activate_on_set")) - { - User *u = User::Find(na->GetNick()); - - if (u && u->Account() == na->GetAccount()) - IRCD->SendVhostDel(u); - } - } -}; - -MODULE_INIT(HostServCore) - diff --git a/modules/pseudoclients/memoserv/CMakeLists.txt b/modules/pseudoclients/memoserv/CMakeLists.txt deleted file mode 100644 index 781f0ef1f..000000000 --- a/modules/pseudoclients/memoserv/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/pseudoclients/memoserv/ignore.cpp b/modules/pseudoclients/memoserv/ignore.cpp deleted file mode 100644 index e85011c00..000000000 --- a/modules/pseudoclients/memoserv/ignore.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "memo.h" -#include "ignoretype.h" - -IgnoreImpl::~IgnoreImpl() -{ -} - -MemoServ::MemoInfo *IgnoreImpl::GetMemoInfo() -{ - return Get(&IgnoreType::mi); -} - -void IgnoreImpl::SetMemoInfo(MemoServ::MemoInfo *m) -{ - Set(&IgnoreType::mi, m); -} - -Anope::string IgnoreImpl::GetMask() -{ - return Get(&IgnoreType::mask); -} - -void IgnoreImpl::SetMask(const Anope::string &mask) -{ - Set(&IgnoreType::mask, mask); -} - diff --git a/modules/pseudoclients/memoserv/ignore.h b/modules/pseudoclients/memoserv/ignore.h deleted file mode 100644 index c98b44adf..000000000 --- a/modules/pseudoclients/memoserv/ignore.h +++ /dev/null @@ -1,15 +0,0 @@ -#include "modules/memoserv.h" - -class IgnoreImpl : public MemoServ::Ignore -{ - public: - IgnoreImpl(Serialize::TypeBase *type) : MemoServ::Ignore(type) { } - IgnoreImpl(Serialize::TypeBase *type, Serialize::ID id) : MemoServ::Ignore(type, id) { } - ~IgnoreImpl(); - - MemoServ::MemoInfo *GetMemoInfo() override; - void SetMemoInfo(MemoServ::MemoInfo *) override; - - Anope::string GetMask() override; - void SetMask(const Anope::string &mask) override; -}; diff --git a/modules/pseudoclients/memoserv/ignoretype.cpp b/modules/pseudoclients/memoserv/ignoretype.cpp deleted file mode 100644 index 375f83272..000000000 --- a/modules/pseudoclients/memoserv/ignoretype.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "module.h" -#include "ignoretype.h" - -IgnoreType::IgnoreType(Module *me) : Serialize::Type(me, "MemoIgnore") - , mi(this, "mi", true) - , mask(this, "mask") -{ - -} - diff --git a/modules/pseudoclients/memoserv/ignoretype.h b/modules/pseudoclients/memoserv/ignoretype.h deleted file mode 100644 index 5c656f9b8..000000000 --- a/modules/pseudoclients/memoserv/ignoretype.h +++ /dev/null @@ -1,10 +0,0 @@ -#include "ignore.h" - -class IgnoreType : public Serialize::Type -{ - public: - Serialize::ObjectField mi; - Serialize::Field mask; - - IgnoreType(Module *); -}; diff --git a/modules/pseudoclients/memoserv/memo.cpp b/modules/pseudoclients/memoserv/memo.cpp deleted file mode 100644 index bfae64284..000000000 --- a/modules/pseudoclients/memoserv/memo.cpp +++ /dev/null @@ -1,72 +0,0 @@ -//#include "memo.h" -#include "memotype.h" - -/*MemoImpl::MemoImpl() -{ - //unread = receipt = false; -}*/ - -MemoImpl::~MemoImpl() -{ -} - -MemoServ::MemoInfo *MemoImpl::GetMemoInfo() -{ - return Get(&MemoType::mi); -} - -void MemoImpl::SetMemoInfo(MemoServ::MemoInfo *mi) -{ - Set(&MemoType::mi, mi); -} - -time_t MemoImpl::GetTime() -{ - return Get(&MemoType::time); -} - -void MemoImpl::SetTime(const time_t &t) -{ - Set(&MemoType::time, t); -} - -Anope::string MemoImpl::GetSender() -{ - return Get(&MemoType::sender); -} - -void MemoImpl::SetSender(const Anope::string &s) -{ - Set(&MemoType::sender, s); -} - -Anope::string MemoImpl::GetText() -{ - return Get(&MemoType::text); -} - -void MemoImpl::SetText(const Anope::string &t) -{ - Set(&MemoType::text, t); -} - -bool MemoImpl::GetUnread() -{ - return Get(&MemoType::unread); -} - -void MemoImpl::SetUnread(const bool &b) -{ - Set(&MemoType::unread, b); -} - -bool MemoImpl::GetReceipt() -{ - return Get(&MemoType::receipt); -} - -void MemoImpl::SetReceipt(const bool &b) -{ - Set(&MemoType::receipt, b); -} - diff --git a/modules/pseudoclients/memoserv/memo.h b/modules/pseudoclients/memoserv/memo.h deleted file mode 100644 index 1a230c1e9..000000000 --- a/modules/pseudoclients/memoserv/memo.h +++ /dev/null @@ -1,29 +0,0 @@ -#include "modules/memoserv.h" - -class MemoImpl : public MemoServ::Memo -{ - public: - MemoImpl(Serialize::TypeBase *type) : MemoServ::Memo(type) { } - MemoImpl(Serialize::TypeBase *type, Serialize::ID id) : MemoServ::Memo(type, id) { } - //using MemoServ::Memo::Memo; -// MemoImpl(); - ~MemoImpl(); - - MemoServ::MemoInfo *GetMemoInfo() override; - void SetMemoInfo(MemoServ::MemoInfo *) override; - - time_t GetTime() override; - void SetTime(const time_t &) override; - - Anope::string GetSender() override; - void SetSender(const Anope::string &) override; - - Anope::string GetText() override; - void SetText(const Anope::string &) override; - - bool GetUnread() override; - void SetUnread(const bool &) override; - - bool GetReceipt() override; - void SetReceipt(const bool &) override; -}; diff --git a/modules/pseudoclients/memoserv/memoinfo.cpp b/modules/pseudoclients/memoserv/memoinfo.cpp deleted file mode 100644 index 45d95533e..000000000 --- a/modules/pseudoclients/memoserv/memoinfo.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "memoinfotype.h" - -MemoServ::Memo *MemoInfoImpl::GetMemo(unsigned index) -{ - auto memos = GetMemos(); - return index >= memos.size() ? nullptr : memos[index]; -} - -unsigned MemoInfoImpl::GetIndex(MemoServ::Memo *m) -{ - auto memos = GetMemos(); - for (unsigned i = 0; i < memos.size(); ++i) - if (this->GetMemo(i) == m) - return i; - return -1; // XXX wtf? -} - -void MemoInfoImpl::Del(unsigned index) -{ - delete GetMemo(index); -} - -bool MemoInfoImpl::HasIgnore(User *u) -{ - for (MemoServ::Ignore *ign : GetIgnores()) - { - const Anope::string &mask = ign->GetMask(); - if (u->nick.equals_ci(mask) || (u->Account() && u->Account()->GetDisplay().equals_ci(mask)) || Anope::Match(u->GetMask(), mask)) - return true; - } - return false; -} - -Serialize::Object *MemoInfoImpl::GetOwner() -{ - return Get(&MemoInfoType::owner); -} - -void MemoInfoImpl::SetOwner(Serialize::Object *o) -{ - Set(&MemoInfoType::owner, o); -} - -int16_t MemoInfoImpl::GetMemoMax() -{ - return Get(&MemoInfoType::memomax); -} - -void MemoInfoImpl::SetMemoMax(const int16_t &i) -{ - Set(&MemoInfoType::memomax, i); -} - -std::vector MemoInfoImpl::GetMemos() -{ - return GetRefs(MemoServ::memo); -} - -std::vector MemoInfoImpl::GetIgnores() -{ - return GetRefs(MemoServ::ignore); -} - diff --git a/modules/pseudoclients/memoserv/memoinfo.h b/modules/pseudoclients/memoserv/memoinfo.h deleted file mode 100644 index 22ea0734f..000000000 --- a/modules/pseudoclients/memoserv/memoinfo.h +++ /dev/null @@ -1,23 +0,0 @@ -#include "modules/memoserv.h" - -class MemoInfoImpl : public MemoServ::MemoInfo -{ - public: - MemoInfoImpl(Serialize::TypeBase *type) : MemoServ::MemoInfo(type) { } - MemoInfoImpl(Serialize::TypeBase *type, Serialize::ID id) : MemoServ::MemoInfo(type, id) { } - - MemoServ::Memo *GetMemo(unsigned index) override; - unsigned GetIndex(MemoServ::Memo *m) override; - void Del(unsigned index) override; - bool HasIgnore(User *u) override; - - Serialize::Object *GetOwner() override; - void SetOwner(Serialize::Object *) override; - - int16_t GetMemoMax() override; - void SetMemoMax(const int16_t &) override; - - std::vector GetMemos() override; - std::vector GetIgnores() override; -}; - diff --git a/modules/pseudoclients/memoserv/memoinfotype.cpp b/modules/pseudoclients/memoserv/memoinfotype.cpp deleted file mode 100644 index 7eb3af72c..000000000 --- a/modules/pseudoclients/memoserv/memoinfotype.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "module.h" -#include "memoinfotype.h" - -MemoInfoType::MemoInfoType(Module *me) : Serialize::Type(me, "MemoInfo") - , owner(this, "owner", true) - , memomax(this, "memomax") -{ - -} - diff --git a/modules/pseudoclients/memoserv/memoinfotype.h b/modules/pseudoclients/memoserv/memoinfotype.h deleted file mode 100644 index 724a898b5..000000000 --- a/modules/pseudoclients/memoserv/memoinfotype.h +++ /dev/null @@ -1,10 +0,0 @@ -#include "memoinfo.h" - -class MemoInfoType : public Serialize::Type -{ - public: - Serialize::ObjectField owner; - Serialize::Field memomax; - - MemoInfoType(Module *); -}; diff --git a/modules/pseudoclients/memoserv/memoserv.cpp b/modules/pseudoclients/memoserv/memoserv.cpp deleted file mode 100644 index a52ee746c..000000000 --- a/modules/pseudoclients/memoserv/memoserv.cpp +++ /dev/null @@ -1,298 +0,0 @@ -/* MemoServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_update.h" -#include "modules/help.h" -#include "modules/bs_bot.h" -#include "modules/memoserv.h" -//#include "memoinfo.h" -//#include "memo.h" -//#include "ignore.h" -#include "memotype.h" -#include "memoinfotype.h" -#include "ignoretype.h" - -class MemoServCore : public Module, public MemoServ::MemoServService - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - Reference MemoServ; - EventHandlers onmemosend; - EventHandlers onmemodel; - - MemoInfoType memoinfo_type; - MemoType memo_type; - IgnoreType ignore_type; - - bool SendMemoMail(NickServ::Account *nc, MemoServ::MemoInfo *mi, MemoServ::Memo *m) - { - Anope::string subject = Language::Translate(nc, Config->GetBlock("mail")->Get("memo_subject").c_str()), - message = Language::Translate(Config->GetBlock("mail")->Get("memo_message").c_str()); - - subject = subject.replace_all_cs("%n", nc->GetDisplay()); - subject = subject.replace_all_cs("%s", m->GetSender()); - subject = subject.replace_all_cs("%d", stringify(mi->GetIndex(m) + 1)); - subject = subject.replace_all_cs("%t", m->GetText()); - subject = subject.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); - - message = message.replace_all_cs("%n", nc->GetDisplay()); - message = message.replace_all_cs("%s", m->GetSender()); - message = message.replace_all_cs("%d", stringify(mi->GetIndex(m) + 1)); - message = message.replace_all_cs("%t", m->GetText()); - message = message.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); - - return Mail::Send(nc, subject, message); - } - - public: - MemoServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) - , MemoServ::MemoServService(this) - , onmemosend(this) - , onmemodel(this) - , memoinfo_type(this) - , memo_type(this) - , ignore_type(this) - { - } - - MemoResult Send(const Anope::string &source, const Anope::string &target, const Anope::string &message, bool force) override - { - bool ischan, isregistered; - MemoServ::MemoInfo *mi = GetMemoInfo(target, ischan, isregistered, true); - - if (mi == NULL) - return MEMO_INVALID_TARGET; - - User *sender = User::Find(source); - if (sender != NULL && !sender->HasPriv("memoserv/no-limit") && !force) - { - time_t send_delay = Config->GetModule("memoserv")->Get("senddelay"); - if (send_delay > 0 && sender->lastmemosend + send_delay > Anope::CurTime) - return MEMO_TOO_FAST; - else if (!mi->GetMemoMax()) - return MEMO_TARGET_FULL; - else if (mi->GetMemoMax() > 0 && mi->GetMemos().size() >= static_cast(mi->GetMemoMax())) - return MEMO_TARGET_FULL; - else if (mi->HasIgnore(sender)) - return MEMO_SUCCESS; - } - - if (sender != NULL) - sender->lastmemosend = Anope::CurTime; - - MemoServ::Memo *m = new MemoImpl(&memo_type); - m->SetMemoInfo(mi); - m->SetSender(source); - m->SetTime(Anope::CurTime); - m->SetText(message); - m->SetUnread(true); - - this->onmemosend(&MemoServ::Event::MemoSend::OnMemoSend, source, target, mi, m); - - if (ischan) - { - ChanServ::Channel *ci = ChanServ::Find(target); - - if (ci->c) - { - for (Channel::ChanUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) - { - ChanUserContainer *cu = it->second; - - if (ci->AccessFor(cu->user).HasPriv("MEMO")) - { - if (cu->user->Account() && cu->user->Account()->HasFieldS("MEMO_RECEIVE")) - cu->user->SendMessage(*MemoServ, _("There is a new memo on channel \002{0}\002. Type \002{1}{2} READ {3} {4}\002 to read it."), ci->GetName(), Config->StrictPrivmsg, MemoServ->nick, ci->GetName(), mi->GetMemos().size()); // XXX - } - } - } - } - else - { - NickServ::Account *nc = NickServ::FindNick(target)->GetAccount(); - - if (nc->HasFieldS("MEMO_RECEIVE")) - for (User *u : nc->users) - u->SendMessage(*MemoServ, _("You have a new memo from \002{0}\002. Type \002{1}{2} READ {3}\002 to read it."), source, Config->StrictPrivmsg, MemoServ->nick, mi->GetMemos().size());//XXX - - /* let's get out the mail if set in the nickcore - certus */ - if (nc->HasFieldS("MEMO_MAIL")) - SendMemoMail(nc, mi, m); - } - - return MEMO_SUCCESS; - } - - void Check(User *u) override - { - NickServ::Account *nc = u->Account(); - if (!nc || !nc->GetMemos()) - return; - - auto memos = nc->GetMemos()->GetMemos(); - unsigned i = 0, end = memos.size(), newcnt = 0; - for (; i < end; ++i) - if (memos[i]->GetUnread()) - ++newcnt; - if (newcnt > 0) - u->SendMessage(*MemoServ, newcnt == 1 ? _("You have 1 new memo.") : _("You have %d new memos."), newcnt); - if (nc->GetMemos()->GetMemoMax() > 0 && memos.size() >= static_cast(nc->GetMemos()->GetMemoMax())) - { - if (memos.size() > static_cast(nc->GetMemos()->GetMemoMax())) - u->SendMessage(*MemoServ, _("You are over your maximum number of memos (%d). You will be unable to receive any new memos until you delete some of your current ones."), nc->GetMemos()->GetMemoMax()); - else - u->SendMessage(*MemoServ, _("You have reached your maximum number of memos (%d). You will be unable to receive any new memos until you delete some of your current ones."), nc->GetMemos()->GetMemoMax()); - } - } - - MemoServ::Memo *CreateMemo() override - { - return new MemoImpl(&memo_type); - } - - MemoServ::Ignore *CreateIgnore() override - { - return new IgnoreImpl(&ignore_type); - } - - MemoServ::MemoInfo *GetMemoInfo(const Anope::string &target, bool &is_registered, bool &ischan, bool create) override - { - if (!target.empty() && target[0] == '#') - { - ischan = true; - ChanServ::Channel *ci = ChanServ::Find(target); - if (ci != NULL) - { - is_registered = true; - if (create && !ci->GetMemos()) - { - MemoServ::MemoInfo *mi = new MemoInfoImpl(&memoinfo_type); - mi->SetOwner(ci); - } - return ci->GetMemos(); - } - else - is_registered = false; - } - else - { - ischan = false; - NickServ::Nick *na = NickServ::FindNick(target); - if (na != NULL) - { - is_registered = true; - if (create && !na->GetAccount()->GetMemos()) - { - MemoServ::MemoInfo *mi = new MemoInfoImpl(&memoinfo_type); - mi->SetOwner(na->GetAccount()); - } - return na->GetAccount()->GetMemos(); - } - else - is_registered = false; - } - - return NULL; - } - - void OnReload(Configuration::Conf *conf) override - { - const Anope::string &msnick = conf->GetModule(this)->Get("client"); - - if (msnick.empty()) - throw ConfigException(Module::name + ": must be defined"); - - ServiceBot *bi = ServiceBot::Find(msnick, true); - if (!bi) - throw ConfigException(Module::name + ": no bot named " + msnick); - - MemoServ = bi; - } - - void OnNickCoreCreate(NickServ::Account *nc) override - { - MemoServ::MemoInfo *mi = new MemoInfoImpl(&memoinfo_type); - mi->SetOwner(mi); - mi->SetMemoMax(Config->GetModule(this)->Get("maxmemos")); - } - - void OnCreateChan(ChanServ::Channel *ci) override - { - MemoServ::MemoInfo *mi = new MemoInfoImpl(&memoinfo_type); - mi->SetOwner(ci); - mi->SetMemoMax(Config->GetModule(this)->Get("maxmemos")); - } - - void OnBotDelete(ServiceBot *bi) override - { - if (bi == MemoServ) - MemoServ = NULL; - } - - void OnNickIdentify(User *u) override - { - this->Check(u); - } - - void OnJoinChannel(User *u, Channel *c) override - { - if (u->server && u->server->IsSynced() && c->ci && c->ci->GetMemos() && !c->ci->GetMemos()->GetMemos().empty() && c->ci->AccessFor(u).HasPriv("MEMO")) - { - if (c->ci->GetMemos()->GetMemos().size() == 1) - u->SendMessage(*MemoServ, _("There is \002{0}\002 memo on channel \002{1}\002."), c->ci->GetMemos()->GetMemos().size(), c->ci->GetName()); - else - u->SendMessage(*MemoServ, _("There are \002{0}\002 memos on channel \002{1}\002."), c->ci->GetMemos()->GetMemos().size(), c->ci->GetName()); - } - } - - void OnUserAway(User *u, const Anope::string &message) override - { - if (message.empty()) - this->Check(u); - } - - void OnNickUpdate(User *u) override - { - this->Check(u); - } - - EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override - { - if (!params.empty() || source.c || source.service != *MemoServ) - return EVENT_CONTINUE; - source.Reply(_("\002%s\002 is a utility allowing IRC users to send short\n" - "messages to other IRC users, whether they are online at\n" - "the time or not, or to channels(*). Both the sender's\n" - "nickname and the target nickname or channel must be\n" - "registered in order to send a memo.\n" - "%s's commands include:"), MemoServ->nick.c_str(), MemoServ->nick.c_str()); - return EVENT_CONTINUE; - } - - void OnPostHelp(CommandSource &source, const std::vector ¶ms) override - { - if (!params.empty() || source.c || source.service != *MemoServ) - return; - source.Reply(_(" \n" - "Type \002%s%s HELP \037command\037\002 for help on any of the\n" - "above commands."), Config->StrictPrivmsg.c_str(), MemoServ->nick.c_str()); - } -}; - -MODULE_INIT(MemoServCore) - diff --git a/modules/pseudoclients/memoserv/memotype.cpp b/modules/pseudoclients/memoserv/memotype.cpp deleted file mode 100644 index c9144bbaa..000000000 --- a/modules/pseudoclients/memoserv/memotype.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "module.h" -#include "memotype.h" - -MemoType::MemoType(Module *me) : Serialize::Type(me, "Memo") - , mi(this, "mi", true) - , time(this, "time") - , sender(this, "sender") - , text(this, "text") - , unread(this, "unread") - , receipt(this, "receipt") -{ - -} - diff --git a/modules/pseudoclients/memoserv/memotype.h b/modules/pseudoclients/memoserv/memotype.h deleted file mode 100644 index 16ac6f082..000000000 --- a/modules/pseudoclients/memoserv/memotype.h +++ /dev/null @@ -1,13 +0,0 @@ -#include "memo.h" - -class MemoType : public Serialize::Type -{ - public: - Serialize::ObjectField mi; - Serialize::Field time; - Serialize::Field sender; - Serialize::Field text; - Serialize::Field unread, receipt; - - MemoType(Module *); -}; diff --git a/modules/pseudoclients/nickserv/CMakeLists.txt b/modules/pseudoclients/nickserv/CMakeLists.txt deleted file mode 100644 index 781f0ef1f..000000000 --- a/modules/pseudoclients/nickserv/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/pseudoclients/nickserv/account.cpp b/modules/pseudoclients/nickserv/account.cpp deleted file mode 100644 index 85adf1a33..000000000 --- a/modules/pseudoclients/nickserv/account.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - * - */ - -#include "module.h" -#include "accounttype.h" -#include "modules/ns_access.h" - -AccountImpl::~AccountImpl() -{ - NickServ::nickcore_map& map = NickServ::service->GetAccountMap(); - map.erase(this->GetDisplay()); -} - -void AccountImpl::Delete() -{ - Event::OnDelCore(&Event::DelCore::OnDelCore, this); - - for (unsigned i = users.size(); i > 0; --i) - users[i - 1]->Logout(); - - return Serialize::Object::Delete(); -} - -Anope::string AccountImpl::GetDisplay() -{ - return Get(&AccountType::display); -} - -void AccountImpl::SetDisplay(const Anope::string &disp) -{ - Set(&AccountType::display, disp); -} - -Anope::string AccountImpl::GetPassword() -{ - return Get(&AccountType::pass); -} - -void AccountImpl::SetPassword(const Anope::string &pass) -{ - Set(&AccountType::pass, pass); -} - -Anope::string AccountImpl::GetEmail() -{ - return Get(&AccountType::email); -} - -void AccountImpl::SetEmail(const Anope::string &email) -{ - Set(&AccountType::email, email); -} - -Anope::string AccountImpl::GetLanguage() -{ - return Get(&AccountType::language); -} - -void AccountImpl::SetLanguage(const Anope::string &lang) -{ - Set(&AccountType::language, lang); -} - -MemoServ::MemoInfo *AccountImpl::GetMemos() -{ - return GetRef(MemoServ::memoinfo); -} - -void AccountImpl::SetDisplay(NickServ::Nick *na) -{ - if (na->GetAccount() != this || na->GetNick() == this->GetDisplay()) - return; - - Event::OnChangeCoreDisplay(&Event::ChangeCoreDisplay::OnChangeCoreDisplay, this, na->GetNick()); - - NickServ::nickcore_map& map = NickServ::service->GetAccountMap(); - - /* Remove the core from the list */ - map.erase(this->GetDisplay()); - - this->SetDisplay(na->GetNick()); - - NickServ::Account* &nc = map[this->GetDisplay()]; - if (nc) - Log(LOG_DEBUG) << "Duplicate account " << this->GetDisplay() << " in nickcore table?"; - - nc = this; -} - -bool AccountImpl::IsServicesOper() const -{ - return this->o != NULL; -} - -bool AccountImpl::IsOnAccess(User *u) -{ - Anope::string buf = u->GetIdent() + "@" + u->host, buf2, buf3; - if (!u->vhost.empty()) - buf2 = u->GetIdent() + "@" + u->vhost; - if (!u->GetCloakedHost().empty()) - buf3 = u->GetIdent() + "@" + u->GetCloakedHost(); - - for (NickAccess *access : GetRefs(nsaccess)) - { - Anope::string a = access->GetMask(); - if (Anope::Match(buf, a) || (!buf2.empty() && Anope::Match(buf2, a)) || (!buf3.empty() && Anope::Match(buf3, a))) - return true; - } - return false; -} - -unsigned int AccountImpl::GetChannelCount() -{ - unsigned int i = 0; - for (ChanServ::Channel *c : GetRefs(ChanServ::channel)) - if (c->GetFounder() == this) - ++i; - return i; -} - diff --git a/modules/pseudoclients/nickserv/account.h b/modules/pseudoclients/nickserv/account.h deleted file mode 100644 index 584548563..000000000 --- a/modules/pseudoclients/nickserv/account.h +++ /dev/null @@ -1,35 +0,0 @@ -#include "modules/nickserv.h" - -class AccountImpl : public NickServ::Account -{ - public: - AccountImpl(Serialize::TypeBase *type) : NickServ::Account(type) { } - AccountImpl(Serialize::TypeBase *type, Serialize::ID id) : NickServ::Account(type, id) { } - ~AccountImpl(); - void Delete() override; - - Anope::string GetDisplay() override; - void SetDisplay(const Anope::string &) override; - - Anope::string GetPassword() override; - void SetPassword(const Anope::string &) override; - - Anope::string GetEmail() override; - void SetEmail(const Anope::string &) override; - - Anope::string GetLanguage() override; - void SetLanguage(const Anope::string &) override; - - MemoServ::MemoInfo *GetMemos() override; - - void SetDisplay(NickServ::Nick *na) override; - bool IsServicesOper() const override; - /*void AddAccess(const Anope::string &entry) override; - Anope::string GetAccess(unsigned entry) const override; - unsigned GetAccessCount() const override; - bool FindAccess(const Anope::string &entry) override; - void EraseAccess(const Anope::string &entry) override; - void ClearAccess() override;*/ - bool IsOnAccess(User *u) override; - unsigned int GetChannelCount() override; -}; diff --git a/modules/pseudoclients/nickserv/accounttype.cpp b/modules/pseudoclients/nickserv/accounttype.cpp deleted file mode 100644 index 3dad436b4..000000000 --- a/modules/pseudoclients/nickserv/accounttype.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "module.h" -#include "accounttype.h" -//#include "account.h" - -AccountType::AccountType(Module *me) : Serialize::Type(me, "NickCore") - , display(this, "display") - , pass(this, "pass") - , email(this, "email") - , language(this, "language") -{ - -} - -void AccountType::Display::SetField(AccountImpl *acc, const Anope::string &disp) -{ - NickServ::nickcore_map& map = NickServ::service->GetAccountMap(); - - if (!acc->GetDisplay().empty()) - map.erase(acc->GetDisplay()); - - Serialize::Field::SetField(acc, disp); - - if (!disp.empty()) - map[disp] = acc; - - acc->o = Oper::Find(disp); -} - -NickServ::Account *AccountType::FindAccount(const Anope::string &acc) -{ - Serialize::ID id; - EventReturn result = Event::OnSerialize(&Event::SerializeEvents::OnSerializeFind, this, &this->display, acc, id); - if (result == EVENT_ALLOW) - return RequireID(id); - - NickServ::nickcore_map &map = NickServ::service->GetAccountMap(); - auto it = map.find(acc); - if (it != map.end()) - return it->second; - return nullptr; -} - diff --git a/modules/pseudoclients/nickserv/accounttype.h b/modules/pseudoclients/nickserv/accounttype.h deleted file mode 100644 index 792629877..000000000 --- a/modules/pseudoclients/nickserv/accounttype.h +++ /dev/null @@ -1,23 +0,0 @@ -#include "account.h" - -class AccountType : public Serialize::Type -{ - public: - /* Name of the account */ - struct Display : Serialize::Field - { - using Serialize::Field::Field; - - void SetField(AccountImpl *s, const Anope::string &) override; - } display; - /* User password in form of hashm:data */ - Serialize::Field pass; - Serialize::Field email; - /* Locale name of the language of the user. Empty means default language */ - Serialize::Field language; - - - AccountType(Module *); - - NickServ::Account *FindAccount(const Anope::string &nick); -}; diff --git a/modules/pseudoclients/nickserv/identifyrequest.cpp b/modules/pseudoclients/nickserv/identifyrequest.cpp deleted file mode 100644 index 4fc2f9989..000000000 --- a/modules/pseudoclients/nickserv/identifyrequest.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "identifyrequest.h" - -IdentifyRequestImpl::IdentifyRequestImpl(NickServ::IdentifyRequestListener *li, Module *o, const Anope::string &acc, const Anope::string &pass) : NickServ::IdentifyRequest(li, o, acc, pass) -{ - std::set &requests = NickServ::service->GetIdentifyRequests(); - requests.insert(this); -} - -IdentifyRequestImpl::~IdentifyRequestImpl() -{ - std::set &requests = NickServ::service->GetIdentifyRequests(); - requests.erase(this); - delete l; -} - -void IdentifyRequestImpl::Hold(Module *m) -{ - holds.insert(m); -} - -void IdentifyRequestImpl::Release(Module *m) -{ - holds.erase(m); - if (holds.empty() && dispatched) - { - if (!success) - l->OnFail(this); - delete this; - } -} - -void IdentifyRequestImpl::Success(Module *m) -{ - if (!success) - { - l->OnSuccess(this); - success = true; - } -} - -void IdentifyRequestImpl::Dispatch() -{ - if (holds.empty()) - { - if (!success) - l->OnFail(this); - delete this; - } - else - dispatched = true; -} diff --git a/modules/pseudoclients/nickserv/identifyrequest.h b/modules/pseudoclients/nickserv/identifyrequest.h deleted file mode 100644 index e74d3129e..000000000 --- a/modules/pseudoclients/nickserv/identifyrequest.h +++ /dev/null @@ -1,13 +0,0 @@ -#include "modules/nickserv.h" - -class IdentifyRequestImpl : public NickServ::IdentifyRequest -{ - public: - IdentifyRequestImpl(NickServ::IdentifyRequestListener *, Module *o, const Anope::string &acc, const Anope::string &pass); - virtual ~IdentifyRequestImpl(); - - void Hold(Module *m) override; - void Release(Module *m) override; - void Success(Module *m) override; - void Dispatch() override; -}; diff --git a/modules/pseudoclients/nickserv/mode.cpp b/modules/pseudoclients/nickserv/mode.cpp deleted file mode 100644 index 61fe551c1..000000000 --- a/modules/pseudoclients/nickserv/mode.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "modules/nickserv.h" -#include "modetype.h" - -NickServ::Account *ModeImpl::GetAccount() -{ - return Get(&NSModeType::account); -} - -void ModeImpl::SetAccount(NickServ::Account *a) -{ - Set(&NSModeType::account, a); -} - -Anope::string ModeImpl::GetMode() -{ - return Get(&NSModeType::mode); -} - -void ModeImpl::SetMode(const Anope::string &m) -{ - Set(&NSModeType::mode, m); -} - diff --git a/modules/pseudoclients/nickserv/mode.h b/modules/pseudoclients/nickserv/mode.h deleted file mode 100644 index 9fc4c331e..000000000 --- a/modules/pseudoclients/nickserv/mode.h +++ /dev/null @@ -1,14 +0,0 @@ - -class ModeImpl : public NickServ::Mode -{ - public: - ModeImpl(Serialize::TypeBase *type) : NickServ::Mode(type) { } - ModeImpl(Serialize::TypeBase *type, Serialize::ID id) : NickServ::Mode(type, id) { } - - NickServ::Account *GetAccount() override; - void SetAccount(NickServ::Account *) override; - - Anope::string GetMode() override; - void SetMode(const Anope::string &) override; -}; - diff --git a/modules/pseudoclients/nickserv/modetype.h b/modules/pseudoclients/nickserv/modetype.h deleted file mode 100644 index 0c4aefecc..000000000 --- a/modules/pseudoclients/nickserv/modetype.h +++ /dev/null @@ -1,14 +0,0 @@ -#include "mode.h" - -class NSModeType : public Serialize::Type -{ - public: - Serialize::ObjectField account; - Serialize::Field mode; - - NSModeType(Module *creator) : Serialize::Type(creator, "NSKeepMode") - , account(this, "account", true) - , mode(this, "mode") - { - } -}; diff --git a/modules/pseudoclients/nickserv/nick.cpp b/modules/pseudoclients/nickserv/nick.cpp deleted file mode 100644 index 0ac55ff93..000000000 --- a/modules/pseudoclients/nickserv/nick.cpp +++ /dev/null @@ -1,192 +0,0 @@ -/* - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - * - */ - -#include "module.h" -#include "nicktype.h" - -NickImpl::~NickImpl() -{ - /* Remove us from the aliases list */ - NickServ::nickalias_map &map = NickServ::service->GetNickMap(); - map.erase(GetNick()); -} - -void NickImpl::Delete() -{ - Event::OnDelNick(&Event::DelNick::OnDelNick, this); - - if (this->GetAccount()) - { - /* Next: see if our core is still useful. */ - std::vector aliases = this->GetAccount()->GetRefs(NickServ::nick); - - auto it = std::find(aliases.begin(), aliases.end(), this); - if (it != aliases.end()) - aliases.erase(it); - - if (aliases.empty()) - { - /* just me */ - this->GetAccount()->Delete(); - } - else - { - /* Display updating stuff */ - if (GetNick().equals_ci(this->GetAccount()->GetDisplay())) - this->GetAccount()->SetDisplay(aliases[0]); - } - } - - return Serialize::Object::Delete(); -} - -Anope::string NickImpl::GetNick() -{ - return Get(&NickType::nick); -} - -void NickImpl::SetNick(const Anope::string &nick) -{ - Set(&NickType::nick, nick); -} - -Anope::string NickImpl::GetLastQuit() -{ - return Get(&NickType::last_quit); -} - -void NickImpl::SetLastQuit(const Anope::string &lq) -{ - Set(&NickType::last_quit, lq); -} - -Anope::string NickImpl::GetLastRealname() -{ - return Get(&NickType::last_realname); -} - -void NickImpl::SetLastRealname(const Anope::string &lr) -{ - Set(&NickType::last_realname, lr); -} - -Anope::string NickImpl::GetLastUsermask() -{ - return Get(&NickType::last_usermask); -} - -void NickImpl::SetLastUsermask(const Anope::string &lu) -{ - Set(&NickType::last_usermask, lu); -} - -Anope::string NickImpl::GetLastRealhost() -{ - return Get(&NickType::last_realhost); -} - -void NickImpl::SetLastRealhost(const Anope::string &lr) -{ - Set(&NickType::last_realhost, lr); -} - -time_t NickImpl::GetTimeRegistered() -{ - return Get(&NickType::time_registered); -} - -void NickImpl::SetTimeRegistered(const time_t &tr) -{ - Set(&NickType::time_registered, tr); -} - -time_t NickImpl::GetLastSeen() -{ - return Get(&NickType::last_seen); -} - -void NickImpl::SetLastSeen(const time_t &ls) -{ - Set(&NickType::last_seen, ls); -} - -NickServ::Account *NickImpl::GetAccount() -{ - return Get(&NickType::nc); -} - -void NickImpl::SetAccount(NickServ::Account *acc) -{ - Set(&NickType::nc, acc); -} - -void NickImpl::SetVhost(const Anope::string &ident, const Anope::string &host, const Anope::string &creator, time_t created) -{ - Set(&NickType::vhost_ident, ident); - Set(&NickType::vhost_host, host); - Set(&NickType::vhost_creator, creator); - Set(&NickType::vhost_created, created); -} - -void NickImpl::RemoveVhost() -{ - Anope::string e; - Set(&NickType::vhost_ident, e); - Set(&NickType::vhost_host, e); - Set(&NickType::vhost_creator, e); - Set(&NickType::vhost_created, 0); -} - -bool NickImpl::HasVhost() -{ - return !GetVhostHost().empty(); -} - -Anope::string NickImpl::GetVhostIdent() -{ - return Get(&NickType::vhost_ident); -} - -void NickImpl::SetVhostIdent(const Anope::string &i) -{ - Set(&NickType::vhost_ident, i); -} - -Anope::string NickImpl::GetVhostHost() -{ - return Get(&NickType::vhost_host); -} - -void NickImpl::SetVhostHost(const Anope::string &h) -{ - Set(&NickType::vhost_host, h); -} - -Anope::string NickImpl::GetVhostCreator() -{ - return Get(&NickType::vhost_creator); -} - -void NickImpl::SetVhostCreator(const Anope::string &c) -{ - Set(&NickType::vhost_creator, c); -} - -time_t NickImpl::GetVhostCreated() -{ - return Get(&NickType::vhost_created); -} - -void NickImpl::SetVhostCreated(const time_t &cr) -{ - Set(&NickType::vhost_created, cr); -} diff --git a/modules/pseudoclients/nickserv/nick.h b/modules/pseudoclients/nickserv/nick.h deleted file mode 100644 index 043d7c052..000000000 --- a/modules/pseudoclients/nickserv/nick.h +++ /dev/null @@ -1,46 +0,0 @@ - -class NickImpl : public NickServ::Nick -{ - public: - NickImpl(Serialize::TypeBase *type) : NickServ::Nick(type) { } - NickImpl(Serialize::TypeBase *type, Serialize::ID id) : NickServ::Nick(type, id) { } - ~NickImpl(); - void Delete() override; - - Anope::string GetNick() override; - void SetNick(const Anope::string &) override; - - Anope::string GetLastQuit() override; - void SetLastQuit(const Anope::string &) override; - - Anope::string GetLastRealname() override; - void SetLastRealname(const Anope::string &) override; - - Anope::string GetLastUsermask() override; - void SetLastUsermask(const Anope::string &) override; - - Anope::string GetLastRealhost() override; - void SetLastRealhost(const Anope::string &) override; - - time_t GetTimeRegistered() override; - void SetTimeRegistered(const time_t &) override; - - time_t GetLastSeen() override; - void SetLastSeen(const time_t &) override; - - NickServ::Account *GetAccount() override; - void SetAccount(NickServ::Account *acc) override; - - void SetVhost(const Anope::string &ident, const Anope::string &host, const Anope::string &creator, time_t created = Anope::CurTime) override; - void RemoveVhost() override; - bool HasVhost() override; - - Anope::string GetVhostIdent() override; - void SetVhostIdent(const Anope::string &) override; - Anope::string GetVhostHost() override; - void SetVhostHost(const Anope::string &) override; - Anope::string GetVhostCreator() override; - void SetVhostCreator(const Anope::string &) override; - time_t GetVhostCreated() override; - void SetVhostCreated(const time_t &) override; -}; diff --git a/modules/pseudoclients/nickserv/nickserv.cpp b/modules/pseudoclients/nickserv/nickserv.cpp deleted file mode 100644 index 26f1457cc..000000000 --- a/modules/pseudoclients/nickserv/nickserv.cpp +++ /dev/null @@ -1,686 +0,0 @@ -/* NickServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/ns_info.h" -#include "modules/ns_group.h" -#include "modules/ns_update.h" -#include "modules/help.h" -#include "modules/nickserv.h" -#include "identifyrequest.h" -#include "nicktype.h" -#include "accounttype.h" -#include "modetype.h" - -class NickServCollide; -static std::set collides; - -/** Timer for colliding nicks to force people off of nicknames - */ -class NickServCollide : public Timer -{ - NickServ::NickServService *service; - Reference u; - time_t ts; - Reference na; - - public: - NickServCollide(Module *me, NickServ::NickServService *nss, User *user, NickServ::Nick *nick, time_t delay) : Timer(me, delay), service(nss), u(user), ts(user->timestamp), na(nick) - { - collides.insert(this); - } - - ~NickServCollide() - { - collides.erase(this); - } - - NickServ::Nick *GetNick() - { - return na; - } - - User *GetUser() - { - return u; - } - - void Tick(time_t t) override - { - if (!u || !na) - return; - /* If they identified or don't exist anymore, don't kill them. */ - if (u->Account() == na->GetAccount() || u->timestamp > ts) - return; - - service->Collide(u, na); - } -}; - -/** Timer for removing HELD status from nicks. - */ -class NickServHeld : public Timer -{ - Reference na; - Anope::string nick; - public: - NickServHeld(Module *me, NickServ::Nick *n, long l) : Timer(me, l), na(n), nick(na->GetNick()) - { - n->SetS("HELD", true); - } - - void Tick(time_t) - { - if (na) - na->UnsetS("HELD"); - } -}; - -class NickServRelease; -static Anope::map NickServReleases; - -/** Timer for releasing nicks to be available for use - */ -class NickServRelease : public User, public Timer -{ - Anope::string nick; - - public: - NickServRelease(Module *me, NickServ::Nick *na, time_t delay) : User(na->GetNick(), Config->GetModule("nickserv")->Get("enforceruser", "user"), - Config->GetModule("nickserv")->Get("enforcerhost", "services.localhost.net"), "", "", Me, "Services Enforcer", Anope::CurTime, "", IRCD->UID_Retrieve(), NULL), Timer(me, delay), nick(na->GetNick()) - { - /* Erase the current release timer and use the new one */ - Anope::map::iterator nit = NickServReleases.find(this->nick); - if (nit != NickServReleases.end()) - { - IRCD->SendQuit(nit->second, ""); - delete nit->second; - } - - NickServReleases.insert(std::make_pair(this->nick, this)); - - IRCD->SendClientIntroduction(this); - } - - ~NickServRelease() - { - IRCD->SendQuit(this, ""); - NickServReleases.erase(this->nick); - } - - void Tick(time_t t) override { } -}; - -class NickServCore : public Module, public NickServ::NickServService - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - Reference NickServ; - std::vector defaults; - ExtensibleItem held, collided; - EventHandlers onprenickexpire; - EventHandlers onnickexpire; - EventHandlers onnickregister; - EventHandlers onnickvalidate; - std::set identifyrequests; - NickServ::nickalias_map NickList; - NickServ::nickcore_map AccountList; - NickType nick_type; - AccountType account_type; - NSModeType mode_type; - - void OnCancel(User *u, NickServ::Nick *na) - { - if (collided.HasExt(na)) - { - collided.Unset(na); - - new NickServHeld(this, na, Config->GetModule("nickserv")->Get("releasetimeout", "1m")); - - if (IRCD->CanSVSHold) - IRCD->SendSVSHold(na->GetNick(), Config->GetModule("nickserv")->Get("releasetimeout", "1m")); - else - new NickServRelease(this, na, Config->GetModule("nickserv")->Get("releasetimeout", "1m")); - } - } - - public: - NickServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) - , NickServ::NickServService(this) - , held(this, "HELD") - , collided(this, "COLLIDED") - , onprenickexpire(this) - , onnickexpire(this) - , onnickregister(this) - , onnickvalidate(this) - , nick_type(this) - , account_type(this) - , mode_type(this) - { - } - - ~NickServCore() - { - OnShutdown(); - } - - void OnShutdown() override - { - /* On shutdown, restart, or mod unload, remove all of our holds for nicks (svshold or qlines) - * because some IRCds do not allow us to have these automatically expire - */ - for (NickServ::Nick *na : nick_type.List()) - this->Release(na); - } - - void OnRestart() override - { - OnShutdown(); - } - - void Validate(User *u) override - { - NickServ::Nick *na = NickServ::FindNick(u->nick); - if (!na) - return; - - EventReturn MOD_RESULT = this->onnickvalidate(&NickServ::Event::NickValidate::OnNickValidate, u, na); - if (MOD_RESULT == EVENT_STOP) - { - this->Collide(u, na); - return; - } - else if (MOD_RESULT == EVENT_ALLOW) - return; - - if (!na->GetAccount()->HasFieldS("NS_SECURE") && u->IsRecognized()) - { - na->SetLastSeen(Anope::CurTime); - na->SetLastUsermask(u->GetIdent() + "@" + u->GetDisplayedHost()); - na->SetLastRealname(u->realname); - return; - } - - if (Config->GetModule("nickserv")->Get("nonicknameownership")) - return; - - bool on_access = u->IsRecognized(false); - - if (on_access || !na->GetAccount()->HasFieldS("KILL_IMMED")) - { - if (na->GetAccount()->HasFieldS("NS_SECURE")) - u->SendMessage(*NickServ, _("This nickname is registered and protected. If this is your nickname, type \002{0}{1} IDENTIFY \037password\037\002. Otherwise, please choose a different nickname."), Config->StrictPrivmsg, NickServ->nick); // XXX - else - u->SendMessage(*NickServ, _("This nickname is owned by someone else. If this is your nickname, type \002{0}{1} IDENTIFY \037password\037\002. Otherwise, please choose a different nickname."), Config->StrictPrivmsg, NickServ->nick); // XXX - } - if (na->GetAccount()->HasFieldS("KILLPROTECT") && !on_access) - { - if (na->GetAccount()->HasFieldS("KILL_IMMED")) - { - u->SendMessage(*NickServ, _("This nickname has been registered; you may not use it.")); - this->Collide(u, na); - } - else if (na->GetAccount()->HasFieldS("KILL_QUICK")) - { - time_t killquick = Config->GetModule("nickserv")->Get("killquick", "20s"); - u->SendMessage(*NickServ, _("If you do not change within %s, I will change your nick."), Anope::Duration(killquick, u->Account()).c_str()); - new NickServCollide(this, this, u, na, killquick); - } - else - { - time_t kill = Config->GetModule("nickserv")->Get("kill", "60s"); - u->SendMessage(*NickServ, _("If you do not change within %s, I will change your nick."), Anope::Duration(kill, u->Account()).c_str()); - new NickServCollide(this, this, u, na, kill); - } - } - - } - - void OnUserLogin(User *u) override - { - NickServ::Nick *na = NickServ::FindNick(u->nick); - if (na && na->GetAccount() == u->Account() && !Config->GetModule("nickserv")->Get("nonicknameownership") && !na->GetAccount()->HasFieldS("UNCONFIRMED")) - u->SetMode(NickServ, "REGISTERED"); - - const Anope::string &modesonid = Config->GetModule(this)->Get("modesonid"); - if (!modesonid.empty()) - u->SetModes(NickServ, "%s", modesonid.c_str()); - } - - void Collide(User *u, NickServ::Nick *na) override - { - if (na) - collided.Set(na, true); - - if (IRCD->CanSVSNick) - { - unsigned nicklen = Config->GetBlock("networkinfo")->Get("nicklen"); - const Anope::string &guestprefix = Config->GetModule("nickserv")->Get("guestnickprefix", "Guest"); - - Anope::string guestnick; - - int i = 0; - do - { - guestnick = guestprefix + stringify(static_cast(rand())); - if (guestnick.length() > nicklen) - guestnick = guestnick.substr(0, nicklen); - } - while (User::Find(guestnick) && i++ < 10); - - if (i == 11) - u->Kill(*NickServ, "Services nickname-enforcer kill"); - else - { - u->SendMessage(*NickServ, _("Your nickname is now being changed to \002%s\002"), guestnick.c_str()); - IRCD->SendForceNickChange(u, guestnick, Anope::CurTime); - } - } - else - u->Kill(*NickServ, "Services nickname-enforcer kill"); - } - - void Release(NickServ::Nick *na) override - { - if (held.HasExt(na)) - { - if (IRCD->CanSVSHold) - IRCD->SendSVSHoldDel(na->GetNick()); - else - { - User *u = User::Find(na->GetNick()); - if (u && u->server == Me) - { - u->Quit(); - } - } - - held.Unset(na); - } - collided.Unset(na); /* clear pending collide */ - } - - NickServ::IdentifyRequest *CreateIdentifyRequest(NickServ::IdentifyRequestListener *l, Module *o, const Anope::string &acc, const Anope::string &pass) override - { - return new IdentifyRequestImpl(l, o, acc, pass); - } - - std::set& GetIdentifyRequests() override - { - return identifyrequests; - } - - std::vector GetNickList() override - { - return nick_type.List(); - } - - NickServ::nickalias_map& GetNickMap() override - { - return NickList; - } - - std::vector GetAccountList() override - { - return account_type.List(); - } - - NickServ::nickcore_map& GetAccountMap() override - { - return AccountList; - } - - NickServ::Nick *FindNick(const Anope::string &nick) override - { - return nick_type.FindNick(nick); - } - - NickServ::Account *FindAccount(const Anope::string &acc) override - { - return account_type.FindAccount(acc); - } - - void OnReload(Configuration::Conf *conf) override - { - const Anope::string &nsnick = conf->GetModule(this)->Get("client"); - - if (nsnick.empty()) - throw ConfigException(Module::name + ": must be defined"); - - ServiceBot *bi = ServiceBot::Find(nsnick, true); - if (!bi) - throw ConfigException(Module::name + ": no bot named " + nsnick); - - NickServ = bi; - - spacesepstream(conf->GetModule(this)->Get("defaults", "ns_secure memo_signon memo_receive")).GetTokens(defaults); - if (defaults.empty()) - { - defaults.push_back("NS_SECURE"); - defaults.push_back("MEMO_SIGNON"); - defaults.push_back("MEMO_RECEIVE"); - } - else if (defaults[0].equals_ci("none")) - defaults.clear(); - } - - void OnDelNick(NickServ::Nick *na) override - { - User *u = User::Find(na->GetNick()); - if (u && u->Account() == na->GetAccount()) - { - IRCD->SendLogout(u); - u->RemoveMode(NickServ, "REGISTERED"); - u->Logout(); - } - } - - void OnDelCore(NickServ::Account *nc) override - { - Log(NickServ, "nick") << "Deleting nickname group " << nc->GetDisplay(); - - /* Clean up this nick core from any users online */ - for (unsigned int i = nc->users.size(); i > 0; --i) - { - User *user = nc->users[i - 1]; - IRCD->SendLogout(user); - user->RemoveMode(NickServ, "REGISTERED"); - user->Logout(); - Event::OnNickLogout(&Event::NickLogout::OnNickLogout, user); - } - } - - void OnChangeCoreDisplay(NickServ::Account *nc, const Anope::string &newdisplay) override - { - Log(LOG_NORMAL, "nick", NickServ) << "Changing " << nc->GetDisplay() << " nickname group display to " << newdisplay; - } - - void OnNickIdentify(User *u) override - { - Configuration::Block *block = Config->GetModule(this); - - if (block->Get("modeonid", "yes")) - - for (User::ChanUserList::iterator it = u->chans.begin(), it_end = u->chans.end(); it != it_end; ++it) - { - ChanUserContainer *cc = it->second; - Channel *c = cc->chan; - if (c) - c->SetCorrectModes(u, true); - } - - const Anope::string &modesonid = block->Get("modesonid"); - if (!modesonid.empty()) - u->SetModes(NickServ, "%s", modesonid.c_str()); - - if (block->Get("forceemail", "yes") && u->Account()->GetEmail().empty()) - { - u->SendMessage(*NickServ, _("You must now supply an e-mail for your nick.\n" - "This e-mail will allow you to retrieve your password in\n" - "case you forget it.")); - u->SendMessage(*NickServ, _("Type \002%s%s SET EMAIL \037e-mail\037\002 in order to set your e-mail.\n" - "Your privacy is respected; this e-mail won't be given to\n" - "any third-party person."), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str()); - } - - for (std::set::iterator it = collides.begin(); it != collides.end(); ++it) - { - NickServCollide *c = *it; - if (c->GetUser() == u && c->GetNick() && c->GetNick()->GetAccount() == u->Account()) - { - delete c; - break; - } - } - } - - void OnNickGroup(User *u, NickServ::Nick *target) override - { - if (!target->GetAccount()->HasFieldS("UNCONFIRMED")) - u->SetMode(NickServ, "REGISTERED"); - } - - void OnNickUpdate(User *u) override - { - for (User::ChanUserList::iterator it = u->chans.begin(), it_end = u->chans.end(); it != it_end; ++it) - { - ChanUserContainer *cc = it->second; - Channel *c = cc->chan; - if (c) - c->SetCorrectModes(u, true); - } - } - - void OnUserConnect(User *u, bool &exempt) override - { - if (u->Quitting() || !u->server->IsSynced() || u->server->IsULined()) - return; - - const NickServ::Nick *na = NickServ::FindNick(u->nick); - - const Anope::string &unregistered_notice = Config->GetModule(this)->Get("unregistered_notice"); - if (!Config->GetModule("nickserv")->Get("nonicknameownership") && !unregistered_notice.empty() && !na && !u->Account()) - u->SendMessage(*NickServ, unregistered_notice); - else if (na && !u->IsIdentified(true)) - this->Validate(u); - } - - void OnPostUserLogoff(User *u) override - { - NickServ::Nick *na = NickServ::FindNick(u->nick); - if (na) - OnCancel(u, na); - } - - void OnServerSync(Server *s) override - { - for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) - { - User *u = it->second; - - if (u->server == s) - { - if (u->HasMode("REGISTERED") && !u->IsIdentified(true)) - u->RemoveMode(NickServ, "REGISTERED"); - if (!u->IsIdentified()) - this->Validate(u); - } - } - } - - void OnUserNickChange(User *u, const Anope::string &oldnick) override - { - NickServ::Nick *old_na = NickServ::FindNick(oldnick), *na = NickServ::FindNick(u->nick); - /* If the new nick isn't registered or it's registered and not yours */ - if (!na || na->GetAccount() != u->Account()) - { - /* Remove +r, but keep an account associated with the user */ - u->RemoveMode(NickServ, "REGISTERED"); - - this->Validate(u); - } - else - { - /* Reset +r and re-send account (even though it really should be set at this point) */ - IRCD->SendLogin(u, na); - if (!Config->GetModule("nickserv")->Get("nonicknameownership") && na->GetAccount() == u->Account() && !na->GetAccount()->HasFieldS("UNCONFIRMED")) - u->SetMode(NickServ, "REGISTERED"); - Log(u, "", NickServ) << u->GetMask() << " automatically identified for group " << u->Account()->GetDisplay(); - } - - if (!u->nick.equals_ci(oldnick) && old_na) - OnCancel(u, old_na); - } - - void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override - { - if (u->server->IsSynced() && mname == "REGISTERED" && !u->IsIdentified(true)) - u->RemoveMode(NickServ, mname); - } - - EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override - { - if (!params.empty() || source.c || source.service != *NickServ) - return EVENT_CONTINUE; - if (!Config->GetModule("nickserv")->Get("nonicknameownership")) - source.Reply(_("\002%s\002 allows you to register a nickname and\n" - "prevent others from using it. The following\n" - "commands allow for registration and maintenance of\n" - "nicknames; to use them, type \002%s%s \037command\037\002.\n" - "For more information on a specific command, type\n" - "\002%s%s %s \037command\037\002.\n"), NickServ->nick.c_str(), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str(), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str(), source.command.c_str()); - else - source.Reply(_("\002%s\002 allows you to register an account.\n" - "The following commands allow for registration and maintenance of\n" - "accounts; to use them, type \002%s%s \037command\037\002.\n" - "For more information on a specific command, type\n" - "\002%s%s %s \037command\037\002.\n"), NickServ->nick.c_str(), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str(), Config->StrictPrivmsg.c_str(), NickServ->nick.c_str(), source.command.c_str()); - return EVENT_CONTINUE; - } - - void OnPostHelp(CommandSource &source, const std::vector ¶ms) override - { - if (!params.empty() || source.c || source.service != *NickServ) - return; - if (source.IsServicesOper()) - source.Reply(_(" \n" - "Services Operators can also drop any nickname without needing\n" - "to identify for the nick, and may view the access list for\n" - "any nickname.")); - time_t nickserv_expire = Config->GetModule(this)->Get("expire", "21d"); - if (nickserv_expire >= 86400) - source.Reply(_(" \n" - "Accounts that are not used anymore are subject to\n" - "the automatic expiration, i.e. they will be deleted\n" - "after %d days if not used."), nickserv_expire / 86400); - source.Reply(_(" \n" - "\002NOTICE:\002 This service is intended to provide a way for\n" - "IRC users to ensure their identity is not compromised.\n" - "It is \002NOT\002 intended to facilitate \"stealing\" of\n" - "nicknames or other malicious actions. Abuse of %s\n" - "will result in, at minimum, loss of the abused\n" - "nickname(s)."), NickServ->nick.c_str()); - } - - void OnNickCoreCreate(NickServ::Account *nc) override - { - /* Set default flags */ - for (unsigned i = 0; i < defaults.size(); ++i) - nc->SetS(defaults[i].upper(), true); - } - - void OnUserQuit(User *u, const Anope::string &msg) override - { - if (u->server && !u->server->GetQuitReason().empty() && Config->GetModule(this)->Get("hidenetsplitquit")) - return; - - /* Update last quit and last seen for the user */ - NickServ::Nick *na = NickServ::FindNick(u->nick); - if (na && !na->GetAccount()->HasFieldS("NS_SUSPENDED") && (u->IsRecognized() || u->IsIdentified(true))) - { - na->SetLastSeen(Anope::CurTime); - na->SetLastQuit(msg); - } - } - - void OnExpireTick() override - { - if (Anope::NoExpire || Anope::ReadOnly) - return; - - time_t nickserv_expire = Config->GetModule(this)->Get("expire", "21d"); - - for (NickServ::Nick *na : nick_type.List()) - { - User *u = User::Find(na->GetNick()); - if (u && (u->IsIdentified(true) || u->IsRecognized())) - na->SetLastSeen(Anope::CurTime); - - bool expire = false; - - if (nickserv_expire && Anope::CurTime - na->GetLastSeen() >= nickserv_expire) - expire = true; - - this->onprenickexpire(&NickServ::Event::PreNickExpire::OnPreNickExpire, na, expire); - - if (expire) - { - Log(LOG_NORMAL, "nickserv/expire", NickServ) << "Expiring nickname " << na->GetNick() << " (group: " << na->GetAccount()->GetDisplay() << ") (e-mail: " << (na->GetAccount()->GetEmail().empty() ? "none" : na->GetAccount()->GetEmail()) << ")"; - this->onnickexpire(&NickServ::Event::NickExpire::OnNickExpire, na); - delete na; - } - } - } - - void OnNickInfo(CommandSource &source, NickServ::Nick *na, InfoFormatter &info, bool show_hidden) override - { - if (!na->GetAccount()->HasFieldS("UNCONFIRMED")) - { - time_t nickserv_expire = Config->GetModule(this)->Get("expire", "21d"); - if (!na->HasFieldS("NS_NO_EXPIRE") && nickserv_expire && !Anope::NoExpire && (source.HasPriv("nickserv/auspex") || na->GetLastSeen() != Anope::CurTime)) - info[_("Expires")] = Anope::strftime(na->GetLastSeen() + nickserv_expire, source.GetAccount()); - } - else - { - time_t unconfirmed_expire = Config->GetModule(this)->Get("unconfirmedexpire", "1d"); - info[_("Expires")] = Anope::strftime(na->GetTimeRegistered() + unconfirmed_expire, source.GetAccount()); - } - } - - void OnModuleUnload(User *u, Module *m) override - { - for (std::set::iterator it = identifyrequests.begin(), it_end = identifyrequests.end(); it != it_end;) - { - NickServ::IdentifyRequest *ir = *it; - ++it; - - ir->Release(m); -#if 0 - ir->holds.erase(m); - if (ir->holds.empty() && ir->dispatched) - { - if (!ir->success) - ir->OnFail(); - delete ir; - continue; - } - - if (ir->GetOwner() == m) - { - if (!ir->success) - ir->OnFail(); - delete ir; - } -#endif - } - } -}; - -MODULE_INIT(NickServCore) - diff --git a/modules/pseudoclients/nickserv/nicktype.cpp b/modules/pseudoclients/nickserv/nicktype.cpp deleted file mode 100644 index 9607aaa2f..000000000 --- a/modules/pseudoclients/nickserv/nicktype.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "module.h" -#include "nicktype.h" - -NickType::NickType(Module *me) : Serialize::Type(me, "NickAlias") - , nick(this, "nick") - , last_quit(this, "last_quit") - , last_realname(this, "last_realname") - , last_usermask(this, "last_usermask") - , last_realhost(this, "last_realhost") - , time_registered(this, "time_registered") - , last_seen(this, "last_seen") - , vhost_ident(this, "vhost_ident") - , vhost_host(this, "vhost_host") - , vhost_creator(this, "vhost_creator") - , vhost_created(this, "vhost_created") - , nc(this, "nc") -{ - -} - -void NickType::Nick::SetField(NickImpl *na, const Anope::string &value) -{ - /* Remove us from the aliases list */ - NickServ::nickalias_map &map = NickServ::service->GetNickMap(); - map.erase(GetField(na)); - - Serialize::Field::SetField(na, value); - - map[value] = na; -} - -NickServ::Nick *NickType::FindNick(const Anope::string &n) -{ - Serialize::ID id; - EventReturn result = Event::OnSerialize(&Event::SerializeEvents::OnSerializeFind, this, &this->nick, n, id); - if (result == EVENT_ALLOW) - return RequireID(id); - - NickServ::nickalias_map &map = NickServ::service->GetNickMap(); - auto it = map.find(n); - if (it != map.end()) - return it->second; - return nullptr; -} - diff --git a/modules/pseudoclients/nickserv/nicktype.h b/modules/pseudoclients/nickserv/nicktype.h deleted file mode 100644 index d205845e9..000000000 --- a/modules/pseudoclients/nickserv/nicktype.h +++ /dev/null @@ -1,32 +0,0 @@ -#include "nick.h" - -class NickType : public Serialize::Type -{ - public: - struct Nick : Serialize::Field - { - using Serialize::Field::Field; - - void SetField(NickImpl *s, const Anope::string &value) override; - } nick; - Serialize::Field last_quit; - Serialize::Field last_realname; - /* Last usermask this nick was seen on, eg user@host */ - Serialize::Field last_usermask; - /* Last uncloaked usermask, requires nickserv/auspex to see */ - Serialize::Field last_realhost; - Serialize::Field time_registered; - Serialize::Field last_seen; - - Serialize::Field vhost_ident; - Serialize::Field vhost_host; - Serialize::Field vhost_creator; - Serialize::Field vhost_created; - - /* Account this nick is tied to. Multiple nicks can be tied to a single account. */ - Serialize::ObjectField nc; - - NickType(Module *); - - NickServ::Nick *FindNick(const Anope::string &nick); -}; diff --git a/modules/pseudoclients/operserv.cpp b/modules/pseudoclients/operserv.cpp deleted file mode 100644 index 0a4f1b692..000000000 --- a/modules/pseudoclients/operserv.cpp +++ /dev/null @@ -1,337 +0,0 @@ -/* OperServ core functions - * - * (C) 2003-2014 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - */ - -#include "module.h" -#include "modules/help.h" -#include "modules/nickserv.h" - -class SGlineType : public XLineType -{ - public: - SGlineType(Module *creator) : XLineType(creator, "SGLine") - { - SetParent(xline); - } -}; - -class SGLineManager : public XLineManager -{ - public: - SGLineManager(Module *creator) : XLineManager(creator, "xlinemanager/sgline", 'G') { } - - void OnMatch(User *u, XLine *x) override - { - this->Send(u, x); - } - - void OnExpire(XLine *x) override - { - ::Log(Config->GetClient("OperServ"), "expire/akill") << "AKILL on \002" << x->GetMask() << "\002 has expired"; - } - - void Send(User *u, XLine *x) override - { - IRCD->SendAkill(u, x); - } - - void SendDel(XLine *x) override - { - IRCD->SendAkillDel(x); - } - - bool Check(User *u, XLine *x) override - { - if (x->regex) - { - Anope::string uh = u->GetIdent() + "@" + u->host, nuhr = u->nick + "!" + uh + "#" + u->realname; - return std::regex_match(uh.str(), *x->regex) || std::regex_match(nuhr.str(), *x->regex); - } - - if (!x->GetNick().empty() && !Anope::Match(u->nick, x->GetNick())) - return false; - - if (!x->GetUser().empty() && !Anope::Match(u->GetIdent(), x->GetUser())) - return false; - - if (!x->GetReal().empty() && !Anope::Match(u->realname, x->GetReal())) - return false; - - if (x->c && x->c->match(u->ip)) - return true; - - if (x->GetHost().empty() || Anope::Match(u->host, x->GetHost()) || Anope::Match(u->ip.addr(), x->GetHost())) - return true; - - return false; - } -}; - -class SQlineType : public XLineType -{ - public: - SQlineType(Module *creator) : XLineType(creator, "SQLine") - { - SetParent(xline); - } -}; - -class SQLineManager : public XLineManager -{ - public: - SQLineManager(Module *creator) : XLineManager(creator, "xlinemanager/sqline", 'Q') { } - - void OnMatch(User *u, XLine *x) override - { - this->Send(u, x); - } - - void OnExpire(XLine *x) override - { - ::Log(Config->GetClient("OperServ"), "expire/sqline") << "SQLINE on \002" << x->GetMask() << "\002 has expired"; - } - - void Send(User *u, XLine *x) override - { - if (!IRCD->CanSQLine) - { - if (!u) - ; - else if (NickServ::service) - NickServ::service->Collide(u, NULL); - else - u->Kill(Config->GetClient("OperServ"), "Q-Lined: " + x->GetReason()); - } - else if (x->IsRegex()) - ; - else if (x->GetMask()[0] != '#' || IRCD->CanSQLineChannel) - IRCD->SendSQLine(u, x); - } - - void SendDel(XLine *x) override - { - if (!IRCD->CanSQLine || x->IsRegex()) - ; - else if (x->GetMask()[0] != '#' || IRCD->CanSQLineChannel) - IRCD->SendSQLineDel(x); - } - - bool Check(User *u, XLine *x) override - { - if (x->regex) - return std::regex_match(u->nick.c_str(), *x->regex); - return Anope::Match(u->nick, x->GetMask()); - } - - XLine *CheckChannel(Channel *c) - { - for (XLine *x : this->GetXLines()) - { - if (x->regex) - { - if (std::regex_match(c->name.str(), *x->regex)) - return x; - } - else - { - if (x->GetMask().empty() || x->GetMask()[0] != '#') - continue; - - if (Anope::Match(c->name, x->GetMask(), false, true)) - return x; - } - } - return nullptr; - } -}; - -class SNlineType : public XLineType -{ - public: - SNlineType(Module *creator) : XLineType(creator, "SNLine") - { - SetParent(xline); - } -}; - -class SNLineManager : public XLineManager -{ - public: - SNLineManager(Module *creator) : XLineManager(creator, "xlinemanager/snline", 'N') { } - - void OnMatch(User *u, XLine *x) override - { - this->Send(u, x); - } - - void OnExpire(XLine *x) override - { - ::Log(Config->GetClient("OperServ"), "expire/snline") << "SNLINE on \002" << x->GetMask() << "\002 has expired"; - } - - void Send(User *u, XLine *x) override - { - if (IRCD->CanSNLine && !x->IsRegex()) - IRCD->SendSGLine(u, x); - - if (u) - u->Kill(Config->GetClient("OperServ"), "SNLined: " + x->GetReason()); - } - - void SendDel(XLine *x) override - { - if (IRCD->CanSNLine && !x->IsRegex()) - IRCD->SendSGLineDel(x); - } - - bool Check(User *u, XLine *x) override - { - if (x->regex) - return std::regex_match(u->realname.str(), *x->regex); - return Anope::Match(u->realname, x->GetMask(), false, true); - } -}; - -class OperServCore : public Module - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook - , public EventHook -{ - Reference OperServ; - SGlineType sgtype; - SGLineManager sglines; - SQlineType sqtype; - SQLineManager sqlines; - SNlineType sntype; - SNLineManager snlines; - - public: - OperServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) - , sgtype(this) - , sglines(this) - , sqtype(this) - , sqlines(this) - , sntype(this) - , snlines(this) - { - - /* Yes, these are in this order for a reason. Most violent->least violent. */ - XLineManager::RegisterXLineManager(&sglines); - XLineManager::RegisterXLineManager(&sqlines); - XLineManager::RegisterXLineManager(&snlines); - } - - ~OperServCore() - { - this->sglines.Clear(); - this->sqlines.Clear(); - this->snlines.Clear(); - - XLineManager::UnregisterXLineManager(&sglines); - XLineManager::UnregisterXLineManager(&sqlines); - XLineManager::UnregisterXLineManager(&snlines); - } - - void OnReload(Configuration::Conf *conf) override - { - const Anope::string &osnick = conf->GetModule(this)->Get("client"); - - if (osnick.empty()) - throw ConfigException(this->name + ": must be defined"); - - ServiceBot *bi = ServiceBot::Find(osnick, true); - if (!bi) - throw ConfigException(this->name + ": no bot named " + osnick); - - OperServ = bi; - } - - EventReturn OnBotPrivmsg(User *u, ServiceBot *bi, Anope::string &message) override - { - if (bi == OperServ && !u->HasMode("OPER") && Config->GetModule(this)->Get("opersonly")) - { - u->SendMessage(bi, "Access denied."); - ::Log(bi, "bados") << "Denied access to " << bi->nick << " from " << u->GetMask() << " (non-oper)"; - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } - - void OnServerQuit(Server *server) override - { - if (server->IsJuped()) - ::Log(server, "squit", OperServ) << "Received SQUIT for juped server " << server->GetName(); - } - - void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override - { - if (mname == "OPER") - ::Log(u, "oper", OperServ) << "is now an IRC operator."; - } - - void OnUserModeUnset(const MessageSource &setter, User *u, const Anope::string &mname) override - { - if (mname == "OPER") - ::Log(u, "oper", OperServ) << "is no longer an IRC operator"; - } - - void OnUserConnect(User *u, bool &exempt) override - { - if (!u->Quitting() && !exempt) - XLineManager::CheckAll(u); - } - - void OnUserNickChange(User *u, const Anope::string &oldnick) override - { - if (!u->HasMode("OPER")) - this->sqlines.CheckAllXLines(u); - } - - EventReturn OnCheckKick(User *u, Channel *c, Anope::string &mask, Anope::string &reason) override - { - XLine *x = this->sqlines.CheckChannel(c); - if (x) - { - this->sqlines.OnMatch(u, x); - reason = x->GetReason(); - return EVENT_STOP; - } - - return EVENT_CONTINUE; - } - - EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override - { - if (!params.empty() || source.c || source.service != *OperServ) - return EVENT_CONTINUE; - source.Reply(_("%s commands:"), OperServ->nick.c_str()); - return EVENT_CONTINUE; - } - - void OnPostHelp(CommandSource &source, const std::vector ¶ms) override - { - } - - void OnLog(::Log *l) override - { - if (l->type == LOG_SERVER) - l->bi = OperServ; - } -}; - -MODULE_INIT(OperServCore) - diff --git a/modules/redis.cpp b/modules/redis.cpp new file mode 100644 index 000000000..81c958f7c --- /dev/null +++ b/modules/redis.cpp @@ -0,0 +1,598 @@ +/* + * + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * + */ + +#include "module.h" +#include "modules/redis.h" + +using namespace Redis; + +class MyRedisService; + +class RedisSocket : public BinarySocket, public ConnectionSocket +{ + size_t ParseReply(Reply &r, const char *buf, size_t l); + public: + MyRedisService *provider; + std::deque interfaces; + std::map subinterfaces; + + RedisSocket(MyRedisService *pro, bool v6) : Socket(-1, v6), provider(pro) { } + + ~RedisSocket(); + + void OnConnect() override; + void OnError(const Anope::string &error) override; + + bool Read(const char *buffer, size_t l) override; +}; + +class Transaction : public Interface +{ + public: + std::deque interfaces; + + Transaction(Module *creator) : Interface(creator) { } + + ~Transaction() + { + for (unsigned i = 0; i < interfaces.size(); ++i) + { + Interface *inter = interfaces[i]; + + if (!inter) + continue; + + inter->OnError("Interface going away"); + } + } + + void OnResult(const Reply &r) override + { + /* This is a multi bulk reply of the results of the queued commands + * in this transaction + */ + + Log(LOG_DEBUG_2) << "redis: transaction complete with " << r.multi_bulk.size() << " results"; + + for (unsigned i = 0; i < r.multi_bulk.size(); ++i) + { + const Reply *reply = r.multi_bulk[i]; + + if (interfaces.empty()) + break; + + Interface *inter = interfaces.front(); + interfaces.pop_front(); + + if (inter) + inter->OnResult(*reply); + } + } +}; + +class MyRedisService : public Provider +{ + public: + Anope::string host; + int port; + unsigned db; + + RedisSocket *sock, *sub; + + Transaction ti; + bool in_transaction; + + MyRedisService(Module *c, const Anope::string &n, const Anope::string &h, int p, unsigned d) : Provider(c, n), host(h), port(p), db(d), sock(NULL), sub(NULL), + ti(c), in_transaction(false) + { + sock = new RedisSocket(this, host.find(':') != Anope::string::npos); + sock->Connect(host, port); + + sub = new RedisSocket(this, host.find(':') != Anope::string::npos); + sub->Connect(host, port); + } + + ~MyRedisService() + { + if (sock) + { + sock->flags[SF_DEAD] = true; + sock->provider = NULL; + } + + if (sub) + { + sub->flags[SF_DEAD] = true; + sub->provider = NULL; + } + } + + private: + inline void Pack(std::vector &buffer, const char *buf, size_t sz = 0) + { + if (!sz) + sz = strlen(buf); + + size_t old_size = buffer.size(); + buffer.resize(old_size + sz); + std::copy(buf, buf + sz, buffer.begin() + old_size); + } + + void Send(RedisSocket *s, Interface *i, const std::vector > &args) + { + std::vector buffer; + + Pack(buffer, "*"); + Pack(buffer, stringify(args.size()).c_str()); + Pack(buffer, "\r\n"); + + for (unsigned j = 0; j < args.size(); ++j) + { + const std::pair &pair = args[j]; + + Pack(buffer, "$"); + Pack(buffer, stringify(pair.second).c_str()); + Pack(buffer, "\r\n"); + + Pack(buffer, pair.first, pair.second); + Pack(buffer, "\r\n"); + } + + if (buffer.empty()) + return; + + s->Write(&buffer[0], buffer.size()); + if (in_transaction) + { + ti.interfaces.push_back(i); + s->interfaces.push_back(NULL); // For the +Queued response + } + else + s->interfaces.push_back(i); + } + + public: + void SendCommand(RedisSocket *s, Interface *i, const std::vector &cmds) + { + std::vector > args; + for (unsigned j = 0; j < cmds.size(); ++j) + args.push_back(std::make_pair(cmds[j].c_str(), cmds[j].length())); + this->Send(s, i, args); + } + + void Send(Interface *i, const std::vector > &args) + { + if (!sock) + { + sock = new RedisSocket(this, host.find(':') != Anope::string::npos); + sock->Connect(host, port); + } + + this->Send(sock, i, args); + } + + void SendCommand(Interface *i, const std::vector &cmds) override + { + std::vector > args; + for (unsigned j = 0; j < cmds.size(); ++j) + args.push_back(std::make_pair(cmds[j].c_str(), cmds[j].length())); + this->Send(i, args); + } + + void SendCommand(Interface *i, const Anope::string &str) override + { + std::vector args; + spacesepstream(str).GetTokens(args); + this->SendCommand(i, args); + } + + bool BlockAndProcess() override + { + this->sock->ProcessWrite(); + this->sock->SetBlocking(true); + this->sock->ProcessRead(); + this->sock->SetBlocking(false); + return !this->sock->interfaces.empty(); + } + + void Subscribe(Interface *i, const Anope::string &ch) override + { + if (sub == NULL) + { + sub = new RedisSocket(this, host.find(':') != Anope::string::npos); + sub->Connect(host, port); + } + + std::vector args = { "SUBSCRIBE", ch }; + this->SendCommand(sub, NULL, args); + + sub->subinterfaces[ch] = i; + } + + void Unsubscribe(const Anope::string &pattern) override + { + if (sub) + sub->subinterfaces.erase(pattern); + } + + void StartTransaction() override + { + if (in_transaction) + throw CoreException(); + + this->SendCommand(NULL, "MULTI"); + in_transaction = true; + } + + void CommitTransaction() override + { + /* The result of the transaction comes back to the reply of EXEC as a multi bulk. + * The reply to the individual commands that make up the transaction when executed + * is a simple +QUEUED + */ + in_transaction = false; + this->SendCommand(&this->ti, "EXEC"); + } +}; + +RedisSocket::~RedisSocket() +{ + if (provider) + { + if (provider->sock == this) + provider->sock = NULL; + else if (provider->sub == this) + provider->sub = NULL; + } + + for (unsigned i = 0; i < interfaces.size(); ++i) + { + Interface *inter = interfaces[i]; + + if (!inter) + continue; + + inter->OnError("Interface going away"); + } +} + +void RedisSocket::OnConnect() +{ + Log() << "redis: Successfully connected to " << provider->name << (this == this->provider->sub ? " (sub)" : ""); + + this->provider->SendCommand(NULL, "CLIENT SETNAME Anope"); + this->provider->SendCommand(NULL, "SELECT " + stringify(provider->db)); +} + +void RedisSocket::OnError(const Anope::string &error) +{ + Log() << "redis: Error on " << provider->name << (this == this->provider->sub ? " (sub)" : "") << ": " << error; +} + +size_t RedisSocket::ParseReply(Reply &r, const char *buffer, size_t l) +{ + size_t used = 0; + + if (!l) + return used; + + if (r.type == Reply::MULTI_BULK) + goto multi_bulk_cont; + + switch (*buffer) + { + case '+': + { + Anope::string reason(buffer, 1, l - 1); + size_t nl = reason.find("\r\n"); + Log(LOG_DEBUG_2) << "redis: status ok: " << reason.substr(0, nl); + if (nl != Anope::string::npos) + { + r.type = Reply::OK; + used = 1 + nl + 2; + } + break; + } + case '-': + { + Anope::string reason(buffer, 1, l - 1); + size_t nl = reason.find("\r\n"); + Log(LOG_DEBUG) << "redis: status error: " << reason.substr(0, nl); + if (nl != Anope::string::npos) + { + r.type = Reply::NOT_OK; + used = 1 + nl + 2; + } + break; + } + case ':': + { + Anope::string ibuf(buffer, 1, l - 1); + size_t nl = ibuf.find("\r\n"); + if (nl != Anope::string::npos) + { + try + { + r.i = convertTo(ibuf.substr(0, nl)); + } + catch (const ConvertException &) { } + + r.type = Reply::INT; + used = 1 + nl + 2; + } + break; + } + case '$': + { + Anope::string reply(buffer + 1, l - 1); + /* This assumes one bulk can always fit in our recv buffer */ + size_t nl = reply.find("\r\n"); + if (nl != Anope::string::npos) + { + int len; + try + { + len = convertTo(reply.substr(0, nl)); + if (len >= 0) + { + if (1 + nl + 2 + len + 2 <= l) + { + used = 1 + nl + 2 + len + 2; + r.bulk = reply.substr(nl + 2, len); + r.type = Reply::BULK; + } + } + else + { + used = 1 + nl + 2 + 2; + r.type = Reply::BULK; + } + } + catch (const ConvertException &) { } + } + break; + } + multi_bulk_cont: + case '*': + { + if (r.type != Reply::MULTI_BULK) + { + Anope::string reply(buffer + 1, l - 1); + size_t nl = reply.find("\r\n"); + if (nl != Anope::string::npos) + { + r.type = Reply::MULTI_BULK; + try + { + r.multi_bulk_size = convertTo(reply.substr(0, nl)); + } + catch (const ConvertException &) { } + + used = 1 + nl + 2; + } + else + break; + } + else if (r.multi_bulk_size >= 0 && r.multi_bulk.size() == static_cast(r.multi_bulk_size)) + { + /* This multi bulk is already complete, so check the sub bulks */ + for (unsigned i = 0; i < r.multi_bulk.size(); ++i) + if (r.multi_bulk[i]->type == Reply::MULTI_BULK) + ParseReply(*r.multi_bulk[i], buffer + used, l - used); + break; + } + + for (int i = r.multi_bulk.size(); i < r.multi_bulk_size; ++i) + { + Reply *reply = new Reply(); + size_t u = ParseReply(*reply, buffer + used, l - used); + if (!u) + { + Log(LOG_DEBUG) << "redis: ran out of data to parse"; + delete reply; + break; + } + r.multi_bulk.push_back(reply); + used += u; + } + break; + } + default: + Log(LOG_DEBUG) << "redis: unknown reply " << *buffer; + } + + return used; +} + +bool RedisSocket::Read(const char *buffer, size_t l) +{ + static std::vector save; + std::vector copy; + + if (!save.empty()) + { + std::copy(buffer, buffer + l, std::back_inserter(save)); + + copy = save; + + buffer = ©[0]; + l = copy.size(); + } + + while (l) + { + static Reply r; + + size_t used = this->ParseReply(r, buffer, l); + if (!used) + { + Log(LOG_DEBUG) << "redis: used == 0 ?"; + r.Clear(); + break; + } + else if (used > l) + { + Log(LOG_DEBUG) << "redis: used > l ?"; + r.Clear(); + break; + } + + /* Full result is not here yet */ + if (r.type == Reply::MULTI_BULK && static_cast(r.multi_bulk_size) != r.multi_bulk.size()) + { + buffer += used; + l -= used; + break; + } + + if (this == provider->sub) + { + std::map::iterator it = this->subinterfaces.find(r.multi_bulk[1]->bulk); + if (it != this->subinterfaces.end()) + it->second->OnResult(r); + } + else + { + if (this->interfaces.empty()) + { + Log(LOG_DEBUG) << "redis: no interfaces?"; + } + else + { + Interface *i = this->interfaces.front(); + this->interfaces.pop_front(); + + if (i) + { + if (r.type != Reply::NOT_OK) + i->OnResult(r); + else + i->OnError(r.bulk); + } + } + } + + buffer += used; + l -= used; + + r.Clear(); + } + + if (l) + { + save.resize(l); + std::copy(buffer, buffer + l, save.begin()); + } + else + std::vector().swap(save); + + return true; +} + + +class ModuleRedis : public Module + , public EventHook +{ + std::map services; + + public: + ModuleRedis(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + { + } + + ~ModuleRedis() + { + for (std::map::iterator it = services.begin(); it != services.end(); ++it) + { + MyRedisService *p = it->second; + + delete p->sock; + p->sock = NULL; + delete p->sub; + p->sub = NULL; + + delete p; + } + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + std::vector new_services; + + for (int i = 0; i < block->CountBlock("redis"); ++i) + { + Configuration::Block *redis = block->GetBlock("redis", i); + + const Anope::string &n = redis->Get("name"), + &ip = redis->Get("ip"); + int port = redis->Get("port"); + unsigned db = redis->Get("db"); + + delete services[n]; + services[n] = new MyRedisService(this, n, ip, port, db); + new_services.push_back(n); + } + + for (std::map::iterator it = services.begin(); it != services.end();) + { + Provider *p = it->second; + ++it; + + if (std::find(new_services.begin(), new_services.end(), p->name) == new_services.end()) + delete it->second; + } + } + + void OnModuleUnload(User *, Module *m) override + { + for (std::map::iterator it = services.begin(); it != services.end(); ++it) + { + MyRedisService *p = it->second; + + if (p->sock) + for (unsigned i = p->sock->interfaces.size(); i > 0; --i) + { + Interface *inter = p->sock->interfaces[i - 1]; + + if (inter && inter->owner == m) + { + inter->OnError(m->name + " being unloaded"); + p->sock->interfaces.erase(p->sock->interfaces.begin() + i - 1); + } + } + + if (p->sub) + for (unsigned i = p->sub->interfaces.size(); i > 0; --i) + { + Interface *inter = p->sub->interfaces[i - 1]; + + if (inter && inter->owner == m) + { + inter->OnError(m->name + " being unloaded"); + p->sub->interfaces.erase(p->sub->interfaces.begin() + i - 1); + } + } + + for (unsigned i = p->ti.interfaces.size(); i > 0; --i) + { + Interface *inter = p->ti.interfaces[i - 1]; + + if (inter && inter->owner == m) + { + inter->OnError(m->name + " being unloaded"); + p->ti.interfaces.erase(p->ti.interfaces.begin() + i - 1); + } + } + } + } +}; + +MODULE_INIT(ModuleRedis) diff --git a/modules/rewrite.cpp b/modules/rewrite.cpp new file mode 100644 index 000000000..37c58d5a9 --- /dev/null +++ b/modules/rewrite.cpp @@ -0,0 +1,187 @@ +/* + * (C) 2003-2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" + +struct Rewrite +{ + Anope::string client, source_message, target_message, desc; + + bool Matches(const std::vector &message) + { + std::vector sm; + spacesepstream(this->source_message).GetTokens(sm); + + for (unsigned i = 0; i < sm.size(); ++i) + if (i >= message.size() || (sm[i] != "$" && !sm[i].equals_ci(message[i]))) + return false; + + return true; + } + + Anope::string Process(CommandSource &source, const std::vector ¶ms) + { + spacesepstream sep(this->target_message); + Anope::string token, message; + + while (sep.GetToken(token)) + { + if (token[0] != '$') + message += " " + token; + else if (token == "$me") + message += " " + source.GetNick(); + else + { + int num = -1, end = -1; + try + { + Anope::string num_str = token.substr(1); + size_t hy = num_str.find('-'); + if (hy == Anope::string::npos) + { + num = convertTo(num_str); + end = num + 1; + } + else + { + num = convertTo(num_str.substr(0, hy)); + if (hy == num_str.length() - 1) + end = params.size(); + else + end = convertTo(num_str.substr(hy + 1)) + 1; + } + } + catch (const ConvertException &) + { + continue; + } + + for (int i = num; i < end && static_cast(i) < params.size(); ++i) + message += " " + params[i]; + } + } + + message.trim(); + return message; + } + + static std::vector rewrites; + + static Rewrite *Find(const Anope::string &client, const Anope::string &cmd) + { + for (unsigned i = 0; i < rewrites.size(); ++i) + { + Rewrite &r = rewrites[i]; + + if ((client.empty() || r.client.equals_ci(client)) && (r.source_message.equals_ci(cmd) || r.source_message.find_ci(cmd + " ") == 0)) + return &r; + } + + return NULL; + } + + static Rewrite *Match(const Anope::string &client, const std::vector ¶ms) + { + for (unsigned i = 0; i < rewrites.size(); ++i) + { + Rewrite &r = rewrites[i]; + + if ((client.empty() || r.client.equals_ci(client)) && r.Matches(params)) + return &r; + } + + return NULL; + } +}; + +std::vector Rewrite::rewrites; + +class RewriteCommand : public Command +{ + public: + RewriteCommand(Module *creator) : Command(creator, "rewrite", 0, 0) { } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + std::vector full_params = params; + full_params.insert(full_params.begin(), source.command); + + Rewrite *r = Rewrite::Match(!source.c ? source.service->nick : "", full_params); + if (r != NULL) + { + Anope::string new_message = r->Process(source, full_params); + Log(LOG_DEBUG) << "m_rewrite: Rewrote '" << source.command << (!params.empty() ? " " + params[0] : "") << "' to '" << new_message << "' using '" << r->source_message << "'"; + source.service = ServiceBot::Find(r->client, true); + if (!source.service) + return; + Command::Run(source, new_message); + } + else + Log() << "m_rewrite: Unable to rewrite '" << source.command << (!params.empty() ? " " + params[0] : "") << "'"; + } + + void OnServHelp(CommandSource &source) override + { + Rewrite *r = Rewrite::Find(!source.c ? source.service->nick : "", source.command); + if (r != NULL && !r->desc.empty()) + { + this->SetDesc(r->desc); + Command::OnServHelp(source); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + Rewrite *r = Rewrite::Find(!source.c ? source.service->nick : "", source.command); + if (r != NULL && !r->desc.empty()) + { + source.Reply(r->desc); + size_t sz = r->target_message.find(' '); + source.Reply(_("This command is an alias to the command %s."), sz != Anope::string::npos ? r->target_message.substr(0, sz).c_str() : r->target_message.c_str()); + return true; + } + return false; + } +}; + +class ModuleRewrite : public Module +{ + RewriteCommand cmdrewrite; + + public: + ModuleRewrite(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) + , cmdrewrite(this) + { + } + + void OnReload(Configuration::Conf *conf) override + { + Rewrite::rewrites.clear(); + + for (int i = 0; i < conf->CountBlock("command"); ++i) + { + Configuration::Block *block = conf->GetBlock("command", i); + + if (!block->Get("rewrite")) + continue; + + Rewrite rw; + + rw.client = block->Get("service"); + rw.source_message = block->Get("rewrite_source"); + rw.target_message = block->Get("rewrite_target"); + rw.desc = block->Get("rewrite_description"); + + if (rw.client.empty() || rw.source_message.empty() || rw.target_message.empty()) + continue; + + Rewrite::rewrites.push_back(rw); + } + } +}; + +MODULE_INIT(ModuleRewrite) diff --git a/modules/sasl.cpp b/modules/sasl.cpp new file mode 100644 index 000000000..f12cae8b8 --- /dev/null +++ b/modules/sasl.cpp @@ -0,0 +1,287 @@ +/* + * + * (C) 2014 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" +#include "modules/sasl.h" +#include "modules/ns_cert.h" + +using namespace SASL; + +class Plain : public Mechanism +{ + public: + Plain(Module *o) : Mechanism(o, "PLAIN") { } + + void ProcessMessage(Session *sess, const SASL::Message &m) override + { + if (m.type == "S") + { + sasl->SendMessage(sess, "C", "+"); + } + else if (m.type == "C") + { + Anope::string decoded; + Anope::B64Decode(m.data, decoded); + + size_t p = decoded.find('\0'); + if (p == Anope::string::npos) + { + sasl->Fail(sess); + delete sess; + return; + } + decoded = decoded.substr(p + 1); + + p = decoded.find('\0'); + if (p == Anope::string::npos) + { + sasl->Fail(sess); + delete sess; + return; + } + + Anope::string acc = decoded.substr(0, p), + pass = decoded.substr(p + 1); + + if (!NickServ::service || acc.empty() || pass.empty() || !IRCD->IsNickValid(acc) || pass.find_first_of("\r\n") != Anope::string::npos) + { + sasl->Fail(sess); + delete sess; + return; + } + + NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new IdentifyRequestListener(m.source), this->owner, acc, pass); + Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req); + req->Dispatch(); + } + } +}; + +class External : public Mechanism +{ + ServiceReference certs; + + struct Session : SASL::Session + { + Anope::string cert; + + Session(Mechanism *m, const Anope::string &u) : SASL::Session(m, u) { } + }; + + public: + External(Module *o) : Mechanism(o, "EXTERNAL"), certs("CertService", "certs") + { + if (!IRCD || !IRCD->CanCertFP) + throw ModuleException("No CertFP"); + } + + Session* CreateSession(const Anope::string &uid) override + { + return new Session(this, uid); + } + + void ProcessMessage(SASL::Session *sess, const SASL::Message &m) override + { + Session *mysess = anope_dynamic_static_cast(sess); + + if (m.type == "S") + { + mysess->cert = m.ext; + + sasl->SendMessage(sess, "C", "+"); + } + else if (m.type == "C") + { + if (!certs) + { + sasl->Fail(sess); + delete sess; + return; + } + + NickServ::Account *nc = certs->FindAccountFromCert(mysess->cert); + if (!nc || nc->HasFieldS("NS_SUSPENDED")) + { + sasl->Fail(sess); + delete sess; + return; + } + + Log(Config->GetClient("NickServ")) << "A user identified to account " << nc->GetDisplay() << " using SASL EXTERNAL"; + sasl->Succeed(sess, nc); + delete sess; + } + } +}; + +class SASLService : public SASL::Service, public Timer +{ + std::map sessions; + + public: + SASLService(Module *o) : SASL::Service(o), Timer(o, 60, Anope::CurTime, true) { } + + ~SASLService() + { + for (std::map::iterator it = sessions.begin(); it != sessions.end(); it++) + delete it->second; + } + + void ProcessMessage(const SASL::Message &m) override + { + if (m.target != "*") + { + Server *s = Server::Find(m.target); + if (s != Me) + { + User *u = User::Find(m.target); + if (!u || u->server != Me) + return; + } + } + + Session* &session = sessions[m.source]; + + if (m.type == "S") + { + ServiceReference mech("SASL::Mechanism", m.data); + if (!mech) + { + Session tmp(NULL, m.source); + + sasl->SendMechs(&tmp); + sasl->Fail(&tmp); + return; + } + + if (!session) + session = mech->CreateSession(m.source); + } + else if (m.type == "D") + { + delete session; + sessions.erase(m.source); + return; + } + + if (session && session->mech) + session->mech->ProcessMessage(session, m); + } + + Anope::string GetAgent() override + { + Anope::string agent = Config->GetModule(Service::owner)->Get("agent", "NickServ"); + ServiceBot *bi = Config->GetClient(agent); + if (bi) + agent = bi->GetUID(); + return agent; + } + + Session* GetSession(const Anope::string &uid) override + { + std::map::iterator it = sessions.find(uid); + if (it != sessions.end()) + return it->second; + return NULL; + } + + void RemoveSession(Session *sess) override + { + sessions.erase(sess->uid); + } + + void DeleteSessions(Mechanism *mech, bool da) override + { + for (std::map::iterator it = sessions.begin(); it != sessions.end();) + { + std::map::iterator del = it++; + if (*del->second->mech == mech) + { + if (da) + this->SendMessage(del->second, "D", "A"); + delete del->second; + } + } + } + + void SendMessage(Session *session, const Anope::string &mtype, const Anope::string &data) override + { + SASL::Message msg; + msg.source = this->GetAgent(); + msg.target = session->uid; + msg.type = mtype; + msg.data = data; + + IRCD->SendSASLMessage(msg); + } + + void Succeed(Session *session, NickServ::Account *nc) override + { + IRCD->SendSVSLogin(session->uid, nc->GetDisplay()); + this->SendMessage(session, "D", "S"); + } + + void Fail(Session *session) override + { + this->SendMessage(session, "D", "F"); + } + + void SendMechs(Session *session) override + { + std::vector mechs = Service::GetServiceKeys("SASL::Mechanism"); + Anope::string buf; + for (unsigned j = 0; j < mechs.size(); ++j) + buf += "," + mechs[j]; + + this->SendMessage(session, "M", buf.empty() ? "" : buf.substr(1)); + } + + void Tick(time_t) override + { + for (std::map::iterator it = sessions.begin(); it != sessions.end();) + { + Anope::string key = it->first; + Session *s = it->second; + ++it; + + if (!s || !s->mech || s->created + 60 < Anope::CurTime) + { + delete s; + sessions.erase(key); + } + } + } +}; + +class ModuleSASL : public Module +{ + SASLService sasl; + + Plain plain; + External *external; + + public: + ModuleSASL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , sasl(this) + , plain(this) + , external(NULL) + { + try + { + external = new External(this); + } + catch (ModuleException &) { } + } + + ~ModuleSASL() + { + delete external; + } +}; + +MODULE_INIT(ModuleSASL) diff --git a/modules/xmlrpc.cpp b/modules/xmlrpc.cpp new file mode 100644 index 000000000..2d1cd2c0e --- /dev/null +++ b/modules/xmlrpc.cpp @@ -0,0 +1,221 @@ + +#include "module.h" +#include "modules/xmlrpc.h" +#include "modules/httpd.h" + +static struct special_chars +{ + Anope::string character; + Anope::string replace; + + special_chars(const Anope::string &c, const Anope::string &r) : character(c), replace(r) { } +} +special[] = { + special_chars("&", "&"), + special_chars("\"", """), + special_chars("<", "<"), + special_chars(">", "&qt;"), + special_chars("'", "'"), + special_chars("\n", " "), + special_chars("\002", ""), // bold + special_chars("\003", ""), // color + special_chars("\035", ""), // italics + special_chars("\037", ""), // underline + special_chars("\026", ""), // reverses + special_chars("", "") +}; + +class MyXMLRPCServiceInterface : public XMLRPCServiceInterface, public HTTPPage +{ + std::deque events; + + public: + MyXMLRPCServiceInterface(Module *creator, const Anope::string &sname) : XMLRPCServiceInterface(creator, sname), HTTPPage("/xmlrpc", "text/xml") { } + + void Register(XMLRPCEvent *event) + { + this->events.push_back(event); + } + + void Unregister(XMLRPCEvent *event) + { + std::deque::iterator it = std::find(this->events.begin(), this->events.end(), event); + + if (it != this->events.end()) + this->events.erase(it); + } + + Anope::string Sanitize(const Anope::string &string) override + { + Anope::string ret = string; + for (int i = 0; special[i].character.empty() == false; ++i) + ret = ret.replace_all_cs(special[i].character, special[i].replace); + return ret; + } + + static Anope::string Unescape(const Anope::string &string) + { + Anope::string ret = string; + for (int i = 0; special[i].character.empty() == false; ++i) + if (!special[i].replace.empty()) + ret = ret.replace_all_cs(special[i].replace, special[i].character); + + for (size_t i, last = 0; (i = string.find("&#", last)) != Anope::string::npos;) + { + last = i + 1; + + size_t end = string.find(';', i); + if (end == Anope::string::npos) + break; + + Anope::string ch = string.substr(i + 2, end - (i + 2)); + + if (ch.empty()) + continue; + + long l; + if (!ch.empty() && ch[0] == 'x') + l = strtol(ch.substr(1).c_str(), NULL, 16); + else + l = strtol(ch.c_str(), NULL, 10); + + if (l > 0 && l < 256) + ret = ret.replace_all_cs("&#" + ch + ";", Anope::string(l)); + } + + return ret; + } + + private: + static bool GetData(Anope::string &content, Anope::string &tag, Anope::string &data) + { + if (content.empty()) + return false; + + Anope::string prev, cur; + bool istag; + + do + { + prev = cur; + cur.clear(); + + int len = 0; + istag = false; + + if (content[0] == '<') + { + len = content.find_first_of('>'); + istag = true; + } + else if (content[0] != '>') + { + len = content.find_first_of('<'); + } + + if (len) + { + if (istag) + { + cur = content.substr(1, len - 1); + content.erase(0, len + 1); + while (!content.empty() && content[0] == ' ') + content.erase(content.begin()); + } + else + { + cur = content.substr(0,len); + content.erase(0, len); + } + } + } + while (istag && !content.empty()); + + tag = Unescape(prev); + data = Unescape(cur); + return !istag && !data.empty(); + } + + public: + bool OnRequest(HTTPProvider *provider, const Anope::string &page_name, HTTPClient *client, HTTPMessage &message, HTTPReply &reply) override + { + Anope::string content = message.content, tname, data; + XMLRPCRequest request(reply); + + while (GetData(content, tname, data)) + { + Log(LOG_DEBUG) << "m_xmlrpc: Tag name: " << tname << ", data: " << data; + if (tname == "methodName") + request.name = data; + else if (tname == "name" && data == "id") + { + GetData(content, tname, data); + request.id = data; + } + else if (tname == "string") + request.data.push_back(data); + } + + for (unsigned i = 0; i < this->events.size(); ++i) + { + XMLRPCEvent *e = this->events[i]; + + if (!e->Run(this, client, request)) + return false; + else if (!request.get_replies().empty()) + { + this->Reply(request); + return true; + } + } + + reply.error = HTTP_PAGE_NOT_FOUND; + reply.Write("Unrecognized query"); + return true; + } + + void Reply(XMLRPCRequest &request) + { + if (!request.id.empty()) + request.reply("id", request.id); + + Anope::string r = "\n\n\n\n\n\n"; + for (std::map::const_iterator it = request.get_replies().begin(); it != request.get_replies().end(); ++it) + r += "\n" + it->first + "\n\n" + this->Sanitize(it->second) + "\n\n\n"; + r += "\n\n\n\n"; + + request.r.Write(r); + } +}; + +class ModuleXMLRPC : public Module +{ + ServiceReference httpref; + public: + MyXMLRPCServiceInterface xmlrpcinterface; + + ModuleXMLRPC(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR) + , xmlrpcinterface(this, "xmlrpc") + { + + } + + ~ModuleXMLRPC() + { + if (httpref) + httpref->UnregisterPage(&xmlrpcinterface); + } + + void OnReload(Configuration::Conf *conf) override + { + if (httpref) + httpref->UnregisterPage(&xmlrpcinterface); + this->httpref = ServiceReference("HTTPProvider", conf->GetModule(this)->Get("server", "httpd/main")); + if (!httpref) + throw ConfigException("Unable to find http reference, is m_httpd loaded?"); + httpref->RegisterPage(&xmlrpcinterface); + } +}; + +MODULE_INIT(ModuleXMLRPC) + diff --git a/modules/xmlrpc_main.cpp b/modules/xmlrpc_main.cpp new file mode 100644 index 000000000..af0253028 --- /dev/null +++ b/modules/xmlrpc_main.cpp @@ -0,0 +1,279 @@ +#include "module.h" +#include "modules/xmlrpc.h" + +static Module *me; + +class XMLRPCIdentifyRequest : public NickServ::IdentifyRequestListener +{ + XMLRPCRequest request; + HTTPReply repl; /* Request holds a reference to the HTTPReply, because we might exist long enough to invalidate it + we'll copy it here then reset the reference before we use it */ + Reference client; + Reference xinterface; + + public: + XMLRPCIdentifyRequest(XMLRPCRequest& req, HTTPClient *c, XMLRPCServiceInterface* iface) : request(req), repl(request.r), client(c), xinterface(iface) { } + + void OnSuccess(NickServ::IdentifyRequest *req) override + { + if (!xinterface || !client) + return; + + request.r = this->repl; + + request.reply("result", "Success"); + request.reply("account", req->GetAccount()); + + xinterface->Reply(request); + client->SendReply(&request.r); + } + + void OnFail(NickServ::IdentifyRequest *req) override + { + if (!xinterface || !client) + return; + + request.r = this->repl; + + request.reply("error", "Invalid password"); + + xinterface->Reply(request); + client->SendReply(&request.r); + } +}; + +class MyXMLRPCEvent : public XMLRPCEvent +{ + public: + bool Run(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) override + { + if (request.name == "command") + this->DoCommand(iface, client, request); + else if (request.name == "checkAuthentication") + return this->DoCheckAuthentication(iface, client, request); + else if (request.name == "stats") + this->DoStats(iface, client, request); + else if (request.name == "channel") + this->DoChannel(iface, client, request); + else if (request.name == "user") + this->DoUser(iface, client, request); + else if (request.name == "opers") + this->DoOperType(iface, client, request); + + return true; + } + + private: + void DoCommand(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) + { + Anope::string service = request.data.size() > 0 ? request.data[0] : ""; + Anope::string user = request.data.size() > 1 ? request.data[1] : ""; + Anope::string command = request.data.size() > 2 ? request.data[2] : ""; + + if (service.empty() || user.empty() || command.empty()) + request.reply("error", "Invalid parameters"); + else + { + ServiceBot *bi = ServiceBot::Find(service, true); + if (!bi) + request.reply("error", "Invalid service"); + else + { + request.reply("result", "Success"); + + NickServ::Nick *na = NickServ::FindNick(user); + + Anope::string out; + + struct XMLRPCommandReply : CommandReply + { + Anope::string &str; + + XMLRPCommandReply(Anope::string &s) : str(s) { } + + void SendMessage(const MessageSource &, const Anope::string &msg) override + { + str += msg + "\n"; + }; + } + reply(out); + + CommandSource source(user, NULL, na ? na->GetAccount() : NULL, &reply, bi); + Command::Run(source, command); + + if (!out.empty()) + request.reply("return", iface->Sanitize(out)); + } + } + } + + bool DoCheckAuthentication(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) + { + Anope::string username = request.data.size() > 0 ? request.data[0] : ""; + Anope::string password = request.data.size() > 1 ? request.data[1] : ""; + + if (username.empty() || password.empty() || !NickServ::service) + request.reply("error", "Invalid parameters"); + else + { + NickServ::IdentifyRequest *req = NickServ::service->CreateIdentifyRequest(new XMLRPCIdentifyRequest(request, client, iface), me, username, password); + Event::OnCheckAuthentication(&Event::CheckAuthentication::OnCheckAuthentication, nullptr, req); + req->Dispatch(); + return false; + } + + return true; + } + + void DoStats(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) + { + request.reply("uptime", stringify(Anope::CurTime - Anope::StartTime)); + request.reply("uplinkname", Me->GetLinks().front()->GetName()); + { + Anope::string buf; + for (std::set::iterator it = Servers::Capab.begin(); it != Servers::Capab.end(); ++it) + buf += " " + *it; + if (!buf.empty()) + buf.erase(buf.begin()); + request.reply("uplinkcapab", buf); + } + request.reply("usercount", stringify(UserListByNick.size())); + request.reply("maxusercount", stringify(MaxUserCount)); + request.reply("channelcount", stringify(ChannelList.size())); + } + + void DoChannel(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) + { + if (request.data.empty()) + return; + + Channel *c = Channel::Find(request.data[0]); + + request.reply("name", iface->Sanitize(c ? c->name : request.data[0])); + + if (c) + { + request.reply("bancount", stringify(c->HasMode("BAN"))); + int count = 0; + std::vector v = c->GetModeList("BAN"); + for (unsigned int i = 0; i < v.size(); ++i) + request.reply("ban" + stringify(++count), iface->Sanitize(v[i])); + + request.reply("exceptcount", stringify(c->HasMode("EXCEPT"))); + count = 0; + v = c->GetModeList("EXCEPT"); + for (unsigned int i = 0; i < v.size(); ++i) + request.reply("except" + stringify(++count), iface->Sanitize(v[i])); + + request.reply("invitecount", stringify(c->HasMode("INVITEOVERRIDE"))); + count = 0; + v = c->GetModeList("INVITEOVERRIDE"); + for (unsigned int i = 0; i < v.size(); ++i) + request.reply("invite" + stringify(++count), iface->Sanitize(v[i])); + + Anope::string users; + for (Channel::ChanUserList::const_iterator it = c->users.begin(); it != c->users.end(); ++it) + { + ChanUserContainer *uc = it->second; + users += uc->status.BuildModePrefixList() + uc->user->nick + " "; + } + if (!users.empty()) + { + users.erase(users.length() - 1); + request.reply("users", iface->Sanitize(users)); + } + + if (!c->topic.empty()) + request.reply("topic", iface->Sanitize(c->topic)); + + if (!c->topic_setter.empty()) + request.reply("topicsetter", iface->Sanitize(c->topic_setter)); + + request.reply("topictime", stringify(c->topic_time)); + request.reply("topicts", stringify(c->topic_ts)); + } + } + + void DoUser(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) + { + if (request.data.empty()) + return; + + User *u = User::Find(request.data[0]); + + request.reply("nick", iface->Sanitize(u ? u->nick : request.data[0])); + + if (u) + { + request.reply("ident", iface->Sanitize(u->GetIdent())); + request.reply("vident", iface->Sanitize(u->GetVIdent())); + request.reply("host", iface->Sanitize(u->host)); + if (!u->vhost.empty()) + request.reply("vhost", iface->Sanitize(u->vhost)); + if (!u->chost.empty()) + request.reply("chost", iface->Sanitize(u->chost)); + request.reply("ip", u->ip.addr()); + request.reply("timestamp", stringify(u->timestamp)); + request.reply("signon", stringify(u->signon)); + if (u->Account()) + { + request.reply("account", iface->Sanitize(u->Account()->GetDisplay())); + if (u->Account()->o) + request.reply("opertype", iface->Sanitize(u->Account()->o->GetType()->GetName())); + } + + Anope::string channels; + for (User::ChanUserList::const_iterator it = u->chans.begin(); it != u->chans.end(); ++it) + { + ChanUserContainer *cc = it->second; + channels += cc->status.BuildModePrefixList() + cc->chan->name + " "; + } + if (!channels.empty()) + { + channels.erase(channels.length() - 1); + request.reply("channels", channels); + } + } + } + + void DoOperType(XMLRPCServiceInterface *iface, HTTPClient *client, XMLRPCRequest &request) + { + for (unsigned i = 0; i < Config->MyOperTypes.size(); ++i) + { + OperType *ot = Config->MyOperTypes[i]; + Anope::string perms; + for (std::list::const_iterator it2 = ot->GetPrivs().begin(), it2_end = ot->GetPrivs().end(); it2 != it2_end; ++it2) + perms += " " + *it2; + for (std::list::const_iterator it2 = ot->GetCommands().begin(), it2_end = ot->GetCommands().end(); it2 != it2_end; ++it2) + perms += " " + *it2; + request.reply(ot->GetName(), perms); + } + } +}; + +class ModuleXMLRPCMain : public Module +{ + ServiceReference xmlrpc; + + MyXMLRPCEvent stats; + + public: + ModuleXMLRPCMain(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), xmlrpc("XMLRPCServiceInterface", "xmlrpc") + { + me = this; + + if (!xmlrpc) + throw ModuleException("Unable to find xmlrpc reference, is m_xmlrpc loaded?"); + + xmlrpc->Register(&stats); + } + + ~ModuleXMLRPCMain() + { + if (xmlrpc) + xmlrpc->Unregister(&stats); + } +}; + +MODULE_INIT(ModuleXMLRPCMain) + -- cgit