1
0
mirror of https://github.com/anope/anope.git synced 2026-06-22 04:16:38 +02:00

Compare commits

...

69 Commits

Author SHA1 Message Date
Sadie Powell 206192abdc Release 2.1.17. 2025-08-01 12:21:00 +01:00
Sadie Powell 32d3ddc0e4 Fix the grammar of two messages. 2025-07-30 15:30:44 +01:00
Sadie Powell 4410e5ccce Update the change logs. 2025-07-27 19:23:02 +01:00
Sadie Powell d5f2232140 Fix importing some older databases. 2025-07-27 15:42:32 +01:00
Sadie Powell 6d754b7d73 Add the intended target of the mode to CanSet. 2025-07-25 21:07:26 +01:00
Sadie Powell 4d2870fa45 Make it more clear that db_flatfile is deprecated. 2025-07-25 13:38:13 +01:00
Sadie Powell 5948c2ea53 Simplify the Windows module copying logic.
As far as I can tell from reading the Windows documentation there
is no reason to overcomplicate this so much.
2025-07-25 13:21:15 +01:00
Sadie Powell ace7d99797 Tweak the default ns_set_misc examples slightly.
- Discord is proprietary software so we shouldn't be encouraging
  its use. ;-)

- Time zone is going to be replaced by a future feature that allows
  users to get timestamps in their local time so I'm removing it
  now to prevent future conflict.

- Location is a good example of how this should be used for extra
  fields.
2025-07-25 12:53:47 +01:00
Sadie Powell 52595b90fa Rewrite nickserv/resend with some functionality improvements.
- Allow server operators to resend a confirmation email. Closes #518.
- Allow use of the command whilst unauthenticated.
2025-07-25 12:28:14 +01:00
Sadie Powell b39f002d1b Remove some unused files. 2025-07-21 19:14:34 +01:00
Sadie Powell 5df95d9f86 Remove a variant message which is only used in one place. 2025-07-12 16:33:14 +01:00
Sadie Powell a56d9a4096 Only show the last quit message if the user is not online. 2025-07-12 15:28:57 +01:00
Sadie Powell ce7bb15c18 Use IsIdentValid for validating usernames in hs_request. 2025-07-12 14:41:09 +01:00
Sadie Powell 6873630f2e Improve the "please identify" messages. 2025-07-08 16:24:16 +01:00
Sadie Powell 44a4d62654 Show the last real mask to services operators with nickserv/list. 2025-07-07 11:51:10 +01:00
Sadie Powell 97389cd105 Rename some fields to be more accurately named. 2025-07-07 11:51:10 +01:00
Sadie Powell 80c0adf7c8 Make a message consistent with others. 2025-07-02 14:08:04 +01:00
Sadie Powell 6a539277b9 Bump for 2.1.17-git. 2025-07-01 10:58:05 +01:00
Sadie Powell 9930fa01ac Release 2.1.16. 2025-07-01 10:56:40 +01:00
Sadie Powell 40929a0490 Update the change logs. 2025-07-01 10:13:36 +01:00
Sadie Powell 783be31f64 Update author list. 2025-07-01 09:48:19 +01:00
Sadie Powell 0f5f2aef2e Bump the minimum OpenSSL version to 1.1.1.
Close #517.
2025-07-01 00:00:55 +01:00
Sadie Powell 6cc997c4e9 When unsetting a temporary ban delete the unset timer.
This prevents Anope from unsetting a later-added ban,

