From 7ac1fe58478d58e2480b6919c4abf3a82929169c Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Wed, 24 Jan 2024 12:01:50 +0000 Subject: Rename several modules to remove the m_ prefix. --- .github/workflows/ci-linux.yml | 4 +- .gitignore | 27 +- data/anope.example.conf | 6 +- data/chanstats.example.conf | 2 +- data/modules.example.conf | 122 ++-- data/operserv.example.conf | 2 +- data/stats.standalone.example.conf | 4 +- docs/Changes.conf | 1 + docs/INSTALL | 2 +- docs/INSTALL.fr | 2 +- docs/WIN32.txt | 2 +- docs/XMLRPC/XMLRPC | 2 +- modules/CMakeLists.txt | 20 +- modules/dns.cpp | 1126 +++++++++++++++++++++++++++++++ modules/dnsbl.cpp | 196 ++++++ modules/extra/ldap.cpp | 702 +++++++++++++++++++ modules/extra/ldap_authentication.cpp | 308 +++++++++ modules/extra/ldap_oper.cpp | 140 ++++ modules/extra/m_ldap.cpp | 702 ------------------- modules/extra/m_ldap_authentication.cpp | 308 --------- modules/extra/m_ldap_oper.cpp | 140 ---- modules/extra/m_mysql.cpp | 569 ---------------- modules/extra/m_regex_pcre2.cpp | 91 --- modules/extra/m_regex_posix.cpp | 82 --- modules/extra/m_regex_tre.cpp | 83 --- modules/extra/m_sql_authentication.cpp | 150 ---- modules/extra/m_sql_log.cpp | 108 --- modules/extra/m_sql_oper.cpp | 181 ----- modules/extra/m_sqlite.cpp | 340 ---------- modules/extra/m_ssl_gnutls.cpp | 643 ------------------ modules/extra/m_ssl_openssl.cpp | 445 ------------ modules/extra/mysql.cpp | 569 ++++++++++++++++ modules/extra/regex_pcre2.cpp | 91 +++ modules/extra/regex_posix.cpp | 82 +++ modules/extra/regex_tre.cpp | 83 +++ modules/extra/sql_authentication.cpp | 150 ++++ modules/extra/sql_log.cpp | 108 +++ modules/extra/sql_oper.cpp | 181 +++++ modules/extra/sqlite.cpp | 340 ++++++++++ modules/extra/ssl_gnutls.cpp | 643 ++++++++++++++++++ modules/extra/ssl_openssl.cpp | 445 ++++++++++++ modules/helpchan.cpp | 33 + modules/httpd.cpp | 469 +++++++++++++ modules/m_dns.cpp | 1126 ------------------------------- modules/m_dnsbl.cpp | 196 ------ modules/m_helpchan.cpp | 33 - modules/m_httpd.cpp | 469 ------------- modules/m_proxyscan.cpp | 388 ----------- modules/m_redis.cpp | 618 ----------------- modules/m_regex_stdlib.cpp | 107 --- modules/m_rewrite.cpp | 185 ----- modules/m_sasl.cpp | 414 ------------ modules/m_xmlrpc.cpp | 229 ------- modules/m_xmlrpc_main.cpp | 306 --------- modules/proxyscan.cpp | 388 +++++++++++ modules/redis.cpp | 618 +++++++++++++++++ modules/regex_stdlib.cpp | 107 +++ modules/rewrite.cpp | 185 +++++ modules/sasl.cpp | 414 ++++++++++++ modules/stats/chanstats.cpp | 660 ++++++++++++++++++ modules/stats/cs_fantasy_stats.cpp | 6 +- modules/stats/cs_fantasy_top.cpp | 6 +- modules/stats/irc2sql/irc2sql.cpp | 2 +- modules/stats/irc2sql/irc2sql.h | 4 +- modules/stats/m_chanstats.cpp | 660 ------------------ modules/webcpanel/webcpanel.cpp | 2 +- modules/xmlrpc.cpp | 229 +++++++ modules/xmlrpc_main.cpp | 306 +++++++++ 68 files changed, 8681 insertions(+), 8681 deletions(-) create mode 100644 modules/dns.cpp create mode 100644 modules/dnsbl.cpp create mode 100644 modules/extra/ldap.cpp create mode 100644 modules/extra/ldap_authentication.cpp create mode 100644 modules/extra/ldap_oper.cpp delete mode 100644 modules/extra/m_ldap.cpp delete mode 100644 modules/extra/m_ldap_authentication.cpp delete mode 100644 modules/extra/m_ldap_oper.cpp delete mode 100644 modules/extra/m_mysql.cpp delete mode 100644 modules/extra/m_regex_pcre2.cpp delete mode 100644 modules/extra/m_regex_posix.cpp delete mode 100644 modules/extra/m_regex_tre.cpp delete mode 100644 modules/extra/m_sql_authentication.cpp delete mode 100644 modules/extra/m_sql_log.cpp delete mode 100644 modules/extra/m_sql_oper.cpp delete mode 100644 modules/extra/m_sqlite.cpp delete mode 100644 modules/extra/m_ssl_gnutls.cpp delete mode 100644 modules/extra/m_ssl_openssl.cpp create mode 100644 modules/extra/mysql.cpp create mode 100644 modules/extra/regex_pcre2.cpp create mode 100644 modules/extra/regex_posix.cpp create mode 100644 modules/extra/regex_tre.cpp create mode 100644 modules/extra/sql_authentication.cpp create mode 100644 modules/extra/sql_log.cpp create mode 100644 modules/extra/sql_oper.cpp create mode 100644 modules/extra/sqlite.cpp create mode 100644 modules/extra/ssl_gnutls.cpp create mode 100644 modules/extra/ssl_openssl.cpp create mode 100644 modules/helpchan.cpp create mode 100644 modules/httpd.cpp delete mode 100644 modules/m_dns.cpp delete mode 100644 modules/m_dnsbl.cpp delete mode 100644 modules/m_helpchan.cpp delete mode 100644 modules/m_httpd.cpp delete mode 100644 modules/m_proxyscan.cpp delete mode 100644 modules/m_redis.cpp delete mode 100644 modules/m_regex_stdlib.cpp delete mode 100644 modules/m_rewrite.cpp delete mode 100644 modules/m_sasl.cpp delete mode 100644 modules/m_xmlrpc.cpp delete mode 100644 modules/m_xmlrpc_main.cpp create mode 100644 modules/proxyscan.cpp create mode 100644 modules/redis.cpp create mode 100644 modules/regex_stdlib.cpp create mode 100644 modules/rewrite.cpp create mode 100644 modules/sasl.cpp create mode 100644 modules/stats/chanstats.cpp delete mode 100644 modules/stats/m_chanstats.cpp create mode 100644 modules/xmlrpc.cpp create mode 100644 modules/xmlrpc_main.cpp diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index e30226cdc..8aebe5b4e 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -28,9 +28,9 @@ jobs: ninja-build - name: Enable extras run: | - for MODULE in m_ldap.cpp m_ldap_authentication.cpp m_ldap_oper.cpp m_mysql.cpp m_regex_pcre2.cpp m_regex_posix.cpp m_regex_tre.cpp m_sql_authentication.cpp m_sql_log.cpp m_sql_oper.cpp m_sqlite.cpp m_ssl_gnutls.cpp m_ssl_openssl.cpp + for MODULE in ldap ldap_authentication ldap_oper mysql regex_pcre2 regex_posix regex_tre sql_authentication sql_log sql_oper sqlite ssl_gnutls ssl_openssl do - ln -s ${{ github.workspace }}/modules/extra/$MODULE ${{ github.workspace }}/modules + ln -s ${{ github.workspace }}/modules/extra/$MODULE.cpp ${{ github.workspace }}/modules done - name: Run CMake run: | diff --git a/.gitignore b/.gitignore index 4e0d9badf..1495a75a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,19 @@ build/ config.cache include/sysconf.h -modules/m_ldap.cpp -modules/m_ldap_authentication.cpp -modules/m_ldap_oper.cpp -modules/m_mysql.cpp -modules/m_regex_pcre.cpp -modules/m_regex_pcre2.cpp -modules/m_regex_posix.cpp -modules/m_regex_tre.cpp -modules/m_sql_authentication.cpp -modules/m_sql_log.cpp -modules/m_sql_oper.cpp -modules/m_sqlite.cpp -modules/m_ssl_gnutls.cpp -modules/m_ssl_openssl.cpp +modules/ldap.cpp +modules/ldap_authentication.cpp +modules/ldap_oper.cpp +modules/mysql.cpp +modules/regex_pcre2.cpp +modules/regex_posix.cpp +modules/regex_tre.cpp +modules/sql_authentication.cpp +modules/sql_log.cpp +modules/sql_oper.cpp +modules/sqlite.cpp +modules/ssl_gnutls.cpp +modules/ssl_openssl.cpp modules/stats run/ *.mo diff --git a/data/anope.example.conf b/data/anope.example.conf index 62eaa8154..393eeaa46 100644 --- a/data/anope.example.conf +++ b/data/anope.example.conf @@ -1199,7 +1199,7 @@ module * db_redis. * * This module allows using Redis (https://redis.io/) as a database backend. - * This module requires that m_redis is loaded and configured properly. + * This module requires that redis is loaded and configured properly. * * Redis 2.8 supports keyspace notifications which allows Redis to push notifications * to Anope about outside modifications to the database. This module supports this and @@ -1211,7 +1211,7 @@ module name = "db_redis" /* - * Redis database to use. This must be configured with m_redis. + * Redis database to use. This must be configured with redis. */ engine = "redis/main" } @@ -1225,7 +1225,7 @@ module * * Without any encryption modules loaded users will not be able to authenticate unless * there is another module loaded that provides authentication checking, such as - * m_ldap_authentication or m_sql_authentication. + * ldap_authentication or sql_authentication. * * With enc_none, passwords will be stored in plain text, allowing for passwords * to be recovered later but it isn't secure and therefore is not recommended. diff --git a/data/chanstats.example.conf b/data/chanstats.example.conf index 338b93306..31e6897a6 100644 --- a/data/chanstats.example.conf +++ b/data/chanstats.example.conf @@ -5,7 +5,7 @@ module { - name = "m_chanstats" + name = "chanstats" /* * The name of this engine. diff --git a/data/modules.example.conf b/data/modules.example.conf index fb4f6a6d9..d60c83cce 100644 --- a/data/modules.example.conf +++ b/data/modules.example.conf @@ -19,14 +19,14 @@ module { name = "help" } /* - * m_dns + * dns * * Adds support for the DNS protocol. By itself this module does nothing useful, - * but other modules such as m_dnsbl and os_dns require this. + * but other modules such as dnsbl and os_dns require this. */ #module { - name = "m_dns" + name = "dns" /* * The nameserver to use for resolving hostnames, must be an IP or a resolver configuration file. @@ -80,7 +80,7 @@ module { name = "help" } } /* - * m_dnsbl + * dnsbl * * Allows configurable DNS blacklists to check connecting users against. If a user * is found on the blacklist they will be immediately banned. This is a crucial module @@ -88,7 +88,7 @@ module { name = "help" } */ #module { - name = "m_dnsbl" + name = "dnsbl" /* * If set, Anope will check clients against the DNSBLs when services connect to its uplink. @@ -179,19 +179,19 @@ module { name = "help" } } /* - * m_helpchan + * helpchan * * Gives users who are op in the specified help channel usermode +h (helpop). */ #module { - name = "m_helpchan" + name = "helpchan" helpchannel = "#help" } /* - * m_httpd + * httpd * * Allows services to serve web pages. By itself, this module does nothing useful. * @@ -200,7 +200,7 @@ module { name = "help" } */ #module { - name = "m_httpd" + name = "httpd" httpd { @@ -233,13 +233,13 @@ module { name = "help" } } /* - * m_ldap [EXTRA] + * ldap [EXTRA] * * This module allows other modules to use LDAP. By itself, this module does nothing useful. */ #module { - name = "m_ldap" + name = "ldap" ldap { @@ -254,14 +254,14 @@ module { name = "help" } } /* - * m_ldap_authentication [EXTRA] + * ldap_authentication [EXTRA] * * This module allows many commands such as IDENTIFY, RELEASE, RECOVER, GHOST, etc. use - * LDAP to authenticate users. Requires m_ldap. + * LDAP to authenticate users. Requires ldap. */ #module { - name = "m_ldap_authentication" + name = "ldap_authentication" /* * The distinguished name used for searching for users's accounts. @@ -314,16 +314,16 @@ module { name = "help" } } /* - * m_ldap_oper [EXTRA] + * ldap_oper [EXTRA] * * This module dynamically ties users to Anope opertypes when they identify - * via LDAP group membership. Requires m_ldap. + * via LDAP group membership. Requires ldap. * * Note that this doesn't give the user privileges on the IRCd, only in Anope. */ #module { - name = "m_ldap_oper" + name = "ldap_oper" /* * An optional binddn to use when searching for groups. @@ -355,13 +355,13 @@ module { name = "help" } } /* - * m_mysql [EXTRA] + * mysql [EXTRA] * * This module allows other modules to use MySQL. */ #module { - name = "m_mysql" + name = "mysql" mysql { @@ -376,13 +376,13 @@ module { name = "help" } } /* - * m_redis + * redis * * This module allows other modules to use Redis. */ #module { - name = "m_redis" + name = "redis" /* A redis database */ redis @@ -401,29 +401,29 @@ module { name = "help" } } /* - * m_regex_pcre2 [EXTRA] + * regex_pcre2 [EXTRA] * * Provides the regex engine regex/pcre, which uses version 2 of the Perl Compatible Regular * Expressions library. */ -#module { name = "m_regex_pcre2" } +#module { name = "regex_pcre2" } /* - * m_regex_posix [EXTRA] + * regex_posix [EXTRA] * * Provides the regex engine regex/posix, which uses the POSIX compliant regular expressions. */ -#module { name = "m_regex_posix" } +#module { name = "regex_posix" } /* - * m_regex_stdlib + * regex_stdlib * * Provides the regex engine regex/stdlib, which uses the regular expression library that is part of * the C++ standard library. */ module { - name = "m_regex_stdlib" + name = "regex_stdlib" /* * The syntax scheme to use. Can be set to awk to use the regular expression grammar used by the @@ -439,23 +439,23 @@ module } /* - * m_regex_tre [EXTRA] + * regex_tre [EXTRA] * * Provides the regex engine regex/tre, which uses the TRE regex library. */ -#module { name = "m_regex_tre" } +#module { name = "regex_tre" } /* - * m_rewrite + * rewrite * * Allows rewriting commands sent to/from clients. */ -#module { name = "m_rewrite" } +#module { name = "rewrite" } #command { service = "ChanServ"; name = "CLEAR"; command = "rewrite" - /* Enable m_rewrite. */ + /* Enable rewrite. */ rewrite = true /* Source message to match. A $ can be used to match anything. */ @@ -476,7 +476,7 @@ module } /* - * m_proxyscan + * proxyscan * * This module allows you to scan connecting clients for open proxies. * Note that using this will allow users to get the IP of your services. @@ -490,7 +490,7 @@ module */ #module { - name = "m_proxyscan" + name = "proxyscan" /* * The target IP services tells the proxy to connect back to. This must be a publicly @@ -558,29 +558,29 @@ module } /* - * m_sasl + * sasl * * Some IRCds allow "SASL" authentication to let users identify to services * during the IRCd user registration process. If this module is loaded, Anope will allow * authenticating users through this mechanism. Supported mechanisms are: * PLAIN, EXTERNAL. */ -module { name = "m_sasl" } +module { name = "sasl" } /* - * m_ssl_gnutls [EXTRA] + * ssl_gnutls [EXTRA] * * This module provides SSL services to Anope using GnuTLS, for example to * connect to the uplink server(s) via SSL. * - * You may only load either m_ssl_gnutls or m_ssl_openssl, but not both. + * You may only load either ssl_gnutls or ssl_openssl, but not both. */ #module { - name = "m_ssl_gnutls" + name = "ssl_gnutls" /* - * An optional certificate and key for m_ssl_gnutls to give to the uplink. + * An optional certificate and key for ssl_gnutls to give to the uplink. * * You can generate your own certificate and key pair by using: * @@ -606,20 +606,20 @@ module { name = "m_sasl" } } /* - * m_ssl_openssl [EXTRA] + * ssl_openssl [EXTRA] * * This module provides SSL services to Anope using OpenSSL, for example to * connect to the uplink server(s) via SSL. * - * You may only load either m_ssl_openssl or m_ssl_gnutls, but not both. + * You may only load either ssl_openssl or ssl_gnutls, but not both. * */ #module { - name = "m_ssl_openssl" + name = "ssl_openssl" /* - * An optional certificate and key for m_ssl_openssl to give to the uplink. + * An optional certificate and key for ssl_openssl to give to the uplink. * * You can generate your own certificate and key pair by using: * @@ -640,16 +640,16 @@ module { name = "m_sasl" } } /* - * m_sql_authentication [EXTRA] + * sql_authentication [EXTRA] * * This module allows authenticating users against an external SQL database using a custom * query. */ #module { - name = "m_sql_authentication" + name = "sql_authentication" - /* SQL engine to use. Should be configured elsewhere with m_mysql, m_sqlite, etc. */ + /* SQL engine to use. Should be configured elsewhere with mysql, sqlite, etc. */ engine = "mysql/main" /* Query to execute to authenticate. A non empty result from this query is considered a success, @@ -691,7 +691,7 @@ module { name = "m_sasl" } } /* - * m_sql_log [EXTRA] + * sql_log [EXTRA] * * This module adds an additional target option to log{} blocks * that allows logging Service's logs to SQL. To log to SQL, add @@ -708,19 +708,19 @@ module { name = "m_sasl" } * it if it doesn't exist. This module does not create any indexes (keys) * on the table and it is recommended you add them yourself as necessary. */ -#module { name = "m_sql_log" } +#module { name = "sql_log" } /* - * m_sql_oper [EXTRA] + * sql_oper [EXTRA] * * This module allows granting users services operator privileges and possibly IRC Operator * privileges based on an external SQL database using a custom query. */ #module { - name = "m_sql_oper" + name = "sql_oper" - /* SQL engine to use. Should be configured elsewhere with m_mysql, m_sqlite, etc. */ + /* SQL engine to use. Should be configured elsewhere with mysql, sqlite, etc. */ engine = "mysql/main" /* Query to execute to determine if a user should have operator privileges. @@ -737,13 +737,13 @@ module { name = "m_sasl" } } /* - * m_sqlite [EXTRA] + * sqlite [EXTRA] * * This module allows other modules to use SQLite. */ #module { - name = "m_sqlite" + name = "sqlite" /* A SQLite database */ sqlite @@ -763,7 +763,7 @@ module { name = "m_sasl" } * as they could over IRC. If you are using the default configuration you should be able to access * this panel by visiting http://127.0.0.1:8080 in your web browser from the machine Anope is running on. * - * This module requires m_httpd. + * This module requires httpd. */ #module { @@ -780,23 +780,23 @@ module { name = "m_sasl" } } /* - * m_xmlrpc + * xmlrpc * * Allows remote applications (websites) to execute queries in real time to retrieve data from Anope. * By itself this module does nothing, but allows other modules (m_xmlrpc_main) to receive and send XMLRPC queries. */ #module { - name = "m_xmlrpc" + name = "xmlrpc" - /* Web service to use. Requires m_httpd. */ + /* Web service to use. Requires httpd. */ server = "httpd/main" } /* - * m_xmlrpc_main + * xmlrpc_main * * Adds the main XMLRPC core functions. - * Requires m_xmlrpc. + * Requires xmlrpc. */ -#module { name = "m_xmlrpc_main" } +#module { name = "xmlrpc_main" } diff --git a/data/operserv.example.conf b/data/operserv.example.conf index d4cfeda0b..e23efbb47 100644 --- a/data/operserv.example.conf +++ b/data/operserv.example.conf @@ -267,7 +267,7 @@ command { service = "OperServ"; name = "CHANKILL"; command = "operserv/chankill" * * Provides the command operserv/dns. * - * This module requires that m_dns is loaded. + * This module requires that dns is loaded. * * This module allows controlling a DNS zone. This is useful for * controlling what servers users are placed on for load balancing, diff --git a/data/stats.standalone.example.conf b/data/stats.standalone.example.conf index bd40723e8..51698e795 100644 --- a/data/stats.standalone.example.conf +++ b/data/stats.standalone.example.conf @@ -483,13 +483,13 @@ log /* * [REQUIRED] MySQL Database configuration. * - * m_mysql + * mysql * * This module allows other modules to use MySQL. */ module { - name = "m_mysql" + name = "mysql" mysql { diff --git a/docs/Changes.conf b/docs/Changes.conf index 8e6da9fcf..eae81a617 100644 --- a/docs/Changes.conf +++ b/docs/Changes.conf @@ -4,6 +4,7 @@ Added module:tlsv10 from m_ssl_openssl. Added module:tlsv11 from m_ssl_openssl. Added module:tlsv12 from m_ssl_openssl. Removed module:sslv3 from m_ssl_openssl. +Removed the m_ prefix from the names of the chanstats, dns, dnsbl, helpchan, httpd, ldap, ldap_oper, mysql, proxyscan, redis, regex_pcre2, regex_posix, regex_stdlib, regex_tre, rewrite, sasl, sql_log, sql_oper, sqlite, ssl_gnutls, ssl_openssl, xmlrpc, and xmlrpc_main modules. Anope Version 2.1.1 ------------------- diff --git a/docs/INSTALL b/docs/INSTALL index cbd170804..2a643429a 100644 --- a/docs/INSTALL +++ b/docs/INSTALL @@ -37,7 +37,7 @@ Note: You should also read the README and FAQ files! Next, unpack the package in your home directory, and go into the created directory. - If there are any extra modules you want to enable, such as m_mysql, run + If there are any extra modules you want to enable, such as mysql, run the 'extras' script to enable them. If you do not know you can come back later and enable them. diff --git a/docs/INSTALL.fr b/docs/INSTALL.fr index f80aeb9d6..bdfbccc53 100644 --- a/docs/INSTALL.fr +++ b/docs/INSTALL.fr @@ -37,7 +37,7 @@ Note : Vous devrez également lire les fichiers README et FAQ ! Ensuite, décompressez le package dans votre répertoire home, et allez dans le répértoire qui vient d'être créé. - Si il y a des modules facultatifs que vous voulez activer comme m_mysql, + Si il y a des modules facultatifs que vous voulez activer comme mysql, exécuter le script 'extras' pour les activer. Si vous ne savez pas, vous pouvez les activer plus tard. diff --git a/docs/WIN32.txt b/docs/WIN32.txt index 104ef7441..bf7153010 100644 --- a/docs/WIN32.txt +++ b/docs/WIN32.txt @@ -70,7 +70,7 @@ Anope for Windows If you cannot find whats causing the error, please visit our forums or our IRC Support channel for assistance. - Some Anope modules require third party libraries, such as m_mysql and + Some Anope modules require third party libraries, such as mysql and the SSL modules. If these libraries are installed in nonstandard locations, cmake will probably not find them and should be told where they are by passing their location to Config. diff --git a/docs/XMLRPC/XMLRPC b/docs/XMLRPC/XMLRPC index 52f2248a6..7a64556db 100644 --- a/docs/XMLRPC/XMLRPC +++ b/docs/XMLRPC/XMLRPC @@ -1,7 +1,7 @@ Starting with Anope 1.9.4 XMLRPC using PHP's xmlrpc_encode_request and xmlrpc_decode functions is supported. This allows external applications, such as websites, to execute remote procedure calls to Anope in real time. -Currently there are 5 supported XMLRPC calls, provided by m_xmlrpc_main: +Currently there are 5 supported XMLRPC calls, provided by xmlrpc_main: checkAuthentication - Takes two parameters, an account name and a password. Checks if the account name is valid and the password is correct for the account name, useful for making login pages on websites. diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 8f9455572..df45c211d 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -19,18 +19,18 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../conanbuildinfo.cmake") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/extra/${NAME}.cpp" DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}") endfunction() - enable_extra("m_mysql" "LIBMYSQLCLIENT") - enable_extra("m_regex_pcre2" "PCRE2") - enable_extra("m_sqlite" "SQLITE3") - enable_extra("m_ssl_openssl" "OPENSSL") + enable_extra("mysql" "LIBMYSQLCLIENT") + enable_extra("regex_pcre2" "PCRE2") + enable_extra("sqlite" "SQLITE3") + enable_extra("ssl_openssl" "OPENSSL") # this uses Wldap so should always be available - copy_extra("m_ldap") + copy_extra("ldap") # these don't actually have extra dependencies, but require a module which does - copy_extra("m_sql_authentication") - copy_extra("m_sql_log") - copy_extra("m_sql_oper") - copy_extra("m_ldap_authentication") - copy_extra("m_ldap_oper") + copy_extra("sql_authentication") + copy_extra("sql_log") + copy_extra("sql_oper") + copy_extra("ldap_authentication") + copy_extra("ldap_oper") # Package extra dlls file(GLOB EXTRA_DLLS "${Anope_SOURCE_DIR}/extradll/bin/*.dll" "${Anope_SOURCE_DIR}/extradll/lib/*.dll") diff --git a/modules/dns.cpp b/modules/dns.cpp new file mode 100644 index 000000000..a75ca6820 --- /dev/null +++ b/modules/dns.cpp @@ -0,0 +1,1126 @@ +/* + * + * (C) 2003-2024 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +#include "module.h" +#include "modules/dns.h" + +using namespace DNS; + +namespace +{ + Anope::string admin, nameservers; + int refresh; + time_t timeout; +} + +/** A full packet sent or received to/from the nameserver + */ +class Packet final + : public Query +{ + static bool IsValidName(const Anope::string &name) + { + return name.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-") == Anope::string::npos; + } + + void PackName(unsigned char *output, unsigned short output_size, unsigned short &pos, const Anope::string &name) + { + if (pos + name.length() + 2 > output_size) + throw SocketException("Unable to pack name"); + + Log(LOG_DEBUG_2) << "Resolver: PackName packing " << name; + + sepstream sep(name, '.'); + Anope::string token; + + while (sep.GetToken(token)) + { + output[pos++] = token.length(); + memcpy(&output[pos], token.c_str(), token.length()); + pos += token.length(); + } + + output[pos++] = 0; + } + + Anope::string UnpackName(const unsigned char *input, unsigned short input_size, unsigned short &pos) + { + Anope::string name; + unsigned short pos_ptr = pos, lowest_ptr = input_size; + bool compressed = false; + + if (pos_ptr >= input_size) + throw SocketException("Unable to unpack name - no input"); + + while (input[pos_ptr] > 0) + { + unsigned short offset = input[pos_ptr]; + + if (offset & POINTER) + { + if ((offset & POINTER) != POINTER) + throw SocketException("Unable to unpack name - bogus compression header"); + if (pos_ptr + 1 >= input_size) + throw SocketException("Unable to unpack name - bogus compression header"); + + /* Place pos at the second byte of the first (farthest) compression pointer */ + if (compressed == false) + { + ++pos; + compressed = true; + } + + pos_ptr = (offset & LABEL) << 8 | input[pos_ptr + 1]; + + /* Pointers can only go back */ + if (pos_ptr >= lowest_ptr) + throw SocketException("Unable to unpack name - bogus compression pointer"); + lowest_ptr = pos_ptr; + } + else + { + if (pos_ptr + offset + 1 >= input_size) + throw SocketException("Unable to unpack name - offset too large"); + if (!name.empty()) + name += "."; + for (unsigned i = 1; i <= offset; ++i) + name += input[pos_ptr + i]; + + pos_ptr += offset + 1; + if (compressed == false) + /* Move up pos */ + pos = pos_ptr; + } + } + + /* +1 pos either to one byte after the compression pointer or one byte after the ending \0 */ + ++pos; + + /* Empty names are valid (root domain) */ + + Log(LOG_DEBUG_2) << "Resolver: UnpackName successfully unpacked " << name; + + return name; + } + + Question UnpackQuestion(const unsigned char *input, unsigned short input_size, unsigned short &pos) + { + Question question; + + question.name = this->UnpackName(input, input_size, pos); + + if (pos + 4 > input_size) + throw SocketException("Unable to unpack question"); + + if (!IsValidName(question.name)) + throw SocketException("Invalid question name"); + + question.type = static_cast(input[pos] << 8 | input[pos + 1]); + pos += 2; + + question.qclass = input[pos] << 8 | input[pos + 1]; + pos += 2; + + return question; + } + + ResourceRecord UnpackResourceRecord(const unsigned char *input, unsigned short input_size, unsigned short &pos) + { + ResourceRecord record = static_cast(this->UnpackQuestion(input, input_size, pos)); + + if (pos + 6 > input_size) + throw SocketException("Unable to unpack resource record"); + + record.ttl = (input[pos] << 24) | (input[pos + 1] << 16) | (input[pos + 2] << 8) | input[pos + 3]; + pos += 4; + + //record.rdlength = input[pos] << 8 | input[pos + 1]; + pos += 2; + + switch (record.type) + { + case QUERY_A: + { + if (pos + 4 > input_size) + throw SocketException("Unable to unpack resource record"); + + in_addr a; + a.s_addr = input[pos] | (input[pos + 1] << 8) | (input[pos + 2] << 16) | (input[pos + 3] << 24); + pos += 4; + + sockaddrs addrs; + addrs.ntop(AF_INET, &a); + if (!addrs.valid()) + throw SocketException("Invalid IP"); + + record.rdata = addrs.addr(); + break; + } + case QUERY_AAAA: + { + if (pos + 16 > input_size) + throw SocketException("Unable to unpack resource record"); + + in6_addr a; + for (int j = 0; j < 16; ++j) + a.s6_addr[j] = input[pos + j]; + pos += 16; + + sockaddrs addrs; + addrs.ntop(AF_INET6, &a); + if (!addrs.valid()) + throw SocketException("Invalid IP"); + + record.rdata = addrs.addr(); + break; + } + case QUERY_CNAME: + case QUERY_PTR: + { + record.rdata = this->UnpackName(input, input_size, pos); + + if (!IsValidName(record.rdata)) + throw SocketException("Invalid cname/ptr record data"); + + break; + } + default: + break; + } + + Log(LOG_DEBUG_2) << "Resolver: " << record.name << " -> " << record.rdata; + + return record; + } + +public: + static const int POINTER = 0xC0; + static const int LABEL = 0x3F; + static const int HEADER_LENGTH = 12; + + Manager *manager; + /* Source or destination of the packet */ + sockaddrs addr; + /* ID for this packet */ + unsigned short id = 0; + /* Flags on the packet */ + unsigned short flags = 0; + + Packet(Manager *m, sockaddrs *a) : manager(m) + { + if (a) + addr = *a; + } + + void Fill(const unsigned char *input, const unsigned short len) + { + if (len < HEADER_LENGTH) + throw SocketException("Unable to fill packet"); + + unsigned short packet_pos = 0; + + this->id = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + this->flags = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + unsigned short qdcount = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + unsigned short ancount = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + unsigned short nscount = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + unsigned short arcount = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + Log(LOG_DEBUG_2) << "Resolver: qdcount: " << qdcount << " ancount: " << ancount << " nscount: " << nscount << " arcount: " << arcount; + + for (unsigned i = 0; i < qdcount; ++i) + this->questions.push_back(this->UnpackQuestion(input, len, packet_pos)); + + for (unsigned i = 0; i < ancount; ++i) + this->answers.push_back(this->UnpackResourceRecord(input, len, packet_pos)); + + try + { + for (unsigned i = 0; i < nscount; ++i) + this->authorities.push_back(this->UnpackResourceRecord(input, len, packet_pos)); + + for (unsigned i = 0; i < arcount; ++i) + this->additional.push_back(this->UnpackResourceRecord(input, len, packet_pos)); + } + catch (const SocketException &ex) + { + Log(LOG_DEBUG_2) << "Unable to parse ns/ar records: " << ex.GetReason(); + } + } + + unsigned short Pack(unsigned char *output, unsigned short output_size) + { + if (output_size < HEADER_LENGTH) + throw SocketException("Unable to pack packet"); + + unsigned short pos = 0; + + output[pos++] = this->id >> 8; + output[pos++] = this->id & 0xFF; + output[pos++] = this->flags >> 8; + output[pos++] = this->flags & 0xFF; + output[pos++] = this->questions.size() >> 8; + output[pos++] = this->questions.size() & 0xFF; + output[pos++] = this->answers.size() >> 8; + output[pos++] = this->answers.size() & 0xFF; + output[pos++] = this->authorities.size() >> 8; + output[pos++] = this->authorities.size() & 0xFF; + output[pos++] = this->additional.size() >> 8; + output[pos++] = this->additional.size() & 0xFF; + + for (auto &q : this->questions) + { + if (q.type == QUERY_PTR) + { + sockaddrs ip(q.name); + if (!ip.valid()) + throw SocketException("Invalid IP"); + + switch (ip.family()) + { + case AF_INET6: + q.name = ip.reverse() + ".ip6.arpa"; + break; + case AF_INET: + q.name = ip.reverse() + ".in-addr.arpa"; + break; + default: + throw SocketException("Unsupported IP Family"); + } + } + + this->PackName(output, output_size, pos, q.name); + + if (pos + 4 >= output_size) + throw SocketException("Unable to pack packet"); + + short s = htons(q.type); + memcpy(&output[pos], &s, 2); + pos += 2; + + s = htons(q.qclass); + memcpy(&output[pos], &s, 2); + pos += 2; + } + + std::vector types[] = { this->answers, this->authorities, this->additional }; + for (auto &type : types) + { + for (const auto &rr : type) + { + this->PackName(output, output_size, pos, rr.name); + + if (pos + 8 >= output_size) + throw SocketException("Unable to pack packet"); + + short s = htons(rr.type); + memcpy(&output[pos], &s, 2); + pos += 2; + + s = htons(rr.qclass); + memcpy(&output[pos], &s, 2); + pos += 2; + + long l = htonl(rr.ttl); + memcpy(&output[pos], &l, 4); + pos += 4; + + switch (rr.type) + { + case QUERY_A: + { + if (pos + 6 > output_size) + throw SocketException("Unable to pack packet"); + + sockaddrs a(rr.rdata); + if (!a.valid()) + throw SocketException("Invalid IP"); + + s = htons(4); + memcpy(&output[pos], &s, 2); + pos += 2; + + memcpy(&output[pos], &a.sa4.sin_addr, 4); + pos += 4; + break; + } + case QUERY_AAAA: + { + if (pos + 18 > output_size) + throw SocketException("Unable to pack packet"); + + sockaddrs a(rr.rdata); + if (!a.valid()) + throw SocketException("Invalid IP"); + + s = htons(16); + memcpy(&output[pos], &s, 2); + pos += 2; + + memcpy(&output[pos], &a.sa6.sin6_addr, 16); + pos += 16; + break; + } + case QUERY_NS: + case QUERY_CNAME: + case QUERY_PTR: + { + if (pos + 2 >= output_size) + throw SocketException("Unable to pack packet"); + + unsigned short packet_pos_save = pos; + pos += 2; + + this->PackName(output, output_size, pos, rr.rdata); + + s = htons(pos - packet_pos_save - 2); + memcpy(&output[packet_pos_save], &s, 2); + break; + } + case QUERY_SOA: + { + if (pos + 2 >= output_size) + throw SocketException("Unable to pack packet"); + + unsigned short packet_pos_save = pos; + pos += 2; + + std::vector ns; + spacesepstream(nameservers).GetTokens(ns); + this->PackName(output, output_size, pos, !ns.empty() ? ns[0] : ""); + this->PackName(output, output_size, pos, admin.replace_all_cs('@', '.')); + + if (pos + 20 >= output_size) + throw SocketException("Unable to pack SOA"); + + l = htonl(manager->GetSerial()); + memcpy(&output[pos], &l, 4); + pos += 4; + + l = htonl(refresh); // Refresh + memcpy(&output[pos], &l, 4); + pos += 4; + + l = htonl(refresh); // Retry + memcpy(&output[pos], &l, 4); + pos += 4; + + l = htonl(604800); // Expire + memcpy(&output[pos], &l, 4); + pos += 4; + + l = htonl(0); // Minimum + memcpy(&output[pos], &l, 4); + pos += 4; + + s = htons(pos - packet_pos_save - 2); + memcpy(&output[packet_pos_save], &s, 2); + + break; + } + default: + break; + } + } + } + + return pos; + } +}; + +namespace DNS +{ + class ReplySocket + : public virtual Socket + { + public: + ~ReplySocket() override = default; + virtual void Reply(Packet *p) = 0; + }; +} + +/* Listens for TCP requests */ +class TCPSocket final + : public ListenSocket +{ + Manager *manager; + +public: + /* A TCP client */ + class Client final + : public ClientSocket + , public Timer + , public ReplySocket + { + Manager *manager; + Packet *packet = nullptr; + unsigned char packet_buffer[524]; + int length = 0; + + public: + Client(Manager *m, TCPSocket *l, int fd, const sockaddrs &addr) : Socket(fd, l->GetFamily()), ClientSocket(l, addr), Timer(5), + manager(m) + { + Log(LOG_DEBUG_2) << "Resolver: New client from " << addr.addr(); + } + + ~Client() override + { + Log(LOG_DEBUG_2) << "Resolver: Exiting client from " << clientaddr.addr(); + delete packet; + } + + /* Times out after a few seconds */ + void Tick(time_t) override { } + + void Reply(Packet *p) override + { + delete packet; + packet = p; + SocketEngine::Change(this, true, SF_WRITABLE); + } + + bool ProcessRead() override + { + Log(LOG_DEBUG_2) << "Resolver: Reading from DNS TCP socket"; + + int i = recv(this->GetFD(), reinterpret_cast(packet_buffer) + length, sizeof(packet_buffer) - length, 0); + if (i <= 0) + return false; + + length += i; + + unsigned short want_len = packet_buffer[0] << 8 | packet_buffer[1]; + if (length >= want_len + 2) + { + int len = length - 2; + length -= want_len + 2; + return this->manager->HandlePacket(this, packet_buffer + 2, len, NULL); + } + return true; + } + + bool ProcessWrite() override + { + Log(LOG_DEBUG_2) << "Resolver: Writing to DNS TCP socket"; + + if (packet != NULL) + { + try + { + unsigned char buffer[65535]; + unsigned short len = packet->Pack(buffer + 2, sizeof(buffer) - 2); + + short s = htons(len); + memcpy(buffer, &s, 2); + len += 2; + + send(this->GetFD(), reinterpret_cast(buffer), len, 0); + } + catch (const SocketException &) { } + + delete packet; + packet = NULL; + } + + SocketEngine::Change(this, false, SF_WRITABLE); + return true; /* Do not return false here, bind is unhappy we close the connection so soon after sending */ + } + }; + + TCPSocket(Manager *m, const Anope::string &ip, int port) : Socket(-1, ip.find(':') == Anope::string::npos ? AF_INET : AF_INET6), ListenSocket(ip, port, ip.find(':') != Anope::string::npos), manager(m) { } + + ClientSocket *OnAccept(int fd, const sockaddrs &addr) override + { + return new Client(this->manager, this, fd, addr); + } +}; + +/* Listens for UDP requests */ +class UDPSocket final + : public ReplySocket +{ + Manager *manager; + std::deque packets; + +public: + UDPSocket(Manager *m, const Anope::string &ip, int port) : Socket(-1, ip.find(':') == Anope::string::npos ? AF_INET : AF_INET6, SOCK_DGRAM), manager(m) { } + + ~UDPSocket() override + { + for (const auto *packet : packets) + delete packet; + } + + void Reply(Packet *p) override + { + packets.push_back(p); + SocketEngine::Change(this, true, SF_WRITABLE); + } + + std::deque& GetPackets() { return packets; } + + bool ProcessRead() override + { + Log(LOG_DEBUG_2) << "Resolver: Reading from DNS UDP socket"; + + unsigned char packet_buffer[524]; + sockaddrs from_server; + socklen_t x = sizeof(from_server); + int length = recvfrom(this->GetFD(), reinterpret_cast(&packet_buffer), sizeof(packet_buffer), 0, &from_server.sa, &x); + return this->manager->HandlePacket(this, packet_buffer, length, &from_server); + } + + bool ProcessWrite() override + { + Log(LOG_DEBUG_2) << "Resolver: Writing to DNS UDP socket"; + + Packet *r = !packets.empty() ? packets.front() : NULL; + if (r != NULL) + { + try + { + unsigned char buffer[524]; + unsigned short len = r->Pack(buffer, sizeof(buffer)); + + sendto(this->GetFD(), reinterpret_cast(buffer), len, 0, &r->addr.sa, r->addr.size()); + } + catch (const SocketException &) { } + + delete r; + packets.pop_front(); + } + + if (packets.empty()) + SocketEngine::Change(this, false, SF_WRITABLE); + + return true; + } +}; + +class NotifySocket final + : public Socket +{ + Packet *packet; +public: + NotifySocket(int family, Packet *p) : Socket(-1, family, SOCK_DGRAM), packet(p) + { + SocketEngine::Change(this, false, SF_READABLE); + SocketEngine::Change(this, true, SF_WRITABLE); + } + + bool ProcessWrite() override + { + if (!packet) + return false; + + Log(LOG_DEBUG_2) << "Resolver: Notifying slave " << packet->addr.addr(); + + try + { + unsigned char buffer[524]; + unsigned short len = packet->Pack(buffer, sizeof(buffer)); + + sendto(this->GetFD(), reinterpret_cast(buffer), len, 0, &packet->addr.sa, packet->addr.size()); + } + catch (const SocketException &) { } + + delete packet; + packet = NULL; + + return false; + } +}; + +class MyManager final + : public Manager + , public Timer +{ + uint32_t serial; + + typedef std::unordered_map cache_map; + cache_map cache; + + TCPSocket *tcpsock = nullptr; + UDPSocket *udpsock = nullptr; + + bool listen = false; + sockaddrs addrs; + + std::vector > notify; +public: + std::map requests; + + MyManager(Module *creator) : Manager(creator), Timer(300, Anope::CurTime, true), serial(Anope::CurTime), cur_id(rand()) + { + } + + ~MyManager() override + { + delete udpsock; + delete tcpsock; + + for (std::map::iterator it = this->requests.begin(), it_end = this->requests.end(); it != it_end;) + { + Request *request = it->second; + ++it; + + Query rr(*request); + rr.error = ERROR_UNKNOWN; + request->OnError(&rr); + + delete request; + } + this->requests.clear(); + + this->cache.clear(); + } + + void SetIPPort(const Anope::string &nameserver, const Anope::string &ip, unsigned short port, std::vector > n) + { + delete udpsock; + delete tcpsock; + + udpsock = NULL; + tcpsock = NULL; + + try + { + this->addrs.pton(nameserver.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, nameserver, 53); + + udpsock = new UDPSocket(this, ip, port); + + if (!ip.empty()) + { + udpsock->Bind(ip, port); + tcpsock = new TCPSocket(this, ip, port); + listen = true; + } + } + catch (const SocketException &ex) + { + Log() << "Unable to bind dns to " << ip << ":" << port << ": " << ex.GetReason(); + } + + notify = n; + } + +private: + unsigned short cur_id; + + unsigned short GetID() + { + if (this->udpsock->GetPackets().size() == 65535) + throw SocketException("DNS queue full"); + + do + cur_id = (cur_id + 1) & 0xFFFF; + while (!cur_id || this->requests.count(cur_id)); + + return cur_id; + } + +public: + void Process(Request *req) override + { + Log(LOG_DEBUG_2) << "Resolver: Processing request to lookup " << req->name << ", of type " << req->type; + + if (req->use_cache && this->CheckCache(req)) + { + Log(LOG_DEBUG_2) << "Resolver: Using cached result"; + delete req; + return; + } + + if (!this->udpsock) + throw SocketException("No dns socket"); + + req->id = GetID(); + this->requests[req->id] = req; + + req->SetSecs(timeout); + + auto *p = new Packet(this, &this->addrs); + p->flags = QUERYFLAGS_RD; + p->id = req->id; + p->questions.push_back(*req); + + this->udpsock->Reply(p); + } + + void RemoveRequest(Request *req) override + { + this->requests.erase(req->id); + } + + bool HandlePacket(ReplySocket *s, const unsigned char *const packet_buffer, int length, sockaddrs *from) override + { + if (length < Packet::HEADER_LENGTH) + return true; + + Packet recv_packet(this, from); + + try + { + recv_packet.Fill(packet_buffer, length); + } + catch (const SocketException &ex) + { + Log(LOG_DEBUG_2) << ex.GetReason(); + return true; + } + + if (!(recv_packet.flags & QUERYFLAGS_QR)) + { + if (!listen) + return true; + else if (recv_packet.questions.empty()) + { + Log(LOG_DEBUG_2) << "Resolver: Received a question with no questions?"; + return true; + } + + auto *packet = new Packet(recv_packet); + packet->flags |= QUERYFLAGS_QR; /* This is a response */ + packet->flags |= QUERYFLAGS_AA; /* And we are authoritative */ + + packet->answers.clear(); + packet->authorities.clear(); + packet->additional.clear(); + + for (auto &q : recv_packet.questions) + { + if (q.type == QUERY_AXFR || q.type == QUERY_SOA) + { + ResourceRecord rr(q.name, QUERY_SOA); + packet->answers.push_back(rr); + + if (q.type == QUERY_AXFR) + { + Anope::string token; + spacesepstream sep(nameservers); + while (sep.GetToken(token)) + { + ResourceRecord rr2(q.name, QUERY_NS); + rr2.rdata = token; + packet->answers.push_back(rr2); + } + } + break; + } + } + + FOREACH_MOD(OnDnsRequest, (recv_packet, packet)); + + for (auto &q : recv_packet.questions) + { + if (q.type == QUERY_AXFR) + { + ResourceRecord rr(q.name, QUERY_SOA); + packet->answers.push_back(rr); + break; + } + } + + if (packet->answers.empty() && packet->authorities.empty() && packet->additional.empty() && packet->error == ERROR_NONE) + packet->error = ERROR_REFUSED; // usually safe, won't cause an NXDOMAIN to get cached + + s->Reply(packet); + return true; + } + + if (from == NULL) + { + Log(LOG_DEBUG_2) << "Resolver: Received an answer over TCP. This is not supported."; + return true; + } + else if (this->addrs != *from) + { + Log(LOG_DEBUG_2) << "Resolver: Received an answer from the wrong nameserver, Bad NAT or DNS forging attempt? '" << this->addrs.addr() << "' != '" << from->addr() << "'"; + return true; + } + + std::map::iterator it = this->requests.find(recv_packet.id); + if (it == this->requests.end()) + { + Log(LOG_DEBUG_2) << "Resolver: Received an answer for something we didn't request"; + return true; + } + Request *request = it->second; + + if (recv_packet.flags & QUERYFLAGS_OPCODE) + { + Log(LOG_DEBUG_2) << "Resolver: Received a nonstandard query"; + recv_packet.error = ERROR_NONSTANDARD_QUERY; + request->OnError(&recv_packet); + } + else if (recv_packet.flags & QUERYFLAGS_RCODE) + { + Error error = ERROR_UNKNOWN; + + switch (recv_packet.flags & QUERYFLAGS_RCODE) + { + case 1: + Log(LOG_DEBUG_2) << "Resolver: format error"; + error = ERROR_FORMAT_ERROR; + break; + case 2: + Log(LOG_DEBUG_2) << "Resolver: server error"; + error = ERROR_SERVER_FAILURE; + break; + case 3: + Log(LOG_DEBUG_2) << "Resolver: domain not found"; + error = ERROR_DOMAIN_NOT_FOUND; + break; + case 4: + Log(LOG_DEBUG_2) << "Resolver: not implemented"; + error = ERROR_NOT_IMPLEMENTED; + break; + case 5: + Log(LOG_DEBUG_2) << "Resolver: refused"; + error = ERROR_REFUSED; + break; + default: + break; + } + + recv_packet.error = error; + request->OnError(&recv_packet); + } + else if (recv_packet.questions.empty() || recv_packet.answers.empty()) + { + Log(LOG_DEBUG_2) << "Resolver: No resource records returned"; + recv_packet.error = ERROR_NO_RECORDS; + request->OnError(&recv_packet); + } + else + { + Log(LOG_DEBUG_2) << "Resolver: Lookup complete for " << request->name; + request->OnLookupComplete(&recv_packet); + this->AddCache(recv_packet); + } + + delete request; + return true; + } + + void UpdateSerial() override + { + serial = Anope::CurTime; + } + + void Notify(const Anope::string &zone) override + { + /* notify slaves of the update */ + for (const auto &[ip, port] : notify) + { + sockaddrs addr; + addr.pton(ip.find(':') != Anope::string::npos ? AF_INET6 : AF_INET, ip, port); + if (!addr.valid()) + return; + + auto *packet = new Packet(this, &addr); + packet->flags = QUERYFLAGS_AA | QUERYFLAGS_OPCODE_NOTIFY; + try + { + packet->id = GetID(); + } + catch (const SocketException &) + { + delete packet; + continue; + } + + packet->questions.emplace_back(zone, QUERY_SOA); + + new NotifySocket(ip.find(':') == Anope::string::npos ? AF_INET : AF_INET6, packet); + } + } + + uint32_t GetSerial() const override + { + return serial; + } + + void Tick(time_t now) override + { + Log(LOG_DEBUG_2) << "Resolver: Purging DNS cache"; + + for (cache_map::iterator it = this->cache.begin(), it_next; it != this->cache.end(); it = it_next) + { + const Query &q = it->second; + const ResourceRecord &req = q.answers[0]; + it_next = it; + ++it_next; + + if (req.created + static_cast(req.ttl) < now) + this->cache.erase(it); + } + } + +private: + /** Add a record to the dns cache + * @param r The record + */ + void AddCache(Query &r) + { + const ResourceRecord &rr = r.answers[0]; + Log(LOG_DEBUG_3) << "Resolver cache: added cache for " << rr.name << " -> " << rr.rdata << ", ttl: " << rr.ttl; + this->cache[r.questions[0]] = r; + } + + /** Check the DNS cache to see if request can be handled by a cached result + * @return true if a cached result was found. + */ + bool CheckCache(Request *request) + { + cache_map::iterator it = this->cache.find(*request); + if (it != this->cache.end()) + { + Query &record = it->second; + Log(LOG_DEBUG_3) << "Resolver: Using cached result for " << request->name; + request->OnLookupComplete(&record); + return true; + } + + return false; + } + +}; + +class ModuleDNS final + : public Module +{ + MyManager manager; + + Anope::string nameserver; + Anope::string ip; + int port; + + std::vector > notify; + +public: + ModuleDNS(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), manager(this) + { + + } + + ~ModuleDNS() override + { + for (std::map::const_iterator it = SocketEngine::Sockets.begin(), it_end = SocketEngine::Sockets.end(); it != it_end;) + { + Socket *s = it->second; + ++it; + + if (dynamic_cast(s) || dynamic_cast(s)) + delete s; + } + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + + nameserver = block->Get("nameserver", "127.0.0.1"); + timeout = block->Get("timeout", "5"); + ip = block->Get("ip", "0.0.0.0"); + port = block->Get("port", "53"); + admin = block->Get("admin", "admin@example.com"); + nameservers = block->Get("nameservers", "ns1.example.com"); + refresh = block->Get("refresh", "3600"); + + for (int i = 0; i < block->CountBlock("notify"); ++i) + { + Configuration::Block *n = block->GetBlock("notify", i); + Anope::string nip = n->Get("ip"); + short nport = n->Get("port"); + + notify.emplace_back(nip, nport); + } + + if (Anope::IsFile(nameserver)) + { + std::ifstream f(nameserver.c_str()); + bool success = false; + + if (f.is_open()) + { + for (Anope::string server; std::getline(f, server.str());) + { + if (server.find("nameserver") == 0) + { + size_t i = server.find_first_of("123456789"); + if (i != Anope::string::npos) + { + if (server.substr(i).is_pos_number_only()) + { + nameserver = server.substr(i); + Log(LOG_DEBUG) << "Nameserver set to " << nameserver; + success = true; + break; + } + } + } + } + + f.close(); + } + + if (!success) + { + Log() << "Unable to find nameserver, defaulting to 127.0.0.1"; + nameserver = "127.0.0.1"; + } + } + + try + { + this->manager.SetIPPort(nameserver, ip, port, notify); + } + catch (const SocketException &ex) + { + throw ModuleException(ex.GetReason()); + } + } + + void OnModuleUnload(User *u, Module *m) override + { + for (std::map::iterator it = this->manager.requests.begin(), it_end = this->manager.requests.end(); it != it_end;) + { + unsigned short id = it->first; + Request *req = it->second; + ++it; + + if (req->creator == m) + { + Query rr(*req); + rr.error = ERROR_UNLOADED; + req->OnError(&rr); + + delete req; + this->manager.requests.erase(id); + } + } + } +}; + +MODULE_INIT(ModuleDNS) diff --git a/modules/dnsbl.cpp b/modules/dnsbl.cpp new file mode 100644 index 000000000..af4d686e1 --- /dev/null +++ b/modules/dnsbl.cpp @@ -0,0 +1,196 @@ +/* + * + * (C) 2003-2024 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" +#include "modules/dns.h" + +using namespace DNS; + +static ServiceReference akills("XLineManager", "xlinemanager/sgline"); +static ServiceReference dnsmanager("DNS::Manager", "dns/manager"); + +struct Blacklist final +{ + struct Reply final + { + int code = 0; + Anope::string reason; + bool allow_account = false; + + Reply() = default; + }; + + Anope::string name; + time_t bantime = 0; + Anope::string reason; + std::vector replies; + + const Reply *Find(int code) + { + for (const auto &reply : replies) + if (reply.code == code) + return &reply; + return NULL; + } +}; + +class DNSBLResolver final + : public Request +{ + Reference user; + Blacklist blacklist; + bool add_to_akill; + +public: + DNSBLResolver(Module *c, User *u, const Blacklist &b, const Anope::string &host, bool add_akill) : Request(dnsmanager, c, host, QUERY_A, true), user(u), blacklist(b), add_to_akill(add_akill) { } + + void OnLookupComplete(const Query *record) override + { + if (!user || user->Quitting()) + return; + + const ResourceRecord &ans_record = record->answers[0]; + // Replies should be in 127.0.0.0/8 + if (ans_record.rdata.find("127.") != 0) + return; + + sockaddrs sresult; + sresult.pton(AF_INET, ans_record.rdata); + int result = sresult.sa4.sin_addr.s_addr >> 24; + + const Blacklist::Reply *reply = blacklist.Find(result); + if (!blacklist.replies.empty() && !reply) + return; + + if (reply && reply->allow_account && user->Account()) + return; + + Anope::string reason = this->blacklist.reason, addr = user->ip.addr(); + reason = reason.replace_all_cs("%n", user->nick); + reason = reason.replace_all_cs("%u", user->GetIdent()); + reason = reason.replace_all_cs("%g", user->realname); + reason = reason.replace_all_cs("%h", user->host); + reason = reason.replace_all_cs("%i", addr); + reason = reason.replace_all_cs("%r", reply ? reply->reason : ""); + reason = reason.replace_all_cs("%N", Config->GetBlock("networkinfo")->Get("networkname")); + + BotInfo *OperServ = Config->GetClient("OperServ"); + Log(creator, "dnsbl", OperServ) << user->GetMask() << " (" << addr << ") appears in " << this->blacklist.name; + auto *x = new XLine("*@" + addr, OperServ ? OperServ->nick : "dnsbl", Anope::CurTime + this->blacklist.bantime, reason, XLineManager::GenerateUID()); + if (this->add_to_akill && akills) + { + akills->AddXLine(x); + akills->Send(NULL, x); + } + else + { + IRCD->SendAkill(NULL, x); + delete x; + } + } +}; + +class ModuleDNSBL final + : public Module +{ + std::vector blacklists; + std::set exempts; + bool check_on_connect; + bool check_on_netburst; + bool add_to_akill; + +public: + ModuleDNSBL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR | EXTRA) + { + + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + this->check_on_connect = block->Get("check_on_connect"); + this->check_on_netburst = block->Get("check_on_netburst"); + this->add_to_akill = block->Get("add_to_akill", "yes"); + + this->blacklists.clear(); + for (int i = 0; i < block->CountBlock("blacklist"); ++i) + { + Configuration::Block *bl = block->GetBlock("blacklist", i); + Blacklist blacklist; + + blacklist.name = bl->Get("name"); + if (blacklist.name.empty()) + continue; + blacklist.bantime = bl->Get("time", "4h"); + blacklist.reason = bl->Get("reason"); + + for (int j = 0; j < bl->CountBlock("reply"); ++j) + { + Configuration::Block *reply = bl->GetBlock("reply", j); + Blacklist::Reply r; + + r.code = reply->Get("code"); + r.reason = reply->Get("reason"); + r.allow_account = reply->Get("allow_account"); + + blacklist.replies.push_back(r); + } + + this->blacklists.push_back(blacklist); + } + + this->exempts.clear(); + for (int i = 0; i < block->CountBlock("exempt"); ++i) + { + Configuration::Block *bl = block->GetBlock("exempt", i); + this->exempts.insert(bl->Get("ip")); + } + } + + void OnUserConnect(User *user, bool &exempt) override + { + if (exempt || user->Quitting() || (!this->check_on_connect && !Me->IsSynced()) || !dnsmanager) + return; + + if (!this->check_on_netburst && !user->server->IsSynced()) + return; + + if (!user->ip.valid()) + /* User doesn't have a valid IP (spoof/etc) */ + return; + + if (this->blacklists.empty()) + return; + + if (this->exempts.count(user->ip.addr())) + { + Log(LOG_DEBUG) << "User " << user->nick << " is exempt from dnsbl check - ip: " << user->ip.addr(); + return; + } + + Anope::string reverse = user->ip.reverse(); + + for (const auto &b : this->blacklists) + { + Anope::string dnsbl_host = reverse + "." + b.name; + DNSBLResolver *res = NULL; + try + { + res = new DNSBLResolver(this, user, b, dnsbl_host, this->add_to_akill); + dnsmanager->Process(res); + } + catch (const SocketException &ex) + { + delete res; + Log(this) << ex.GetReason(); + } + } + } +}; + +MODULE_INIT(ModuleDNSBL) diff --git a/modules/extra/ldap.cpp b/modules/extra/ldap.cpp new file mode 100644 index 000000000..31e712662 --- /dev/null +++ b/modules/extra/ldap.cpp @@ -0,0 +1,702 @@ +/* + * + * (C) 2011-2024 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +/* RequiredLibraries: ldap_r|ldap,lber */ + +#include "module.h" +#include "modules/ldap.h" + +#ifdef _WIN32 +# include +# include +# include +# define LDAP_OPT_SUCCESS LDAP_SUCCESS +# define LDAP_OPT_NETWORK_TIMEOUT LDAP_OPT_SEND_TIMEOUT +# define LDAP_STR(X) const_cast((X).c_str()) +# define LDAP_SASL_SIMPLE static_cast(0) +# define LDAP_TIME(X) reinterpret_cast(&(X)) +# define ldap_first_message ldap_first_entry +# define ldap_next_message ldap_next_entry +# define ldap_unbind_ext(LDAP, UNUSED1, UNUSED2) ldap_unbind(LDAP) +# pragma comment(lib, "Wldap32.lib") +# pragma comment(lib, "Wininet.lib") +#else +# include +# define LDAP_STR(X) ((X).c_str()) +# define LDAP_TIME(X) (&(X)) +#endif + +#if defined LDAP_API_FEATURE_X_OPENLDAP_REENTRANT && !LDAP_API_FEATURE_X_OPENLDAP_REENTRANT +# error Anope requires OpenLDAP to be built as reentrant. +#endif + + +class LDAPService; +static Pipe *me; + +class LDAPRequest +{ +public: + LDAPService *service; + LDAPInterface *inter; + LDAPMessage *message = nullptr; /* message returned by ldap_ */ + LDAPResult *result = nullptr; /* final result */ + struct timeval tv; + QueryType type = QUERY_UNKNOWN; + + LDAPRequest(LDAPService *s, LDAPInterface *i) + : service(s) + , inter(i) + { + tv.tv_sec = 0; + tv.tv_usec = 100000; + } + + virtual ~LDAPRequest() + { + delete result; + if (inter != NULL) + inter->OnDelete(); + if (message != NULL) + ldap_msgfree(message); + } + + virtual int run() = 0; +}; + +class LDAPBind final + : public LDAPRequest +{ + Anope::string who, pass; + +public: + LDAPBind(LDAPService *s, LDAPInterface *i, const Anope::string &w, const Anope::string &p) + : LDAPRequest(s, i) + , who(w) + , pass(p) + { + type = QUERY_BIND; + } + + int run() override; +}; + +class LDAPSearchRequest final + : public LDAPRequest +{ + Anope::string base; + Anope::string filter; + +public: + LDAPSearchRequest(LDAPService *s, LDAPInterface *i, const Anope::string &b, const Anope::string &f) + : LDAPRequest(s, i) + , base(b) + , filter(f) + { + type = QUERY_SEARCH; + } + + int run() override; +}; + +class LDAPAdd final + : public LDAPRequest +{ + Anope::string dn; + LDAPMods attributes; + +public: + LDAPAdd(LDAPService *s, LDAPInterface *i, const Anope::string &d, const LDAPMods &attr) + : LDAPRequest(s, i) + , dn(d) + , attributes(attr) + { + type = QUERY_ADD; + } + + int run() override; +}; + +class LDAPDel final + : public LDAPRequest +{ + Anope::string dn; + +public: + LDAPDel(LDAPService *s, LDAPInterface *i, const Anope::string &d) + : LDAPRequest(s, i) + , dn(d) + { + type = QUERY_DELETE; + } + + int run() override; +}; + +class LDAPModify final + : public LDAPRequest +{ + Anope::string base; + LDAPMods attributes; + +public: + LDAPModify(LDAPService *s, LDAPInterface *i, const Anope::string &b, const LDAPMods &attr) + : LDAPRequest(s, i) + , base(b) + , attributes(attr) + { + type = QUERY_MODIFY; + } + + int run() override; +}; + +class LDAPService final + : public LDAPProvider + , public Thread + , public Condition +{ + Anope::string server; + Anope::string admin_binddn; + Anope::string admin_pass; + + LDAP *con; + + time_t last_connect = 0; + +public: + static LDAPMod **BuildMods(const LDAPMods &attributes) + { + LDAPMod **mods = new LDAPMod*[attributes.size() + 1]; + memset(mods, 0, sizeof(LDAPMod*) * (attributes.size() + 1)); + for (unsigned x = 0; x < attributes.size(); ++x) + { + const LDAPModification &l = attributes[x]; + mods[x] = new LDAPMod(); + + if (l.op == LDAPModification::LDAP_ADD) + mods[x]->mod_op = LDAP_MOD_ADD; + else if (l.op == LDAPModification::LDAP_DEL) + mods[x]->mod_op = LDAP_MOD_DELETE; + else if (l.op == LDAPModification::LDAP_REPLACE) + mods[x]->mod_op = LDAP_MOD_REPLACE; + else if (l.op != 0) + throw LDAPException("Unknown LDAP operation"); + mods[x]->mod_type = strdup(l.name.c_str()); + mods[x]->mod_values = new char*[l.values.size() + 1]; + memset(mods[x]->mod_values, 0, sizeof(char *) * (l.values.size() + 1)); + for (unsigned j = 0, c = 0; j < l.values.size(); ++j) + if (!l.values[j].empty()) + mods[x]->mod_values[c++] = strdup(l.values[j].c_str()); + } + return mods; + } + + static void FreeMods(LDAPMod **mods) + { + for (int i = 0; mods[i] != NULL; ++i) + { + free(mods[i]->mod_type); + for (int j = 0; mods[i]->mod_values[j] != NULL; ++j) + free(mods[i]->mod_values[j]); + delete [] mods[i]->mod_values; + } + delete [] mods; + } + +private: +#ifdef _WIN32 + // Windows LDAP does not implement this so we need to do it. + int ldap_initialize(LDAP** ldap, const char* url) + { + URL_COMPONENTS urlComponents; + memset(&urlComponents, 0, sizeof(urlComponents)); + urlComponents.dwStructSize = sizeof(urlComponents); + + urlComponents.lpszScheme = new char[8]; + urlComponents.dwSchemeLength = 8; + + urlComponents.lpszHostName = new char[1024]; + urlComponents.dwHostNameLength = 1024; + + if (!InternetCrackUrlA(url, 0, 0, &urlComponents)) + { + delete[] urlComponents.lpszScheme; + delete[] urlComponents.lpszHostName; + return LDAP_CONNECT_ERROR; // Malformed url. + } + + unsigned long port = 389; // Default plaintext port. + bool secure = false; // LDAP defaults to plaintext. + if (urlComponents.dwSchemeLength > 0) + { + const Anope::string scheme(urlComponents.lpszScheme); + if (scheme.equals_ci("ldaps")) + { + port = 636; // Default encrypted port. + secure = true; + } + else if (!scheme.equals_ci("ldap")) + { + delete[] urlComponents.lpszScheme; + delete[] urlComponents.lpszHostName; + return LDAP_CONNECT_ERROR; // Invalid protocol. + } + } + + if (urlComponents.nPort > 0) + { + port = urlComponents.nPort; + } + + *ldap = ldap_sslinit(urlComponents.lpszHostName, port, secure); + delete[] urlComponents.lpszScheme; + delete[] urlComponents.lpszHostName; + if (!*ldap) + { + return LdapGetLastError(); // Something went wrong, find out what. + } + + // We're connected to the