Resolves MantisBT bug 1709.
2025-06-25 19:16:15 +01:00
Sadie Powell eda6d8cc0f Fix storing the setter of a list mode. 2025-06-25 19:10:49 +01:00
Sadie Powell 3440b38a21 Merge branch '2.0' into 2.1. 2025-06-25 17:50:01 +01:00
Sadie Powell fca421aa2a Fix resetpass confirming accounts when not using email confirmation.
Fixes MantisBT bug 1734.
2025-06-25 17:36:16 +01:00
Sadie Powell 035905d321 Fix example messages using .site which is a real TLD now. 2025-06-25 15:39:35 +01:00
Sadie Powell d1cd57d98e Resynchronise the en_US language file. 2025-06-25 15:34:01 +01:00
Sadie Powell 48daeeac1d Merge branch '2.0' into 2.1. 2025-06-25 15:33:36 +01:00
Sadie Powell 9bcf46f8ea Fix using service's instead of services' where appropriate. 2025-06-25 15:29:16 +01:00
Sadie Powell 0b6c7ce5d6 Update some messages for the language used by modern BIND versions. 2025-06-25 15:28:54 +01:00
Sadie Powell a0d21b207b Fix reading the purge time in cs_seen. 2025-06-25 14:08:44 +01:00
Sadie Powell 3cc5b5143f Remove the old 1.8-style seen system. 2025-06-25 14:06:59 +01:00
Sadie Powell da738126a4 Merge branch '2.0' into 2.1. 2025-06-25 12:07:36 +01:00
Sadie Powell 0bb1bc5c67 Backport various minor config changes from 2.1. 2025-06-25 12:00:33 +01:00
Sadie Powell 72010cd1a9 Fix messages that say IRC Operator when they mean Services Operator. 2025-06-24 13:58:47 +01:00
Sadie Powell c48b3af3d4 Remove an obsolete workaround from Config. 2025-06-24 09:29:33 +01:00
Sadie Powell 85c129701b Clarify how to migrate db_flatfile databases to db_json. 2025-06-23 23:39:20 +01:00
Sadie Powell a9e5a79e91 Remove some unused strings in os_news. 2025-06-22 15:45:21 +01:00
Sadie Powell adc1343d6c Serialize item types as strings instead of integers.
Using the type cast to an integer here was undefined behaviour.
2025-06-22 15:45:21 +01:00
Sadie Powell 8aa1102c7a Default to db_json. 2025-06-22 15:45:21 +01:00
TehPeGaSuS 1a89d32926 Document NS_STATS and CS_STATS. 2025-06-21 17:09:26 +01:00
Sadie Powell ad06853edf Fix suggesting unloaded commands in the "did you mean" message. 2025-06-21 09:56:02 +01:00
Sadie Powell 66ae20e0f2 Tweak a message to make it easier for ESL speakers to understand. 2025-06-21 09:48:01 +01:00
Sadie Powell 2850e3c65c Implement serializer hashing in db_json. 2025-06-21 00:31:02 +01:00
Sadie Powell 80b8856254 Consistently use "account" instead of "nick group". 2025-06-20 20:30:21 +01:00
Sadie Powell 21c8164539 When assigning a new display nick use the oldest not the first. 2025-06-20 20:27:39 +01:00
Sadie Powell a4abd27ffd Fix the grammar of a message in ns_confirm. 2025-06-20 19:43:02 +01:00
Sadie Powell b912b403f0 Fix a comment in CommandInfo. 2025-06-20 19:43:02 +01:00
Sadie Powell 59e9d47667 Update another message to use pluralisation. 2025-06-18 20:53:11 +01:00
Sadie Powell 1108e54250 We don't need to reserialize aliases now we use account identifiers. 2025-06-17 14:50:36 +01:00
Sadie Powell 8b37cdb5d5 Make the access description more useful when importing from Atheme. 2025-06-17 14:50:36 +01:00
Sadie Powell c5bff3a099 Fix various issues with language strings. 2025-06-16 12:30:42 +01:00
Sadie Powell 61b0c82884 Rework how confirmation works and make it modular. 2025-06-16 10:00:41 +01:00
Sadie Powell c4c159d197 Fix showing the MORE_INFO message. 2025-06-15 11:49:26 +01:00
Sadie Powell 04a32be1e1 Add support for code authentication via IRC.
This breaks spambots which try to register but that can't handle
reading the response from services.
2025-06-15 11:22:03 +01:00
Sadie Powell fe4b8ee669 When a command is missing in GetQueryCommand just return (MISSING). 2025-06-15 11:20:34 +01:00
Sadie Powell 34b451f36c The require_privilege option should default to yes not no. 2025-06-11 09:28:46 +01:00
Sadie Powell 16aff210fd Allow configuring fantasy commands to not require the FANTASY priv. 2025-06-10 16:05:11 +01:00
Sadie Powell 5702fb9145 Add a PREPEND subcommand to chanserv/topic. 2025-06-10 12:06:02 +01:00
Sadie Powell 783ba3fd74 Undocument DISCOURAGED; we do not use this anymore. 2025-06-08 16:21:08 +01:00
Sadie Powell f1ddd7cd02 Promote NickServ from OPTIONAL to RECOMMENDED.
Not sure why this wasn't already.
2025-06-08 16:09:43 +01:00
Sadie Powell c424c4d24d Document cs_statusupdate in the same way as other modules. 2025-06-08 12:22:45 +01:00
Sadie Powell 2d88383d9e Group some more chanserv commands. 2025-06-08 12:18:10 +01:00
Sadie Powell c73a6c621f Make the set_misc help format consistent with other set commands. 2025-06-07 13:27:30 +01:00
Sadie Powell 96a503b4d9 Fix a memory leak in the db_json module.
This reworks the module to store Data instead of a yyjson document
which does not free until yyjson_doc_free even if an array is cleared
(which annoyingly was only documented using a single comment in the
yyjson docs).
2025-06-05 22:01:45 +01:00
Sadie Powell 0632abd111 Banish irc2sql to the shadow realm. 2025-06-05 18:22:13 +01:00
TehPeGaSuS 1043e2189c Remove a reference to a config option that doesn't exist.
There's no `memoreceipt` anywhere, as far as I could check.
2025-06-01 15:29:35 +01:00
Sadie Powell 85f0d56c39 Bump for 2.1.16-git. 2025-06-01 10:14:44 +01:00
96 changed files with 1557 additions and 3075 deletions
+2 -10
View File
@@ -13,14 +13,6 @@
#
###########################################################################
exists () { # because some shells don't have test -e
if [ -f $1 -o -d $1 -o -p $1 -o -c $1 -o -b $1 ] ; then
return 0
else
return 1
fi
}
Load_Cache () {
if [ -f $SOURCE_DIR/config.cache -a -r $SOURCE_DIR/config.cache -a ! "$IGNORE_CACHE" ] ; then
echo "Using defaults from config.cache. To ignore, $SOURCE_DIR/Config -nocache"
@@ -208,7 +200,7 @@ while [ $ok -eq 0 ] ; do
INPUT=$INSTDIR
fi
if [ ! -d "$INPUT" ] ; then
if exists "$INPUT" ; then
if [ -e "$INPUT" ]; then
echo "$INPUT exists, but is not a directory!"
else
echo "$INPUT does not exist. Create it?"
@@ -220,7 +212,7 @@ while [ $ok -eq 0 ] ; do
fi
fi
fi
elif exists "$INPUT/include/services.h" ; then
elif [ -e "$INPUT/include/services.h" ]; then
echo "You cannot use the Anope source directory as a target directory."
else
ok=1
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
# Only install cron.example.sh and anope.example.conf from this directory
# NOTE: I would've had this just find all files in the directory, but that would include files not needed (like this file)
set(DATA cron.example.sh anope.example.conf botserv.example.conf hostserv.example.conf modules.example.conf operserv.example.conf chanserv.example.conf global.example.conf memoserv.example.conf nickserv.example.conf chanstats.example.conf irc2sql.example.conf stats.standalone.example.conf)
set(DATA cron.example.sh anope.example.conf botserv.example.conf hostserv.example.conf modules.example.conf operserv.example.conf chanserv.example.conf global.example.conf memoserv.example.conf nickserv.example.conf chanstats.example.conf)
install(FILES ${DATA}
DESTINATION ${CONF_DIR}
)
+20 -41
View File
@@ -65,10 +65,6 @@
* will typically be disabled. If this is not the case, more
* information will be given in the documentation.
*
* [DISCOURAGED]
* Indicates a directive which may cause undesirable side effects if
* specified.
*
* [DEPRECATED]
* Indicates a directive which will disappear in a future version of
* Anope, usually because its functionality has been either
@@ -347,7 +343,7 @@ networkinfo
*
* It is recommended you DON'T change this.
*/
disallow_start_or_end = ".-"
disallow_start_or_end = ".-/"
}
/*
@@ -587,7 +583,7 @@ include
}
/*
* [OPTIONAL] NickServ
* [RECOMMENDED] NickServ
*
* Includes nickserv.example.conf, which is necessary for NickServ functionality.
*
@@ -745,11 +741,13 @@ log
* nickserv/alist - Can see the channel access list of other users
* nickserv/auspex - Can see any information with /NICKSERV INFO
* nickserv/cert - Can modify other users certificate lists
* nickserv/confirm - Can confirm other users nicknames
* nickserv/confirm/email - Can confirm other users email address change
* nickserv/confirm/register - Can confirm other users account registration
* nickserv/drop - Can drop other users nicks
* nickserv/drop/display - Allows dropping display nicks when preservedisplay is enabled
* nickserv/drop/override - Allows dropping nicks without using a confirmation code
* nickserv/recover - Can recover other users nicks
* nickserv/resend - Can resend confirmation codes via email
* operserv/config - Can modify services's configuration
* operserv/oper/modify - Can add and remove operators with at most the same privileges
* protected - Can not be kicked from channels by services
@@ -814,10 +812,10 @@ opertype
inherits = "Helper, Another Helper"
/* What commands (see above) this opertype may use */
commands = "chanserv/list chanserv/suspend chanserv/topic memoserv/staff nickserv/list nickserv/suspend operserv/mode operserv/chankill operserv/akill operserv/session operserv/modinfo operserv/sqline operserv/oper operserv/kick operserv/ignore operserv/snline"
commands = "chanserv/list chanserv/suspend chanserv/topic memoserv/staff nickserv/list nickserv/resend nickserv/suspend operserv/mode operserv/chankill operserv/akill operserv/session operserv/modinfo operserv/sqline operserv/oper operserv/kick operserv/ignore operserv/snline"
/* What privs (see above) this opertype has */
privs = "chanserv/auspex chanserv/no-register-limit memoserv/* nickserv/auspex nickserv/confirm"
privs = "chanserv/auspex chanserv/no-register-limit memoserv/* nickserv/auspex nickserv/confirm/*"
/*
* Modes to be set on users when they identify to accounts linked to this opertype.
@@ -994,7 +992,7 @@ mail
registration_message = "Hi,
You have requested to register the nickname {nick} on {network}.
Please type \" /msg NickServ CONFIRM {code} \" to complete registration.
Please type \" /msg NickServ CONFIRM REGISTER {code} \" to complete registration.
If you don't know why this mail was sent to you, please ignore it silently.
@@ -1012,7 +1010,7 @@ mail
reset_message = "Hi,
You have requested to have the password for {nick} reset.
To reset your password, type \" /msg NickServ CONFIRM {nick} {code} \"
To reset your password, type \" /msg NickServ CONFIRM RESETPASS {nick} {code} \"
If you don't know why this mail was sent to you, please ignore it silently.
@@ -1032,7 +1030,7 @@ mail
emailchange_message = "Hi,
You have requested to change your email address from {old_email} to {new_email}.
Please type \" /msg NickServ CONFIRM {code} \" to confirm this change.
Please type \" /msg NickServ CONFIRM EMAIL {code} \" to confirm this change.
If you don't know why this mail was sent to you, please ignore it silently.
@@ -1104,11 +1102,17 @@ mail
}
/*
* [RECOMMENDED] db_flatfile
* [DEPRECATED] db_flatfile
*
* This is the default flatfile database format.
* Stores your database in a custom flatfile format.
*
* This was the recommended database module in 2.0 but it is now recommended
* that you use db_json instead. You can migrate your db_flatfile database to
* db_json by loading db_flatfile BEFORE db_json, sending SIGUSR1 to force a
* database write, and then unloading db_flatfile. This module will become
* import-only in a future release.
*/
module
#module
{
name = "db_flatfile"
@@ -1149,14 +1153,9 @@ module
}
/*
* db_json
* [RECOMMENDED] db_json
*
* Stores your database in a JSON file.
*
* IMPORTANT: This will become the default database module in the future but is
* currently experimental and has not been fully tested so make sure you have
* db_flatfile loaded as a secondary database module if you use this as your
* primary database module.
*/
module
{
@@ -1414,23 +1413,3 @@ include
type = "file"
name = "chanstats.example.conf"
}
/*
* [DEPRECATED] IRC2SQL Gateway
*
* This module collects data about users, channels and servers. It doesn't build stats
* itself, however, it gives you the database, it's up to you how you use it.
*
* This module is deprecated in favour of the RPC interface and the rpc_data
* module. This should provide almost all of the same information as irc2sql but
* without requiring MySQL and with much better performance. If you have a use
* case which is not covered by rpc_data then please open an issue so we can
* extend rpc_data before irc2sql is removed.
*
* Requires a MySQL Database and MySQL version 5.5 or higher
*/
#include
{
type = "file"
name = "irc2sql.example.conf"
}
+12 -19
View File
@@ -100,6 +100,7 @@ module
* not in use.
* - cs_no_expire: Enables no expire. Needs founder, successor (if set) or anyone in the access list
* to be a registered nick, otherwise the channel will be dropped.
* - cs_stats: Enable Chanstats for newly registered channels
* - none: No defaults
*
* This directive is optional, if left blank, the options will default to cs_keep_modes, keeptopic, peace,
@@ -946,7 +947,7 @@ command { service = "ChanServ"; name = "AKICK"; command = "chanserv/akick"; grou
* Used for banning users from channels.
*/
module { name = "cs_ban" }
command { service = "ChanServ"; name = "BAN"; command = "chanserv/ban"; }
command { service = "ChanServ"; name = "BAN"; command = "chanserv/ban"; group = "chanserv/management"; }
/*
* cs_clone
@@ -1047,7 +1048,7 @@ command { service = "ChanServ"; name = "INVITE"; command = "chanserv/invite"; }
* Used for kicking users from channels.
*/
module { name = "cs_kick" }
command { service = "ChanServ"; name = "KICK"; command = "chanserv/kick"; }
command { service = "ChanServ"; name = "KICK"; command = "chanserv/kick"; group = "chanserv/management"; }
/*
* cs_list
@@ -1178,9 +1179,6 @@ module
{
name = "cs_seen"
/* If set, uses the older 1.8 style seen, which is less resource intensive */
simple = no
/* Sets the time to keep seen entries in the seen database. */
purgetime = 90d
}
@@ -1271,6 +1269,14 @@ module { name = "cs_status" }
command { service = "ChanServ"; name = "STATUS"; command = "chanserv/status"; }
command { service = "ChanServ"; name = "WHY"; command = "chanserv/status"; hide = yes; }
/*
* cs_statusupdate
*
* This module automatically updates users' status on channels when the
* channel's access list is modified.
*/
module { name = "cs_statusupdate" }
/*
* cs_suspend
*
@@ -1330,7 +1336,7 @@ command { service = "ChanServ"; name = "SET KEEPTOPIC"; command = "chanserv/set/
* Used for unbanning users from channels.
*/
module { name = "cs_unban" }
command { service = "ChanServ"; name = "UNBAN"; command = "chanserv/unban"; }
command { service = "ChanServ"; name = "UNBAN"; command = "chanserv/unban"; group = "chanserv/management"; }
/*
* cs_updown
@@ -1363,16 +1369,3 @@ command { service = "ChanServ"; name = "SOP"; command = "chanserv/xop"; group =
command { service = "ChanServ"; name = "AOP"; command = "chanserv/xop"; group = "chanserv/access"; }
command { service = "ChanServ"; name = "HOP"; command = "chanserv/xop"; group = "chanserv/access"; }
command { service = "ChanServ"; name = "VOP"; command = "chanserv/xop"; group = "chanserv/access"; }
/*
* Extra ChanServ related modules.
*/
/*
* cs_statusupdate
*
* This module automatically updates users' status on channels when the
* channel's access list is modified.
*/
module { name = "cs_statusupdate" }
+1 -1
View File
@@ -93,7 +93,7 @@ module
#globaloncycleup = "Services are now back online - have a nice day"
/*
* If set, Anope will hide the IRC Operator's nick in a global
* If set, Anope will hide the Services Operator's nick in a global
* message/notice.
*
* This directive is optional.
-97
View File
@@ -1,97 +0,0 @@
/*
* Example configuration file for the irc2sql gateway
*
*/
service
{
/*
* The name of the StatServ client.
*/
nick = "StatServ"
/*
* The username of the StatServ client.
*/
user = "StatServ"
/*
* The hostname of the StatServ client.
*/
host = "${services.host}"
/*
* The realname of the StatServ client.
*/
real = "Statistical Service"
/*
* The modes this client should use.
* Do not modify this unless you know what you are doing.
*
* These modes are very IRCd specific. If left commented, sane defaults
* are used based on what protocol module you have loaded.
*
* Note that setting this option incorrectly could potentially BREAK some, if
* not all, usefulness of the client. We will not support you if this client is
* unable to do certain things if this option is enabled.
*/
#modes = "+o"
/*
* An optional comma separated list of channels this service should join. Outside
* of log channels this is not very useful, as the service will just idle in the
* specified channels, and will not accept any types of commands.
*
* Prefixes may be given to the channels in the form of mode characters or prefix symbols.
*/
#channels = "@#stats,#mychan"
}
module
{
name = "irc2sql"
/*
* The name of the client that should send the CTCP VERSION requests.
* It must already exist or must be defined in the following service block.
*/
client = "StatServ"
/*
* The name of the SQL engine to use.
* This must be MySQL and must match the name in the mysql{} block
*/
engine = "mysql/main"
/*
* An optional prefix to prepended to the name of each created table.
* Do not use the same prefix for other programs.
*/
prefix = "anope_"
/*
* GeoIP - Automatically adds users geoip location to the user table.
* Tables are created by irc2sql, you have to run the
* geoipupdate script after you started Anope to download
* and import the GeoIP database.
*
* The geoip database can be the smaller "country" database or the
* larger "city" database. Comment to disable geoip lookup.
*/
geoip_database = "country"
/*
* Get the CTCP version from users
* The users connecting to the network will receive a CTCP VERSION
* request from the above configured stats client
*/
ctcpuser = "yes"
/*
* Send out CTCP VERSION requests to users during burst.
* Disable this if you restart Anope often and don't want to
* annoy your users.
*/
ctcpeob = "yes"
}
-2
View File
@@ -196,8 +196,6 @@ command { service = "MemoServ"; name = "READ"; command = "memoserv/read"; }
* Provides the command memoserv/rsend.
*
* Used to send a memo requiring a receipt be sent back once it is read.
*
* Requires configuring memoserv:memoreceipt.
*/
#module
{
+16 -16
View File
@@ -65,7 +65,7 @@ module { name = "help" }
/* This should be the names of the public facing nameservers serving the records. */
nameservers = "ns1.example.com ns2.example.com"
/* The time slave servers are allowed to cache. This should be reasonably low
/* The time secondary servers are allowed to cache for. This should be reasonably low
* if you want your records to be updated without much delay.
*/
refresh = 3600
@@ -233,7 +233,7 @@ module { name = "help" }
}
/*
* ldap [EXTRA]
* [EXTRA] ldap
*
* This module allows other modules to use LDAP. By itself, this module does nothing useful.
*/
@@ -304,13 +304,13 @@ module { name = "help" }
*
* If not set, then registration is not blocked.
*/
#disable_register_reason = "To register on this network visit https://some.misconfigured.site/register"
#disable_register_reason = "To register on this network visit https://some.misconfigured.site.example/register"
/*
* If set, the reason to give the users who try to "/msg NickServ SET EMAIL".
* If not set, then email changing is not blocked.
*/
#disable_email_reason = "To change your email address visit https://some.misconfigured.site"
#disable_email_reason = "To change your email address visit https://some.misconfigured.site.example"
}
/*
@@ -355,7 +355,7 @@ module { name = "help" }
}
/*
* mysql [EXTRA]
* [EXTRA] mysql
*
* This module allows other modules to use MySQL.
*/
@@ -402,7 +402,7 @@ module { name = "help" }
}
/*
* regex_pcre2 [EXTRA]
* [EXTRA] regex_pcre2
*
* Provides the regex engine regex/pcre, which uses version 2 of the Perl Compatible Regular
* Expressions library.
@@ -410,7 +410,7 @@ module { name = "help" }
#module { name = "regex_pcre2" }
/*
* regex_posix [EXTRA]
* [EXTRA] regex_posix
*
* Provides the regex engine regex/posix, which uses the POSIX compliant regular expressions.
*/
@@ -440,7 +440,7 @@ module
}
/*
* regex_tre [EXTRA]
* [EXTRA] regex_tre
*
* Provides the regex engine regex/tre, which uses the TRE regex library.
*/
@@ -559,7 +559,7 @@ module
}
/*
* ssl_gnutls [EXTRA]
* [EXTRA] ssl_gnutls
*
* This module provides SSL services to Anope using GnuTLS, for example to
* connect to the uplink server(s) via SSL.
@@ -598,7 +598,7 @@ module
}
/*
* ssl_openssl [EXTRA]
* [EXTRA] ssl_openssl
*
* This module provides SSL services to Anope using OpenSSL, for example to
* connect to the uplink server(s) via SSL.
@@ -691,20 +691,20 @@ module
* If set, the reason to give the users who try to "/msg NickServ REGISTER".
* If not set, then registration is not blocked.
*/
#disable_reason = "To register on this network visit https://some.misconfigured.site/register"
#disable_reason = "To register on this network visit https://some.misconfigured.site.example/register"
/*
* If set, the reason to give the users who try to "/msg NickServ SET EMAIL".
* If not set, then email changing is not blocked.
*/
#disable_email_reason = "To change your email address visit https://some.misconfigured.site"
#disable_email_reason = "To change your email address visit https://some.misconfigured.site.example"
}
/*
* sql_log
*
* This module adds an additional target option to log{} blocks
* that allows logging Service's logs to SQL. To log to SQL, add
* that allows logging Services' logs to SQL. To log to SQL, add
* the SQL service name to log:targets prefixed by sql_log:. For
* example:
*
@@ -723,8 +723,8 @@ module
/*
* sql_oper
*
* This module allows granting users services operator privileges and possibly IRC Operator
* privileges based on an external SQL database using a custom query.
* This module allows granting users Services Operator privileges based on an
* external SQL database using a custom query.
*/
#module
{
@@ -747,7 +747,7 @@ module
}
/*
* sqlite [EXTRA]
* [EXTRA] sqlite
*
* This module allows other modules to use SQLite.
*/
+45 -15
View File
@@ -118,6 +118,7 @@ module
* - msg: Messages will be sent as PRIVMSGs instead of NOTICEs
* - ns_keep_modes: Enables keepmodes, which retains user modes across sessions
* - protect: Protects the registered nickname from use by unidentified users.
* - ns_stats: Enable Chanstats for newly registered nicks
*
* This directive is optional, if left blank, the options will default to memo_signon, and
* memo_receive. If you really want no defaults, use "none" by itself as the option.
@@ -250,8 +251,8 @@ module
maxpasslen = 50
/*
* Whether all of the secondary nicks of a nick group have to expire or be
dropped before the display nick can expire or be dropped.
* Whether all of the secondary nicks of an account have to expire or be
* dropped before the display nick can expire or be dropped.
*/
preservedisplay = no
}
@@ -335,6 +336,16 @@ module
}
command { service = "NickServ"; name = "CERT"; command = "nickserv/cert"; }
/*
* ns_confirm
*
* Provides the command nickserv/confirm.
*
* Used for confirming previous account actions.
*/
module { name = "ns_confirm" }
command { service = "NickServ"; name = "CONFIRM"; command = "nickserv/confirm"; }
/*
* ns_drop
*
@@ -351,6 +362,7 @@ command { service = "NickServ"; name = "DROP"; command = "nickserv/drop"; }
* Provides various functionality relating to email addresses. This includes the
* following commands:
*
* - nickserv/confirm/email: Used for confirming email changes.
* - nickserv/getemail: Used for getting accounts by searching for emails.
* - nickserv/set/email, nickserv/saset/email: Used for setting an account's
* emailvaddress.
@@ -359,6 +371,12 @@ module
{
name = "ns_email"
/*
* The amount of time a user has after requesting a change of email address
* before it expires. Defaults to 1 day.
*/
#changeexpire = 1d
/*
* The limit to how many registered accounts can use the same email address.
* If set to 0 or left commented there will be no limit enforced when
@@ -373,7 +391,7 @@ module
*/
#remove_aliases = yes
}
command { service = "NickServ"; name = "CONFIRM EMAIL"; command = "nickserv/confirm/email"; }
command { service = "NickServ"; name = "GETEMAIL"; command = "nickserv/getemail"; permission = "nickserv/getemail"; group = "nickserv/admin"; }
command { service = "NickServ"; name = "SET EMAIL"; command = "nickserv/set/email"; }
command { service = "NickServ"; name = "SASET EMAIL"; command = "nickserv/saset/email"; permission = "nickserv/saset/email"; }
@@ -383,7 +401,7 @@ command { service = "NickServ"; name = "SASET EMAIL"; command = "nickserv/saset/
*
* Provides the commands nickserv/group, nickserv/glist, and nickserv/ungroup.
*
* Used for controlling nick groups.
* Used for controlling grouped nicknames.
*/
module
{
@@ -504,7 +522,7 @@ command { service = "NickServ"; name = "RELEASE"; command = "nickserv/recover";
/*
* ns_register
*
* Provides the commands nickserv/confirm, nickserv/register, and nickserv/resend.
* Provides the commands nickserv/confirm/register, nickserv/register, and nickserv/resend.
*
* Used for registering accounts.
*/
@@ -513,9 +531,13 @@ module
name = "ns_register"
/*
* Registration confirmation setting. Set to "none" for no registration confirmation,
* "mail" for email confirmation, and "admin" to have services operators manually confirm
* every registration. Set to "disable" to completely disable all registrations.
* The method for confirming account registrations. Possible values are:
*
* "admin" to require confirmation by a Services Operator.
* "code" to require confirmation with a code provided via IRC.
* "disable" to disable account registration.
* "mail" to require confirmation with a code provided via email.
* "none" to automatically confirm (this is the default).
*/
registration = "none"
@@ -540,18 +562,28 @@ module
*/
#unconfirmedexpire = 1d
}
command { service = "NickServ"; name = "CONFIRM"; command = "nickserv/confirm"; }
command { service = "NickServ"; name = "CONFIRM REGISTER"; command = "nickserv/confirm/register"; }
command { service = "NickServ"; name = "REGISTER"; command = "nickserv/register"; }
command { service = "NickServ"; name = "RESEND"; command = "nickserv/resend"; }
/*
* ns_resetpass
*
* Provides the command nickserv/resetpass.
* Provides the command nickserv/confirm/resetpass and nickserv/resetpass.
*
* Used for resetting passwords by emailing users a temporary one.
*/
module { name = "ns_resetpass" }
module
{
name = "ns_resetpass"
/*
* The amount of time a user has after requesting a password reset before it
* expires. Defaults to 1 day.
*/
#resetexpire = 1d
}
command { service = "NickServ"; name = "CONFIRM RESETPASS"; command = "nickserv/confirm/resetpass"; }
command { service = "NickServ"; name = "RESETPASS"; command = "nickserv/resetpass"; }
/*
@@ -664,12 +696,10 @@ command { service = "NickServ"; name = "SASET LANGUAGE"; command = "nickserv/sas
module { name = "ns_set_misc" }
command { service = "NickServ"; name = "SET URL"; command = "nickserv/set/misc"; misc_description = _("Associate a URL with your account"); }
command { service = "NickServ"; name = "SASET URL"; command = "nickserv/saset/misc"; misc_description = _("Associate a URL with this account"); permission = "nickserv/saset/url"; group = "nickserv/admin"; }
#command { service = "NickServ"; name = "SET DISCORD"; command = "nickserv/set/misc"; misc_description = _("Associate a Discord account with your account"); }
#command { service = "NickServ"; name = "SASET DISCORD"; command = "nickserv/saset/misc"; misc_description = _("Associate a Discord account with this account"); permission = "nickserv/saset/discord"; group = "nickserv/admin"; }
#command { service = "NickServ"; name = "SET MASTODON"; command = "nickserv/set/misc"; misc_description = _("Associate a Mastodon account with your account"); }
#command { service = "NickServ"; name = "SASET MASTODON"; command = "nickserv/saset/misc"; misc_description = _("Associate a Mastodon account with this account"); permission = "nickserv/saset/mastodon"; group = "nickserv/admin"; }
#command { service = "NickServ"; name = "SET TIMEZONE"; command = "nickserv/set/misc"; misc_description = _("Associate a time zone with your account"); }
#command { service = "NickServ"; name = "SASET TIMEZONE"; command = "nickserv/saset/misc"; misc_description = _("Associate a time zone with this account"); permission = "nickserv/saset/timezone"; group = "nickserv/admin"; }
#command { service = "NickServ"; name = "SET LOCATION"; command = "nickserv/set/misc"; misc_description = _("Associate a location with your account"); }
#command { service = "NickServ"; name = "SASET LOCATION"; command = "nickserv/saset/misc"; misc_description = _("Associate a location with this account"); permission = "nickserv/saset/location"; group = "nickserv/admin"; }
/*
* ns_set_protect
+4 -4
View File
@@ -111,7 +111,7 @@ module
killonsqline = yes
/*
* Adds the nickname of the IRC Operator issuing an AKILL to the kill reason.
* Adds the nickname of the Services Operator issuing an AKILL to the kill reason.
*
* This directive is optional.
*/
@@ -287,15 +287,15 @@ command { service = "OperServ"; name = "CHANKILL"; command = "operserv/chankill"
* To use this module you must set a nameserver record for services
* so that DNS queries go to services.
*
* Alternatively, you may use a slave DNS server to hide service's IP,
* Alternatively, you may use a secondary DNS server to hide services' IP,
* provide query caching, and provide better fault tolerance.
*
* To do this using BIND, configure similar to:
*
* options { max-refresh-time 60; };
* zone "irc.example.com" IN {
* type slave;
* masters { 127.0.0.1 port 5353; };
* type secondary;
* primaries { 127.0.0.1 port 5353; };
* };
*
* Where 127.0.0.1:5353 is the IP and port services are listening on.
-514
View File
@@ -1,514 +0,0 @@
/*
* Example configuration file for Anope. After making the appropriate
* changes to this file, place it in the Anope conf directory (as
* specified in the "Config" script, default /home/username/anope/conf)
* under the name "anope.conf".
*
* The format of this file is fairly simple: three types of comments are supported:
* - All text after a '#' on a line is ignored, as in shell scripting
* - All text after '//' on a line is ignored, as in C++
* - A block of text like this one is ignored, as in C
*
* Outside of comments, there are three structures: blocks, keys, and values.
*
* A block is a named container, which contains a number of key to value pairs
* - you may think of this as an array.
*
* A block is created like so:
* foobar
* {
* moo = "cow"
* foo = bar
* }
*
* Note that nameless blocks are allowed and are often used with comments to allow
* easily commenting an entire block, for example:
* #foobar
* {
* moo = "cow"
* foo = bar
* }
* is an entirely commented block.
*
* Keys are case insensitive. Values depend on what key - generally, information is
* given in the key comment. The quoting of values (and most other syntax) is quite
* flexible, however, please do not forget to quote your strings:
*
* "This is a parameter string with spaces in it"
*
* If you need to include a double quote inside a quoted string, precede it
* by a backslash:
*
* "This string has \"double quotes\" in it"
*
* Time parameters can be specified either as an integer representing a
* number of seconds (e.g. "3600" = 1 hour), or as an integer with a unit
* specifier: "s" = seconds, "m" = minutes, "h" = hours, "d" = days.
* Combinations (such as "1h30m") are not permitted. Examples (all of which
* represent the same length of time, one day):
*
* "86400", "86400s", "1440m", "24h", "1d"
*
* In the documentation for each directive, one of the following will be
* included to indicate whether an option is required:
*
* [REQUIRED]
* Indicates a directive which must be given. Without it, Anope will
* not start.
*
* [RECOMMENDED]
* Indicates a directive which may be omitted, but omitting it may cause
* undesirable side effects.
*
* [OPTIONAL]
* Indicates a directive which is optional. If not given, the feature
* will typically be disabled. If this is not the case, more
* information will be given in the documentation.
*
* [DISCOURAGED]
* Indicates a directive which may cause undesirable side effects if
* specified.
*
* [DEPRECATED]
* Indicates a directive which will disappear in a future version of
* Anope, usually because its functionality has been either
* superseded by that of other directives or incorporated into the main
* program.
*/
/*
* [OPTIONAL] Defines
*
* You can use defines for repeated information, which can be used to easily change many
* values in the configuration at once.
*
* To use a define called foo.bar you use ${foo.bar} in your config file. You can also use
* environment variables by prefixing their name with "env." like ${env.USER}.
*/
/*
* The services.host define is used in multiple different locations throughout the
* configuration for the server name and pseudoclient hostnames.
*/
define
{
name = "services.host"
value = "stats.example.com"
}
/*
* [OPTIONAL] Additional Includes
*
* You can include additional configuration files here.
* You may also include executable files, which will be executed and
* the output from it will be included into your configuration.
*/
#include
{
type = "file"
name = "some.conf"
}
#include
{
type = "executable"
name = "/usr/bin/wget -q -O - https://some.misconfigured.network.com/stats.conf"
}
/*
* [REQUIRED] IRCd Config
*
* This section is used to set up Anope to connect to your IRC network.
* This section can be included multiple times, and Anope will attempt to
* connect to each server until it finally connects.
*
* Each uplink IRCd should have a corresponding configuration to allow Anope
* to link to it.
*
* An example configuration for InspIRCd that is compatible with the below uplink
* and serverinfo configuration would look like:
*
* # This goes in inspircd.conf, *NOT* your Anope config!
* <link name="stats.example.com"
* ipaddr="127.0.0.1"
* port="7000"
* sendpass="mypassword"
* recvpass="mypassword">
* <uline server="stats.example.com" silent="yes">
* <bind address="127.0.0.1" port="7000" type="servers">
*
* An example configuration for UnrealIRCd that is compatible with the below uplink
* and serverinfo configuration would look like:
*
* // This goes in unrealircd.conf, *NOT* your Anope config!
* listen {
* ip 127.0.0.1;
* port 7000;
* options {
* serversonly;
* };
* };
* link stats.example.com {
* incoming {
* mask *@127.0.0.1;
* };
* password "mypassword";
* class servers;
* };
* ulines { stats.example.com; };
*/
uplink
{
/*
* The IP address, hostname, or UNIX socket path of the IRC server you wish
* to connect Anope to.
* Usually, you will want to connect over 127.0.0.1 (aka localhost).
*
* NOTE: On some shell providers, this will not be an option.
*/
host = "127.0.0.1"
/*
* The protocol that Anope should use when connecting to the uplink. Can
* be set to "ipv4" (the default), "ipv6", or "unix".
*/
protocol = "ipv4"
/*
* Enable if Anope should connect using SSL.
* You must have an SSL module loaded for this to work.
*/
ssl = no
/*
* The port to connect to.
* The IRCd *MUST* be configured to listen on this port, and to accept
* server connections.
*
* Refer to your IRCd documentation for how this is to be done.
*/
port = 7000
/*
* The password to send to the IRC server for authentication.
* This must match the link block on your IRCd.
*
* Refer to your IRCd documentation for more information on link blocks.
*/
password = "mypassword"
}
/*
* [REQUIRED] Server Information
*
* This section contains information about the services server.
*/
serverinfo
{
/*
* The hostname that Anope will be seen as, it must have no conflicts with any
* other server names on the rest of your IRC network. Note that it does not have
* to be an existing hostname, just one that isn't on your network already.
*/
name = "${services.host}"
/*
* The text which should appear as the server's information in /WHOIS and similar
* queries.
*/
description = "Anope IRC Statistics"
/*
* The local address that Anope will bind to before connecting to the remote
* server. This may be useful for multihomed hosts. If omitted, Anope will let
* the Operating System choose the local address. This directive is optional.
*
* If you don't know what this means or don't need to use it, just leave this
* directive commented out.
*/
#localhost = "nowhere."
/*
* What Server ID to use for this connection?
* Note: This should *ONLY* be used for TS6/P10 IRCds. Refer to your IRCd documentation
* to see if this is needed.
*/
#id = "00A"
/*
* The filename containing the Anope process ID. The path is relative to the
* data directory.
*/
pid = "anope.pid"
/*
* The filename containing the Message of the Day. The path is relative to the
* config directory.
*/
motd = "motd.txt"
}
/*
* [REQUIRED] Protocol module
*
* This directive tells Anope which IRCd Protocol to speak when connecting.
* You MUST modify this to match the IRCd you run.
*
* Supported:
* - hybrid
* - inspircd
* - ngircd
* - plexus
* - ratbox
* - solanum
* - unrealircd
*/
module
{
name = "inspircd"
}
/*
* [REQUIRED] Network Information
*
* This section contains information about the IRC network that Anope will be
* connecting to.
*/
networkinfo
{
/*
* This is the name of the network that Anope will be running on.
*/
networkname = "LocalNet"
/*
* Set this to the maximum allowed nick length on your network.
* Be sure to set this correctly, as setting this wrong can result in
* Anope being disconnected from the network. Defaults to 31.
*/
#nicklen = 31
/* Set this to the maximum allowed ident length on your network.
* Be sure to set this correctly, as setting this wrong can result in
* Anope being disconnected from the network. Defaults to 10.
*/
#userlen = 10
/* Set this to the maximum allowed hostname length on your network.
* Be sure to set this correctly, as setting this wrong can result in
* Anope being disconnected from the network. Defaults to 64.
*/
#hostlen = 64
/* Set this to the maximum allowed channel length on your network.
* Defaults to 64.
*/
#chanlen = 32
/* The maximum number of list modes settable on a channel (such as b, e, I).
* Comment out or set to 0 to disable.
*/
modelistsize = 100
/*
* The characters allowed in hostnames. This is used for validating hostnames given
* to services, such as BotServ bot hostnames and user vhosts. Changing this is not
* recommended unless you know for sure your IRCd supports whatever characters you are
* wanting to use. Telling services to set a vhost containing characters your IRCd
* disallows could potentially break the IRCd and/or Anope.
*
* It is recommended you DON'T change this.
*/
vhost_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"
/*
* If enabled, allows vhosts to not contain dots (.).
* Newer IRCds generally do not have a problem with this, but the same warning as
* vhost_chars applies.
*
* It is recommended you DON'T change this.
*/
allow_undotted_vhosts = no
/*
* The characters that are not allowed to be at the very beginning or very ending
* of a vhost. The same warning as vhost_chars applies.
*
* It is recommended you DON'T change this.
*/
disallow_start_or_end = ".-"
}
/*
* [REQUIRED] Anope Options
*
* This section contains various options which determine how Anope will operate.
*/
options
{
/*
* On Linux/UNIX systems Anope can setuid and setgid to this user and group
* after starting up. This is useful if Anope has to bind to privileged ports.
*/
#user = "anope"
#group = "anope"
/*
* The case mapping used by services. This must be set to a valid locale name
* installed on your machine. Anope uses this case map to compare, with
* case insensitivity, things such as nick names, channel names, etc.
*
* We provide two special casemaps shipped with Anope, ascii and rfc1459.
*
* This value should be set to what your IRCd uses, which is probably rfc1459,
* however Anope has always used ascii for comparison, so the default is ascii.
*
* Changing this value once set is not recommended.
*/
casemap = "ascii"
/*
* Sets the timeout period for reading from the uplink.
*/
readtimeout = 5s
/*
* If set, Anope will only show /stats o to IRC Operators. This directive
* is optional.
*/
#hidestatso = yes
/*
* A space-separated list of U-lined servers on your network, it is assumed that
* the servers in this list are allowed to set channel modes and Anope will
* not attempt to reverse their mode changes.
*
* WARNING: Do NOT put your normal IRC user servers in this directive.
*
* This directive is optional.
*/
#ulineservers = "services.your.network"
/*
* How long to wait between connection retries with the uplink(s).
*/
retrywait = 60s
}
/*
* [RECOMMENDED] Logging Configuration
*
* This section is used for configuring what is logged and where it is logged to.
* You may have multiple log blocks if you wish. Remember to properly secure any
* channels you choose to have Anope log to!
*/
log
{
/*
* Target(s) to log to, which may be one of the following:
* - a channel name
* - a filename
* - globops
*/
target = "stats.log"
/* Log to both stats.log and the channel #stats
*
* Note that some older IRCds, such as Ratbox, require services to be in the
* log channel to be able to message it. To do this, configure service:channels to
* join your logging channel.
*/
#target = "stats.log #stats"
/*
* The source(s) to only accept log messages from. Leave commented to allow all sources.
* This can be a users name, a channel name, one of our clients (e.g. OperServ), or a server name.
*/
#source = ""
/*
* The bot used to log generic messages which have no predefined sender if there
* is a channel in the target directive.
*/
bot = "Global"
/*
* The number of days to keep log files, only useful if you are logging to a file.
* Set to 0 to never delete old log files.
*
* Note that Anope must run 24 hours a day for this feature to work correctly.
*/
logage = 7
/*
* What types of log messages should be logged by this block. There are nine general categories:
*
* servers - Server actions, linking, squitting, etc.
* channels - Actions in channels such as joins, parts, kicks, etc.
* users - User actions such as connecting, disconnecting, changing name, etc.
* other - All other messages without a category.
* rawio - Logs raw input and output from services
* debug - Debug messages (log files can become VERY large from this).
*
* These options determine what messages from the categories should be logged. Wildcards are accepted, and
* you can also negate values with a ~. For example, "~operserv/akill operserv/*" would log all operserv
* messages except for operserv/akill. Note that processing stops at the first matching option, which
* means "* ~operserv/*" would log everything because * matches everything.
*
* Valid server options are:
* connect, quit, sync, squit
*
* Valid channel options are:
* create, destroy, join, part, kick, leave, mode
*
* Valid user options are:
* connect, disconnect, quit, nick, ident, host, mode, maxusers, oper, away
*
* Rawio and debug are simple yes/no answers, there are no types for them.
*
* Note that modules may add their own values to these options.
*/
servers = "*"
#channels = "~mode *"
users = "connect disconnect nick"
other = "*"
rawio = no
debug = no
}
/*
* [REQUIRED] MySQL Database configuration.
*
* mysql
*
* This module allows other modules to use MySQL.
*/
module
{
name = "mysql"
mysql
{
/* The name of this service. */
name = "mysql/main"
database = "anope"
server = "127.0.0.1"
username = "anope"
password = "mypassword"
port = 3306
}
}
/*
* IRC2SQL Gateway
* This module collects data about users, channels and servers. It doesn't build stats
* itself, however, it gives you the database, it's up to you how you use it.
*
* Requires a MySQL Database and MySQL version 5.5 or higher
*/
include
{
type = "file"
name = "irc2sql.example.conf"
}
+1 -1
View File
@@ -22,9 +22,9 @@ contributions they have made, are:
* Michael Wobst <wobst.michael@web.de>
* Mark Summers <mark@goopler.net>
* Matt Schatz <genius3000@g3k.solutions>
* PeGaSuS <droider.pc@gmail.com>
* Daniel Vassdal <shutter@canternet.org>
* MatthewM <mcm@they-got.us>
* PeGaSuS <droider.pc@gmail.com>
* Sebastian V. <hal9000@denorastats.org>
* Alvaro Toledo <atoledo@keldon.org>
* Dragone2 <dragone2@risposteinformatiche.it>
+27
View File
@@ -1,3 +1,30 @@
Anope Version 2.1.17
--------------------
Allowed opers to resend passwords for users in nickserv/resend.
Fixed HostServ using a different valid username character set to the protocol module.
Fixed losing the channel and nickname registration time when upgrading from an earlier 2.1 release.
Improved the messages sent when a user is forced off a protected nickname.
Simplified copying modules to the runtime directory on Windows.
Anope Version 2.1.16
--------------------
Added support for on-IRC code confirmation.
Added the ability for fantasy commands to be executable without the FANTASY privilege.
Added the ability to prepend to topics as well as appending to them.
Changed various fields to serialize to the database as a string not an integer.
Disabled db_flatfile by default in preparation for becoming import-only.
Fixed a memory leak in the db_json module.
Fixed building on OpenSSL 1.1.1 (for now).
Fixed removed and later re-added temporary bans being removed automatically.
Fixed sometimes sending malformed LMODE messages on InspIRCd.
Fixed the "did you mean" message suggesting unloaded commands.
Fixed various issues with the example config files.
Marked db_json as the recommended database module.
Moved the BAN, UNBAN, and KICK commands to the chanserv/management group.
Removed support for the 1.8-style seen command.
Reworked confirmation to allow confirmation of multiple account actions.
When dropping a display nickname the new display will now be the oldest in the group.
Anope Version 2.1.15
--------------------
Added a workaround to the jsonrpc module for JavaScript truncating big integers.
+17
View File
@@ -1,3 +1,20 @@
Anope Version 2.1.17
--------------------
Added the nickserv/resend oper privilege.
Anope Version 2.1.16
--------------------
Added fantasy:require_privilege (defaults to yes).
Added the nickserv/confirm/email command.
Added the nickserv/confirm/email oper privilege.
Added the nickserv/confirm/register command.
Added the ns_confirm module.
Added {ns_email}:changeexpire (defaults to 1 day).
Added {ns_resetpass}:resetexpire (defaults to 1 day).
Removed the irc2sql module (migrate to JSON-RPC instead).
Removed {ns_seen}:simple (1.8-style seen has been removed).
Renamed the nickserv/confirm oper privilege to nickserv/confirm/register.
Anope Version 2.1.15
--------------------
Added the ns_email module.
+5 -5
View File
@@ -49,10 +49,10 @@ public:
Anope::string nick;
Anope::string last_quit;
Anope::string last_realname;
/* Last usermask this nick was seen on, eg user@host */
Anope::string last_usermask;
/* Last uncloaked usermask, requires nickserv/auspex to see */
Anope::string last_realhost;
/* Last cloaked user@host this nick was seen using. */
Anope::string last_userhost;
/* Last real user@host this nick was seen using. */
Anope::string last_userhost_real;
time_t registered = Anope::CurTime;
time_t last_seen = Anope::CurTime;
@@ -164,7 +164,7 @@ public:
/* Unsaved data */
/** The display nick for this account. */
NickAlias *na = nullptr;
Serialize::Reference<NickAlias> na;
/* Number of channels registered by this account */
uint16_t channelcount = 0;
/* Users online now logged into this account */
+4 -2
View File
@@ -32,10 +32,12 @@ struct CommandInfo final
Anope::string permission;
/* Group this command is in */
Anope::string group;
/* whether or not to hide this command in help output */
/* Whether to hide this command in help and suggestions */
bool hide = false;
/* Only used with fantasy */
/* Whether to prepend the channel name (only used with fantasy) */
bool prepend_channel = false;
/* Whether to require the FANTASY privilege (only used with fantasy) */
bool require_privilege = true;
};
/* Where the replies from commands go to. User inherits from this and is the normal
+1 -7
View File
@@ -102,7 +102,7 @@ namespace Language
/* Commonly used language strings */
#define CONFIRM_DROP _("Please confirm that you want to drop \002%s\002 with \002%s\032%s\032%s\002")
#define SERVICE_UNAVAILABLE _("Sorry, %s is temporarily unavailable.")
#define MORE_INFO _("\002%s\002 for more information.")
#define MORE_INFO _("Type \002%s\002 for more information.")
#define BAD_USERHOST_MASK _("Mask must be in the form \037user\037@\037host\037.")
#define BAD_EXPIRY_TIME _("Invalid expiry time.")
#define USERHOST_MASK_TOO_WIDE _("%s coverage is too wide; Please use a more specific mask.")
@@ -114,7 +114,6 @@ namespace Language
"cannot contain the space or tab characters.")
#define PASSWORD_TOO_SHORT _("Your password is too short. It must be longer than %u characters.")
#define PASSWORD_TOO_LONG _("Your password is too long. It must be shorter than %u characters.")
#define NICK_NOT_REGISTERED _("Your nick isn't registered.")
#define NICK_X_NOT_REGISTERED _("Nick \002%s\002 isn't registered.")
#define NICK_X_NOT_IN_USE _("Nick \002%s\002 isn't currently in use.")
#define NICK_X_NOT_ON_CHAN _("\002%s\002 is not currently on channel %s.")
@@ -127,14 +126,9 @@ namespace Language
#define UNKNOWN _("<unknown>")
#define NO_EXPIRE _("does not expire")
#define LIST_INCORRECT_RANGE _("Incorrect range specified. The correct syntax is \002#\037from\037-\037to\037\002.")
#define NICK_IS_SECURE _("This nickname is registered and protected. If it is your " \
"nick, type \002%s\032\037password\037\002. Otherwise, " \
"please choose a different nick.")
#define FORCENICKCHANGE_NOW _("This nickname has been registered; you may not use it.")
#define NICK_CANNOT_BE_REGISTERED _("Nickname \002%s\002 may not be registered.")
#define NICK_ALREADY_REGISTERED _("Nickname \002%s\002 is already registered!")
#define NICK_SET_DISPLAY_CHANGED _("The new display is now \002%s\002.")
#define NICK_CONFIRM_INVALID _("Invalid passcode has been entered, please check the email again, and retry.")
#define CHAN_NOT_ALLOWED_TO_JOIN _("You are not permitted to be on this channel.")
#define CHAN_X_INVALID _("Channel %s is not a valid channel.")
#define CHAN_REACHED_CHANNEL_LIMIT _("Sorry, you have already reached your limit of \002%d\002 channels.")
+15 -10
View File
@@ -70,11 +70,6 @@ public:
*/
Mode(const Anope::string &mname, ModeClass mclass, char mc, ModeType type);
virtual ~Mode() = default;
/** Can a user set this mode, used for mlock
* @param u The user
*/
virtual bool CanSet(User *u) const;
};
/** This class is a user mode, all user modes use this/inherit from this
@@ -88,6 +83,12 @@ public:
* @param mc The mode char
*/
UserMode(const Anope::string &name, char mc);
/** Can a user set this mode, used for mlock
* @param source The user who is setting the mode.
* @param target The user the mode is being set on.
*/
virtual bool CanSet(User *source, User *target) const { return true; }
};
class CoreExport UserModeParam
@@ -122,7 +123,11 @@ public:
*/
ChannelMode(const Anope::string &name, char mc);
bool CanSet(User *u) const override;
/** Can a user set this mode, used for mlock
* @param u The user who is setting the mode.
* @param c The channel the mode is being set on.
*/
virtual bool CanSet(User *u, Channel *c) const;
virtual void Check() { }
@@ -270,7 +275,7 @@ class CoreExport UserModeOperOnly
public:
UserModeOperOnly(const Anope::string &mname, char um) : UserMode(mname, um) { }
bool CanSet(User *u) const override;
bool CanSet(User *source, User *target) const override;
};
class CoreExport UserModeNoone
@@ -279,7 +284,7 @@ class CoreExport UserModeNoone
public:
UserModeNoone(const Anope::string &mname, char um) : UserMode(mname, um) { }
bool CanSet(User *u) const override;
bool CanSet(User *source, User *target) const override;
};
/** Channel mode +k (key)
@@ -302,7 +307,7 @@ public:
ChannelModeOperOnly(const Anope::string &mname, char mc) : ChannelMode(mname, mc) { }
/* Opers only */
bool CanSet(User *u) const override;
bool CanSet(User *u, Channel *c) const override;
};
/** This class is used for channel modes only servers may set
@@ -313,7 +318,7 @@ class CoreExport ChannelModeNoone
public:
ChannelModeNoone(const Anope::string &mname, char mc) : ChannelMode(mname, mc) { }
bool CanSet(User *u) const override;
bool CanSet(User *u, Channel *c) const override;
};
/** This is the mode manager
+2 -1
View File
@@ -1046,9 +1046,10 @@ public:
/** Called to determine if a channel mode can be set by a user
* @param u The user
* @param c The channel
* @param cm The mode
*/
virtual EventReturn OnCanSet(User *u, const ChannelMode *cm) ATTR_NOT_NULL(2, 3) { throw NotImplementedException(); }
virtual EventReturn OnCanSet(User *u, Channel *c, const ChannelMode *cm) ATTR_NOT_NULL(2, 3) { throw NotImplementedException(); }
virtual EventReturn OnCheckDelete(Channel *c) ATTR_NOT_NULL(2) { throw NotImplementedException(); }
-7
View File
@@ -15,13 +15,6 @@ enum NewsType
NEWS_OPER
};
struct NewsMessages final
{
NewsType type;
Anope::string name;
const char *msgs[10];
};
struct NewsItem
: Serializable
{
+1 -1
View File
@@ -253,7 +253,7 @@ public:
*/
bool HasPriv(const Anope::string &privstr);
/** Update the last usermask stored for a user. */
/** Update the last mask stored for a user. */
void UpdateHost();
/** Update the away state for a user. */
+1 -1
View File
@@ -6607,7 +6607,7 @@ msgid "Service"
msgstr "Server gefunden: %d"
#, fuzzy, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "Die Services haben den Nicknamen wieder verfügbar gemacht."
#, fuzzy
+1 -1
View File
@@ -6832,7 +6832,7 @@ msgid "Service"
msgstr "Τα services είναι ενεργά για %s"
#, fuzzy, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "Οι υπηρεσίες απελευθέρωααν το ψευδώνυμό σου."
#, fuzzy
+195 -198
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -6273,7 +6273,7 @@ msgid "Service"
msgstr "Servicio"
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "El nick %s retenido por los servicios ha sido liberado."
#, fuzzy
+1 -1
View File
@@ -6155,7 +6155,7 @@ msgid "Service"
msgstr "Service"
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "La tutelle des Services sur %s a été enlevée."
msgid "Services Operator commands"
+1 -1
View File
@@ -6146,7 +6146,7 @@ msgid "Service"
msgstr "Servizio"
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "Il blocco dei servizi sul nick %s è stato rilasciato."
msgid "Services Operator commands"
+1 -1
View File
@@ -6182,7 +6182,7 @@ msgid "Service"
msgstr "Service"
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "Nick %s werd vrijgegeven."
msgid "Services Operator commands"
+1 -1
View File
@@ -6123,7 +6123,7 @@ msgid "Service"
msgstr "Usługa"
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "Serwisy właśnie zwolniły nicka %s."
msgid "Services Operator commands"
+1 -1
View File
@@ -5675,7 +5675,7 @@ msgid "Service"
msgstr ""
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr ""
msgid "Services Operator commands"
+1 -1
View File
@@ -6067,7 +6067,7 @@ msgid "Service"
msgstr "Servis"
#, c-format
msgid "Service's hold on %s has been released."
msgid "Services' hold on %s has been released."
msgstr "Servisin %s üzerindeki bekletmesi kaldırıldı."
msgid "Services Operator commands"
+39 -13
View File
@@ -12,6 +12,39 @@
#include "module.h"
#include "modules/botserv/badwords.h"
namespace
{
Anope::string TypeToString(BadWordType bw)
{
switch (bw)
{
case BW_ANY:
return "ANY";
case BW_SINGLE:
return "SINGLE";
case BW_START:
return "START";
case BW_END:
return "END";
}
return ""; // Should never happen.
}
BadWordType StringToType(const Anope::string &bw)
{
if (bw.equals_ci("ANY") || bw.equals_ci("0"))
return BW_ANY;
if (bw.equals_ci("SINGLE") || bw.equals_ci("1"))
return BW_SINGLE;
if (bw.equals_ci("START") || bw.equals_ci("2"))
return BW_START;
if (bw.equals_ci("END") || bw.equals_ci("3"))
return BW_END;
return BW_ANY; // Should never happen.
}
}
struct BadWordImpl final
: BadWord
, Serializable
@@ -33,7 +66,7 @@ struct BadWordTypeImpl final
const auto *bw = static_cast<const BadWordImpl *>(obj);
data.Store("ci", bw->chan);
data.Store("word", bw->word);
data.Store("type", bw->type);
data.Store("type", TypeToString(bw->type));
}
Serializable *Unserialize(Serializable *obj, Serialize::Data &) const override;
@@ -138,7 +171,7 @@ Serializable *BadWordTypeImpl::Unserialize(Serializable *obj, Serialize::Data &d
if (!ci)
return NULL;
unsigned int n;
Anope::string n;
data["type"] >> n;
BadWordImpl *bw;
@@ -148,7 +181,7 @@ Serializable *BadWordTypeImpl::Unserialize(Serializable *obj, Serialize::Data &d
bw = new BadWordImpl();
bw->chan = sci;
bw->word = sword;
bw->type = static_cast<BadWordType>(n);
bw->type = StringToType(n);
BadWordsImpl *bws = ci->Require<BadWordsImpl>("badwords");
if (!obj)
@@ -244,7 +277,7 @@ private:
ListFormatter::ListEntry entry;
entry["Number"] = Anope::ToString(Number);
entry["Word"] = b->word;
entry["Type"] = b->type == BW_SINGLE ? "(SINGLE)" : (b->type == BW_START ? "(START)" : (b->type == BW_END ? "(END)" : ""));
entry["Type"] = TypeToString(b->type);
this->list.AddEntry(entry);
}
}
@@ -263,7 +296,7 @@ private:
ListFormatter::ListEntry entry;
entry["Number"] = Anope::ToString(i + 1);
entry["Word"] = b->word;
entry["Type"] = b->type == BW_SINGLE ? "(SINGLE)" : (b->type == BW_START ? "(START)" : (b->type == BW_END ? "(END)" : ""));
entry["Type"] = TypeToString(b->type);
list.AddEntry(entry);
}
}
@@ -295,14 +328,7 @@ private:
{
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;
}
bwtype = StringToType(opt);
realword = word.substr(0, pos);
}
+2 -2
View File
@@ -173,7 +173,7 @@ class CommandBSSetPrivate final
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 IRC operators"));
this->SetDesc(_("Prevent a bot from being assigned by non Services Operators"));
this->SetSyntax(_("\037botname\037 {\037ON|OFF\037}"));
}
@@ -214,7 +214,7 @@ public:
source.Reply(" ");
source.Reply(_(
"This option prevents a bot from being assigned to a "
"channel by users that aren't IRC Operators."
"channel by users that aren't Services Operators."
));
return true;
}
+1 -1
View File
@@ -330,7 +330,7 @@ public:
ci->Extend<bool>(def.upper());
}
EventReturn OnCanSet(User *u, const ChannelMode *cm) override
EventReturn OnCanSet(User *u, Channel *c, const ChannelMode *cm) override
{
if (Config->GetModule(this).Get<const Anope::string>("nomlock").find(cm->mchar) != Anope::string::npos
|| Config->GetModule(this).Get<const Anope::string>("require").find(cm->mchar) != Anope::string::npos)
+3 -3
View File
@@ -141,7 +141,7 @@ class CommandCSAKick final
}
}
/* Match against the lastusermask of all nickalias's with equal
/* Match against the last mask of all nickalias's with equal
* or higher access. - Viper */
for (const auto &[_, na2] : *NickAliasList)
{
@@ -150,7 +150,7 @@ class CommandCSAKick final
AccessGroup nc_access = ci->AccessFor(na->nc), u_access = source.AccessFor(ci);
if (na->nc && (na->nc == ci->GetFounder() || nc_access >= u_access))
{
Anope::string buf = na->nick + "!" + na->last_usermask;
Anope::string buf = na->nick + "!" + na->last_userhost;
if (Anope::Match(buf, mask))
{
source.Reply(ACCESS_DENIED);
@@ -506,7 +506,7 @@ public:
"%s will ban that user from the channel, then kick "
"the user."
"\n\n"
"The \002%s\032ADD\002 command adds the given nick or usermask "
"The \002%s\032ADD\002 command adds the given nick or mask "
"to the AutoKick list. If a \037reason\037 is given with "
"the command, that reason will be used when the user is "
"kicked; if not, the default reason is \"User has been "
+34 -1
View File
@@ -13,6 +13,9 @@
static Module *me;
class TempBan;
static std::vector<TempBan *> tempbans;
class TempBan final
: public Timer
{
@@ -28,6 +31,21 @@ public:
, mask(banmask)
, mode(mod)
{
tempbans.push_back(this);
}
~TempBan()
{
auto it = std::find(tempbans.begin(), tempbans.end(), this);
if (it != tempbans.end())
tempbans.erase(it);
}
bool Matches(Channel *chan, ChannelMode *cmode, const Anope::string &bmask) const
{
return chan->name.equals_ci(this->channel)
&& cmode->name == this->mode
&& bmask == this->mask;
}
void Tick() override
@@ -258,10 +276,25 @@ class CSBan final
CommandCSBan commandcsban;
public:
CSBan(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), commandcsban(this)
CSBan(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandcsban(this)
{
me = this;
}
EventReturn OnChannelModeUnset(Channel *c, MessageSource &setter, ChannelMode *cmode, const Anope::string &param) override
{
for (const auto *tempban : tempbans)
{
if (tempban->Matches(c, cmode, param))
{
delete tempban;
break;
}
}
return EVENT_CONTINUE;
}
};
MODULE_INIT(CSBan)
+5 -5
View File
@@ -288,7 +288,7 @@ class CommandCSMode final
for (auto *ml : mlocks)
{
ChannelMode *cm = ModeManager::FindChannelModeByName(ml->name);
if (cm && cm->CanSet(source.GetUser()))
if (cm && cm->CanSet(source.GetUser(), ci->c))
modelocks->RemoveMLock(cm, ml->set, ml->param);
}
}
@@ -320,7 +320,7 @@ class CommandCSMode final
source.Reply(_("Unknown mode character %c ignored."), mode);
break;
}
else if (u && !cm->CanSet(u))
else if (u && !cm->CanSet(u, ci->c))
{
source.Reply(_("You may not (un)lock mode %c."), mode);
break;
@@ -412,7 +412,7 @@ class CommandCSMode final
source.Reply(_("Unknown mode character %c ignored."), mode);
break;
}
else if (u && !cm->CanSet(u))
else if (u && !cm->CanSet(u, ci->c))
{
source.Reply(_("You may not (un)lock mode %c."), mode);
break;
@@ -508,7 +508,7 @@ class CommandCSMode final
{
ChannelMode *cm = ModeManager::GetChannelModes()[j];
if (!u || cm->CanSet(u) || can_override)
if (!u || cm->CanSet(u, ci->c) || can_override)
{
if (cm->type == MODE_REGULAR || (!adding && cm->type == MODE_PARAM))
{
@@ -524,7 +524,7 @@ class CommandCSMode final
if (adding == -1)
break;
ChannelMode *cm = ModeManager::FindChannelModeByChar(mode);
if (!cm || (u && !cm->CanSet(u) && !can_override))
if (!cm || (u && !cm->CanSet(u, ci->c) && !can_override))
continue;
switch (cm->type)
{
+50 -86
View File
@@ -16,7 +16,6 @@ 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::unordered_map<SeenInfo *> database_map;
@@ -53,12 +52,54 @@ struct SeenInfoType final
{
}
static Anope::string TypeToString(TypeInfo ti)
{
switch (ti)
{
case NEW:
return "NEW";
case NICK_TO:
return "NICK_TO";
case NICK_FROM:
return "NICK_FROM";
case JOIN:
return "JOIN";
case PART:
return "PART";
case QUIT:
return "QUIT";
case KICK:
return "KICK";
}
return ""; // Should never happen.
}
static TypeInfo StringToType(const Anope::string &ti)
{
if (ti.equals_ci("NEW") || ti.equals_ci("0"))
return NEW;
if (ti.equals_ci("NICK_TO") || ti.equals_ci("1"))
return NICK_TO;
if (ti.equals_ci("NICK_FROM") || ti.equals_ci("2"))
return NICK_FROM;
if (ti.equals_ci("JOIN") || ti.equals_ci("3"))
return JOIN;
if (ti.equals_ci("PART") || ti.equals_ci("4"))
return PART;
if (ti.equals_ci("QUIT") || ti.equals_ci("5"))
return QUIT;
if (ti.equals_ci("KICK") || ti.equals_ci("6"))
return KICK;
return NEW; // Should never happen.
}
void Serialize(Serializable *obj, Serialize::Data &data) const override
{
const auto *s = static_cast<const SeenInfo *>(obj);
data.Store("nick", s->nick);
data.Store("vhost", s->vhost);
data.Store("type", s->type);
data.Store("type", TypeToString(s->type));
data.Store("nick2", s->nick2);
data.Store("channel", s->channel);
data.Store("message", s->message);
@@ -84,9 +125,9 @@ struct SeenInfoType final
s->nick = snick;
data["vhost"] >> s->vhost;
unsigned int n;
Anope::string n;
data["type"] >> n;
s->type = static_cast<TypeInfo>(n);
s->type = StringToType(n);
data["nick2"] >> s->nick2;
data["channel"] >> s->channel;
data["message"] >> s->message;
@@ -199,76 +240,6 @@ public:
class CommandSeen final
: public Command
{
static void SimpleSeen(CommandSource &source, const std::vector<Anope::string> &params)
{
if (!source.c || !source.c->ci)
{
if (source.IsOper())
source.Reply("Seen in simple mode is designed as a fantasy command only!");
return;
}
BotInfo *bi = BotInfo::Find(params[0], true);
if (bi)
{
if (bi == source.c->ci->bi)
source.Reply(_("You found me, %s!"), source.GetNick().c_str());
else
source.Reply(_("%s is a network service."), bi->nick.c_str());
return;
}
NickAlias *na = NickAlias::Find(params[0]);
if (!na)
{
source.Reply(_("I don't know who %s is."), params[0].c_str());
return;
}
if (source.GetAccount() == na->nc)
{
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 (const auto &[_, uc] : source.c->users)
{
User *u = uc->user;
if (u->Account() == na->nc)
{
source.Reply(_("%s is on the channel right now (as %s)!"), params[0].c_str(), u->nick.c_str());
return;
}
}
AccessGroup ag = source.c->ci->AccessFor(na->nc);
time_t last = 0;
for (const auto &p : ag.paths)
{
if (p.empty())
continue;
ChanAccess *a = p[p.size() - 1];
if (a->GetAccount() == na->nc && a->last_seen > last)
last = a->last_seen;
}
if (last > Anope::CurTime || !last)
source.Reply(_("I've never seen %s on this channel."), na->nick.c_str());
else
source.Reply(_("%s was last seen here %s ago."), na->nick.c_str(), Anope::Duration(Anope::CurTime - last, source.GetAccount()).c_str());
}
public:
CommandSeen(Module *creator) : Command(creator, "chanserv/seen", 1, 2)
{
@@ -281,9 +252,6 @@ public:
{
const Anope::string &target = params[0];
if (simple)
return this->SimpleSeen(source, params);
if (target.length() > IRCD->MaxNick)
{
source.Reply(_("Nick too long, max length is %zu characters."), IRCD->MaxNick);
@@ -401,17 +369,13 @@ public:
{
}
void OnReload(Configuration::Conf &conf) override
{
simple = conf.GetModule(this).Get<bool>("simple");
}
void OnExpireTick() override
{
size_t previous_size = database.size();
time_t purgetime = Config->GetModule(this).Get<time_t>("purgetime");
auto purgetime = Config->GetModule(this).Get<time_t>("purgetime", "90d");
if (!purgetime)
purgetime = Anope::DoTime("30d");
return;
auto previous_size = database.size();
for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end;)
{
database_map::iterator cur = it;
@@ -461,7 +425,7 @@ public:
private:
static 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())
if (!u->server->IsSynced())
return;
SeenInfo *&info = database[nick];
+1 -1
View File
@@ -1333,7 +1333,7 @@ public:
if (persist.HasExt(ci))
info.AddOption(_("Persistent"));
if (noexpire.HasExt(ci))
info.AddOption(_("No expire"));
info.AddOption(_("No expiry"));
if (keep_modes.HasExt(ci))
info.AddOption(_("Keep modes"));
if (noautoop.HasExt(ci))
+1
View File
@@ -171,6 +171,7 @@ public:
if (descriptions.count(source.command))
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply("%s", Language::Translate(source.nc, descriptions[source.command].c_str()));
return true;
}
+10 -3
View File
@@ -132,14 +132,18 @@ class CommandCSTopic final
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, ChannelInfo *ci, const std::vector<Anope::string> &params)
void Combine(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params, bool append)
{
const Anope::string &topic = params[2];
Anope::string new_topic;
if (!ci->c->topic.empty())
{
new_topic = ci->c->topic + " " + topic;
if (append)
new_topic = ci->c->topic + " " + topic;
else
new_topic = topic + " " + ci->c->topic;
ci->last_topic.clear();
}
else
@@ -155,6 +159,7 @@ public:
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 PREPEND \037topic\037"));
this->SetSyntax(_("\037channel\037 [UNLOCK|LOCK]"));
}
@@ -174,7 +179,9 @@ public:
else if (!ci->c)
source.Reply(CHAN_X_NOT_IN_USE, ci->name.c_str());
else if (subcmd.equals_ci("APPEND") && params.size() > 2)
this->Append(source, ci, params);
this->Combine(source, ci, params, true);
else if (subcmd.equals_ci("PREPEND") && params.size() > 2)
this->Combine(source, ci, params, false);
else
{
Anope::string topic;
+11 -10
View File
@@ -134,9 +134,9 @@ struct UserData final
Anope::string info_adder;
Anope::string info_message;
time_t info_ts = 0;
Anope::string last_mask;
Anope::string last_quit;
Anope::string last_real_mask;
Anope::string last_userhost;
Anope::string last_userhost_real;
bool noexpire = false;
bool protect = false;
std::optional<time_t> protectafter;
@@ -512,7 +512,7 @@ private:
if (!forbid_service)
{
Log(this) << "Unable to convert forbidden email " << email << " as os_forbid is not loaded";
Log(this) << "Unable to convert forbidden email address " << email << " as os_forbid is not loaded";
return true;
}
@@ -625,6 +625,7 @@ private:
return true;
}
auto originalflags = flags;
Anope::string accessflags;
ApplyAccess(flags, 'A', accessflags, { "ACCESS_LIST" });
ApplyAccess(flags, 'a', accessflags, { "AUTOPROTECT", "PROTECT", "PROTECTME" });
@@ -647,7 +648,7 @@ private:
auto *access = accessprov->Create();
access->SetMask(mask, ci);
access->creator = setter;
access->description = "Imported from Atheme";
access->description = "Imported from Atheme: " + flags;
access->last_seen = modifiedtime;
access->created = modifiedtime;
access->AccessUnserialize(accessflags);
@@ -1145,9 +1146,9 @@ private:
else if (key == "private:freeze:timestamp")
data->suspend_ts = Anope::Convert<time_t>(value, 0);
else if (key == "private:host:actual")
data->last_real_mask = value;
data->last_userhost_real = value;
else if (key == "private:host:vhost")
data->last_mask = value;
data->last_userhost = value;
else if (key == "private:lastquit:message")
data->last_quit = value;
else if (key == "private:loginfail:failnum")
@@ -1313,14 +1314,14 @@ private:
auto *data = userdata.Get(nc);
if (data)
{
if (!data->last_mask.empty())
na->last_usermask = data->last_mask;
if (!data->last_userhost.empty())
na->last_userhost = data->last_userhost;
if (!data->last_quit.empty())
na->last_quit = data->last_quit;
if (!data->last_real_mask.empty())
na->last_realhost = data->last_real_mask;
if (!data->last_userhost_real.empty())
na->last_userhost_real = data->last_userhost_real;
if (data->noexpire)
na->Extend<bool>("NS_NO_EXPIRE");
+180 -151
View File
@@ -20,23 +20,13 @@ namespace fs = std::filesystem;
// TODO:
// * forking into background
// * stable key order
inline Anope::string yyjson_mut_get_astr(yyjson_mut_val *val)
inline Anope::string yyjson_get_astr(yyjson_val *val)
{
const auto *str = yyjson_mut_get_str(val);
const auto *str = yyjson_get_str(val);
return str ? str : "";
}
inline void yyjson_mut_obj_upsert(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, yyjson_mut_val *val)
{
auto *oldval = yyjson_mut_obj_get(obj, key);
if (oldval)
yyjson_mut_obj_replace(obj, yyjson_mut_str(doc, key), val);
else
yyjson_mut_obj_add_val(doc, obj, key, val);
}
class Data final
: public Serialize::Data
{
@@ -48,37 +38,42 @@ public:
Anope::map<std::stringstream> data;
// Used when writing data.
Data() = default;
Data(Serialize::Type *s_type, Serializable *obj)
{
if (obj->id)
this->id = obj->id;
s_type->Serialize(obj, *this);
}
// Used when reading data.
Data(yyjson_mut_val *elem)
Data(yyjson_val *elem)
{
size_t idx, max;
yyjson_mut_val *key, *value;
yyjson_mut_obj_foreach(elem, idx, max, key, value)
yyjson_val *key, *value;
yyjson_obj_foreach(elem, idx, max, key, value)
{
if (yyjson_mut_get_type(key) != YYJSON_TYPE_STR)
if (yyjson_get_type(key) != YYJSON_TYPE_STR)
continue;
Anope::string akey(yyjson_mut_get_str(key));
auto akey = yyjson_get_astr(key);
if (akey.equals_ci("@id"))
{
this->id = yyjson_mut_get_uint(value);
this->id = yyjson_get_uint(value);
continue;
}
if (yyjson_mut_is_bool(value))
data[akey] << yyjson_mut_get_bool(value);
else if (yyjson_mut_is_int(value))
data[akey] << yyjson_mut_get_int(value);
else if (yyjson_mut_is_null(value))
if (yyjson_is_bool(value))
data[akey] << yyjson_get_bool(value);
else if (yyjson_is_int(value))
data[akey] << yyjson_get_int(value);
else if (yyjson_is_null(value))
data[akey];
else if (yyjson_mut_is_real(value))
data[akey] << yyjson_mut_get_real(value);
else if (yyjson_mut_is_str(value))
data[akey] << yyjson_mut_get_str(value);
else if (yyjson_mut_is_uint(value))
data[akey] << yyjson_mut_get_uint(value);
else if (yyjson_is_real(value))
data[akey] << yyjson_get_real(value);
else if (yyjson_is_str(value))
data[akey] << yyjson_get_astr(value);
else if (yyjson_is_uint(value))
data[akey] << yyjson_get_uint(value);
}
}
@@ -86,16 +81,29 @@ public:
{
return data[key];
}
size_t Hash() const override
{
size_t hash = 0;
for (const auto &[_, value] : this->data)
{
auto valuestr = value.str();
if (!valuestr.empty())
hash ^= Anope::hash_cs()(valuestr);
}
return hash;
}
};
class DBJSON final
: public Module
{
private:
using DBPair = std::pair<yyjson_mut_doc *, yyjson_mut_val *>;
// Multimap from serializable type to serializable data.
using DBData = Anope::multimap<Data>;
// The databases which have already been loaded from disk.
std::unordered_map<Module *, DBPair> databases;
std::unordered_map<Module *, DBData> databases;
// Whether OnLoadDatabase has been called yet.
bool loaded = false;
@@ -201,71 +209,131 @@ private:
CreateBackup(backupdir, dbpath, monthly_backups, "%Y-%m", "\?\?\?\?-\?\?");
}
void LoadType(Serialize::Type *s_type, yyjson_mut_val *data)
void LoadType(Serialize::Type *s_type, DBData &data)
{
auto *entries = yyjson_mut_obj_get(data, s_type->GetName().c_str());
if (!entries || !yyjson_mut_is_arr(entries))
auto entries = data.equal_range(s_type->GetName());
if (entries.first == entries.second)
return;
Log(LOG_DEBUG) << "Loading " << yyjson_mut_arr_size(entries) << " " << s_type->GetName() << " records";
size_t idx, max;
yyjson_mut_val *elem;
yyjson_mut_arr_foreach(entries, idx, max, elem)
{
Data ld(elem);
s_type->Unserialize(nullptr, ld);
}
for (auto it = entries.first; it != entries.second; ++it)
s_type->Unserialize(nullptr, it->second);
}
DBPair ReadDatabase(const Anope::string &dbname)
std::optional<DBData> ReadDatabase(const Anope::string &dbname)
{
yyjson_read_err errmsg;
const auto flags = YYJSON_READ_ALLOW_TRAILING_COMMAS | YYJSON_READ_ALLOW_INVALID_UNICODE;
auto *idoc = yyjson_read_file(dbname.c_str(), flags, nullptr, &errmsg);
if (!idoc)
auto *doc = yyjson_read_file(dbname.c_str(), flags, nullptr, &errmsg);
if (!doc)
{
Log(this) << "Unable to read " << dbname << ": error #" << errmsg.code << ": " << errmsg.msg;
return { nullptr, nullptr };
return std::nullopt;
}
// We operate on a mutable document because we need to write to it later.
auto *doc = yyjson_doc_mut_copy(idoc, nullptr);
yyjson_doc_free(idoc);
auto *root = yyjson_mut_doc_get_root(doc);
if (!yyjson_mut_is_obj(root))
auto *root = yyjson_doc_get_root(doc);
if (!yyjson_is_obj(root))
{
Log(this) << "Unable to read " << dbname << ": root element is not an object";
return { nullptr, nullptr };
return std::nullopt;
}
auto version = yyjson_mut_get_uint(yyjson_mut_obj_get(root, "version"));
auto version = yyjson_get_uint(yyjson_obj_get(root, "version"));
if (version && version != ANOPE_DATABASE_VERSION)
{
Log(this) << "Refusing to load an unsupported database version: " << version;
return { nullptr, nullptr };
return std::nullopt;
}
auto generator = yyjson_mut_get_astr(yyjson_mut_obj_get(root, "generator"));
auto updated = yyjson_mut_get_uint(yyjson_mut_obj_get(root, "updated"));
auto generator = yyjson_get_astr(yyjson_obj_get(root, "generator"));
auto updated = yyjson_get_uint(yyjson_obj_get(root, "updated"));
Log(LOG_DEBUG) << "Database " << dbname << " was generated on " << Anope::strftime(updated) << " by " << generator;
auto *data = yyjson_mut_obj_get(root, "data");
if (!data || !yyjson_mut_is_obj(data))
auto *data = yyjson_obj_get(root, "data");
if (!data || !yyjson_is_obj(data))
{
Log(this) << "Unable to read " << dbname << ": data element is missing or not an object";
return { nullptr, nullptr };
return std::nullopt;
}
return { doc, data };
DBData ret;
size_t idx, max;
yyjson_val *key, *val;
yyjson_obj_foreach(data, idx, max, key, val)
{
if (!yyjson_is_str(key))
{
Log(this) << "Unable to read part of " << dbname << ": key of data element #" << idx << " is not a string";
continue;
}
auto keystr = yyjson_get_astr(key);
if (!yyjson_is_arr(val))
{
Log(this) << "Unable to read part of " << dbname << ": " << keystr << " value of data element #" << idx << " is not an array";
continue;
}
Log(LOG_DEBUG) << "Loading " << yyjson_arr_size(val) << " " << keystr << " records";
size_t idx, max;
yyjson_val *elem;
yyjson_arr_foreach(val, idx, max, elem)
{
Data ld(elem);
ret.emplace(keystr, std::move(ld));
}
}
yyjson_doc_free(doc);
return ret;
}
static void UpdateMetadata(yyjson_mut_doc *doc, yyjson_mut_val *obj)
void SaveType(yyjson_mut_doc *doc, yyjson_mut_val *obj, const Anope::string &typestr, const Data &data)
{
const auto generator = "Anope " + Anope::Version() + " " + Anope::VersionBuildString();
yyjson_mut_obj_upsert(doc, obj, "generator", yyjson_mut_strncpy(doc, generator.c_str(), generator.length()));
yyjson_mut_obj_upsert(doc, obj, "version", yyjson_mut_uint(doc, ANOPE_DATABASE_VERSION));
yyjson_mut_obj_upsert(doc, obj, "updated", yyjson_mut_int(doc, Anope::CurTime));
auto *type = yyjson_mut_obj_getn(obj, typestr.c_str(), typestr.length());
if (!type || !yyjson_mut_is_arr(type))
{
// We haven't seen this element before.
type = yyjson_mut_arr(doc);
yyjson_mut_obj_add_val(doc, obj, typestr.c_str(), type);
}
auto *elem = yyjson_mut_obj(doc);
if (data.id)
yyjson_mut_obj_add_uint(doc, elem, "@id", data.id);
for (const auto &[key, value] : data.data)
{
yyjson_mut_val *v;
switch (data.GetType(key))
{
case Serialize::DataType::BOOL:
v = yyjson_mut_bool(doc, Anope::Convert<bool>(value.str(), false));
break;
case Serialize::DataType::FLOAT:
v = yyjson_mut_real(doc, Anope::Convert<double>(value.str(), 0.0));
break;
case Serialize::DataType::INT:
v = yyjson_mut_int(doc, Anope::Convert<int64_t>(value.str(), 0));
break;
case Serialize::DataType::TEXT:
{
auto str = value.str();
v = str.empty() ? yyjson_mut_null(doc) : yyjson_mut_strncpy(doc, str.c_str(), str.length());
break;
}
case Serialize::DataType::UINT:
v = yyjson_mut_uint(doc, Anope::Convert<uint64_t>(value.str(), 0));
break;
}
auto *k = yyjson_mut_strncpy(doc, key.c_str(), key.length());
yyjson_mut_obj_add(elem, k, v);
}
yyjson_mut_arr_add_val(type, elem);
}
public:
@@ -278,15 +346,15 @@ public:
{
auto dbname = GetDatabaseFile(nullptr);
auto [doc, data] = ReadDatabase(dbname);
if (!data)
auto db = ReadDatabase(dbname);
if (!db)
return EVENT_STOP;
for (const auto &type : Serialize::Type::GetTypeOrder())
{
auto *s_type = Serialize::Type::Find(type);
if (s_type && !s_type->GetOwner())
LoadType(s_type, data);
LoadType(s_type, db.value());
}
loaded = true;
@@ -295,111 +363,73 @@ public:
void OnSaveDatabase() override
{
std::set<Module *> updated;
for (const auto &[_, s_type] : Serialize::Type::GetTypes())
// Step 1: clear the old data.
for (const auto &type : Serialize::Type::GetTypeOrder())
{
auto *s_type = Serialize::Type::Find(type);
if (!s_type)
continue; // Provider has been unloaded.
auto it = databases.find(s_type->GetOwner());
if (it == databases.end())
{
auto *doc = yyjson_mut_doc_new(nullptr);
auto *root = yyjson_mut_obj(doc);
yyjson_mut_doc_set_root(doc, root);
UpdateMetadata(doc, root);
auto *data = yyjson_mut_obj(doc);
yyjson_mut_obj_add_val(doc, root, "data", data);
databases[s_type->GetOwner()] = { doc, data };
databases.emplace(s_type->GetOwner(), DBData());
continue; // We just need to create for this type.
}
else if (updated.find(s_type->GetOwner()) == updated.end())
{
auto *doc = it->second.first;
auto *root = yyjson_mut_doc_get_root(doc);
UpdateMetadata(doc, root);
updated.insert(s_type->GetOwner());
}
// As this type has been written before we need to clear the entries
// from the previous writes so we can update it in the next loop. We
// have to do it this way so we don't purge any entries for unloaded
// modules.
it->second.erase(s_type->GetName());
}
std::set<Serialize::Type *> seen;
// Step 2: store the new data.
for (auto *item : Serializable::GetItems())
{
auto *s_type = item->GetSerializableType();
if (!s_type)
continue; // Provider has been unloaded.
// This should always be found because we create it in the previous step.
auto it = databases.find(s_type->GetOwner());
if (it == databases.end())
continue; // Type has not been registered?
auto &[doc, data] = it->second;
// If the type object doesn't exist then create it. Otherwise, clear.
// all of the previous objects stored in it.
auto *type = yyjson_mut_obj_getn(data, s_type->GetName().c_str(), s_type->GetName().length());
if (!type || yyjson_mut_get_type(type) != YYJSON_TYPE_ARR)
{
// We haven't seen this element before.
type = yyjson_mut_arr(doc);
yyjson_mut_obj_add_val(doc, data, s_type->GetName().c_str(), type);
}
else if (seen.find(s_type) == seen.end())
{
// We are reusing an existing element, clear it.
yyjson_mut_arr_clear(type);
seen.insert(s_type);
}
auto *elem = yyjson_mut_arr_add_obj(doc, type);
if (item->id)
yyjson_mut_obj_add_uint(doc, elem, "@id", item->id);
Data sd;
s_type->Serialize(item, sd);
for (const auto &[key, value] : sd.data)
{
yyjson_mut_val *v;
switch (sd.GetType(key))
{
case Serialize::DataType::BOOL:
v = yyjson_mut_bool(doc, Anope::Convert<bool>(value.str(), false));
break;
case Serialize::DataType::FLOAT:
v = yyjson_mut_real(doc, Anope::Convert<double>(value.str(), 0.0));
break;
case Serialize::DataType::INT:
v = yyjson_mut_int(doc, Anope::Convert<int64_t>(value.str(), 0));
break;
case Serialize::DataType::TEXT:
{
auto str = value.str();
v = str.empty() ? yyjson_mut_null(doc) : yyjson_mut_strncpy(doc, str.c_str(), str.length());
break;
}
case Serialize::DataType::UINT:
v = yyjson_mut_uint(doc, Anope::Convert<uint64_t>(value.str(), 0));
break;
}
auto *k = yyjson_mut_strncpy(doc, key.c_str(), key.length());
yyjson_mut_obj_add(elem, k, v);
}
if (it != databases.end())
it->second.emplace(s_type->GetName(), Data(s_type, item));
}
// Step 3: serialize to JSON.
for (auto &[mod, database] : databases)
{
auto dbname = GetDatabaseFile(mod);
BackupDatabase(dbname);
auto *doc = yyjson_mut_doc_new(nullptr);
auto *root = yyjson_mut_obj(doc);
yyjson_mut_doc_set_root(doc, root);
const auto generator = "Anope " + Anope::Version() + " " + Anope::VersionBuildString();
yyjson_mut_obj_add_strncpy(doc, root, "generator", generator.c_str(), generator.length());
yyjson_mut_obj_add_uint(doc, root, "version", ANOPE_DATABASE_VERSION);
yyjson_mut_obj_add_int(doc, root, "updated", Anope::CurTime);
auto *data = yyjson_mut_obj(doc);
yyjson_mut_obj_add_val(doc, root, "data", data);
for (const auto &[name, items] : database)
SaveType(doc, data, name, items);
Log(LOG_DEBUG) << "Writing " << dbname;
yyjson_write_err errmsg;
const auto flags = YYJSON_WRITE_ALLOW_INVALID_UNICODE | YYJSON_WRITE_NEWLINE_AT_END | YYJSON_WRITE_PRETTY;
if (!yyjson_mut_write_file(dbname.c_str(), database.first, flags, nullptr, &errmsg))
if (!yyjson_mut_write_file(dbname.c_str(), doc, flags, nullptr, &errmsg))
{
Log(this) << "Unable to write " << dbname << ": error #" << errmsg.code << ": " << errmsg.msg;
// TODO: exit??? retry???
}
yyjson_mut_doc_free(doc);
}
}
@@ -415,14 +445,13 @@ public:
const auto dbname = GetDatabaseFile(s_type->GetOwner());
auto db = ReadDatabase(dbname);
if (!db.second)
if (!db)
return; // Not much we can do here.
it = databases.emplace(s_type->GetOwner(), db).first;
it = databases.emplace(s_type->GetOwner(), std::move(db.value())).first;
}
auto &[_, data] = it->second;
LoadType(s_type, data);
LoadType(s_type, it->second);
}
};
+4 -4
View File
@@ -616,11 +616,11 @@ static void LoadNicks()
for (int i = 0; i < 1024; ++i)
for (int c; (c = getc_db(f)) == 1;)
{
Anope::string nick, last_usermask, last_realname, last_quit;
Anope::string nick, last_userhost, last_realname, last_quit;
time_t registered, last_seen;
READ(read_string(nick, f));
READ(read_string(last_usermask, f));
READ(read_string(last_userhost, f));
READ(read_string(last_realname, f));
READ(read_string(last_quit, f));
@@ -658,7 +658,7 @@ static void LoadNicks()
ForbidData *d = forbid->CreateForbid();
d->mask = nc->display;
d->creator = last_usermask;
d->creator = last_userhost;
d->reason = last_realname;
d->expires = 0;
d->created = 0;
@@ -669,7 +669,7 @@ static void LoadNicks()
}
auto *na = new NickAlias(nick, nc);
na->last_usermask = last_usermask;
na->last_userhost = last_userhost;
na->last_realname = last_realname;
na->last_quit = last_quit;
na->registered = registered;
+2 -2
View File
@@ -638,7 +638,7 @@ public:
if (!packet)
return false;
Log(LOG_DEBUG_2) << "Resolver: Notifying slave " << packet->addr.addr();
Log(LOG_DEBUG_2) << "Resolver: Notifying secondary " << packet->addr.addr();
try
{
@@ -939,7 +939,7 @@ public:
void Notify(const Anope::string &zone) override
{
/* notify slaves of the update */
/* notify secondaries of the update */
for (const auto &[ip, port] : notify)
{
sockaddrs addr;
+3 -1
View File
@@ -12,7 +12,7 @@
#include "module.h"
#include "modules/ssl.h"
#define OPENSSL_API_COMPAT 0x10100000L
#define OPENSSL_API_COMPAT 0x10101000L
#define OPENSSL_NO_DEPRECATED
#include <openssl/bio.h>
@@ -126,7 +126,9 @@ public:
SSL_CTX_set_session_id_context(client_ctx, reinterpret_cast<const unsigned char *>(context_name.c_str()), context_name.length());
SSL_CTX_set_session_id_context(server_ctx, reinterpret_cast<const unsigned char *>(context_name.c_str()), context_name.length());
#ifdef OPENSSL_VERSION_STR
Log(this) << "Module was compiled against OpenSSL version " << OPENSSL_VERSION_STR << " and is running against version " << OpenSSL_version(OPENSSL_VERSION_STRING);
#endif
}
~SSLModule()
+1 -1
View File
@@ -188,7 +188,7 @@ public:
source.permission = info.permission;
AccessGroup ag = c->ci->AccessFor(u);
bool has_fantasy = ag.HasPriv("FANTASY") || source.HasPriv("botserv/fantasy");
bool has_fantasy = !info.require_privilege || ag.HasPriv("FANTASY") || source.HasPriv("botserv/fantasy");
EventReturn MOD_RESULT;
if (has_fantasy)
+4 -4
View File
@@ -57,7 +57,7 @@ class CommandHSDelAll final
public:
CommandHSDelAll(Module *creator) : Command(creator, "hostserv/delall", 1, 1)
{
this->SetDesc(_("Deletes the vhost for all nicks in a group"));
this->SetDesc(_("Deletes the vhost for all nicks in an account"));
this->SetSyntax(_("\037nick\037"));
}
@@ -80,8 +80,8 @@ public:
na = alias;
na->RemoveVHost();
}
Log(LOG_ADMIN, source, this) << "for all nicks in group " << nc->display;
source.Reply(_("VHosts for group \002%s\002 have been removed."), nc->display.c_str());
Log(LOG_ADMIN, source, this) << "for all nicks in account " << nc->display;
source.Reply(_("VHosts for account \002%s\002 have been removed."), nc->display.c_str());
}
else
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
@@ -91,7 +91,7 @@ public:
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_("Deletes the vhost for all nicks in the same group as that of the given nick."));
source.Reply(_("Deletes the vhost for all nicks in the same account as that of the given nick."));
return true;
}
};
+2 -2
View File
@@ -39,7 +39,7 @@ public:
CommandHSGroup(Module *creator) : Command(creator, "hostserv/group", 0, 0)
{
this->SetDesc(_("Syncs the vhost for all nicks in a group"));
this->SetDesc(_("Syncs the vhost for all nicks in an account"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
@@ -54,7 +54,7 @@ public:
if (na && source.GetAccount() == na->nc && na->HasVHost())
{
this->Sync(na);
source.Reply(_("All vhosts in the group \002%s\002 have been set to \002%s\002."),
source.Reply(_("All vhosts for the account \002%s\002 have been set to \002%s\002."),
source.nc->display.c_str(), na->GetVHostMask().c_str());
}
else
+4 -11
View File
@@ -77,11 +77,6 @@ struct HostRequestTypeImpl final
class CommandHSRequest final
: public Command
{
static bool isvalidchar(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '-';
}
public:
CommandHSRequest(Module *creator) : Command(creator, "hostserv/request", 1, 1)
{
@@ -142,13 +137,11 @@ public:
source.Reply(HOST_NO_VIDENT);
return;
}
for (const auto &chr : user)
if (!IRCD->IsIdentValid(user))
{
if (!isvalidchar(chr))
{
source.Reply(HOST_SET_IDENT_ERROR);
return;
}
source.Reply(HOST_SET_IDENT_ERROR);
return;
}
}
+4 -4
View File
@@ -122,7 +122,7 @@ class CommandHSSetAll final
public:
CommandHSSetAll(Module *creator) : Command(creator, "hostserv/setall", 2, 2)
{
this->SetDesc(_("Set the vhost for all nicks in a group"));
this->SetDesc(_("Set the vhost for all nicks in an account"));
this->SetSyntax(_("\037nick\037 \037hostmask\037"));
}
@@ -193,7 +193,7 @@ public:
na->SetVHost(user, host, source.GetNick());
this->Sync(na);
FOREACH_MOD(OnSetVHost, (na));
source.Reply(_("VHost for group \002%s\002 set to \002%s\002."), nick.c_str(), na->GetVHostMask().c_str());
source.Reply(_("VHost for account \002%s\002 set to \002%s\002."), nick.c_str(), na->GetVHostMask().c_str());
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
@@ -201,13 +201,13 @@ public:
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Sets the vhost for all nicks in the same group as that "
"Sets the vhost for all nicks in the same account as that "
"of the given nick. If your IRCD supports vidents, then "
"using SETALL <nick> <ident>@<hostmask> will set idents "
"for users as well as vhosts."
"\n\n"
"* NOTE, this will not update the vhost for any nicks "
"added to the group after this command was used."
"added to the account after this command was used."
));
return true;
}
-1
View File
@@ -1 +0,0 @@
build_subdir(${CMAKE_CURRENT_SOURCE_DIR})
-325
View File
@@ -1,325 +0,0 @@
/*
*
* (C) 2013-2025 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*/
#include "irc2sql.h"
void IRC2SQL::OnShutdown()
{
// TODO: test if we really have to use blocking query here
// (sometimes mysql get unloaded before the other thread executed all queries)
if (this->sql)
SQL::Result r = this->sql->RunQuery(SQL::Query("CALL " + prefix + "OnShutdown()"));
quitting = true;
}
void IRC2SQL::OnReload(Configuration::Conf &conf)
{
const auto &block = Config->GetModule(this);
prefix = block.Get<const Anope::string>("prefix", "anope_");
GeoIPDB = block.Get<const Anope::string>("geoip_database");
ctcpuser = block.Get<bool>("ctcpuser", "no");
ctcpeob = block.Get<bool>("ctcpeob", "yes");
Anope::string engine = block.Get<const Anope::string>("engine");
this->sql = ServiceReference<SQL::Provider>("SQL::Provider", engine);
if (sql)
this->CheckTables();
else
Log() << "IRC2SQL: no database connection to " << engine;
const Anope::string &snick = block.Get<const Anope::string>("client");
if (snick.empty())
throw ConfigException(Module::name + ": <client> must be defined");
StatServ = BotInfo::Find(snick, true);
if (!StatServ)
throw ConfigException(Module::name + ": no bot named " + snick);
if (firstrun)
{
firstrun = false;
for (Anope::map<Server *>::const_iterator it = Servers::ByName.begin(); it != Servers::ByName.end(); ++it)
{
this->OnNewServer(it->second);
}
for (const auto &[_, c] : ChannelList)
{
this->OnChannelCreate(c);
}
for (const auto &[_, u] : UserListByNick)
{
bool exempt = false;
this->OnUserConnect(u, exempt);
for (const auto &[_, uc] : u->chans)
{
this->OnJoinChannel(u, uc->chan);
}
}
}
}
void IRC2SQL::OnNewServer(Server *server)
{
query = "INSERT DELAYED INTO `" + prefix + "server` (name, hops, comment, link_time, online, ulined) "
"VALUES (@name@, @hops@, @comment@, now(), 'Y', @ulined@) "
"ON DUPLICATE KEY UPDATE name=VALUES(name), hops=VALUES(hops), comment=VALUES(comment), "
"link_time=VALUES(link_time), online=VALUES(online), ulined=VALUES(ulined)";
query.SetValue("name", server->GetName());
query.SetValue("hops", server->GetHops());
query.SetValue("comment", server->GetDescription());
query.SetValue("ulined", server->IsULined() ? "Y" : "N");
this->RunQuery(query);
}
void IRC2SQL::OnServerQuit(Server *server)
{
if (quitting)
return;
query = "CALL " + prefix + "ServerQuit(@name@)";
query.SetValue("name", server->GetName());
this->RunQuery(query);
}
void IRC2SQL::OnUserConnect(User *u, bool &exempt)
{
if (!introduced_myself)
{
this->OnNewServer(Me);
introduced_myself = true;
}
query = "CALL " + prefix + "UserConnect(@nick@,@host@,@vhost@,@chost@,@realname@,@ip@,@ident@,@vident@,"
"@account@,@secure@,@fingerprint@,@signon@,@server@,@uuid@,@modes@,@oper@)";
query.SetValue("nick", u->nick);
query.SetValue("host", u->host);
query.SetValue("vhost", u->vhost);
query.SetValue("chost", u->chost);
query.SetValue("realname", u->realname);
query.SetValue("ip", u->ip.addr());
query.SetValue("ident", u->GetIdent());
query.SetValue("vident", u->GetVIdent());
query.SetValue("secure", u->IsSecurelyConnected() ? "Y" : "N");
query.SetValue("account", u->IsIdentified() ? u->Account()->display : "");
query.SetValue("fingerprint", u->fingerprint);
query.SetValue("signon", u->signon);
query.SetValue("server", u->server->GetName());
query.SetValue("uuid", u->GetUID());
query.SetValue("modes", u->GetModes());
query.SetValue("oper", u->HasMode("OPER") ? "Y" : "N");
this->RunQuery(query);
if (ctcpuser && (Me->IsSynced() || ctcpeob) && u->server != Me)
IRCD->SendPrivmsg(StatServ, u->GetUID(), Anope::FormatCTCP("VERSION"));
}
void IRC2SQL::OnUserQuit(User *u, const Anope::string &msg)
{
if (quitting || u->server->IsQuitting())
return;
query = "CALL " + prefix + "UserQuit(@nick@)";
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
void IRC2SQL::OnUserNickChange(User *u, const Anope::string &oldnick)
{
query = "UPDATE `" + prefix + "user` SET nick=@newnick@ WHERE nick=@oldnick@";
query.SetValue("newnick", u->nick);
query.SetValue("oldnick", oldnick);
this->RunQuery(query);
}
void IRC2SQL::OnUserAway(User *u, const Anope::string &message)
{
query = "UPDATE `" + prefix + "user` SET away=@away@, awaymsg=@awaymsg@ WHERE nick=@nick@";
query.SetValue("away", (!message.empty()) ? "Y" : "N");
query.SetValue("awaymsg", message);
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
void IRC2SQL::OnFingerprint(User *u)
{
query = "UPDATE `" + prefix + "user` SET secure=@secure@, fingerprint=@fingerprint@ WHERE nick=@nick@";
query.SetValue("secure", u->IsSecurelyConnected() ? "Y" : "N");
query.SetValue("fingerprint", u->fingerprint);
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
void IRC2SQL::OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname)
{
query = "UPDATE `" + prefix + "user` SET modes=@modes@, oper=@oper@ WHERE nick=@nick@";
query.SetValue("nick", u->nick);
query.SetValue("modes", u->GetModes());
query.SetValue("oper", u->HasMode("OPER") ? "Y" : "N");
this->RunQuery(query);
}
void IRC2SQL::OnUserModeUnset(const MessageSource &setter, User *u, const Anope::string &mname)
{
this->OnUserModeSet(setter, u, mname);
}
void IRC2SQL::OnUserLogin(User *u)
{
query = "UPDATE `" + prefix + "user` SET account=@account@ WHERE nick=@nick@";
query.SetValue("nick", u->nick);
query.SetValue("account", u->IsIdentified() ? u->Account()->display : "");
this->RunQuery(query);
}
void IRC2SQL::OnNickLogout(User *u)
{
this->OnUserLogin(u);
}
void IRC2SQL::OnSetDisplayedHost(User *u)
{
query = "UPDATE `" + prefix + "user` "
"SET vhost=@vhost@ "
"WHERE nick=@nick@";
query.SetValue("vhost", u->GetDisplayedHost());
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
void IRC2SQL::OnChannelCreate(Channel *c)
{
query = "INSERT INTO `" + prefix + "chan` (channel, topic, topicauthor, topictime, modes) "
"VALUES (@channel@,@topic@,@topicauthor@,@topictime@,@modes@) "
"ON DUPLICATE KEY UPDATE channel=VALUES(channel), topic=VALUES(topic),"
"topicauthor=VALUES(topicauthor), topictime=VALUES(topictime), modes=VALUES(modes)";
query.SetValue("channel", c->name);
query.SetValue("topic", c->topic);
query.SetValue("topicauthor", c->topic_setter);
if (c->topic_ts > 0)
query.SetValue("topictime", c->topic_ts);
else
query.SetValue("topictime", "NULL", false);
query.SetValue("modes", c->GetModes(true,true));
this->RunQuery(query);
}
void IRC2SQL::OnChannelDelete(Channel *c)
{
query = "DELETE FROM `" + prefix + "chan` WHERE channel=@channel@";
query.SetValue("channel", c->name);
this->RunQuery(query);
}
void IRC2SQL::OnJoinChannel(User *u, Channel *c)
{
Anope::string modes;
ChanUserContainer *cu = u->FindChannel(c);
if (cu)
modes = cu->status.Modes();
query = "CALL " + prefix + "JoinUser(@nick@,@channel@,@modes@)";
query.SetValue("nick", u->nick);
query.SetValue("channel", c->name);
query.SetValue("modes", modes);
this->RunQuery(query);
}
EventReturn IRC2SQL::OnChannelModeSet(Channel *c, MessageSource &setter, ChannelMode *mode, const ModeData &data)
{
if (mode->type == MODE_STATUS)
{
User *u = User::Find(data.value);
if (u == NULL)
return EVENT_CONTINUE;
ChanUserContainer *cc = u->FindChannel(c);
if (cc == NULL)
return EVENT_CONTINUE;
query = "UPDATE `" + prefix + "user` AS u, `" + prefix + "ison` AS i, `" + prefix + "chan` AS c"
" SET i.modes=@modes@"
" WHERE u.nick=@nick@ AND c.channel=@channel@"
" AND u.nickid = i.nickid AND c.chanid = i.chanid";
query.SetValue("nick", u->nick);
query.SetValue("modes", cc->status.Modes());
query.SetValue("channel", c->name);
this->RunQuery(query);
}
else
{
query = "UPDATE `" + prefix + "chan` SET modes=@modes@ WHERE channel=@channel@";
query.SetValue("channel", c->name);
query.SetValue("modes", c->GetModes(true,true));
this->RunQuery(query);
}
return EVENT_CONTINUE;
}
EventReturn IRC2SQL::OnChannelModeUnset(Channel *c, MessageSource &setter, ChannelMode *mode, const Anope::string &param)
{
this->OnChannelModeSet(c, setter, mode, param);
return EVENT_CONTINUE;
}
void IRC2SQL::OnLeaveChannel(User *u, Channel *c)
{
if (quitting)
return;
/*
* user is quitting, we already received a OnUserQuit()
* at this point the user is already removed from SQL and all channels
*/
if (u->Quitting())
return;
query = "CALL " + prefix + "PartUser(@nick@,@channel@)";
query.SetValue("nick", u->nick);
query.SetValue("channel", c->name);
this->RunQuery(query);
}
void IRC2SQL::OnTopicUpdated(User *source, Channel *c, const Anope::string &user, const Anope::string &topic)
{
query = "UPDATE `" + prefix + "chan` "
"SET topic=@topic@, topicauthor=@author@, topictime=FROM_UNIXTIME(@time@) "
"WHERE channel=@channel@";
query.SetValue("topic", c->topic);
query.SetValue("author", c->topic_setter);
query.SetValue("time", c->topic_ts);
query.SetValue("channel", c->name);
this->RunQuery(query);
}
void IRC2SQL::OnBotNotice(User *u, BotInfo *bi, Anope::string &message, const Anope::map<Anope::string> &tags)
{
if (bi != StatServ)
return;
Anope::string ctcpname, ctcpbody;
if (!Anope::ParseCTCP(message, ctcpname, ctcpbody) || ctcpname != "VERSION")
return;
if (u->HasExt("CTCPVERSION"))
return;
u->Extend<bool>("CTCPVERSION");
auto versionstr = Anope::NormalizeBuffer(ctcpbody);
if (versionstr.empty())
return;
query = "UPDATE `" + prefix + "user` "
"SET version=@version@ "
"WHERE nick=@nick@";
query.SetValue("version", versionstr);
query.SetValue("nick", u->nick);
this->RunQuery(query);
}
MODULE_INIT(IRC2SQL)
-88
View File
@@ -1,88 +0,0 @@
/*
*
* (C) 2013-2025 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*/
#pragma once
#include "module.h"
#include "modules/sql.h"
class MySQLInterface final
: 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) << "irc2sql: Error executing query " << r.finished_query << ": " << r.GetError();
else
Log(LOG_DEBUG) << "irc2sql: Error executing query: " << r.GetError();
}
};
class IRC2SQL final
: public Module
{
ServiceReference<SQL::Provider> sql;
MySQLInterface sqlinterface;
SQL::Query query;
std::vector<Anope::string> TableList, ProcedureList, EventList;
Anope::string prefix, GeoIPDB;
bool quitting, introduced_myself, ctcpuser, ctcpeob, firstrun;
BotInfo *StatServ;
PrimitiveExtensibleItem<bool> versionreply;
void RunQuery(const SQL::Query &q);
void GetTables();
bool HasTable(const Anope::string &table);
bool HasProcedure(const Anope::string &table);
bool HasEvent(const Anope::string &table);
void CheckTables();
public:
IRC2SQL(const Anope::string &modname, const Anope::string &creator) :
Module(modname, creator, EXTRA | VENDOR), sql("", ""), sqlinterface(this), versionreply(this, "CTCPVERSION")
{
firstrun = true;
quitting = false;
introduced_myself = false;
}
void OnShutdown() override;
void OnReload(Configuration::Conf &config) override;
void OnNewServer(Server *server) override;
void OnServerQuit(Server *server) override;
void OnUserConnect(User *u, bool &exempt) override;
void OnUserQuit(User *u, const Anope::string &msg) override;
void OnUserNickChange(User *u, const Anope::string &oldnick) override;
void OnUserAway(User *u, const Anope::string &message) override;
void OnFingerprint(User *u) override;
void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override;
void OnUserModeUnset(const MessageSource &setter, User *u, const Anope::string &mname) override;
void OnUserLogin(User *u) override;
void OnNickLogout(User *u) override;
void OnSetDisplayedHost(User *u) override;
void OnChannelCreate(Channel *c) override;
void OnChannelDelete(Channel *c) override;
void OnLeaveChannel(User *u, Channel *c) override;
void OnJoinChannel(User *u, Channel *c) override;
EventReturn OnChannelModeSet(Channel *c, MessageSource &setter, ChannelMode *mode, const ModeData &data) override;
EventReturn OnChannelModeUnset(Channel *c, MessageSource &setter, ChannelMode *mode, const Anope::string &param) override;
void OnTopicUpdated(User *source, Channel *c, const Anope::string &user, const Anope::string &topic) override;
void OnBotNotice(User *u, BotInfo *bi, Anope::string &message, const Anope::map<Anope::string> &tags) override;
};
-343
View File
@@ -1,343 +0,0 @@
/*
*
* (C) 2013-2025 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*/
#include "irc2sql.h"
void IRC2SQL::CheckTables()
{
Anope::string geoquery("");
if (firstrun)
{
/*
* reset some tables to make sure they are really empty
*/
this->sql->RunQuery("TRUNCATE TABLE " + prefix + "user");
this->sql->RunQuery("TRUNCATE TABLE " + prefix + "chan");
this->sql->RunQuery("TRUNCATE TABLE " + prefix + "ison");
this->sql->RunQuery("UPDATE `" + prefix + "server` SET currentusers=0, online='N'");
}
this->GetTables();
if (GeoIPDB.equals_ci("country"))
{
if (!this->HasTable(prefix + "geoip_country"))
{
query = "CREATE TABLE `" + prefix + "geoip_country` ("
"`start` INT UNSIGNED NOT NULL,"
"`end` INT UNSIGNED NOT NULL,"
"`countrycode` varchar(2),"
"`countryname` varchar(50),"
"PRIMARY KEY `end` (`end`),"
"KEY `start` (`start`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
}
else if (GeoIPDB.equals_ci("city"))
{
if (!this->HasTable(prefix + "geoip_city_blocks"))
{
query = "CREATE TABLE `" + prefix + "geoip_city_blocks` ("
"`start` INT UNSIGNED NOT NULL,"
"`end` INT UNSIGNED NOT NULL,"
"`locId` INT UNSIGNED NOT NULL,"
"PRIMARY KEY `end` (`end`),"
"KEY `start` (`start`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "geoip_city_location"))
{
query = "CREATE TABLE `" + prefix + "geoip_city_location` ("
"`locId` INT UNSIGNED NOT NULL,"
"`country` CHAR(2) NOT NULL,"
"`region` CHAR(2) NOT NULL,"
"`city` VARCHAR(50),"
"`latitude` FLOAT,"
"`longitude` FLOAT,"
"`areaCode` INT,"
"PRIMARY KEY (`locId`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "geoip_city_region"))
{ query = "CREATE TABLE `" + prefix + "geoip_city_region` ("
"`country` CHAR(2) NOT NULL,"
"`region` CHAR(2) NOT NULL,"
"`regionname` VARCHAR(100) NOT NULL,"
"PRIMARY KEY (`country`,`region`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
}
if (!this->HasTable(prefix + "server"))
{
query = "CREATE TABLE `" + prefix + "server` ("
"`id` int UNSIGNED NOT NULL AUTO_INCREMENT,"
"`name` varchar(64) NOT NULL,"
"`hops` tinyint NOT NULL,"
"`comment` varchar(255) NOT NULL,"
"`link_time` datetime DEFAULT NULL,"
"`split_time` datetime DEFAULT NULL,"
"`version` varchar(127) DEFAULT NULL,"
"`currentusers` int DEFAULT 0,"
"`online` enum('Y','N') NOT NULL DEFAULT 'Y',"
"`ulined` enum('Y','N') NOT NULL DEFAULT 'N',"
"PRIMARY KEY (`id`),"
"UNIQUE KEY `name` (`name`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "chan"))
{
query = "CREATE TABLE `" + prefix + "chan` ("
"`chanid` int UNSIGNED NOT NULL AUTO_INCREMENT,"
"`channel` varchar(255) NOT NULL,"
"`topic` varchar(512) DEFAULT NULL,"
"`topicauthor` varchar(255) DEFAULT NULL,"
"`topictime` datetime DEFAULT NULL,"
"`modes` varchar(512) DEFAULT NULL,"
"PRIMARY KEY (`chanid`),"
"UNIQUE KEY `channel`(`channel`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "user"))
{
query = "CREATE TABLE `" + prefix + "user` ("
"`nickid` int UNSIGNED NOT NULL AUTO_INCREMENT,"
"`nick` varchar(255) NOT NULL DEFAULT '',"
"`host` varchar(255) NOT NULL DEFAULT '',"
"`vhost` varchar(255) NOT NULL DEFAULT '',"
"`chost` varchar(255) NOT NULL DEFAULT '',"
"`realname` varchar(255) NOT NULL DEFAULT '',"
"`ip` varchar(255) NOT NULL DEFAULT '',"
"`ident` varchar(32) NOT NULL DEFAULT '',"
"`vident` varchar(32) NOT NULL DEFAULT '',"
"`modes` varchar(255) NOT NULL DEFAULT '',"
"`account` varchar(255) NOT NULL DEFAULT '',"
"`secure` enum('Y','N') NOT NULL DEFAULT 'N',"
"`fingerprint` varchar(128) NOT NULL DEFAULT '',"
"`signon` datetime DEFAULT NULL,"
"`server` varchar(255) NOT NULL DEFAULT '',"
"`servid` int UNSIGNED NOT NULL DEFAULT '0',"
"`uuid` varchar(32) NOT NULL DEFAULT '',"
"`oper` enum('Y','N') NOT NULL DEFAULT 'N',"
"`away` enum('Y','N') NOT NULL DEFAULT 'N',"
"`awaymsg` varchar(255) NOT NULL DEFAULT '',"
"`version` varchar(255) NOT NULL DEFAULT '',"
"`geocode` varchar(16) NOT NULL DEFAULT '',"
"`geocountry` varchar(64) NOT NULL DEFAULT '',"
"`georegion` varchar(100) NOT NULL DEFAULT '',"
"`geocity` varchar(128) NOT NULL DEFAULT '',"
"`locId` INT UNSIGNED,"
"PRIMARY KEY (`nickid`),"
"UNIQUE KEY `nick` (`nick`),"
"KEY `servid` (`servid`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "ison"))
{
query = "CREATE TABLE `" + prefix + "ison` ("
"`nickid` int unsigned NOT NULL default '0',"
"`chanid` int unsigned NOT NULL default '0',"
"`modes` varchar(255) NOT NULL default '',"
"PRIMARY KEY (`nickid`,`chanid`),"
"KEY `modes` (`modes`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (!this->HasTable(prefix + "maxusers"))
{
query = "CREATE TABLE `" + prefix + "maxusers` ("
"`name` VARCHAR(255) NOT NULL,"
"`maxusers` int NOT NULL,"
"`maxtime` DATETIME NOT NULL,"
"`lastused` DATETIME NOT NULL,"
"UNIQUE KEY `name` (`name`)"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
this->RunQuery(query);
}
if (this->HasProcedure(prefix + "UserConnect"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "UserConnect"));
if (GeoIPDB.equals_ci("country"))
geoquery = "UPDATE `" + prefix + "user` AS u "
"JOIN ( SELECT `countrycode`, `countryname` "
"FROM `" + prefix + "geoip_country` "
"WHERE INET_ATON(ip_) <= `end` "
"AND `start` <= INET_ATON(ip_) "
"ORDER BY `end` ASC LIMIT 1 ) as c "
"SET u.geocode = c.countrycode, u.geocountry = c.countryname "
"WHERE u.nick = nick_; ";
else if (GeoIPDB.equals_ci("city"))
geoquery = "UPDATE `" + prefix + "user` as u "
"JOIN ( SELECT * FROM `" + prefix + "geoip_city_location` "
"WHERE `locID` = ( SELECT `locID` "
"FROM `" + prefix + "geoip_city_blocks` "
"WHERE INET_ATON(ip_) <= `end` "
"AND `start` <= INET_ATON(ip_) "
"ORDER BY `end` ASC LIMIT 1 ) "
") as l "
"SET u.geocode = l.country, "
"u.geocity = l.city, "
"u.locID = l.locID, "
"u.georegion = ( SELECT `regionname` "
"FROM `" + prefix + "geoip_city_region` "
"WHERE `country` = l.country "
"AND `region` = l.region )"
"WHERE u.nick = nick_;";
query = "CREATE PROCEDURE `" + prefix + "UserConnect`"
"(nick_ varchar(255), host_ varchar(255), vhost_ varchar(255), "
"chost_ varchar(255), realname_ varchar(255), ip_ varchar(255), "
"ident_ varchar(255), vident_ varchar(255), account_ varchar(255), "
"secure_ enum('Y','N'), fingerprint_ varchar(255), signon_ int, "
"server_ varchar(255), uuid_ varchar(32), modes_ varchar(255), "
"oper_ enum('Y','N')) "
"BEGIN "
"DECLARE cur int;"
"DECLARE max int;"
"INSERT INTO `" + prefix + "user` "
"(nick, host, vhost, chost, realname, ip, ident, vident, account, "
"secure, fingerprint, signon, server, uuid, modes, oper) "
"VALUES (nick_, host_, vhost_, chost_, realname_, ip_, ident_, vident_, "
"account_, secure_, fingerprint_, FROM_UNIXTIME(signon_), server_, "
"uuid_, modes_, oper_) "
"ON DUPLICATE KEY UPDATE host=VALUES(host), vhost=VALUES(vhost), "
"chost=VALUES(chost), realname=VALUES(realname), ip=VALUES(ip), "
"ident=VALUES(ident), vident=VALUES(vident), account=VALUES(account), "
"secure=VALUES(secure), fingerprint=VALUES(fingerprint), signon=VALUES(signon), "
"server=VALUES(server), uuid=VALUES(uuid), modes=VALUES(modes), "
"oper=VALUES(oper);"
"UPDATE `" + prefix + "user` as `u`, `" + prefix + "server` as `s`"
"SET u.servid = s.id, "
"s.currentusers = s.currentusers + 1 "
"WHERE s.name = server_ AND u.nick = nick_;"
"SELECT `currentusers` INTO cur FROM `" + prefix + "server` WHERE name=server_;"
"SELECT `maxusers` INTO max FROM `" + prefix + "maxusers` WHERE name=server_;"
"IF found_rows() AND cur <= max THEN "
"UPDATE `" + prefix + "maxusers` SET lastused=now() WHERE name=server_;"
"ELSE "
"INSERT INTO `" + prefix + "maxusers` (name, maxusers, maxtime, lastused) "
"VALUES ( server_, cur, now(), now() ) "
"ON DUPLICATE KEY UPDATE "
"name=VALUES(name), maxusers=VALUES(maxusers),"
"maxtime=VALUES(maxtime), lastused=VALUES(lastused);"
"END IF;"
+ geoquery +
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "ServerQuit"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "ServerQuit"));
query = "CREATE PROCEDURE " + prefix + "ServerQuit(sname_ varchar(255)) "
"BEGIN "
/* 1.
* remove all users on the splitting server from the ison table
*/
"DELETE i FROM `" + prefix + "ison` AS i "
"INNER JOIN `" + prefix + "server` AS s "
"INNER JOIN `" + prefix + "user` AS u "
"WHERE i.nickid = u.nickid "
"AND u.servid = s.id "
"AND s.name = sname_;"
/* 2.
* remove all users on the splitting server from the user table
*/
"DELETE u FROM `" + prefix + "user` AS u "
"INNER JOIN `" + prefix + "server` AS s "
"WHERE s.id = u.servid "
"AND s.name = sname_;"
/* 3.
* on the splitting server, set usercount = 0, split_time = now(), online = 'N'
*/
"UPDATE `" + prefix + "server` SET currentusers = 0, split_time = now(), online = 'N' "
"WHERE name = sname_;"
"END;"; // end of the procedure
this->RunQuery(query);
if (this->HasProcedure(prefix + "UserQuit"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "UserQuit"));
query = "CREATE PROCEDURE `" + prefix + "UserQuit`"
"(nick_ varchar(255)) "
"BEGIN "
/* decrease usercount on the server where the user was on */
"UPDATE `" + prefix + "user` AS `u`, `" + prefix + "server` AS `s` "
"SET s.currentusers = s.currentusers - 1 "
"WHERE u.nick=nick_ AND u.servid = s.id; "
/* remove from all channels where the user was on */
"DELETE i FROM `" + prefix + "ison` AS i "
"INNER JOIN `" + prefix + "user` as u "
"WHERE u.nick = nick_ "
"AND i.nickid = u.nickid;"
/* remove the user from the user table */
"DELETE FROM `" + prefix + "user` WHERE nick = nick_; "
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "ShutDown"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "ShutDown"));
query = "CREATE PROCEDURE `" + prefix + "ShutDown`()"
"BEGIN "
"UPDATE `" + prefix + "server` "
"SET currentusers=0, online='N', split_time=now();"
"TRUNCATE TABLE `" + prefix + "user`;"
"TRUNCATE TABLE `" + prefix + "chan`;"
"TRUNCATE TABLE `" + prefix + "ison`;"
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "JoinUser"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "JoinUser"));
query = "CREATE PROCEDURE `"+ prefix + "JoinUser`"
"(nick_ varchar(255), channel_ varchar(255), modes_ varchar(255)) "
"BEGIN "
"DECLARE cur int;"
"DECLARE max int;"
"INSERT INTO `" + prefix + "ison` (nickid, chanid, modes) "
"SELECT u.nickid, c.chanid, modes_ "
"FROM " + prefix + "user AS u, " + prefix + "chan AS c "
"WHERE u.nick=nick_ AND c.channel=channel_;"
"SELECT count(i.chanid) INTO cur "
"FROM `" + prefix + "chan` AS c, " + prefix + "ison AS i "
"WHERE i.chanid = c.chanid AND c.channel=channel_;"
"SELECT `maxusers` INTO max FROM `" + prefix + "maxusers` WHERE name=channel_;"
"IF found_rows() AND cur <= max THEN "
"UPDATE `" + prefix + "maxusers` SET lastused=now() WHERE name=channel_;"
"ELSE "
"INSERT INTO `" + prefix + "maxusers` (name, maxusers, maxtime, lastused) "
"VALUES ( channel_, cur, now(), now() ) "
"ON DUPLICATE KEY UPDATE "
"name=VALUES(name), maxusers=VALUES(maxusers),"
"maxtime=VALUES(maxtime), lastused=VALUES(lastused);"
"END IF;"
"END";
this->RunQuery(query);
if (this->HasProcedure(prefix + "PartUser"))
this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "PartUser"));
query = "CREATE PROCEDURE `" + prefix + "PartUser`"
"(nick_ varchar(255), channel_ varchar(255)) "
"BEGIN "
"DELETE i FROM `" + prefix + "ison` AS i "
"INNER JOIN `" + prefix + "user` AS u "
"INNER JOIN `" + prefix + "chan` AS c "
"WHERE i.nickid = u.nickid "
"AND u.nick = nick_ "
"AND i.chanid = c.chanid "
"AND c.channel = channel_;"
"END";
this->RunQuery(query);
}
-68
View File
@@ -1,68 +0,0 @@
/*
*
* (C) 2013-2025 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*/
#include "irc2sql.h"
void IRC2SQL::RunQuery(const SQL::Query &q)
{
if (sql)
sql->Run(&sqlinterface, q);
}
void IRC2SQL::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<Anope::string, Anope::string> &map = r.Row(i);
for (std::map<Anope::string, Anope::string>::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 IRC2SQL::HasTable(const Anope::string &table)
{
for (std::vector<Anope::string>::const_iterator it = TableList.begin(); it != TableList.end(); ++it)
if (*it == table)
return true;
return false;
}
bool IRC2SQL::HasProcedure(const Anope::string &table)
{
for (std::vector<Anope::string>::const_iterator it = ProcedureList.begin(); it != ProcedureList.end(); ++it)
if (*it == table)
return true;
return false;
}
bool IRC2SQL::HasEvent(const Anope::string &table)
{
for (std::vector<Anope::string>::const_iterator it = EventList.begin(); it != EventList.end(); ++it)
if (*it == table)
return true;
return false;
}
+1 -1
View File
@@ -161,7 +161,7 @@ public:
u->Account()->email = email;
BotInfo *NickServ = Config->GetClient("NickServ");
if (NickServ)
u->SendMessage(NickServ, _("Your email has been updated to \002%s\002"), email.c_str());
u->SendMessage(NickServ, _("Your email address has been updated to \002%s\002"), email.c_str());
Log(this->owner) << "Updated email address for " << u->nick << " (" << u->Account()->display << ") to " << email;
}
}
+3 -4
View File
@@ -187,10 +187,9 @@ public:
{
if (c->ci && !c->ci->memos.memos->empty() && c->ci->AccessFor(u).HasPriv("MEMO"))
{
if (c->ci->memos.memos->size() == 1)
u->SendMessage(MemoServ, _("There is \002%zu\002 memo on channel %s."), c->ci->memos.memos->size(), c->ci->name.c_str());
else
u->SendMessage(MemoServ, _("There are \002%zu\002 memos on channel %s."), c->ci->memos.memos->size(), c->ci->name.c_str());
auto memocount = c->ci->memos.memos->size();
u->SendMessage(MemoServ, memocount, N_("There is \002%zu\002 memo on channel %s.", "There are \002%zu\002 memos on channel %s."),
memocount, c->ci->name.c_str());
}
}
+15 -11
View File
@@ -224,16 +224,21 @@ public:
auto protect = protectafter ? *protectafter : block.Get<time_t>("defaultprotect", "1m");
protect = std::clamp(protect, block.Get<time_t>("minprotect", "10s"), block.Get<time_t>("maxprotect", "10m"));
u->SendMessage(NickServ, _(
"This nickname is registered and has protection enabled. If it belongs to you, "
"type \002%s\032\037password\037\002 to identify to your account."
),
NickServ->GetQueryCommand("nickserv/identify", u->nick).c_str()
);
if (protect)
{
u->SendMessage(NickServ, NICK_IS_SECURE, NickServ->GetQueryCommand("nickserv/identify").c_str());
u->SendMessage(NickServ, _("If you do not change within %s, I will change your nick."),
u->SendMessage(NickServ, _("Your nickname will be changed in %s if you do not identify."),
Anope::Duration(protect, u->Account()).c_str());
new NickServCollide(this, this, u, na, protect);
}
else
{
u->SendMessage(NickServ, FORCENICKCHANGE_NOW);
this->Collide(u, na);
}
}
@@ -361,7 +366,7 @@ public:
void OnDelCore(NickCore *nc) override
{
Log(NickServ, "nick") << "Deleting nickname group " << nc->display;
Log(NickServ, "nick") << "Deleting account " << nc->display;
/* Clean up this nick core from any users online */
for (std::list<User *>::iterator it = nc->users.begin(); it != nc->users.end();)
@@ -377,7 +382,7 @@ public:
void OnChangeCoreDisplay(NickCore *nc, const Anope::string &newdisplay) override
{
Log(LOG_NORMAL, "nick", NickServ) << "Changing " << nc->display << " nickname group display to " << newdisplay;
Log(LOG_NORMAL, "nick", NickServ) << "Changing " << nc->display << " account display nickname to " << newdisplay;
}
void OnNickIdentify(User *u) override
@@ -401,10 +406,9 @@ public:
if (block.Get<bool>("forceemail", "yes") && u->Account()->email.empty())
{
u->SendMessage(NickServ, _(
"You must now supply an email for your nick. "
"This email will allow you to retrieve your password in "
"case you forget it. "
"Type \002%s\032\037email\037\002 in order to set your email."
"You must now supply an email address for your nick. This email address will "
"allow you to recover your account in case you forget your password. Type "
"\002%s\032\037email\037\002 in order to set your email address."
),
NickServ->GetQueryCommand("nickserv/set/email").c_str());
}
@@ -492,7 +496,7 @@ public:
IRCD->SendLogin(u, na);
if (!Config->GetModule("nickserv").Get<bool>("nonicknameownership") && na->nc == u->Account() && !na->nc->HasExt("UNCONFIRMED"))
u->SetMode(NickServ, "REGISTERED");
Log(u, "", NickServ) << u->GetMask() << " automatically identified for group " << u->Account()->display;
Log(u, "", NickServ) << u->GetMask() << " automatically identified for account " << u->Account()->display;
}
if (!u->nick.equals_ci(oldnick) && old_na)
@@ -617,7 +621,7 @@ public:
if (expire)
{
Log(LOG_NORMAL, "nickserv/expire", NickServ) << "Expiring nickname " << na->nick << " (group: " << na->nc->display << ") (email: " << (na->nc->email.empty() ? "none" : na->nc->email) << ")";
Log(LOG_NORMAL, "nickserv/expire", NickServ) << "Expiring nickname " << na->nick << " (account: " << na->nc->display << ") (email: " << (na->nc->email.empty() ? "none" : na->nc->email) << ")";
FOREACH_MOD(OnNickExpire, (na));
delete na;
}
+90
View File
@@ -0,0 +1,90 @@
/* NickServ core functions
*
* (C) 2003-2025 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 CommandNSConfirm final
: public Command
{
public:
CommandNSConfirm(Module *creator)
: Command(creator, "nickserv/confirm", 1, 3)
{
this->AllowUnregistered(true);
this->SetDesc(_("Confirm a previous action"));
this->SetSyntax(_("\037type\037 \037parameters\037"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
this->OnSyntaxError(source, "");
return;
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"This command is used by several other commands as a way to confirm changes made "
"to your account. \037type\037 can be one of:"
));
auto this_name = source.command;
auto hide_privileged_commands = Config->GetBlock("options").Get<bool>("hideprivilegedcommands");
auto hide_registered_commands = Config->GetBlock("options").Get<bool>("hideregisteredcommands");
HelpWrapper help;
for (const auto &[c_name, info] : source.service->commands)
{
if (c_name.find_ci(this_name + " ") == 0)
{
if (info.hide)
continue;
ServiceReference<Command> c("Command", info.name);
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, help);
}
}
help.SendTo(source);
source.Reply(_("Type \002%s\032\037option\037\002 for more information on a specific option."),
source.service->GetQueryCommand("generic/help", this_name).c_str());
return true;
}
};
class NSConfirm final
: public Module
{
private:
CommandNSConfirm commandnsconfirm;
public:
NSConfirm(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandnsconfirm(this)
{
}
};
MODULE_INIT(NSConfirm)
+2 -2
View File
@@ -79,7 +79,7 @@ public:
FOREACH_MOD(OnNickDrop, (source, na));
Log(!is_mine ? LOG_ADMIN : LOG_COMMAND, source, this) << "to drop nickname " << na->nick << " (group: " << na->nc->display << ") (email: " << (!na->nc->email.empty() ? na->nc->email : "none") << ")";
Log(!is_mine ? LOG_ADMIN : LOG_COMMAND, source, this) << "to drop nickname " << na->nick << " (account: " << na->nc->display << ") (email: " << (!na->nc->email.empty() ? na->nc->email : "none") << ")";
delete na;
source.Reply(_("Nickname \002%s\002 has been dropped."), nick.c_str());
@@ -98,7 +98,7 @@ public:
source.Reply(" ");
if (!source.HasPriv("nickserv/drop"))
source.Reply(_("You may drop any nick within your group."));
source.Reply(_("You may drop any nick within your account."));
else
source.Reply(_("As a Services Operator, you may drop any nick."));
+141 -31
View File
@@ -68,6 +68,133 @@ namespace
}
}
struct EmailChange final
{
Anope::string code;
Anope::string email;
time_t requested = Anope::CurTime;
};
class CommandNSConfirmEmail final
: public Command
{
private:
PrimitiveExtensibleItem<EmailChange> &ns_set_email;
public:
CommandNSConfirmEmail(Module *creator, PrimitiveExtensibleItem<EmailChange> &nse)
: Command(creator, "nickserv/confirm/email", 1, 2)
, ns_set_email(nse)
{
this->SetDesc(_("Confirm a previous change of email address"));
this->SetSyntax(_("\037code\037"));
this->SetSyntax(_("@\037nickname\037"), [](auto &source) { return source.HasPriv("nickserv/confirm/email"); });
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
auto has_priv = source.HasPriv("nickserv/confirm/email");
Anope::string code;
NickAlias *na;
if (params[0] == '@')
{
if (!has_priv)
{
source.Reply(ACCESS_DENIED);
return;
}
auto nick = params[0].substr(0);
na = NickAlias::Find(nick);
if (!na)
{
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
return;
}
}
else
{
code = params[0];
na = source.GetAccount()->na;
}
NickCore *nc = na->nc;
if (nc->HasExt("NS_SUSPENDED"))
{
source.Reply(NICK_X_SUSPENDED, na->nick.c_str());
return;
}
auto *nse = ns_set_email.Get(nc);
if (!nse)
{
source.Reply(_("There is no email address change confirmation pending for %s."),
na->nick.c_str());
return;
}
if (!has_priv)
{
if (!code.equals_cs(nse->code))
{
source.Reply(_("The email address change confirmation code you specified for %s is incorrect."),
na->nick.c_str());
return;
}
auto changeexpire = Config->GetModule(owner).Get<time_t>("changeexpire", "1d");
if (nse->requested < Anope::CurTime - changeexpire)
{
ns_set_email.Unset(nc);
source.Reply(_("The email address change request for %s has expired."),
na->nick.c_str());
return;
}
}
if (!CheckLimitReached(source, nse->email, true))
{
ns_set_email.Unset(nc);
return;
}
auto old_email = nc->email;
nc->email = nse->email;
ns_set_email.Unset(nc);
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to confirm the email address change of "
<< nc->display << " from " << old_email << " to " << nc->email;
source.Reply(_("The email address of %s has been changed from \002%s\002 to \002%s\002."),
na->nick.c_str(), old_email.c_str(), nc->email.c_str());
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
auto changeexpire = Config->GetModule(owner).Get<time_t>("changeexpire", "1d");
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Confirms an change of email address. You have %s after requesting an email "
"address change to do this before your request expires."
),
Anope::Duration(changeexpire, source.GetAccount()).c_str());
if (source.HasPriv("nickserv/confirm/email"))
{
source.Reply(" ");
source.Reply(_(
"Additionally, Services Operators with the \037nickserv/confirm/email\037 "
"permission can specify @\037nickname\037 instead of \037code\037 to force "
"confirm another user's change of email address."
));
}
return true;
}
};
class CommandNSGetEmail final
: public Command
{
@@ -75,7 +202,7 @@ public:
CommandNSGetEmail(Module *creator)
: Command(creator, "nickserv/getemail", 1, 1)
{
this->SetDesc(_("Matches and returns all users that registered using given email"));
this->SetDesc(_("Matches and returns all users that registered using given email address"));
this->SetSyntax(_("\037email\037"));
}
@@ -118,18 +245,16 @@ class CommandNSSetEmail
{
static bool SendConfirmMail(User *u, NickCore *nc, BotInfo *bi, const Anope::string &new_email)
{
Anope::string code = Anope::Random(Config->GetBlock("options").Get<size_t>("codelength", "15"));
std::pair<Anope::string, Anope::string> *n = nc->Extend<std::pair<Anope::string, Anope::string> >("ns_set_email");
n->first = new_email;
n->second = code;
auto *nse = nc->Extend<EmailChange>("ns_set_email");
nse->code = Anope::Random(Config->GetBlock("options").Get<size_t>("codelength", "15"));
nse->email = new_email;
Anope::map<Anope::string> vars = {
{ "old_email", nc->email },
{ "new_email", new_email },
{ "account", nc->display },
{ "network", Config->GetBlock("networkinfo").Get<const Anope::string>("networkname") },
{ "code", code },
{ "code", nse->code },
};
auto subject = Anope::Template(Config->GetBlock("mail").Get<const Anope::string>("emailchange_subject"), vars);
@@ -167,18 +292,18 @@ public:
if (nc->HasExt("UNCONFIRMED"))
{
source.Reply(_("You may not change the email of an unconfirmed account."));
source.Reply(_("You may not change the email address of an unconfirmed account."));
return;
}
if (param.empty() && Config->GetModule("nickserv").Get<bool>("forceemail", "yes"))
{
source.Reply(_("You cannot unset the email on this network."));
source.Reply(_("You cannot unset the email address on this network."));
return;
}
else if (Config->GetModule("nickserv").Get<bool>("secureadmins", "yes") && source.nc != nc && nc->IsServicesOper())
{
source.Reply(_("You may not change the email of other Services Operators."));
source.Reply(_("You may not change the email address of other Services Operators."));
return;
}
else if (!param.empty() && !Mail::Validate(param))
@@ -199,7 +324,7 @@ public:
{
if (SendConfirmMail(source.GetUser(), source.GetAccount(), source.service, param))
{
Log(LOG_COMMAND, source, this) << "to request changing the email of " << nc->display << " to " << param;
Log(LOG_COMMAND, source, this) << "to request changing the email address of " << nc->display << " to " << param;
source.Reply(_("A confirmation email has been sent to \002%s\002. Follow the instructions in it to change your email address."), param.c_str());
}
}
@@ -207,13 +332,13 @@ public:
{
if (!param.empty())
{
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the email of " << nc->display << " to " << param;
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to change the email address of " << nc->display << " to " << param;
nc->email = param;
source.Reply(_("Email address for \002%s\002 changed to \002%s\002."), nc->display.c_str(), param.c_str());
}
else
{
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to unset the email of " << nc->display;
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to unset the email address of " << nc->display;
nc->email.clear();
source.Reply(_("Email address for \002%s\002 unset."), nc->display.c_str());
}
@@ -266,16 +391,18 @@ class NSEmail final
: public Module
{
private:
CommandNSConfirmEmail commandnsconfirmemail;
CommandNSGetEmail commandnsgetemail;
CommandNSSetEmail commandnssetemail;
CommandNSSASetEmail commandnssasetemail;
/* email, passcode */
PrimitiveExtensibleItem<std::pair<Anope::string, Anope::string>> ns_set_email;
PrimitiveExtensibleItem<EmailChange> ns_set_email;
public:
NSEmail(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandnsconfirmemail(this, ns_set_email)
, commandnsgetemail(this)
, commandnssetemail(this)
, commandnssasetemail(this)
@@ -292,23 +419,6 @@ public:
EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> &params) override
{
NickCore *uac = source.nc;
if (command->name == "nickserv/confirm" && !params.empty() && uac)
{
std::pair<Anope::string, Anope::string> *n = ns_set_email.Get(uac);
if (n)
{
if (params[0] == n->second)
{
uac->email = n->first;
Log(LOG_COMMAND, source, command) << "to confirm their email address change to " << uac->email;
source.Reply(_("Your email address has been changed to \002%s\002."), uac->email.c_str());
ns_set_email.Unset(uac);
return EVENT_STOP;
}
}
}
if (!source.IsOper() && command->name == "nickserv/register")
{
if (CheckLimitReached(source, params.size() > 1 ? params[1] : "", false))
+30 -32
View File
@@ -55,7 +55,7 @@ public:
if (u != NULL)
{
na->last_usermask = u->GetIdent() + "@" + u->GetDisplayedHost();
na->last_userhost = u->GetIdent() + "@" + u->GetDisplayedHost();
na->last_realname = u->realname;
}
else
@@ -70,8 +70,8 @@ public:
FOREACH_MOD(OnNickGroup, (u, target));
}
Log(LOG_COMMAND, source, cmd) << "to make " << nick << " join group of " << target->nick << " (" << target->nc->display << ") (email: " << (!target->nc->email.empty() ? target->nc->email : "none") << ")";
source.Reply(_("You are now in the group of \002%s\002."), target->nick.c_str());
Log(LOG_COMMAND, source, cmd) << "to make " << nick << " join account of of " << target->nick << " (" << target->nc->display << ") (email: " << (!target->nc->email.empty() ? target->nc->email : "none") << ")";
source.Reply(_("Your nickname now belongs to the account \002%s\002."), target->nick.c_str());
if (u)
u->lastnickreg = Anope::CurTime;
@@ -99,7 +99,7 @@ class CommandNSGroup final
public:
CommandNSGroup(Module *creator) : Command(creator, "nickserv/group", 0, 2)
{
this->SetDesc(_("Join a group"));
this->SetDesc(_("Add a nick to an account"));
this->SetSyntax(_("\037[target]\037 \037[password]\037"));
this->AllowUnregistered(true);
}
@@ -162,17 +162,17 @@ public:
}
else if (target->nc->HasExt("NS_SUSPENDED"))
{
Log(LOG_COMMAND, source, this) << "and tried to group to SUSPENDED nick " << target->nick;
Log(LOG_COMMAND, source, this) << "and tried to group to SUSPENDED account " << target->nick;
source.Reply(NICK_X_SUSPENDED, target->nick.c_str());
}
else if (na && Config->GetModule(this->owner).Get<bool>("nogroupchange"))
source.Reply(_("Your nick is already registered."));
else if (na && *target->nc == *na->nc)
source.Reply(_("You are already a member of the group of \002%s\002."), target->nick.c_str());
source.Reply(_("You are already a member of the account of \002%s\002."), target->nick.c_str());
else if (na && na->nc != source.GetAccount())
source.Reply(NICK_IDENTIFY_REQUIRED);
else if (maxaliases && target->nc->aliases->size() >= maxaliases && !target->nc->IsServicesOper())
source.Reply(_("There are too many nicks in your group."));
source.Reply(_("There are too many nicks in your account."));
else if (nickserv && nickserv->IsGuestNick(source.GetNick()))
source.Reply(NICK_CANNOT_BE_REGISTERED, source.GetNick().c_str());
else
@@ -209,16 +209,16 @@ public:
source.Reply(" ");
source.Reply(_(
"This command makes your nickname join the \037target\037 nickname's "
"group. \037password\037 is the password of the target nickname. "
"account. \037password\037 is the password of the target nickname. "
"\n\n"
"Joining a group will allow you to share your configuration, "
"Joining an account will allow you to share your configuration, "
"memos, and channel privileges with all the nicknames in the "
"group, and much more!"
"account, and much more!"
"\n\n"
"A group exists as long as it is useful. This means that even "
"if a nick of the group is dropped, you won't lose the "
"An account exists as long as it is useful. This means that even "
"if a nick of the account is dropped, you won't lose the "
"shared things described above, as long as there is at "
"least one nick remaining in the group."
"least one nick remaining in the account."
"\n\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 "
@@ -227,13 +227,13 @@ public:
"It is recommended to use this command with a non-registered "
"nick because it will be registered automatically when "
"using this command. You may use it with a registered nick (to "
"change your group) only if your network administrators allowed "
"change your account) only if your network administrators allowed "
"it."
"\n\n"
"You can only be in one group at a time. Group merging is "
"You can only be in one account at a time. Group merging is "
"not possible. "
"\n\n"
"\037Note\037: all the nicknames of a group have the same password."
"\037Note\037: all the nicknames of an account have the same password."
));
return true;
}
@@ -245,7 +245,7 @@ class CommandNSUngroup final
public:
CommandNSUngroup(Module *creator) : Command(creator, "nickserv/ungroup", 0, 1)
{
this->SetDesc(_("Remove a nick from a group"));
this->SetDesc(_("Remove a nick from an account"));
this->SetSyntax(_("[\037nick\037]"));
}
@@ -255,11 +255,11 @@ public:
NickAlias *na = NickAlias::Find(!nick.empty() ? nick : source.GetNick());
if (source.GetAccount()->aliases->size() == 1)
source.Reply(_("Your nick is not grouped to anything, you can't ungroup it."));
source.Reply(_("Your nick does not belong to an account, you can't ungroup it."));
else if (!na)
source.Reply(NICK_X_NOT_REGISTERED, !nick.empty() ? nick.c_str() : source.GetNick().c_str());
else if (na->nc != source.GetAccount())
source.Reply(_("Nick %s is not in your group."), na->nick.c_str());
source.Reply(_("Nick %s does not belong to your account."), na->nick.c_str());
else
{
NickCore *oldcore = na->nc;
@@ -268,8 +268,8 @@ public:
if (it != oldcore->aliases->end())
oldcore->aliases->erase(it);
if (na->nick.equals_ci(oldcore->display))
oldcore->SetDisplay(oldcore->aliases->front());
if (oldcore->na == na)
oldcore->SetDisplay(nullptr);
auto *nc = new NickCore(na->nick);
na->nc = nc;
@@ -280,7 +280,7 @@ public:
nc->email = oldcore->email;
nc->language = oldcore->language;
Log(LOG_COMMAND, source, this) << "to make " << na->nick << " leave group of " << oldcore->display << " (email: " << (!oldcore->email.empty() ? oldcore->email : "none") << ")";
Log(LOG_COMMAND, source, this) << "to make " << na->nick << " leave account of " << oldcore->display << " (email: " << (!oldcore->email.empty() ? oldcore->email : "none") << ")";
source.Reply(_("Nick %s has been ungrouped from %s."), na->nick.c_str(), oldcore->display.c_str());
User *user = User::Find(na->nick, true);
@@ -296,10 +296,10 @@ public:
source.Reply(" ");
source.Reply(_(
"This command ungroups your nick, or if given, the specified nick, "
"from the group it is in. The ungrouped nick keeps its registration "
"from the account it is in. The ungrouped nick keeps its registration "
"time, password, email, greet, language, and url. Everything else "
"is reset. You may not ungroup yourself if there is only one nick in "
"your group."
"your account."
));
return true;
}
@@ -311,7 +311,7 @@ class CommandNSGList final
public:
CommandNSGList(Module *creator) : Command(creator, "nickserv/glist", 0, 1)
{
this->SetDesc(_("Lists all nicknames in your group"));
this->SetDesc(_("Lists all nicknames in your account"));
this->SetSyntax(_("[\037nickname\037]"), [](auto &source) { return source.IsServicesOper(); });
}
@@ -361,14 +361,14 @@ public:
list.AddEntry(entry);
}
source.Reply(!nick.empty() ? _("List of nicknames in the group of \002%s\002:") : _("List of nicknames in your group:"), nc->display.c_str());
source.Reply(!nick.empty() ? _("List of nicknames belonging to \002%s\002:") : _("List of nicknames belonging to your account:"), nc->display.c_str());
std::vector<Anope::string> replies;
list.Process(replies);
for (const auto &reply : replies)
source.Reply(reply);
source.Reply(nc->aliases->size(), N_("%zu nickname in the group.", "%zu nicknames in the group."), nc->aliases->size());
source.Reply(nc->aliases->size(), N_("%zu nickname in the account.", "%zu nicknames in the account."), nc->aliases->size());
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
@@ -377,18 +377,16 @@ public:
if (source.IsServicesOper())
{
source.Reply(_(
"Without a parameter, lists all nicknames that are in "
"your group."
"Without a parameter, lists all nicknames that belong to your account."
"\n\n"
"With a parameter, lists all nicknames that are in the "
"group of the given nick."
"With a parameter, lists all nicknames that belong to the account of the given nick."
"\n\n"
"Specifying a nick is limited to \002Services Operators\002."
));
}
else
{
source.Reply(_("Lists all nicks in your group."));
source.Reply(_("Lists all nicknames that belong to your account."));
}
return true;
+27 -20
View File
@@ -93,13 +93,13 @@ public:
if (nick_online)
{
bool shown = false;
if (show_hidden && !na->last_realhost.empty())
if (show_hidden && !na->last_userhost_real.empty())
{
info[_("Online from")] = na->last_realhost;
info[_("Online from")] = na->last_userhost_real;
shown = true;
}
if ((show_hidden || !na->nc->HasExt("HIDE_MASK")) && (!shown || na->last_usermask != na->last_realhost))
info[_("Online from")] = na->last_usermask;
if ((show_hidden || !na->nc->HasExt("HIDE_MASK")) && (!shown || na->last_userhost != na->last_userhost_real))
info[_("Online from")] = na->last_userhost;
else
source.Reply(_("%s is currently online."), na->nick.c_str());
}
@@ -108,22 +108,24 @@ public:
Anope::string shown;
if (show_hidden || !na->nc->HasExt("HIDE_MASK"))
{
info[_("Last seen address")] = na->last_usermask;
shown = na->last_usermask;
info[_("Last seen mask")] = na->last_userhost;
shown = na->last_userhost;
}
if (show_hidden && !na->last_realhost.empty() && na->last_realhost != shown)
info[_("Last seen address")] = na->last_realhost;
if (show_hidden && !na->last_userhost_real.empty() && na->last_userhost_real != shown)
info[_("Last seen mask")] = na->last_userhost_real;
}
info[_("Account registered")] = Anope::strftime(na->nc->registered, source.GetAccount());
info[_("Nick registered")] = Anope::strftime(na->registered, source.GetAccount());
if (!nick_online)
{
info[_("Last seen")] = Anope::strftime(na->last_seen, source.GetAccount());
if (!na->last_quit.empty() && (show_hidden || !na->nc->HasExt("HIDE_QUIT")))
info[_("Last quit message")] = na->last_quit;
if (!na->last_quit.empty() && (show_hidden || !na->nc->HasExt("HIDE_QUIT")))
info[_("Last quit message")] = na->last_quit;
}
if (!na->nc->email.empty() && (show_hidden || !na->nc->HasExt("HIDE_EMAIL")))
info[_("Email address")] = na->nc->email;
@@ -167,7 +169,7 @@ 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}");
this->SetSyntax("{EMAIL | STATUS | MASK | QUIT} {ON | OFF}");
}
void Run(CommandSource &source, const Anope::string &user, const Anope::string &param, const Anope::string &arg)
@@ -199,7 +201,7 @@ public:
onmsg = _("The email address of \002%s\002 will now be hidden from %s INFO displays.");
offmsg = _("The email address of \002%s\002 will now be shown in %s INFO displays.");
}
else if (param.equals_ci("USERMASK"))
else if (param.equals_ci("MASK"))
{
flag = "HIDE_MASK";
onmsg = _("The last seen user@host mask of \002%s\002 will now be hidden from %s INFO displays.");
@@ -252,7 +254,7 @@ public:
"Allows you to prevent certain pieces of information from "
"being displayed when someone does a %s\032\002INFO\002 on your "
"nick. You can hide your email address\032(\002EMAIL\002), last seen "
"user@host mask (\002USERMASK\002), your services access status "
"user@host mask (\002MASK\002), your 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)."
@@ -268,7 +270,7 @@ class CommandNSSASetHide final
public:
CommandNSSASetHide(Module *creator) : CommandNSSetHide(creator, "nickserv/saset/hide", 3)
{
this->SetSyntax(_("\037nickname\037 {EMAIL | STATUS | USERMASK | QUIT} {ON | OFF}"));
this->SetSyntax(_("\037nickname\037 {EMAIL | STATUS | MASK | QUIT} {ON | OFF}"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
@@ -285,7 +287,7 @@ public:
"Allows you to prevent certain pieces of information from "
"being displayed when someone does a %s\032\002INFO\002 on the "
"nick. You can hide the email address (\002EMAIL\002), last seen "
"user@host mask (\002USERMASK\002), the services access status "
"user@host mask (\002MASK\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)."
@@ -303,13 +305,18 @@ class NSInfo final
CommandNSSetHide commandnssethide;
CommandNSSASetHide commandnssasethide;
SerializableExtensibleItem<bool> hide_email, hide_usermask, hide_status, hide_quit;
SerializableExtensibleItem<bool> hide_email, hide_mask, hide_status, hide_quit;
public:
NSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
commandnsinfo(this), commandnssethide(this), commandnssasethide(this),
hide_email(this, "HIDE_EMAIL"), hide_usermask(this, "HIDE_MASK"), hide_status(this, "HIDE_STATUS"),
hide_quit(this, "HIDE_QUIT")
NSInfo(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandnsinfo(this)
, commandnssethide(this)
, commandnssasethide(this)
, hide_email(this, "HIDE_EMAIL")
, hide_mask(this, "HIDE_MASK")
, hide_status(this, "HIDE_STATUS")
, hide_quit(this, "HIDE_QUIT")
{
}
+6 -6
View File
@@ -73,7 +73,7 @@ public:
mync = source.nc;
ListFormatter list(source.GetAccount());
list.AddColumn(_("Nick")).AddColumn(_("Last usermask"));
list.AddColumn(_("Nick")).AddColumn(_("Last mask"));
Anope::map<NickAlias *> ordered_map;
for (const auto &[nick, na] : *NickAliasList)
@@ -94,7 +94,7 @@ public:
/* 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->nick.c_str(), !na->last_usermask.empty() ? na->last_usermask.c_str() : "*@*");
Anope::string buf = Anope::printf("%s!%s", na->nick.c_str(), !na->last_userhost.empty() ? na->last_userhost.c_str() : "*@*");
if (na->nick.equals_ci(pattern) || Anope::Match(buf, pattern, false, true))
{
if (((count + 1 >= from && count + 1 <= to) || (!from && !to)) && ++nnicks <= listmax)
@@ -106,13 +106,13 @@ public:
ListFormatter::ListEntry entry;
entry["Nick"] = (isnoexpire ? "!" : "") + na->nick;
if (na->nc->HasExt("HIDE_MASK") && !is_servadmin && na->nc != mync)
entry["Last usermask"] = Language::Translate(source.GetAccount(), _("[Hostname hidden]"));
entry["Last mask"] = Language::Translate(source.GetAccount(), _("[Hostname hidden]"));
else if (na->nc->HasExt("NS_SUSPENDED"))
entry["Last usermask"] = Language::Translate(source.GetAccount(), _("[Suspended]"));
entry["Last mask"] = Language::Translate(source.GetAccount(), _("[Suspended]"));
else if (na->nc->HasExt("UNCONFIRMED"))
entry["Last usermask"] = Language::Translate(source.GetAccount(), _("[Unconfirmed]"));
entry["Last mask"] = Language::Translate(source.GetAccount(), _("[Unconfirmed]"));
else
entry["Last usermask"] = na->last_usermask;
entry["Last mask"] = is_servadmin ? na->last_userhost_real : na->last_userhost;
list.AddEntry(entry);
}
++count;
+1 -1
View File
@@ -55,7 +55,7 @@ public:
if (na->HasExt("HELD"))
{
nickserv->Release(na);
source.Reply(_("Service's hold on \002%s\002 has been released."), na->nick.c_str());
source.Reply(_("Services' hold on \002%s\002 has been released."), na->nick.c_str());
}
else if (!u)
{
+215 -154
View File
@@ -11,119 +11,24 @@
#include "module.h"
namespace
{
Anope::string *GetCode(NickCore *nc)
{
auto *code = nc->GetExt<Anope::string>("passcode");
if (!code)
{
code = nc->Extend<Anope::string>("passcode");
*code = Anope::Random(Config->GetBlock("options").Get<size_t>("codelength", "15"));
}
return code;
}
}
static bool SendRegmail(User *u, const NickAlias *na, BotInfo *bi);
static ServiceReference<NickServService> nickserv("NickServService", "NickServ");
class CommandNSConfirm final
: 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<Anope::string> &params) override
{
Anope::string *code = source.nc ? source.nc->GetExt<Anope::string>("passcode") : NULL;
bool confirming_other = !code || *code != params[0];
if (source.nc && (!source.nc->HasExt("UNCONFIRMED") || (source.IsOper() && confirming_other)) && source.HasPriv("nickserv/confirm"))
{
const Anope::string &nick = params[0];
NickAlias *na = NickAlias::Find(nick);
if (na == NULL)
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
else if (!na->nc->HasExt("UNCONFIRMED"))
source.Reply(_("Nick \002%s\002 is already confirmed."), na->nick.c_str());
else
{
na->nc->Shrink<bool>("UNCONFIRMED");
FOREACH_MOD(OnNickConfirm, (source.GetUser(), na->nc));
Log(LOG_ADMIN, source, this) << "to confirm nick " << na->nick << " (" << na->nc->display << ")";
source.Reply(_("Nick \002%s\002 has been confirmed."), na->nick.c_str());
/* Login the users online already */
for (std::list<User *>::iterator it = na->nc->users.begin(); it != na->nc->users.end(); ++it)
{
User *u = *it;
IRCD->SendLogin(u, na);
NickAlias *u_na = NickAlias::Find(u->nick);
/* Set +r if they're on a nick in the group */
if (!Config->GetModule("nickserv").Get<bool>("nonicknameownership") && u_na && *u_na->nc == *na->nc)
u->SetMode(source.service, "REGISTERED");
}
}
}
else if (source.nc)
{
const Anope::string &passcode = params[0];
if (code != NULL && *code == passcode)
{
NickCore *nc = source.nc;
nc->Shrink<Anope::string>("passcode");
Log(LOG_COMMAND, source, this) << "to confirm their email";
source.Reply(_("Your email address of \002%s\002 has been confirmed."), source.nc->email.c_str());
nc->Shrink<bool>("UNCONFIRMED");
FOREACH_MOD(OnNickConfirm, (source.GetUser(), nc));
if (source.GetUser())
{
NickAlias *na = NickAlias::Find(source.GetNick());
if (na)
{
IRCD->SendLogin(source.GetUser(), na);
if (!Config->GetModule("nickserv").Get<bool>("nonicknameownership") && na->nc == source.GetAccount() && !na->nc->HasExt("UNCONFIRMED"))
source.GetUser()->SetMode(source.service, "REGISTERED");
}
}
}
else
source.Reply(_("Invalid passcode."));
}
else
source.Reply(NICK_IDENTIFY_REQUIRED);
return;
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
{
this->SendSyntax(source);
source.Reply(" ");
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 the RESETPASS command has been used to "
"force identify you to your nick so you may change your password."
));
if (source.HasPriv("nickserv/confirm"))
{
source.Reply(_(
"Additionally, Services Operators with the \037nickserv/confirm\037 permission can "
"replace \037passcode\037 with a users nick to force validate them."
));
}
return true;
}
void OnSyntaxError(CommandSource &source, const Anope::string &subcommand) override
{
source.Reply(NICK_CONFIRM_INVALID);
}
};
class CommandNSRegister final
: public Command
{
@@ -236,7 +141,7 @@ public:
if (u)
{
na->last_usermask = u->GetIdent() + "@" + u->GetDisplayedHost();
na->last_userhost = u->GetIdent() + "@" + u->GetDisplayedHost();
na->last_realname = u->realname;
}
else
@@ -245,7 +150,7 @@ public:
Log(LOG_COMMAND, source, this) << "to register " << na->nick << " (email: " << (!na->nc->email.empty() ? na->nc->email : "none") << ")";
source.Reply(_("Nickname \002%s\002 registered."), u_nick.c_str());
if (nsregister.equals_ci("admin"))
if (nsregister.equals_ci("admin") || nsregister.equals_ci("code"))
{
nc->Extend<bool>("UNCONFIRMED");
}
@@ -270,8 +175,14 @@ public:
{
if (nsregister.equals_ci("admin"))
source.Reply(_("All new accounts must be validated by an administrator. Please wait for your registration to be confirmed."));
else if (nsregister.equals_ci("code"))
{
const auto *code = GetCode(na->nc);
source.Reply(_("Your account is not confirmed. To confirm it, type \002%s\002."),
source.service->GetQueryCommand("nickserv/confirm/register", *code).c_str());
}
else if (nsregister.equals_ci("mail"))
source.Reply(_("Your email address is not confirmed. To confirm it, follow the instructions that were emailed to you."));
source.Reply(_("Your account is not confirmed. To confirm it, follow the instructions that were emailed to you."));
}
}
}
@@ -308,19 +219,130 @@ public:
{
source.Reply(" ");
source.Reply(_(
"The \037email\037 parameter is optional and will set the email "
"for your nick immediately. You may also wish to \002SET\032HIDE\002 it "
"after registering if it isn't the default setting already."
"The \037email\037 parameter is optional and will set the email address for your "
"nick immediately. You may also wish to \002SET\032HIDE\002 it after registering "
"if it isn't the default setting already."
));
}
if (!Config->GetModule("nickserv").Get<bool>("nonicknameownership"))
{
source.Reply(" ");
source.Reply(_(
"You can associate multiple nicknames with your account. All nicknames will "
"share the same configuration, set of memos, and channel privileges."
));
}
return true;
}
};
class CommandNSConfirmRegister final
: public Command
{
public:
CommandNSConfirmRegister(Module *creator)
: Command(creator, "nickserv/confirm/register", 1, 2)
{
this->SetDesc(_("Confirm a previous account registration"));
this->SetSyntax(_("\037code\037"));
this->SetSyntax(_("@\037nickname\037"), [](auto &source) { return source.HasPriv("nickserv/confirm/register"); });
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
auto has_priv = source.HasPriv("nickserv/confirm/register");
Anope::string code;
NickAlias *na;
if (params[0] == '@')
{
if (!has_priv)
{
source.Reply(ACCESS_DENIED);
return;
}
auto nick = params[0].substr(0);
na = NickAlias::Find(nick);
if (!na)
{
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
return;
}
}
else
{
code = params[0];
na = source.GetAccount()->na;
}
NickCore *nc = na->nc;
if (nc->HasExt("NS_SUSPENDED"))
{
source.Reply(NICK_X_SUSPENDED, na->nick.c_str());
return;
}
auto *passcode = nc->GetExt<Anope::string>("passcode");
if (!passcode)
{
source.Reply(_("There is no registration confirmation pending for %s."),
na->nick.c_str());
return;
}
if (has_priv || !code.equals_cs(*passcode))
{
source.Reply(_("The registration confirmation code you specified for %s is incorrect."),
na->nick.c_str());
return;
}
na->nc->Shrink<bool>("UNCONFIRMED");
FOREACH_MOD(OnNickConfirm, (source.GetUser(), nc));
auto nonicknameownership = Config->GetModule("nickserv").Get<bool>("nonicknameownership");
for (auto *u : nc->users)
{
IRCD->SendLogin(u, na);
if (!nonicknameownership)
continue;
const auto &aliases = *nc->aliases;
auto it = std::find_if(aliases.begin(), aliases.end(), [&u](const auto *na) {
return na->nick.equals_ci(u->nick);
});
if (it != aliases.end())
u->SetMode(source.service, "REGISTERED"); // nick is in the group
}
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to confirm the registration of " << nc->display;
source.Reply(_("Nick \002%s\002 has been confirmed."), na->nick.c_str());
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
auto unconfirmedexpire = Config->GetModule(owner).Get<time_t>("unconfirmedexpire", "1d");
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"This command also creates a new group for your nickname, "
"that will allow you to register other nicks later sharing "
"the same configuration, the same set of memos and the "
"same channel privileges."
));
"Confirms an account registration. You have %s after registration to do this "
"before your registration expires."
),
Anope::Duration(unconfirmedexpire, source.GetAccount()).c_str());
if (source.HasPriv("nickserv/confirm/register"))
{
source.Reply(" ");
source.Reply(_(
"Additionally, Services Operators with the \037nickserv/confirm/register\037 "
"permission can specify @\037nickname\037 instead of \037code\037 to force "
"confirm another user's account registration."
));
}
return true;
}
};
@@ -329,9 +351,12 @@ class CommandNSResend final
: public Command
{
public:
CommandNSResend(Module *creator) : Command(creator, "nickserv/resend", 0, 0)
CommandNSResend(Module *creator)
: Command(creator, "nickserv/resend", 0, 1)
{
this->SetDesc(_("Resend registration confirmation email"));
this->SetSyntax(_("[\037nickname\037]"), [](auto &source) { return source.HasCommand("nickserv/resend"); });
this->AllowUnregistered(true);
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
@@ -342,27 +367,49 @@ public:
return;
}
const NickAlias *na = NickAlias::Find(source.GetNick());
if (na == NULL)
source.Reply(NICK_NOT_REGISTERED);
else if (na->nc != source.GetAccount() || !source.nc->HasExt("UNCONFIRMED"))
source.Reply(_("Your account is already confirmed."));
else
auto is_oper = false;
Anope::string nick;
if (!params.empty() && source.HasCommand("nickserv/resend"))
{
if (Anope::CurTime < source.nc->lastmail + Config->GetModule(this->owner).Get<time_t>("resenddelay"))
source.Reply(_("Cannot send mail now; please retry a little later."));
else if (SendRegmail(source.GetUser(), na, source.service))
{
na->nc->lastmail = Anope::CurTime;
source.Reply(_("Your passcode has been re-sent to %s."), na->nc->email.c_str());
Log(LOG_COMMAND, source, this) << "to resend registration verification code";
}
else
Log(this->owner) << "Unable to resend registration verification code for " << source.GetNick();
nick = params[0];
is_oper = true;
}
else if (source.nc)
nick = source.GetAccount()->display;
else
nick = source.GetNick();
const auto *na = NickAlias::Find(nick);
if (!na)
{
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
return;
}
return;
NickCore *nc = na->nc;
if (!nc->HasExt("UNCONFIRMED"))
{
source.Reply(_("Nick \002%s\002 is already confirmed."), na->nick.c_str());
return;
}
if (!is_oper && Anope::CurTime < nc->lastmail + Config->GetModule(this->owner).Get<time_t>("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 confirmation code for " << na->nick;
return;
}
nc->lastmail = Anope::CurTime;
source.Reply(_("The confirmation code for \002%s\002 has been re-sent to %s."),
na->nick.c_str(), nc->email.c_str());
Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to resend the registration confirmation code for " << na->nick;
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
@@ -372,7 +419,16 @@ public:
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_("This command will resend you the registration confirmation email."));
source.Reply(_("This command will resend a registration confirmation email."));
if (source.HasCommand("nickserv/resend"))
{
source.Reply(" ");
source.Reply(_(
"Additionally, Services Operators with the \037nickserv/resend\037 permission "
"can specify a nickname to resend a confirmation email for another account."
));
}
return true;
}
@@ -387,16 +443,20 @@ class NSRegister final
: public Module
{
CommandNSRegister commandnsregister;
CommandNSConfirm commandnsconfirm;
CommandNSResend commandnsrsend;
CommandNSConfirmRegister commandnsconfirmregister;
CommandNSResend commandnsresend;
SerializableExtensibleItem<bool> unconfirmed;
SerializableExtensibleItem<Anope::string> passcode;
public:
NSRegister(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
commandnsregister(this), commandnsconfirm(this), commandnsrsend(this), unconfirmed(this, "UNCONFIRMED"),
passcode(this, "passcode")
NSRegister(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandnsregister(this)
, commandnsconfirmregister(this)
, commandnsresend(this)
, unconfirmed(this, "UNCONFIRMED")
, passcode(this, "passcode")
{
if (Config->GetModule(this).Get<const Anope::string>("registration").equals_ci("disable"))
throw ModuleException("Module " + this->name + " will not load with registration disabled.");
@@ -410,8 +470,15 @@ public:
const Anope::string &nsregister = Config->GetModule(this).Get<const Anope::string>("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."));
else if (nsregister.equals_ci("code"))
{
const auto *code = GetCode(u->Account());
u->SendMessage(NickServ, _("Your account is not confirmed. To confirm it, type \002%s\002."),
NickServ->GetQueryCommand("nickserv/confirm/register", *code).c_str());
}
else if (nsregister.equals_ci("mail"))
u->SendMessage(NickServ, _("Your account is not confirmed. To confirm it, follow the instructions that were emailed to you."));
const NickAlias *this_na = u->AccountNick();
time_t registered = Anope::CurTime - this_na->registered;
time_t unconfirmed_expire = Config->GetModule(this).Get<time_t>("unconfirmedexpire", "1d");
@@ -434,13 +501,7 @@ public:
static bool SendRegmail(User *u, const NickAlias *na, BotInfo *bi)
{
NickCore *nc = na->nc;
Anope::string *code = na->nc->GetExt<Anope::string>("passcode");
if (code == NULL)
{
code = na->nc->Extend<Anope::string>("passcode");
*code = Anope::Random(Config->GetBlock("options").Get<size_t>("codelength", "15"));
}
auto *code = GetCode(na->nc);
Anope::map<Anope::string> vars = {
{ "nick", na->nick },
+109 -58
View File
@@ -38,7 +38,7 @@ public:
{
if (SendResetEmail(source.GetUser(), na, source.service))
{
Log(LOG_COMMAND, source, this) << "for " << na->nick << " (group: " << na->nc->display << ")";
Log(LOG_COMMAND, source, this) << "for " << na->nick << " (account: " << na->nc->display << ")";
source.Reply(_("Password reset email for \002%s\002 has been sent."), na->nick.c_str());
}
}
@@ -51,9 +51,9 @@ public:
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Sends a passcode to the nickname with instructions on how to "
"reset their password. Email must be the email address associated "
"to the nickname."
"Sends a confirmation code to the nickname's email address with instructions on "
"how to reset their password. \037email\037 must be the email address associated "
"with the account."
));
return true;
}
@@ -65,71 +65,122 @@ struct ResetInfo final
time_t time;
};
class CommandNSConfirmResetPass final
: public Command
{
private:
PrimitiveExtensibleItem<ResetInfo> &reset;
public:
CommandNSConfirmResetPass(Module *creator, PrimitiveExtensibleItem<ResetInfo> &r)
: Command(creator, "nickserv/confirm/resetpass", 1, 2)
, reset(r)
{
this->AllowUnregistered(true);
this->SetDesc(_("Confirm a previous password reset"));
this->SetSyntax(_("[\037nickname\037] \037code\037"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
Anope::string code, nick;
if (params.size() > 1)
{
nick = params[0];
code = params[1];
}
else
{
code = params[0];
nick = source.GetNick();
}
auto *na = NickAlias::Find(nick);
if (!na)
{
source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
return;
}
NickCore *nc = na->nc;
if (nc->HasExt("NS_SUSPENDED"))
{
source.Reply(NICK_X_SUSPENDED, na->nick.c_str());
return;
}
auto *ri = reset.Get(nc);
if (!ri)
{
source.Reply(_("There is no password reset confirmation pending for %s."),
na->nick.c_str());
return;
}
if (!code.equals_cs(ri->code))
{
source.Reply(_("The password reset code you specified for %s is incorrect."),
na->nick.c_str());
return;
}
auto resetexpire = Config->GetModule(owner).Get<time_t>("resetexpire", "1d");
if (ri->time < Anope::CurTime - resetexpire)
{
reset.Unset(nc);
source.Reply(_("The password reset request for %s has expired."),
na->nick.c_str());
return;
}
reset.Unset(nc);
if (Config->GetModule("nickserv").Get<const Anope::string>("registration").equals_ci("mail"))
nc->Shrink<bool>("UNCONFIRMED");
if (source.GetUser())
source.GetUser()->Identify(na);
Log(LOG_COMMAND, source, this) << "to reset their password and forcibly identify as " << na->nick;
source.Reply(_("You are now identified as %s. Change your password now using %s."),
na->nick.c_str(), source.service->GetQueryCommand("nickserv/set/password").c_str());
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
auto resetexpire = Config->GetModule(owner).Get<time_t>("resetexpire", "1d");
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Confirms a password reset and identifies you to the specified account. You have "
"%s after requesting a reset to do this before your request expires. Once you "
"have done this you can set the password using %s."
),
Anope::Duration(resetexpire, source.GetAccount()).c_str(),
source.service->GetQueryCommand("nickserv/set/password").c_str()
);
return true;
}
};
class NSResetPass final
: public Module
{
private:
CommandNSConfirmResetPass commandnsconfirmpassword;
CommandNSResetPass commandnsresetpass;
PrimitiveExtensibleItem<ResetInfo> reset;
public:
NSResetPass(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
commandnsresetpass(this), reset(this, "reset")
NSResetPass(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, VENDOR)
, commandnsconfirmpassword(this, reset)
, commandnsresetpass(this)
, reset(this, "reset")
{
if (!Config->GetBlock("mail").Get<bool>("usemail"))
throw ModuleException("Not using mail.");
}
EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> &params) override
{
if (command->name == "nickserv/confirm" && params.size() > 1)
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return EVENT_STOP;
}
NickAlias *na = NickAlias::Find(params[0]);
ResetInfo *ri = na ? reset.Get(na->nc) : NULL;
if (na && ri)
{
NickCore *nc = na->nc;
if (nc->HasExt("NS_SUSPENDED"))
{
source.Reply(NICK_X_SUSPENDED, nc->display.c_str());
return EVENT_STOP;
}
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->Shrink<bool>("UNCONFIRMED");
Log(LOG_COMMAND, source, &commandnsresetpass) << "to confirm RESETPASS and forcefully identify as " << na->nick;
if (source.GetUser())
{
source.GetUser()->Identify(na);
}
source.Reply(_("You are now identified for your nick. Change your password now."));
}
else
return EVENT_CONTINUE;
return EVENT_STOP;
}
}
return EVENT_CONTINUE;
}
};
static bool SendResetEmail(User *u, const NickAlias *na, BotInfo *bi)
+7 -7
View File
@@ -439,7 +439,7 @@ class CommandNSSetDisplay
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->SetDesc(_("Set the display nickname for your account"));
this->SetSyntax(_("\037new-display\037"));
}
@@ -465,7 +465,7 @@ public:
}
else if (!na || *na->nc != *user_na->nc)
{
source.Reply(_("The new display MUST be a nickname of the nickname group %s."), user_na->nc->display.c_str());
source.Reply(_("The new display nickname must belong to the %s account."), user_na->nc->display.c_str());
return;
}
@@ -498,8 +498,8 @@ public:
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Changes the display used to refer to your nickname group in "
"services. The new display MUST be a nick of your group."
"Changes the display nickname used to refer to your account. The new display "
"nickname must already be associated with your account."
));
return true;
}
@@ -525,8 +525,8 @@ public:
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Changes the display used to refer to the nickname group in "
"services. The new display MUST be a nick of the group."
"Changes the display nickname used to refer to the account. The new display"
"nickname must already be associated with the account."
));
return true;
}
@@ -648,7 +648,7 @@ public:
if (neverop.HasExt(na->nc))
info.AddOption(_("Never-op"));
if (noexpire.HasExt(na))
info.AddOption(_("No expire"));
info.AddOption(_("No expiry"));
}
void OnUserModeSet(const MessageSource &setter, User *u, const Anope::string &mname) override
+1 -1
View File
@@ -227,7 +227,7 @@ public:
for (const auto &[last_mode, last_data] : modes)
{
auto *um = ModeManager::FindUserModeByName(last_mode);
if (um && um->CanSet(nullptr) && norestore.find(um->mchar) == Anope::string::npos)
if (um && um->CanSet(nullptr, u) && norestore.find(um->mchar) == Anope::string::npos)
u->SetMode(nullptr, last_mode, last_data);
}
}
+1
View File
@@ -167,6 +167,7 @@ public:
if (descriptions.count(source.command))
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply("%s", Language::Translate(source.nc, descriptions[source.command].c_str()));
return true;
}
+4 -5
View File
@@ -100,10 +100,9 @@ public:
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Turns automatic protection for your account on or off. With "
"protection on if another user tries to use a nickname from "
"your group they will be given some time to change their nick "
"after which %s will forcibly change their nick."
"Turns automatic protection for your account on or off. With protection on if "
"another user tries to use a nickname from your account they will be given some "
"time to change their nick after which %s will forcibly change their nick."
),
source.service->nick.c_str());
return true;
@@ -133,7 +132,7 @@ public:
source.Reply(_(
"Turns automatic protection for the nick on or off. With "
"protection on if a user tries to use a nickname from the "
"nick's group they will be given some time to change their "
"nick's account they will be given some time to change their "
"nick after which %s will forcibly change their nick."
),
source.service->nick.c_str());
+1 -1
View File
@@ -450,7 +450,7 @@ public:
"\0371h30m\037) are not permitted. If a unit specifier is not "
"included, the default is days (so \037+30\037 by itself means 30 "
"days). To add an AKILL which does not expire, use \037+0\037. If the "
"usermask to be added starts with a \037+\037, an expiry time must "
"mask to be added starts with a \037+\037, an expiry time must "
"be given, even if it is the same as the default. The "
"current AKILL default expiry time can be found with the "
"\002STATS\032AKILL\002 command."
+42 -45
View File
@@ -12,6 +12,40 @@
#include "module.h"
#include "modules/operserv/forbid.h"
namespace
{
Anope::string TypeToString(ForbidType ft)
{
switch (ft)
{
case FT_NICK:
return "NICK";
case FT_CHAN:
return "CHAN";
case FT_EMAIL:
return "EMAIL";
case FT_REGISTER:
return "REGISTER";
default:
return "UNKNOWN"; // Should never happen.
}
}
ForbidType StringToType(const Anope::string &ft)
{
if (ft.equals_ci("NICK") || ft.equals_ci("1"))
return FT_NICK;
if (ft.equals_ci("CHAN") || ft.equals_ci("2"))
return FT_CHAN;
if (ft.equals_ci("EMAIL") || ft.equals_ci("3"))
return FT_EMAIL;
if (ft.equals_ci("REGISTER") || ft.equals_ci("4"))
return FT_REGISTER;
return FT_SIZE; // Should never happen.
}
}
static ServiceReference<NickServService> nickserv("NickServService", "NickServ");
struct ForbidDataImpl final
@@ -41,7 +75,7 @@ void ForbidDataTypeImpl::Serialize(Serializable *obj, Serialize::Data &data) con
data.Store("reason", fb->reason);
data.Store("created", fb->created);
data.Store("expires", fb->expires);
data.Store("type", fb->type);
data.Store("type", TypeToString(fb->type));
}
Serializable *ForbidDataTypeImpl::Unserialize(Serializable *obj, Serialize::Data &data) const
@@ -60,11 +94,11 @@ Serializable *ForbidDataTypeImpl::Unserialize(Serializable *obj, Serialize::Data
data["reason"] >> fb->reason;
data["created"] >> fb->created;
data["expires"] >> fb->expires;
unsigned int t;
Anope::string t;
data["type"] >> t;
fb->type = static_cast<ForbidType>(t);
fb->type = StringToType(t);
if (t > FT_SIZE - 1)
if (fb->type == FT_SIZE)
return NULL;
if (!obj)
@@ -81,27 +115,7 @@ class MyForbidService final
void Expire(ForbidData *fd, unsigned ft, size_t idx)
{
Anope::string typestr;
switch (ft)
{
case FT_NICK:
typestr = "nick";
break;
case FT_CHAN:
typestr = "chan";
break;
case FT_EMAIL:
typestr = "email";
break;
case FT_REGISTER:
typestr = "register";
break;
default:
typestr = "unknown";
break;
}
Log(LOG_NORMAL, "expire/forbid", Config->GetClient("OperServ")) << "Expiring forbid for " << fd->mask << " type " << typestr;
Log(LOG_NORMAL, "expire/forbid", Config->GetClient("OperServ")) << "Expiring forbid for " << fd->mask << " type " << TypeToString(static_cast<ForbidType>(ft));
this->forbids(ft).erase(this->forbids(ft).begin() + idx);
delete fd;
}
@@ -208,16 +222,7 @@ public:
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;
auto ftype = StringToType(subcommand);
if (command.equals_ci("ADD") && params.size() > 3 && ftype != FT_SIZE)
{
const Anope::string &expiry = params[2][0] == '+' ? params[2] : "";
@@ -404,16 +409,8 @@ public:
if (ftype != FT_SIZE && ftype != forbid->type)
continue;
Anope::string stype;
if (forbid->type == FT_NICK)
stype = "NICK";
else if (forbid->type == FT_CHAN)
stype = "CHAN";
else if (forbid->type == FT_EMAIL)
stype = "EMAIL";
else if (forbid->type == FT_REGISTER)
stype = "REGISTER";
else
auto stype = TypeToString(forbid->type);
if (stype == FT_SIZE)
continue;
ListFormatter::ListEntry entry;
+46 -19
View File
@@ -17,11 +17,37 @@
# pragma GCC diagnostic ignored "-Wformat-security"
#endif
/* List of messages for each news type. This simplifies message sending. */
namespace
{
Anope::string TypeToString(NewsType nt)
{
switch (nt)
{
case NEWS_LOGON:
return "LOGON";
case NEWS_RANDOM:
return "RANDOM";
case NEWS_OPER:
return "OPER";
}
return ""; // Should never happen.
}
NewsType StringToType(const Anope::string &nt)
{
if (nt.equals_ci("LOGON") || nt.equals_ci("0"))
return NEWS_LOGON;
if (nt.equals_ci("RANDOM") || nt.equals_ci("1"))
return NEWS_RANDOM;
if (nt.equals_ci("OPER") || nt.equals_ci("2"))
return NEWS_OPER;
return NEWS_LOGON; // Should never happen.
}
}
enum
{
MSG_SYNTAX,
MSG_NEWS_SHORT,
MSG_NEWS_LONG,
MSG_LIST_HEADER,
@@ -29,45 +55,46 @@ enum
MSG_ADDED,
MSG_DEL_NOT_FOUND,
MSG_DELETED,
MSG_DEL_NONE,
MSG_DELETED_ALL
MSG_DELETED_ALL,
MSG_END,
};
struct NewsMessages final
{
NewsType type;
Anope::string name;
const char *msgs[MSG_END];
};
struct NewsMessages msgarray[] = {
{NEWS_LOGON, "LOGON",
{_("LOGONNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"),
_("[\002Logon News\002] %s"),
{_("[\002Logon News\002] %s"),
_("[\002Logon News\002 - %s] %s"),
_("Logon news items:"),
_("There is no logon news."),
_("Added new logon news item."),
_("Logon news item #%s not found!"),
_("Logon news item #%d deleted."),
_("No logon news items to delete!"),
_("Logon news item #%u deleted."),
_("All logon news items deleted.")}
},
{NEWS_OPER, "OPER",
{_("OPERNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"),
_("[\002Oper News\002] %s"),
{_("[\002Oper News\002] %s"),
_("[\002Oper News\002 - %s] %s"),
_("Oper news items:"),
_("There is no oper news."),
_("Added new oper news item."),
_("Oper news item #%s not found!"),
_("Oper news item #%d deleted."),
_("No oper news items to delete!"),
_("Oper news item #%u deleted."),
_("All oper news items deleted.")}
},
{NEWS_RANDOM, "RANDOM",
{_("RANDOMNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"),
_("[\002Random News\002] %s"),
{_("[\002Random News\002] %s"),
_("[\002Random News\002 - %s] %s"),
_("Random news items:"),
_("There is no random news."),
_("Added new random news item."),
_("Random news item #%s not found!"),
_("Random news item #%d deleted."),
_("No random news items to delete!"),
_("Random news item #%u deleted."),
_("All random news items deleted.")}
}
};
@@ -83,7 +110,7 @@ struct NewsItemType final
void Serialize(Serializable *obj, Serialize::Data &data) const override
{
const auto *ni = static_cast<const NewsItem *>(obj);
data.Store("type", ni->type);
data.Store("type", TypeToString(ni->type));
data.Store("text", ni->text);
data.Store("who", ni->who);
data.Store("time", ni->time);
@@ -100,9 +127,9 @@ struct NewsItemType final
else
ni = new NewsItem();
unsigned int t;
Anope::string t;
data["type"] >> t;
ni->type = static_cast<NewsType>(t);
ni->type = StringToType(t);
data["text"] >> ni->text;
data["who"] >> ni->who;
data["time"] >> ni->time;
+1 -1
View File
@@ -182,7 +182,7 @@ private:
source.Reply(_("Registered nicknames: %zu entries, %zu buckets, longest chain is %zu"), entries, buckets, max_chain);
GetHashStats(*NickCoreList, entries, buckets, max_chain);
source.Reply(_("Registered nick groups: %zu entries, %zu buckets, longest chain is %zu"), entries, buckets, max_chain);
source.Reply(_("Registered accounts: %zu entries, %zu buckets, longest chain is %zu"), entries, buckets, max_chain);
if (session_service)
{
+1 -1
View File
@@ -23,7 +23,7 @@ class ChannelModeLargeBan final
public:
ChannelModeLargeBan(const Anope::string &mname, char modeChar) : ChannelMode(mname, modeChar) { }
bool CanSet(User *u) const override
bool CanSet(User *u, Channel *c) const override
{
return u && u->HasMode("OPER");
}
+1 -1
View File
@@ -749,7 +749,7 @@ public:
{
}
bool CanSet(User *u) const override
bool CanSet(User *u, Channel *c) const override
{
return false;
}
+1 -1
View File
@@ -88,7 +88,7 @@ public:
{
na->nc->email = email;
if (user && NickServ)
user->SendMessage(NickServ, _("Your email has been updated to \002%s\002."), email.c_str());
user->SendMessage(NickServ, _("Your email address has been updated to \002%s\002."), email.c_str());
}
req->Success(me);
+4 -3
View File
@@ -311,7 +311,7 @@ Anope::string BotInfo::GetQueryCommand(const Anope::string &command, const Anope
if (!command.empty())
{
Anope::string actual_command = "\036(MISSING)\036";
Anope::string actual_command;
for (const auto &[c_name, info] : this->commands)
{
if (info.name != command)
@@ -322,8 +322,9 @@ Anope::string BotInfo::GetQueryCommand(const Anope::string &command, const Anope
break; // Keep going to find a non-hidden alternative.
}
if (!actual_command.empty())
buf.append(" ").append(actual_command);
if (actual_command.empty())
return "\036(MISSING)\036";
buf.append(" ").append(actual_command);
}
if (!extra.empty())
+1 -1
View File
@@ -405,7 +405,7 @@ void Channel::SetMode(BotInfo *bi, ChannelMode *cm, const ModeData &data, bool e
// We build a mode data which has more than what the caller gives us.
ModeData mdata;
mdata.set_at = data.set_at ? data.set_at : Anope::CurTime;
mdata.set_by = data.set_by.empty() && bi ? bi->nick : data.set_by;
mdata.set_by = data.set_by.empty() ? (bi ? bi->nick : Me->GetName()) : data.set_by;
mdata.value = data.value;
/* Don't set modes already set */
+9 -5
View File
@@ -215,7 +215,7 @@ void Command::OnSyntaxError(CommandSource &source, const Anope::string &subcomma
// The help command may not be called HELP.
return cmd.second.name == "generic/help";
});
if (it == source.service->commands.end())
if (it != source.service->commands.end())
source.Reply(MORE_INFO, source.service->GetQueryCommand("generic/help", source.command).c_str());
}
@@ -229,12 +229,16 @@ namespace
auto umessage = message.upper();
for (const auto &[command, info] : source.service->commands)
{
if (info.hide || command == message)
continue; // Don't suggest a hidden alias or a missing command.
if (info.hide)
continue; // Don't suggest a hidden alias.
size_t dist = Anope::Distance(umessage, command);
if (dist < distance)
{
ServiceReference<Command> cmd("Command", info.name);
if (!cmd)
continue; // Don't suggest an unloaded command.
distance = dist;
similar = command;
}
@@ -243,12 +247,12 @@ namespace
bool has_help = source.service->commands.find("HELP") != source.service->commands.end();
if (has_help && similar.empty())
{
source.Reply(_("Unknown command \002%s\002. Type \"%s\" for help."), message.c_str(),
source.Reply(_("Unknown command \002%s\002. Type \002%s\002 for help."), message.c_str(),
source.service->GetQueryCommand("generic/help").c_str());
}
else if (has_help)
{
source.Reply(_("Unknown command \002%s\002. Did you mean \002%s\002? Type \"%s\" for help."),
source.Reply(_("Unknown command \002%s\002. Did you mean \002%s\002? Type \002%s\002 for help."),
message.c_str(), similar.c_str(), source.service->GetQueryCommand("generic/help").c_str());
}
else if (similar.empty())
+3 -4
View File
@@ -491,8 +491,6 @@ Conf::Conf() : Block("")
&service = fantasy.Get<const Anope::string>("command"),
&permission = fantasy.Get<const Anope::string>("permission"),
&group = fantasy.Get<const Anope::string>("group");
bool hide = fantasy.Get<bool>("hide"),
prepend_channel = fantasy.Get<bool>("prepend_channel", "yes");
ValidateNotEmpty("fantasy", "name", nname);
ValidateNotEmptyOrSpaces("fantasy", "command", service);
@@ -501,8 +499,9 @@ Conf::Conf() : Block("")
c.name = service;
c.permission = permission;
c.group = group;
c.hide = hide;
c.prepend_channel = prepend_channel;
c.hide = fantasy.Get<bool>("hide");
c.prepend_channel = fantasy.Get<bool>("prepend_channel", "yes");
c.require_privilege = fantasy.Get<bool>("require_privilege", "yes");
}
for (int i = 0; i < this->CountBlock("command_group"); ++i)
+7 -12
View File
@@ -113,11 +113,6 @@ Mode::Mode(const Anope::string &mname, ModeClass mcl, char mch, ModeType mt) : n
{
}
bool Mode::CanSet(User *u) const
{
return true;
}
UserMode::UserMode(const Anope::string &un, char mch) : Mode(un, MC_USER, mch, MODE_REGULAR)
{
}
@@ -131,10 +126,10 @@ ChannelMode::ChannelMode(const Anope::string &cm, char mch) : Mode(cm, MC_CHANNE
{
}
bool ChannelMode::CanSet(User *u) const
bool ChannelMode::CanSet(User *u, Channel *c) const
{
EventReturn MOD_RESULT;
FOREACH_RESULT(OnCanSet, MOD_RESULT, (u, this));
FOREACH_RESULT(OnCanSet, MOD_RESULT, (u, c, this));
return MOD_RESULT != EVENT_STOP;
}
@@ -222,12 +217,12 @@ ChannelMode *ChannelModeVirtual<T>::Wrap(Anope::string &param)
template class ChannelModeVirtual<ChannelMode>;
template class ChannelModeVirtual<ChannelModeList>;
bool UserModeOperOnly::CanSet(User *u) const
bool UserModeOperOnly::CanSet(User *source, User *target) const
{
return u && u->HasMode("OPER");
return source && source->HasMode("OPER");
}
bool UserModeNoone::CanSet(User *u) const
bool UserModeNoone::CanSet(User *source, User *target) const
{
return false;
}
@@ -237,12 +232,12 @@ bool ChannelModeKey::IsValid(Anope::string &value) const
return !value.empty() && value.find(':') == Anope::string::npos && value.find(',') == Anope::string::npos;
}
bool ChannelModeOperOnly::CanSet(User *u) const
bool ChannelModeOperOnly::CanSet(User *u, Channel *c) const
{
return u && u->HasMode("OPER");
}
bool ChannelModeNoone::CanSet(User *u) const
bool ChannelModeNoone::CanSet(User *u, Channel *c) const
{
return false;
}
+16 -73
View File
@@ -20,6 +20,7 @@
#endif
#include <filesystem>
namespace fs = std::filesystem;
std::list<Module *> ModuleManager::Modules;
std::vector<Module *> ModuleManager::EventHandlers[I_SIZE];
@@ -32,77 +33,17 @@ void ModuleManager::CleanupRuntimeDirectory()
Log(LOG_DEBUG) << "Cleaning out Module run time directory (" << dirbuf << ") - this may take a moment, please wait";
try
{
for (const auto &entry : std::filesystem::directory_iterator(dirbuf.str()))
for (const auto &entry : fs::directory_iterator(dirbuf.str()))
{
if (entry.is_regular_file())
std::filesystem::remove(entry);
fs::remove(entry);
}
}
catch (const std::filesystem::filesystem_error &err)
catch (const fs::filesystem_error &err)
{
Log(LOG_DEBUG) << "Cannot open directory (" << dirbuf << "): " << err.what();
}
}
/**
* Copy the module from the modules folder to the runtime folder.
* This will prevent module updates while the modules is loaded from
* triggering a segfault, as the actual file in use will be in the
* runtime folder.
* @param name the name of the module to copy
* @param output the destination to copy the module to
* @return MOD_ERR_OK on success
*/
static ModuleReturn moduleCopyFile(const Anope::string &name, Anope::string &output)
{
const auto input = Anope::ExpandModule(name + DLL_EXT);
struct stat s;
if (stat(input.c_str(), &s) == -1)
return MOD_ERR_NOEXIST;
else if (!S_ISREG(s.st_mode))
return MOD_ERR_NOEXIST;
std::ifstream source(input.c_str(), std::ios_base::in | std::ios_base::binary);
if (!source.is_open())
return MOD_ERR_NOEXIST;
char *tmp_output = strdup(output.c_str());
int target_fd = mkstemp(tmp_output);
if (target_fd == -1 || close(target_fd) == -1)
{
free(tmp_output);
source.close();
return MOD_ERR_FILE_IO;
}
output = tmp_output;
free(tmp_output);
Log(LOG_DEBUG_2) << "Runtime module location: " << output;
std::ofstream target(output.c_str(), std::ios_base::in | std::ios_base::binary);
if (!target.is_open())
{
source.close();
return MOD_ERR_FILE_IO;
}
int want = s.st_size;
char buffer[1024];
while (want > 0 && !source.fail() && !target.fail())
{
source.read(buffer, std::min(want, static_cast<int>(sizeof(buffer))));
int read_len = source.gcount();
target.write(buffer, read_len);
want -= read_len;
}
source.close();
target.close();
return !source.fail() && !target.fail() ? MOD_ERR_OK : MOD_ERR_FILE_IO;
}
#endif
/* This code was found online at https://web.archive.org/web/20180318184211/https://www.linuxjournal.com/article/3687#comment-26593
@@ -131,22 +72,24 @@ ModuleReturn ModuleManager::LoadModule(const Anope::string &modname, User *u)
Log(LOG_DEBUG) << "Trying to load module: " << modname;
const auto modpath = Anope::ExpandModule(modname + DLL_EXT);
std::error_code ec;
if (!fs::exists(modpath.str()) || ec)
{
Log(LOG_TERMINAL) << "Error while loading " << modname << ": file does not exist";
return MOD_ERR_NOEXIST;
}
#ifdef _WIN32
/* Generate the filename for the temporary copy of the module */
auto pbuf = Anope::ExpandData("runtime/" + modname + DLL_EXT ".XXXXXX");
/* Don't skip return value checking! -GD */
ModuleReturn ret = moduleCopyFile(modname, pbuf);
if (ret != MOD_ERR_OK)
if (!fs::copy_file(modpath.str(), pbuf.str(), fs::copy_options::overwrite_existing, ec) || ec)
{
if (ret == MOD_ERR_NOEXIST)
Log(LOG_TERMINAL) << "Error while loading " << modname << " (file does not exist)";
else if (ret == MOD_ERR_FILE_IO)
Log(LOG_TERMINAL) << "Error while loading " << modname << " (file IO error, check file permissions and diskspace)";
return ret;
Log(LOG_TERMINAL) << "Error while loading " << modname << ": " << ec.message();
return MOD_ERR_FILE_IO;
}
#else
const auto pbuf = Anope::ExpandModule(modname + DLL_EXT);
const auto pbuf = modpath;
#endif
dlerror();
+14 -9
View File
@@ -69,8 +69,8 @@ NickAlias::~NickAlias()
else
{
/* Display updating stuff */
if (this->nick.equals_ci(this->nc->display))
this->nc->SetDisplay(this->nc->aliases->front());
if (this->nc->na == this)
this->nc->SetDisplay(nullptr);
}
}
@@ -156,8 +156,8 @@ void NickAlias::Type::Serialize(Serializable *obj, Serialize::Data &data) const
data.Store("nick", na->nick);
data.Store("last_quit", na->last_quit);
data.Store("last_realname", na->last_realname);
data.Store("last_usermask", na->last_usermask);
data.Store("last_realhost", na->last_realhost);
data.Store("last_userhost", na->last_userhost);
data.Store("last_userhost_real", na->last_userhost_real);
data.Store("registered", na->registered);
data.Store("last_seen", na->last_seen);
data.Store("ncid", na->nc->GetId());
@@ -200,8 +200,8 @@ Serializable *NickAlias::Type::Unserialize(Serializable *obj, Serialize::Data &d
if (na->nc->aliases->empty())
delete na->nc;
else if (na->nick.equals_ci(na->nc->display))
na->nc->SetDisplay(na->nc->aliases->front());
if (na->nc->na == na)
na->nc->SetDisplay(nullptr);
na->nc = core;
core->aliases->push_back(na);
@@ -209,8 +209,8 @@ Serializable *NickAlias::Type::Unserialize(Serializable *obj, Serialize::Data &d
data["last_quit"] >> na->last_quit;
data["last_realname"] >> na->last_realname;
data["last_usermask"] >> na->last_usermask;
data["last_realhost"] >> na->last_realhost;
data["last_userhost"] >> na->last_userhost;
data["last_userhost_real"] >> na->last_userhost_real;
data["registered"] >> na->registered;
data["last_seen"] >> na->last_seen;
@@ -235,7 +235,12 @@ Serializable *NickAlias::Type::Unserialize(Serializable *obj, Serialize::Data &d
// End 1.9 compatibility.
// Begin 2.0 compatibility.
if (!na->registered)
if (na->last_userhost.empty())
data["last_usermask"] >> na->last_userhost;
if (na->last_userhost_real.empty())
data["last_realhost"] >> na->last_userhost_real;
if (na->registered == Anope::CurTime)
data["time_registered"] >> na->registered;
if (na->registered < na->nc->registered)
na->nc->registered = na->registered;
+12 -6
View File
@@ -181,7 +181,7 @@ Serializable *NickCore::Type::Unserialize(Serializable *obj, Serialize::Data &da
// End 2.0 compatibility.
// Begin 2.1 compatibility.
if (!nc->registered)
if (nc->registered == Anope::CurTime)
data["time_registered"] >> nc->registered;
// End 2.1 compatibility.
@@ -190,15 +190,21 @@ Serializable *NickCore::Type::Unserialize(Serializable *obj, Serialize::Data &da
void NickCore::SetDisplay(NickAlias *na)
{
if (na->nc != this || na->nick == this->display)
// If no nick is specified then pick the oldest one.
if (!na)
{
for (auto *alias : *this->aliases)
{
if (!na || alias->registered < na->registered)
na = alias;
}
}
if (!na || na->nc != this || na->nick == this->display)
return;
FOREACH_MOD(OnChangeCoreDisplay, (this, na->nick));
/* this affects the serialized aliases */
for (auto *alias : *aliases)
alias->QueueUpdate();
/* Remove the core from the list */
NickCoreList->erase(this->display);
+1 -1
View File
@@ -329,7 +329,7 @@ Serializable *ChannelInfo::Type::Unserialize(Serializable *obj, Serialize::Data
// End 1.9 compatibility.
// Begin 2.0 compatibility.
if (!ci->registered)
if (ci->registered == Anope::CurTime)
data["time_registered"] >> ci->registered;
// End 2.0 compatibility.
-3
View File
@@ -41,9 +41,6 @@ if(NOT WIN32)
install (PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/anoperc
DESTINATION ${BIN_DIR}
)
install (PROGRAMS geoipupdate.sh
DESTINATION ${BIN_DIR}
)
endif()
# On non-Windows platforms, if RUNGROUP is set, change the permissions of the tools directory
-81
View File
@@ -1,81 +0,0 @@
#!/bin/bash
# This script is a helper script for the irc2sql module.
# It downloads the configured geoip databases and inserts
# them into existing mysql tables. The tables are created
# by the irc2sql module on the first load.
# Don't forget to rename this file or your changes
# will be overwritten on the next 'make install'
############################
# Config
############################
geoip_database="country" # available options: "country" and "city"
mysql_host="localhost"
mysql_user="anope"
mysql_password="anope"
mysql_database="anope"
prefix="anope_"
die="yes"
###########################
# The GeoIP data is created by MaxMind, available from www.maxmind.com.
geoip_country_source="https://mirrors-cdn.liferay.com/geolite.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip"
geoip_city_source="https://mirrors-cdn.liferay.com/geolite.maxmind.com/download/geoip/database/GeoLiteCity-latest.tar.xz"
geoip_region_source="https://www.maxmind.com/download/geoip/misc/region_codes.csv"
###########################
LOGIN="--host=$mysql_host --user=$mysql_user --password=$mysql_password $mysql_database"
PARAMS="--delete --local --fields-terminated-by=, --fields-enclosed-by=\" --lines-terminated-by=\n $LOGIN"
download() {
local url=$1
local desc=$2
echo -n " $desc "
wget --progress=dot $url 2>&1 | grep --line-buffered "%" | sed -u -e "s,\.,,g" | awk '{printf("\b\b\b\b%4s", $2)}'
echo -ne " Done\n"
}
if test $die = "yes"; then
echo "You have to edit and configure this script first."
exit
fi
if test $geoip_database = "country"; then
echo "Downloading..."
download "$geoip_country_source" "Country Database:"
echo "Unpacking..."
unzip -jo GeoIPCountryCSV.zip
rm GeoIPCountryCSV.zip
echo "Converting to UTF-8..."
iconv -f ISO-8859-1 -t UTF-8 GeoIPCountryWhois.csv -o $prefix"geoip_country.csv"
rm GeoIPCountryWhois.csv
echo "Importing..."
mysqlimport --columns=@x,@x,start,end,countrycode,countryname $PARAMS $prefix"geoip_country.csv"
rm $prefix"geoip_country.csv" $prefix"geoip_country6.csv"
echo "Done..."
elif test $geoip_database = "city"; then
echo "Downloading..."
download "$geoip_city_source" "City Database:"
download "$geoip_region_source" "Region Database:"
echo "Unpacking..."
tar -xf GeoLiteCity-latest.tar.xz --strip-components 1
rm GeoLiteCity-latest.tar.xz
echo "Converting to utf-8..."
iconv -f ISO-8859-1 -t UTF-8 GeoLiteCity-Blocks.csv -o $prefix"geoip_city_blocks.csv"
iconv -f ISO-8859-1 -t UTF-8 GeoLiteCity-Location.csv -o $prefix"geoip_city_location.csv"
iconv -f ISO-8859-1 -t UTF-8 region_codes.csv -o $prefix"geoip_city_region.csv"
rm GeoLiteCity-Blocks.csv GeoLiteCity-Location.csv region_codes.csv
echo "Importing..."
mysqlimport --columns=start,end,locID --ignore-lines=2 $PARAMS $prefix"geoip_city_blocks.csv"
mysqlimport --columns=locID,country,region,city,@x,latitude,longitude,@x,areaCode --ignore-lines=2 $PARAMS $prefix"geoip_city_location.csv"
mysqlimport --columns=country,region,regionname $PARAMS $prefix"geoip_city_region.csv"
rm $prefix"geoip_city_blocks.csv" $prefix"geoip_city_location.csv" $prefix"geoip_city_region.csv"
echo "Done..."
fi
+4 -6
View File
@@ -376,8 +376,8 @@ void User::Identify(NickAlias *na)
{
if (this->nick.equals_ci(na->nick))
{
na->last_usermask = this->GetIdent() + "@" + this->GetDisplayedHost();
na->last_realhost = this->GetIdent() + "@" + this->host;
na->last_userhost = this->GetIdent() + "@" + this->GetDisplayedHost();
na->last_userhost_real = this->GetIdent() + "@" + this->host;
na->last_realname = this->realname;
na->last_seen = Anope::CurTime;
}
@@ -534,10 +534,8 @@ void User::UpdateHost()
NickAlias *na = NickAlias::Find(this->nick);
if (na && this->IsIdentified(true))
{
Anope::string last_usermask = this->GetIdent() + "@" + this->GetDisplayedHost();
Anope::string last_realhost = this->GetIdent() + "@" + this->host;
na->last_usermask = last_usermask;
na->last_realhost = last_realhost;
na->last_userhost = this->GetIdent() + "@" + this->GetDisplayedHost();
na->last_userhost_real = this->GetIdent() + "@" + this->host;
// This is called on signon, and if users are introduced with an account it won't update
na->last_realname = this->realname;
}
+1 -1
View File
@@ -2,5 +2,5 @@
VERSION_MAJOR=2
VERSION_MINOR=1
VERSION_PATCH=15
VERSION_PATCH=17
VERSION_EXTRA=""
-363
View File
@@ -1,363 +0,0 @@
/*
* Config.cs - Windows Configuration
*
* (C) 2003-2025 Anope Team
* Contact us at team@anope.org
*
* This program is free but copyrighted software; see the file COPYING for
* details.
*
* Based on the original code of Epona by Lara.
* Based on the original code of Services by Andy Church.
*
* Written by Scott <stealtharcher.scott@gmail.com>
* Written by Adam <Adam@anope.org>
* Cleaned up by Naram Qashat <cyberbotx@anope.org>
*
* Compile with: csc /out:../../Config.exe /win32icon:anope-icon.ico Config.cs
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace Config
{
class Config
{
static string ExecutablePath, InstallDirectory, ExtraIncludeDirs, ExtraLibDirs, ExtraArguments;
static bool UseNMake = true, BuildDebug = false;
static bool CheckResponse(string InstallerResponse)
{
if (string.Compare(InstallerResponse, "yes", true) == 0 || string.Compare(InstallerResponse, "y", true) == 0)
return true;
return false;
}
static bool LoadCache()
{
try
{
string[] cache = File.ReadAllLines(string.Format(@"{0}\config.cache", ExecutablePath));
if (cache.Length > 0)
Console.WriteLine("Using defaults from config.cache");
foreach (string line in cache)
{
int e = line.IndexOf('=');
string name = line.Substring(0, e);
string value = line.Substring(e + 1);
if (name == "INSTDIR")
InstallDirectory = value;
else if (name == "DEBUG")
BuildDebug = CheckResponse(value);
else if (name == "USENMAKE")
UseNMake = CheckResponse(value);
else if (name == "EXTRAINCLUDE")
ExtraIncludeDirs = value;
else if (name == "EXTRALIBS")
ExtraLibDirs = value;
else if (name == "EXTRAARGS")
ExtraArguments = value;
}
return true;
}
catch (Exception)
{
}
return false;
}
static void SaveCache()
{
using (TextWriter tw = new StreamWriter(string.Format(@"{0}\config.cache", ExecutablePath)))
{
tw.WriteLine("INSTDIR={0}", InstallDirectory);
tw.WriteLine("DEBUG={0}", BuildDebug ? "yes" : "no");
tw.WriteLine("USENMAKE={0}", UseNMake ? "yes" : "no");
tw.WriteLine("EXTRAINCLUDE={0}", ExtraIncludeDirs);
tw.WriteLine("EXTRALIBS={0}", ExtraLibDirs);
tw.WriteLine("EXTRAARGS={0}", ExtraArguments);
}
}
static string HandleCache(int i)
{
switch (i)
{
case 0:
Console.Write("[{0}] ", InstallDirectory);
return InstallDirectory;
case 1:
Console.Write("[{0}] ", UseNMake ? "yes" : "no");
return UseNMake ? "yes" : "no";
case 2:
Console.Write("[{0}] ", BuildDebug ? "yes" : "no");
return BuildDebug ? "yes" : "no";
case 3:
Console.Write("[{0}] ", ExtraIncludeDirs);
return ExtraIncludeDirs;
case 4:
Console.Write("[{0}] ", ExtraLibDirs);
return ExtraLibDirs;
case 5:
Console.Write("[{0}] ", ExtraArguments);
return ExtraArguments;
default:
break;
}
return null;
}
static string FindAnopeVersion()
{
if (!File.Exists(string.Format(@"{0}\src\version.sh", ExecutablePath)))
return "Unknown";
Dictionary<string, string> versions = new Dictionary<string, string>();
string[] versionfile = File.ReadAllLines(string.Format(@"{0}\src\version.sh", ExecutablePath));
foreach (string line in versionfile)
if (line.StartsWith("VERSION_"))
{
string key = line.Split('_')[1].Split('=')[0];
if (!versions.ContainsKey(key))
versions.Add(key, line.Split('=')[1].Replace("\"", "").Replace("\'", ""));
}
try
{
if (versions.ContainsKey("BUILD"))
return string.Format("{0}.{1}.{2}.{3}{4}", versions["MAJOR"], versions["MINOR"], versions["PATCH"], versions["BUILD"], versions["EXTRA"]);
else
return string.Format("{0}.{1}.{2}{3}", versions["MAJOR"], versions["MINOR"], versions["PATCH"], versions["EXTRA"]);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return "Unknown";
}
}
static void RunCMake(string cMake)
{
Console.WriteLine("cmake {0}", cMake);
try
{
ProcessStartInfo processStartInfo = new ProcessStartInfo("cmake")
{
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
Arguments = cMake
};
Process pCMake = Process.Start(processStartInfo);
StreamReader stdout = pCMake.StandardOutput, stderr = pCMake.StandardError;
string stdoutr, stderrr;
List<string> errors = new List<string>();
while (!pCMake.HasExited)
{
if ((stdoutr = stdout.ReadLine()) != null)
Console.WriteLine(stdoutr);
if ((stderrr = stderr.ReadLine()) != null)
errors.Add(stderrr);
}
foreach (string error in errors)
Console.WriteLine(error);
Console.WriteLine();
if (pCMake.ExitCode == 0)
{
if (UseNMake)
Console.WriteLine("To compile Anope, run 'nmake'. To install, run 'nmake install'");
else
Console.WriteLine("To compile Anope, open Anope.sln and build the solution. To install, do a build on the INSTALL project");
}
else
Console.WriteLine("There was an error attempting to run CMake! Check the above error message, and contact the Anope team if you are unsure how to proceed.");
}
catch (Exception e)
{
Console.WriteLine();
Console.WriteLine(DateTime.UtcNow + " UTC: " + e.Message);
Console.WriteLine("There was an error attempting to run CMake! Check the above error message, and contact the Anope team if you are unsure how to proceed.");
}
}
static int Main(string[] args)
{
bool IgnoreCache = false, NoIntro = false, DoQuick = false;
if (args.Length > 0)
{
if (args[0] == "--help")
{
Console.WriteLine("Config utility for Anope");
Console.WriteLine("------------------------");
Console.WriteLine("Syntax: .\\Config.exe [options]");
Console.WriteLine("-nocache Ignore settings saved in config.cache");
Console.WriteLine("-nointro Skip intro (disclaimer, etc)");
Console.WriteLine("-quick or -q Skip questions, go straight to cmake");
return 0;
}
else if (args[0] == "-nocache")
IgnoreCache = true;
else if (args[0] == "-nointro")
NoIntro = true;
else if (args[0] == "-quick" || args[0] == "-q")
DoQuick = true;
}
ExecutablePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string AnopeVersion = FindAnopeVersion();
if (!NoIntro && File.Exists(string.Format(@"{0}\.BANNER", ExecutablePath)))
Console.WriteLine(File.ReadAllText(string.Format(@"{0}\.BANNER", ExecutablePath)).Replace("CURVER", AnopeVersion).Replace("For more options type SOURCE_DIR/Config --help", ""));
Console.WriteLine("Press Enter to begin");
Console.WriteLine();
Console.ReadKey();
bool UseCache = false;
if (DoQuick || !IgnoreCache)
{
UseCache = LoadCache();
if (DoQuick && !UseCache)
{
Console.WriteLine("Can't find cache file (config.cache), aborting...");
return 1;
}
}
if (!DoQuick)
{
List<string> InstallerQuestions = new List<string>()
{
"Where do you want Anope to be installed?",
"Would you like to build using NMake instead of using Visual Studio?\r\nNOTE: If you decide to use NMake, you must be in an environment where\r\nNMake can function, such as the Visual Studio command line. If you say\r\nyes to this while not in an environment that can run NMake, it can\r\ncause the CMake configuration to enter an endless loop. [y/n]",
"Would you like to build a debug version of Anope? [y/n]",
"Are there any extra include directories you wish to use?\nYou may only need to do this if CMake is unable to locate missing dependencies without hints.\nSeparate directories with semicolons and use slashes (aka /) instead of backslashes (aka \\).\nIf you need no extra include directories, enter NONE in all caps.",
"Are there any extra library directories you wish to use?\nYou may only need to do this if CMake is unable to locate missing dependencies without hints.\nSeparate directories with semicolons and use slashes (aka /) instead of backslashes (aka \\).\nIf you need no extra library directories, enter NONE in all caps.",
"Are there any extra arguments you wish to pass to CMake?\nIf you need no extra arguments to CMake, enter NONE in all caps."
};
for (int i = 0; i < InstallerQuestions.Count; ++i)
{
Console.WriteLine(InstallerQuestions[i]);
string CacheResponse = null;
if (UseCache)
CacheResponse = HandleCache(i);
string InstallerResponse = Console.ReadLine();
Console.WriteLine();
if (!string.IsNullOrWhiteSpace(CacheResponse) && string.IsNullOrWhiteSpace(InstallerResponse))
InstallerResponse = CacheResponse;
// Question 4+ are optional
if (i < 3 && string.IsNullOrWhiteSpace(InstallerResponse))
{
Console.WriteLine("Invalid option");
--i;
continue;
}
switch (i)
{
case 0:
if (!Directory.Exists(InstallerResponse))
{
Console.WriteLine("Directory does not exist! Creating directory.");
Console.WriteLine();
try
{
Directory.CreateDirectory(InstallerResponse);
InstallDirectory = InstallerResponse;
}
catch (Exception e)
{
Console.WriteLine("Unable to create directory: " + e.Message);
--i;
}
}
else if (File.Exists(InstallerResponse + @"\include\services.h"))
{
Console.WriteLine("You cannot use the Anope source directory as the target directory!");
--i;
}
else
InstallDirectory = InstallerResponse;
break;
case 1:
UseNMake = CheckResponse(InstallerResponse);
if (UseNMake)
++i;
break;
case 2:
BuildDebug = CheckResponse(InstallerResponse);
break;
case 3:
if (InstallerResponse == "NONE")
ExtraIncludeDirs = null;
else
ExtraIncludeDirs = InstallerResponse;
break;
case 4:
if (InstallerResponse == "NONE")
ExtraLibDirs = null;
else
ExtraLibDirs = InstallerResponse;
break;
case 5:
if (InstallerResponse == "NONE")
ExtraArguments = null;
else
ExtraArguments = InstallerResponse;
break;
default:
break;
}
}
}
Console.WriteLine("Anope will be compiled with the following options:");
Console.WriteLine("Install directory: {0}", InstallDirectory);
Console.WriteLine("Use NMake: {0}", UseNMake ? "Yes" : "No");
Console.WriteLine("Build debug: {0}", BuildDebug ? "Yes" : "No");
Console.WriteLine("Anope Version: {0}", AnopeVersion);
Console.WriteLine("Extra Include Directories: {0}", ExtraIncludeDirs);
Console.WriteLine("Extra Library Directories: {0}", ExtraLibDirs);
Console.WriteLine("Extra Arguments: {0}", ExtraArguments);
Console.WriteLine("Press Enter to continue...");
Console.ReadKey();
SaveCache();
if (!string.IsNullOrWhiteSpace(ExtraIncludeDirs))
ExtraIncludeDirs = string.Format("-DEXTRA_INCLUDE:STRING={0} ", ExtraIncludeDirs);
else
ExtraIncludeDirs = "";
if (!string.IsNullOrWhiteSpace(ExtraLibDirs))
ExtraLibDirs = string.Format("-DEXTRA_LIBS:STRING={0} ", ExtraLibDirs);
else
ExtraLibDirs = "";
if (!string.IsNullOrWhiteSpace(ExtraArguments))
ExtraArguments += " ";
else
ExtraArguments = "";
InstallDirectory = "-DINSTDIR:STRING=\"" + InstallDirectory.Replace('\\', '/') + "\" ";
string NMake = UseNMake ? "-G\"NMake Makefiles\" " : "";
string Debug = BuildDebug ? "-DCMAKE_BUILD_TYPE:STRING=DEBUG " : "-DCMAKE_BUILD_TYPE:STRING=RELEASE ";
string cMake = InstallDirectory + NMake + Debug + ExtraIncludeDirs + ExtraLibDirs + ExtraArguments + "\"" + ExecutablePath.Replace('\\', '/') + "\"";
RunCMake(cMake);
return 0;
}
}
}
-1
View File
@@ -67,7 +67,6 @@ extern CoreExport void OnShutdown();
extern CoreExport USHORT WindowsGetLanguage(const Anope::string &lang);
extern int setenv(const char *name, const char *value, int overwrite);
extern int unsetenv(const char *name);
extern int mkstemp(char *input);
#endif // _WIN32
#endif // WINDOWS_H
-13
View File
@@ -71,17 +71,4 @@ int unsetenv(const char *name)
return SetEnvironmentVariable(name, NULL);
}
int mkstemp(char *input)
{
input = _mktemp(input);
if (input == NULL)
{
errno = EEXIST;
return -1;
}
int fd = open(input, O_WRONLY | O_CREAT, S_IREAD | S_IWRITE);
return fd;
}
#endif