znc-1.9.1/0000755000175000017500000000000014641222745012547 5ustar somebodysomebodyznc-1.9.1/.clang-format0000644000175000017500000000044214641222733015117 0ustar somebodysomebody--- BasedOnStyle: Google Standard: Cpp11 IndentWidth: 4 TabWidth: 4 AccessModifierOffset: -2 DerivePointerAlignment: false PointerAlignment: Left # Prevent breaking doxygen, as clang-format doesn't support reflowing of doxygen comments yet (as of 3.5.0). CommentPragmas: '^\*|^/|^!' ... znc-1.9.1/.codecov.yml0000644000175000017500000000076314641222733014775 0ustar somebodysomebodyignore: - /cmake/ - /test/ - /modules/modp*/*.cpp - /modules/modp*/swig* - /modules/modpython/znc_core.py - /modules/modperl/ZNC.pm fixes: - "usr/local/lib/znc/::modules/" - "/usr/local/lib/znc/::modules/" codecov: ci: # Cygwin fails integration test with --coverage enabled, I don't know why - !appveyor # FreeBSD doesn't support C++ coverage yet (can't find libprofile_rt.a) - !jenkins.znc.in coverage: status: project: default: threshold: 1% znc-1.9.1/.dockerignore0000644000175000017500000000001414641222733015213 0ustar somebodysomebody.git build* znc-1.9.1/.gitignore0000644000175000017500000000244614641222733014542 0ustar somebodysomebody/man/*.gz .depend # Generated by autogen.sh /aclocal.m4 /autom4te.cache /config.* /configure /install-sh /include/znc/zncconfig.h.in # Generated by configure Makefile *.pc /include/znc/zncconfig.h /znc-buildmod /znc.service /include/znc/znc_export_lib_export.h /test/inttest_bin-prefix/ # modperl and modpython generated code /modules/modperl/functions.cpp /modules/modperl/modperl_biglib.cpp /modules/modperl/perlfunctions.cpp /modules/modperl/ZNC.cpp /modules/modperl/ZNC.pm /modules/modperl/swigperlrun.h /modules/modpython/_znc_core.cpp /modules/modpython/compiler /modules/modpython/functions.cpp /modules/modpython/modpython_biglib.cpp /modules/modpython/pyfunctions.cpp /modules/modpython/swigpyrun.h /modules/modpython/znc_core.py *.pyc __pycache__ /unittest /test/gtest.zip /test/gtest-* # Compiled Object files *.o *.o-* # Compiled Dynamic libraries *.so # Compiled static libraries *.a # Compiled executable /src/znc /znc # Makefile generates this /.version_extra # Generated from third_party/Csocket/ by Makefile /src/Csocket.cpp /include/znc/Csocket.h # version file generated by Makefile /src/version.cpp /src/versionc.cpp # Language files from the .po files generated by msgfmt *.gmo # CMake generated files *.cmake CMakeFiles/ /CMakeCache.txt /install_manifest.txt /install_manifest_*.txt znc-1.9.1/.gitmodules0000644000175000017500000000056314641222733014725 0ustar somebodysomebody[submodule "Csocket"] path = third_party/Csocket url = https://github.com/znc/Csocket [submodule "third_party/googletest"] path = third_party/googletest url = https://github.com/google/googletest [submodule "docker"] path = docker url = https://github.com/znc/znc-docker [submodule "third_party/cctz"] path = third_party/cctz url = https://github.com/google/cctz znc-1.9.1/CMakeLists.txt0000644000175000017500000004060614641222733015312 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # cmake_minimum_required(VERSION 3.13) project(ZNC VERSION 1.9.1 LANGUAGES CXX) set(ZNC_VERSION 1.9.1) set(append_git_version false) set(alpha_version "") # e.g. "-rc1" set(VERSION_EXTRA "" CACHE STRING "Additional string appended to version, e.g. to mark distribution") set(PROJECT_VERSION "${ZNC_VERSION}") # https://cmake.org/pipermail/cmake/2010-September/039388.html set(_all_targets "" CACHE INTERNAL "") function(znc_add_library name) add_library("${name}" ${ARGN}) set(_all_targets "${_all_targets};${name}" CACHE INTERNAL "") endfunction() function(znc_add_executable name) add_executable("${name}" ${ARGN}) set(_all_targets "${_all_targets};${name}" CACHE INTERNAL "") endfunction() function(znc_add_custom_target name) add_custom_target("${name}" ${ARGN}) set(_all_targets "${_all_targets};${name}" CACHE INTERNAL "") endfunction() list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") include(TestCXX17) if(NOT CYGWIN) # We don't want to use -std=gnu++17 instead of -std=c++17, but among other # things, -std=c++17 on cygwin defines __STRICT_ANSI__ which makes cygwin # not to compile: undefined references to strerror_r, to fdopen, to # strcasecmp, etc (their declarations in system headers are between ifdef) set(CMAKE_CXX_EXTENSIONS false) endif() include(CMakeFindFrameworks_fixed) include(use_homebrew) include(GNUInstallDirs) include(CheckCXXSymbolExists) include(copy_csocket) set(CMAKE_THREAD_PREFER_PTHREAD true) set(THREADS_PREFER_PTHREAD_FLAG true) find_package(Threads REQUIRED) if(NOT CMAKE_USE_PTHREADS_INIT) message(FATAL_ERROR "This compiler/OS doesn't seem to support pthreads.") endif() include(TestLargeFiles) test_large_files(HAVE_LARGE_FILES_UNUSED_VAR) find_package(PkgConfig) macro(tristate_option opt help) set(WANT_${opt} AUTO CACHE STRING ${help}) set_property(CACHE WANT_${opt} PROPERTY STRINGS AUTO YES NO) if(WANT_${opt} STREQUAL "AUTO") set(TRISTATE_${opt}_REQUIRED) else() set(TRISTATE_${opt}_REQUIRED REQUIRED) endif() endmacro() set(ZNC_CMAKE_FIND_DEPS "") set(zncpubdeps) tristate_option(OPENSSL "Support SSL") if(WANT_OPENSSL) find_package(OpenSSL ${TRISTATE_OPENSSL_REQUIRED}) if(OPENSSL_FOUND) # SSL_SESSION was made opaque in OpenSSL 1.1.0; # LibreSSL gained that function later too. # TODO: maybe remove this check at some point, and stop supporting old # libssl versions function(check_SSL_SESSION_get0_cipher) set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_LIBRARIES}) set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) check_cxx_symbol_exists(SSL_SESSION_get0_cipher openssl/ssl.h HAVE_SSL_SESSION_get0_cipher) endfunction() check_SSL_SESSION_get0_cipher() set(ZNC_CMAKE_FIND_DEPS "${ZNC_CMAKE_FIND_DEPS}\nfind_dependency(OpenSSL)") list(APPEND zncpubdeps OpenSSL::SSL) endif() endif() set(HAVE_LIBSSL "${OPENSSL_FOUND}") set(WANT_IPV6 true CACHE BOOL "Support IPv6") set(HAVE_IPV6 "${WANT_IPV6}") tristate_option(ZLIB "Compress HTTP traffic with Zlib") if(WANT_ZLIB) find_package(ZLIB ${TRISTATE_ZLIB_REQUIRED}) endif() set(HAVE_ZLIB "${ZLIB_FOUND}") tristate_option(CYRUS "Support authentication with Cyrus") if(WANT_CYRUS) pkg_check_modules(CYRUS IMPORTED_TARGET libsasl2) if(NOT CYRUS_FOUND) # libsasl2.pc is missing on 2.1.25 which is on ubuntu 14.04 # next ubuntu version has 2.1.26 which has libsasl2.pc # # osx (as of El Capitan) doesn't have it either... set(_old_cmake_required_libraries "${CMAKE_REQUIRED_LIBRARIES}") set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} -lsasl2) # sys/types.h here is workaround for sasl 2.1.26: # https://github.com/znc/znc/issues/1243 # https://lists.andrew.cmu.edu/pipermail/cyrus-sasl/2012-December/002572.html # https://cgit.cyrus.foundation/cyrus-sasl/commit/include/sasl.h?id=2f740223fa1820dd71e6ab0e50d4964760789209 check_cxx_symbol_exists(sasl_server_init "sys/types.h;sasl/sasl.h" CYRUS_HARDCODED) set(CMAKE_REQUIRED_LIBRARIES "${_old_cmake_required_libraries}") if(CYRUS_HARDCODED) add_library(HardcodedCyrusDep INTERFACE) add_library(PkgConfig::CYRUS ALIAS HardcodedCyrusDep) target_link_libraries(HardcodedCyrusDep INTERFACE sasl2) set(CYRUS_FOUND true) endif() endif() if(TRISTATE_CYRUS_REQUIRED AND NOT CYRUS_FOUND) message(FATAL_ERROR "Can't find Cyrus SASL 2") endif() endif() tristate_option(ARGON "Store password hashes using Argon2id instead of SHA-256") if(WANT_ARGON) pkg_check_modules(ARGON ${TRISTATE_ARGON_REQUIRED} IMPORTED_TARGET libargon2) endif() set(ZNC_HAVE_ARGON "${ARGON_FOUND}") tristate_option(ICU "Support character encodings") if(WANT_ICU) pkg_check_modules(ICU ${TRISTATE_ICU_REQUIRED} IMPORTED_TARGET icu-uc) endif() set(HAVE_ICU "${ICU_FOUND}") if(ICU_FOUND) set(ZNC_CMAKE_FIND_DEPS "${ZNC_CMAKE_FIND_DEPS}\nfind_dependency_pc(ICU icu-uc)") list(APPEND zncpubdeps PkgConfig::ICU) endif() set(WANT_PERL false CACHE BOOL "Support Perl modules") set(WANT_PYTHON false CACHE BOOL "Support Python modules") set(WANT_PYTHON_VERSION "python3" CACHE STRING "Python version to use, e.g. python-3.5, this name is passed to pkg-config") if(WANT_PYTHON AND NOT ICU_FOUND) message(FATAL_ERROR "Modpython requires ZNC to be compiled with charset " "support, but ICU library not found") endif() tristate_option(SWIG "Use SWIG to generate modperl and modpython") set(search_swig false) if(WANT_SWIG AND TRISTATE_SWIG_REQUIRED) set(search_swig true) endif() if(WANT_PERL AND NOT EXISTS "${PROJECT_SOURCE_DIR}/modules/modperl/generated.tar.gz") if(WANT_SWIG) set(search_swig true) else() message(FATAL_ERROR "Pregenerated modperl files are not available. " "SWIG is required. Alternatively, build ZNC from tarball.") endif() endif() if(WANT_PYTHON AND NOT EXISTS "${PROJECT_SOURCE_DIR}/modules/modpython/generated.tar.gz") if(WANT_SWIG) set(search_swig true) else() message(FATAL_ERROR "Pregenerated modpython files are not available. " "SWIG is required. Alternatively, build ZNC from tarball.") endif() endif() if(search_swig) find_package(SWIG 4.0.1) if(NOT SWIG_FOUND) message(FATAL_ERROR "Can't find SWIG, therefore Perl and Python aren't supported. " "Alternatively, build ZNC from tarball.") endif() endif() if(WANT_PERL) find_package(PerlLibs 5.10 REQUIRED) endif() if (WANT_PYTHON) set (_MIN_PYTHON_VERSION 3.4) find_package(Perl 5.10 REQUIRED) # VERSION_GREATER_EQUAL is available only since 3.7 if (CMAKE_VERSION VERSION_LESS 3.12) else() # Even if FindPython3 is available (since CMake 3.12) we still use # pkg-config, because FindPython has a hardcoded list of python # versions, which may become outdated when new pythons are released, # but when cmake in the distro is old. # # Why FindPython3 is useful at all? Because sometimes there is no # python3.pc, but only python-3.5.pc and python-3.6.pc; which would # force user to provide the version explicitly via # WANT_PYTHON_VERSION. This is the case on Gentoo when NOT building # via emerge. if (WANT_PYTHON_VERSION STREQUAL "python3") find_package(Python3 COMPONENTS Development) else() # We used to pass value like "python-3.5" to the variable. if (WANT_PYTHON_VERSION MATCHES "^(python-)?(.*)$") find_package(Python3 COMPONENTS Development EXACT "${CMAKE_MATCH_2}") else() message(FATAL_ERROR "Invalid value of WANT_PYTHON_VERSION") endif() endif() if (Python3_FOUND AND Python3_VERSION VERSION_LESS ${_MIN_PYTHON_VERSION}) message(STATUS "Python too old, need at least ${_MIN_PYTHON_VERSION}") set(Python3_FOUND OFF) else() # Compatibility with pkg-config variables set(Python3_LDFLAGS "${Python3_LIBRARIES}") endif() endif() if (NOT Python3_FOUND AND WANT_PYTHON_VERSION MATCHES "^python") # Since python 3.8, -embed is required for embedding. pkg_check_modules(Python3 "${WANT_PYTHON_VERSION}-embed >= ${_MIN_PYTHON_VERSION}") if (NOT Python3_FOUND) pkg_check_modules(Python3 "${WANT_PYTHON_VERSION} >= ${_MIN_PYTHON_VERSION}") endif() endif() if (NOT Python3_FOUND) message(FATAL_ERROR "Python 3 is not found. Try disabling it.") endif() endif() set(WANT_TCL false CACHE BOOL "Support Tcl modules") if(WANT_TCL) find_package(TCL QUIET) if(NOT TCL_FOUND) message(FATAL_ERROR "Can't find Tcl") endif() endif() tristate_option(I18N "Native language support (i18n)") if(WANT_I18N) find_package(Boost ${TRISTATE_I18N_REQUIRED} COMPONENTS locale) find_package(Gettext ${TRISTATE_I18N_REQUIRED}) endif() if(Boost_LOCALE_FOUND AND GETTEXT_MSGFMT_EXECUTABLE) set(HAVE_I18N true) else() set(HAVE_I18N false) message(STATUS "Boost.Locale or gettext (msgfmt) is not found, disabling i18n support") endif() if(HAVE_I18N AND GETTEXT_MSGMERGE_EXECUTABLE) find_program(XGETTEXT_EXECUTABLE xgettext) if(XGETTEXT_EXECUTABLE) add_custom_target(translation) endif() endif() # poll() is broken on Mac OS, it fails with POLLNVAL for pipe()s. if(APPLE) set(CSOCK_USE_POLL false) else() set(CSOCK_USE_POLL true) endif() find_package(cctz QUIET) set(cctz_cc "") if (cctz_FOUND) message(STATUS "Found cctz at ${cctz_DIR}") else() message(STATUS "Will build cctz") set(cctz_cc ${PROJECT_SOURCE_DIR}/third_party/cctz/src/civil_time_detail.cc ${PROJECT_SOURCE_DIR}/third_party/cctz/src/time_zone_fixed.cc ${PROJECT_SOURCE_DIR}/third_party/cctz/src/time_zone_format.cc ${PROJECT_SOURCE_DIR}/third_party/cctz/src/time_zone_if.cc ${PROJECT_SOURCE_DIR}/third_party/cctz/src/time_zone_impl.cc ${PROJECT_SOURCE_DIR}/third_party/cctz/src/time_zone_info.cc ${PROJECT_SOURCE_DIR}/third_party/cctz/src/time_zone_libc.cc ${PROJECT_SOURCE_DIR}/third_party/cctz/src/time_zone_lookup.cc ${PROJECT_SOURCE_DIR}/third_party/cctz/src/time_zone_posix.cc ${PROJECT_SOURCE_DIR}/third_party/cctz/src/zone_info_source.cc ) add_library(cctz INTERFACE) add_library(cctz::cctz ALIAS cctz) target_include_directories(cctz INTERFACE $) if (APPLE) find_library(CoreFoundation CoreFoundation REQUIRED) target_link_libraries(cctz INTERFACE ${CoreFoundation}) endif() endif() check_cxx_symbol_exists(getopt_long "getopt.h" HAVE_GETOPT_LONG) check_cxx_symbol_exists(lstat "sys/types.h;sys/stat.h;unistd.h" HAVE_LSTAT) check_cxx_symbol_exists(getpassphrase "stdlib.h" HAVE_GETPASSPHRASE) check_cxx_symbol_exists(tcsetattr "termios.h;unistd.h" HAVE_TCSETATTR) check_cxx_symbol_exists(clock_gettime "time.h" HAVE_CLOCK_GETTIME) # Note that old broken systems, such as OpenBSD, NetBSD, which don't support # AI_ADDRCONFIG, also have thread-unsafe getaddrinfo(). Gladly, they fixed # thread-safety before support of AI_ADDRCONFIG, so this can be abused to # detect the thread-safe getaddrinfo(). # # TODO: drop support of blocking DNS at some point. OpenBSD supports # AI_ADDRCONFIG since Nov 2014, and their getaddrinfo() is thread-safe since # Nov 2013. NetBSD's one is thread-safe since ages ago. check_cxx_symbol_exists(AI_ADDRCONFIG "sys/types.h;sys/socket.h;netdb.h" HAVE_THREADED_DNS) if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT CYGWIN) # These enable some debug options in g++'s STL, e.g. invalid use of # iterators, but they cause crashes on cygwin while loading modules set(_GLIBCXX_DEBUG true) set(_GLIBCXX_DEBUG_PEDANTIC true) endif() if(append_git_version) find_package(Git) endif() file(GLOB csocket_files LIST_DIRECTORIES FALSE "${PROJECT_SOURCE_DIR}/third_party/Csocket/Csocket.*") if(csocket_files STREQUAL "") execute_process(COMMAND git status WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} RESULT_VARIABLE git_status_var OUTPUT_QUIET ERROR_QUIET) if(git_status_var) message(FATAL_ERROR " It looks like git submodules are not initialized.\n" " Either this is not a git clone, or you don't have git installed.\n" " Fetch the tarball from the website: https://znc.in/releases/ ") else() message(FATAL_ERROR " It looks like git submodules are not initialized.\n" " Run: git submodule update --init --recursive") endif() endif() install(DIRECTORY webskins DESTINATION "${CMAKE_INSTALL_DATADIR}/znc") install(DIRECTORY translations DESTINATION "${CMAKE_INSTALL_DATADIR}/znc") install(DIRECTORY man/ DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" FILES_MATCHING PATTERN "znc*") set(WANT_SYSTEMD false CACHE BOOL "Install znc.service to systemd") if(WANT_SYSTEMD) configure_file("znc.service.in" "znc.service") set(SYSTEMD_DIR "" CACHE PATH "Path to systemd units") if(SYSTEMD_DIR STREQUAL "" AND PKG_CONFIG_EXECUTABLE) execute_process(COMMAND "${PKG_CONFIG_EXECUTABLE}" --variable=systemdsystemunitdir systemd OUTPUT_VARIABLE SYSTEMD_DIR) endif() if(SYSTEMD_DIR STREQUAL "") message(FATAL_ERROR "Systemd is enabled, " "but the unit dir can't be found.") endif() install(FILES "${PROJECT_BINARY_DIR}/znc.service" DESTINATION "${SYSTEMD_DIR}") endif() # On cygwin, if to link modules against znc.exe directly, modperl can't call # ZNC's functions very well. They do get called, but global variables have # different addresses. That address actually is in modperl/ZNC.dll if to look # at /proc/123/maps # Example of such global variable is one returned by CZNC::Get() # Modpython seems to work though with STATIC on cygwin... (I didn't test it # too much though) # # Non-cygwin should link modules to /usr/bin/znc directly to prevent this: # error while loading shared libraries: libznc.so: cannot open shared object file: No such file or directory # Without need to touch LD_LIBRARY_PATH if(CYGWIN) set(znc_link "znclib") set(lib_type "SHARED") set(install_lib "znclib") set(znclib_pc "-L${CMAKE_INSTALL_FULL_LIBDIR} -lznc") else() set(znc_link "znc") set(lib_type "STATIC") set(install_lib) set(znclib_pc) endif() configure_file("include/znc/zncconfig.h.cmake.in" "include/znc/zncconfig.h") configure_file("test/integration/znctestconfig.h.cmake.in" "test/integration/znctestconfig.h") add_subdirectory(include) add_subdirectory(src) add_subdirectory(modules) add_subdirectory(test) add_subdirectory(zz_msg) add_custom_target(msg_after_all ALL COMMAND "${CMAKE_COMMAND}" -E echo COMMAND "${CMAKE_COMMAND}" -E echo " ZNC was successfully compiled." COMMAND "${CMAKE_COMMAND}" -E echo " Use 'make install' to install ZNC to '${CMAKE_INSTALL_PREFIX}'." COMMAND "${CMAKE_COMMAND}" -E echo VERBATIM) add_dependencies(msg_after_all ${_all_targets}) # @echo "" # @echo " ZNC was successfully compiled." # @echo " Use '$(MAKE) install' to install ZNC to '$(prefix)'." configure_file("ZNCConfig.cmake.in" "ZNCConfig.cmake" @ONLY) include(CMakePackageConfigHelpers) write_basic_package_version_file("ZNCConfigVersion.cmake" COMPATIBILITY AnyNewerVersion) install(FILES "${PROJECT_SOURCE_DIR}/cmake/CMakeFindDependencyMacroPC.cmake" "${PROJECT_SOURCE_DIR}/cmake/use_homebrew.cmake" "${PROJECT_BINARY_DIR}/ZNCConfig.cmake" "${PROJECT_BINARY_DIR}/ZNCConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_DATADIR}/znc/cmake") configure_file("znc-buildmod.cmake.in" "znc-buildmod" @ONLY) install(PROGRAMS "${PROJECT_BINARY_DIR}/znc-buildmod" DESTINATION "${CMAKE_INSTALL_BINDIR}") configure_file("znc.pc.cmake.in" "znc.pc" @ONLY) install(FILES "${PROJECT_BINARY_DIR}/znc.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") macro(summary_line text var) if(${var}) list(APPEND summary_lines "${text} : yes") else() list(APPEND summary_lines "${text} : no") endif() endmacro() set(summary_lines "ZNC ${ZNC_VERSION}${VERSION_EXTRA}${alpha_version} configured" " " "Prefix : ${CMAKE_INSTALL_PREFIX}") summary_line("SSL " "${OPENSSL_FOUND}") summary_line("IPv6 " "${WANT_IPV6}") summary_line("Async DNS" "${HAVE_THREADED_DNS}") summary_line("Perl " "${PERLLIBS_FOUND}") summary_line("Python " "${Python3_FOUND}") summary_line("Tcl " "${TCL_FOUND}") summary_line("Cyrus " "${CYRUS_FOUND}") summary_line("Charset " "${ICU_FOUND}") summary_line("Zlib " "${ZLIB_FOUND}") summary_line("i18n " "${HAVE_I18N}") summary_line("Argon2 " "${ZNC_HAVE_ARGON}") include(render_framed_multiline) render_framed_multiline("${summary_lines}") message("") message("Now you can run 'make' to compile ZNC") message("") znc-1.9.1/CONTRIBUTING.md0000644000175000017500000000171414641222733015000 0ustar somebodysomebodyReporting bugs ============== * When reporting a bug: * Please ensure that you are on the latest version in case the bug you are reporting is already fixed. * If you did some custom modification to ZNC, please make sure that the bug isn't caused by that modification. * Please include the following information: * OS/distribution version * `/znc version` * If you are reporting a crash, please see [the debugging page] on wiki.znc.in. * If you are reporting an issue with connectivity, please run ZNC in debug mode and include the relevant part of the output. To enable debug mode, run ZNC with the `-D` flag. [The debugging page]:https://wiki.znc.in/Debugging Code changes ============ * Follow the exact same conventions and style of the file you change. * For deciding which branch to pull request, please see [the branches page] on wiki.znc.in. [The branches page]:https://wiki.znc.in/Branches znc-1.9.1/ChangeLog.md0000644000175000017500000031570314641222733014726 0ustar somebodysomebody# ZNC 1.9.1 (2024-07-03) * This is a security release to fix CVE-2024-39844: remote code execution vulnerability in modtcl. * To mitigate this for existing installations, simply unload the modtcl module for every user, if it's loaded. Note that only users with admin rights can load modtcl at all. * Thanks to Johannes Kuhn (DasBrain) for reporting, to glguy for the patch, and to multiple IRC network operators for help with mitigating this on server side before disclosure. * Improve tooltips in webadmin. # ZNC 1.9.0 (2024-02-22) ## New * Support for capability negotiation 302 and `cap-notify`. ZNC now has API `AddServerDependentCapability()`, using which modules can easily implement new capabilities: if server supports a cap, it will automatically be offered to clients which support `cap-notify` and ZNC will notify the module when the capability is enabled or disabled for server and for each client. * Several capabilities (`away-notify`, `account-notify`, `extended-join`) were moved from the core to a new module: corecaps. * The corecaps module is loaded automatically when upgrading from old config and when creating new config, but it's possible to unload it. * Note: users who were using pre-release versions of 1.9.x (from git or from nightly tarballs) won't have it loaded automatically, because the existing config states `Version = 1.9`. In such case you can load it manually. This is to honor choice of users who decide to unload it, since we don't know whether the module is missing intentionally. * Added support for `account-tag` capability, also in corecaps module. * Updated password hashing algorithm from SHA-256 to Argon2id (if libargon2 is installed). Existing passwords are transparently upgraded upon login. * Allow ordering of channels: via `ListChans`, `MoveChan` and `SwapChans` commands, and via webadmin. * New user options: `DenySetIdent`, `DenySetNetwork`, `DenySetRealName`, `DenySetQuitMsg`, `DenySetCTCPReplies`. * Switched `--makeconf` wizard default network from freenode to Libera. * Added Portuguese and Turkish translations. * znc-buildmod: output where the module was written to ## Fixes * Fixed crash when receiving SASL lines from server without having negotiated SASL via CAP. * Fixed build with SWIG 4.2.0. * Fixed build with LibreSSL. * Fixed handling of timezones when parsing server-time tags received from server. * Use module names as the module ident, otherwise some clients were merging conversations with different modules together. * Stopped sending invalid 333 (`RPL_TOPICWHOTIME`) to client if topic owner is unknown. * Fixed an ODR violation. * Better hide password in PASS debug lines, sometimes it was not hidden. * CAP REQ sent by client without CAP LS now suspends the registration as the spec requires. ## Modules * autoop: In some cases settings were parsed incorrectly, resulting in failure to do the autoop, now it's fixed. * clientnotify: Added options to reduce amount of notifications depending on the IP and the client ID of the connecting client. * controlpanel: Fixed help output. * log: Log nickserv account in the joins lines. * modperl: Allow overriding label for timers, which means now there can be more than 1 timer per module. * modpython: * Rewrote internals of how modpython loads modules. * Main motivation for the switch from using `imp` to using `importlib` was to support Python 3.12+. * As an additional benefit, now it's possible to structure the module as a python package (a subdirectory with `__init__.py` and other .py files). * All the old python modules should load as they were before. * ZNC no longer supports loading a C python extension directly through modpython (though I doubt there were any users of that obscure feature): if you want to some parts of the module to be compiled, you can always import that from `__init__.py`. * Implemented `Module.AddCommand()` * route_replies: * Added Solanum-specific 337 (`RPL_WHOISTEXT`) to possible replies of `/whois`. * Route replies to `/topic`. * sasl: Don't forward 908 (`RPL_SASLMECHS`) to clients. * webadmin: Fixed order of breadcrumbs in network page. * watch: Allow new entries to use spaces. ## Notes for package maintainers * Require C++17 compiler. That is, GCC 8+ or Clang 5+. * Removed autoconf, leaving only CMake as the build system. The `configure` script is now merely a wrapper for CMake, and accepts mostly the same parameters as the old `configure`. You can use either `configure` as before, or CMake directly. Minimum supported CMake version is 3.13. * If cctz library is available on the system, it will be used, otherwise the bundled copy will be used. * libargon2 is new optional dependency. * Dropped support for Python < 3.4 * Dropped support for SWIG < 4.0.1 * The systemd unit now passes `--datadir=/var/lib/znc`. ## Internal * Switched to steady clock for cache map and for sockets to fix certain issues with leap seconds and DST. * Made `CUser::Put...()` send to all clients instead of only networkless clients. Deprecate `CUser::PutAllUser()`. * Setup Github Actions to replace old Travis CI setup. * Added CIFuzz. * Added CodeQL. * List of translators is now automatically generated from Crowdin. * Modernized the way how CMake is used. * Updated default SSL settings from Mozilla recommendations. * Rewrote message parsing using `std::string_view`, improving the performance of the parser. * Web: removed legacy xhtml syntax. * Documented more functions. * Made some integration tests run faster by changing ServerThrottle value in the test. # ZNC 1.8.2 (2020-07-07) ## New * Polish translation * List names of translators in TRANSLATORS.md file in source, as this contribution isn't directly reflected in git log * During --makeconf warn about listening on port 6697 too, not only about 6667 ## Fixes * webadmin: When confirming deletion of a network and selecting No, redirect to the edituser page instead of listusers page * Make more client command results translateable, which were missed before # ZNC 1.8.1 (2020-05-07) Fixed bug introduced in ZNC 1.8.0: Authenticated users can trigger an application crash (with a NULL pointer dereference) if echo-message is not enabled and there is no network. CVE-2020-13775 # ZNC 1.8.0 (2020-05-01) ## New * Output of various commands (e.g. `/znc help`) was switched from a table to a list * Support IP while verifying SSL certificates * Make it more visible that admins have lots of privileges ## Fixes * Fix parsing of channel modes when the last parameter starts with a colon, improving compatibility with InspIRCd v3 * Fix null dereference on startup when reading invalid config * Don't show server passwords on ZNC startup * Fix build with newer OpenSSL * Fix in-source CMake build * Fix echo-message for `status` ## Modules * controlpanel: Add already supported NoTrafficTimeout User variable to help output * modpython: * Use FindPython3 in addition to pkg-config in CMake to simplify builds on Gentoo when not using emerge * Support python 3.9 * modtcl: Added GetNetworkName * partyline: Module is removed * q: Module is removed * route_replies: Handle more numerics * sasl: Fix sending of long authentication information * shell: Unblock signals when spawning child processes * simple_away: Convert to UTC time * watch: Better support multiple clients * webadmin: Better wording for TrustPKI setting ## Internal * Refactor the way how SSL certificate is checked to simplify future socket-related refactors * Build integration test and ZNC itself with the same compiler (https://bugs.gentoo.org/699258) * Various improvements for translation CI * Normalize variable name sUserName/sUsername * Make de-escaping less lenient # ZNC 1.7.5 (2019-09-23) * modpython: Add support for Python 3.8 * modtcl: install .tcl files when building with CMake * nickserv: report success of Clear commands * Update translations, add Italian, Bulgarian, fix name of Dutch * Update error messages to be clearer * Add a deprecation warning to ./configure to use CMake instead in addition to an already existing warning in README # ZNC 1.7.4 (2019-06-19) ## Fixes * This is a security release to fix CVE-2019-12816 (remote code execution by existing non-admin users). Thanks to Jeriko One for the bugreport. * Send "Connected!" messages to client to the correct nick. # Internal * Increase znc-buildmod timeout in the test. # ZNC 1.7.3 (2019-03-30) ## Fixes This is a security release to fix CVE-2019-9917. Thanks to LunarBNC for the bugreport. ## New Docker only: the znc image now supports --user option of docker run. # ZNC 1.7.2 (2019-01-19) ## New * Add French translation * Update translations ## Fixes * Fix compilation without deprecated APIs in OpenSSL * Distinguish Channel CTCP Requests and Replies * admindebug: Enforce need of TTY to turn on debug mode * controlpanel: Add missing return to ListNetMods * webadmin: Fix adding the last allowed network ## Internal * Add more details to DNS error logs # ZNC 1.7.1 (2018-07-17) ## Security critical fixes * CVE-2018-14055: non-admin user could gain admin privileges and shell access by injecting values into znc.conf. * CVE-2018-14056: path traversal in HTTP handler via ../ in a web skin name. ## Core * Fix znc-buildmod to not hardcode the compiler used to build ZNC anymore in CMake build * Fix language selector. Russian and German were both not selectable. * Fix build without SSL support * Fix several broken strings * Stop spamming users about debug mode. This feature was added in 1.7.0, now reverted. ## New * Add partial Spanish, Indonesian, and Dutch translations ## Modules * adminlog: Log the error message again (regression of 1.7.0) * admindebug: New module, which allows admins to turn on/off --debug in runtime * flooddetach: Fix description of commands * modperl: Fix memory leak in NV handling * modperl: Fix functions which return VCString * modpython: Fix functions which return VCString * webadmin: Fix fancy CTCP replies editor for Firefox. It was showing the plain version even when JS is enabled ## Internal * Deprecate one of the overloads of CMessage::GetParams(), rename it to CMessage::GetParamsColon() * Don't throw from destructor in the integration test * Fix a warning with integration test / gmake / znc-buildmod interaction. # ZNC 1.7.0 (2018-05-01) ## New * Add CMake build. Minimum supported CMake version is 3.1. For now ZNC can be built with either CMake or autoconf. In future autoconf is going to be removed. * Currently `znc-buildmod` requires python if CMake was used; if that's a concern for you, please open a bug. * Increase minimum GCC version from 4.7 to 4.8. Minimum Clang version stays at 3.2. * Make ZNC UI translateable to different languages (only with CMake), add partial Russian and German translations. * If you want to translate ZNC to your language, please join https://crowdin.com/project/znc-bouncer * Configs written before ZNC 0.206 can't be read anymore * Implement IRCv3.2 capabilities `away-notify`, `account-notify`, `extended-join` * Implement IRCv3.2 capabilities `echo-message`, `cap-notify` on the "client side" * Update capability names as they are named in IRCv3.2: `znc.in/server-time-iso`→`server-time`, `znc.in/batch`→`batch`. Old names will continue working for a while, then will be removed in some future version. * Make ZNC request `server-time` from server when available * Increase accepted line length from 1024 to 2048 to give some space to message tags * Separate buffer size settings for channels and queries * Support separate `SSLKeyFile` and `SSLDHParamFile` configuration in addition to existing `SSLCertFile` * Add "AuthOnlyViaModule" global/user setting * Added pyeval module * Added stripcontrols module * Add new substitutions to ExpandString: `%empty%` and `%network%`. * Stop defaulting real name to "Got ZNC?" * Make the user aware that debug mode is enabled. * Added `ClearAllBuffers` command * Don't require CSRF token for POSTs if the request uses HTTP Basic auth. * Set `HttpOnly` and `SameSite=strict` for session cookies * Add SNI SSL client support * Add support for CIDR notation in allowed hosts list and in trusted proxy list * Add network-specific config for cert validation in addition to user-supplied fingerprints: `TrustAllCerts`, defaults to false, and `TrustPKI`, defaults to true. * Add `/attach` command for symmetry with `/detach`. Unlike `/join` it allows wildcards. * Timestamp format now supports sub-second precision with `%f`. Used in awaystore, listsockets, log modules and buffer playback when client doesn't support server-time * Build on macOS using ICU, Python, and OpenSSL from Homebrew, if available * Remove `--with-openssl=/path` option from ./configure. SSL is still supported and is still configurable ## Fixes * Revert tables to how they were in ZNC 1.4 * Remove flawed Add/Del/ListBindHost(s). They didn't correctly do what they were intended for, but users often confused them with the SetBindHost option. SetBindHost still works. * Fix disconnection issues when being behind NAT by decreasing the interval how often PING is sent and making it configurable via a setting to change ping timeout time * Change default flood rates to match RFC1459, prevent excess flood problems * Match channel names and hostmasks case-insensitively in autoattach, autocycle, autoop, autovoice, log, watch modules * Fix crash in shell module which happens if client disconnects at a wrong time * Decrease CPU usage when joining channels during startup or reconnect, add config write delay setting * Always send the users name in NOTICE when logging in. * Don't try to quit multiple times * Don't send PART to client which sent QUIT * Send failed logins to NOTICE instead of PRIVMSG * Stop creating files with odd permissions on Solaris * Save channel key on JOIN even if user was not on the channel yet * Stop buffering and echoing CTCP requests and responses to other clients with self-message, except for /me * Support discovery of tcl 8.6 during `./configure` ## Modules * adminlog: * Make path configurable * alias: * Add `Dump` command to copy your config between users * awaystore: * Add `-chans` option which records channel highlights * blockmotd: * Add `GetMotd` command * clearbufferonmsg: * Add options which events trigger clearation of buffers. * controlpanel: * Add the `DelServer` command. * Add `$user` and `$network` aliases for `$me` and `$net` respectively * Allow reseting channel-specific `AutoClearChanBuffer` and `BufferSize` settings by setting them to `-` * Change type of values from "double" to "number", which is more obvious for non-programmers * crypt: * Fix build with LibreSSL * Cover notices, actions and topics * Don't use the same or overlapping NickPrefix as StatusPrefix * Add DH1080 key exchange * Add Get/SetNickPrefix commands, hide the internal keyword from ListKeys * cyrusauth: * Improve UI * fail2ban: * Make timeout and attempts configurable, add BAN, UNBAN and LIST commands * flooddetach: * Detach on nick floods * keepnick: * Improve behaviour by listening to ircd-side numeric errors * log: * Add `-timestamp` option * Add options to hide joins, quits and nick changes. * Stop forcing username and network name to be lower case in filenames * Log user quit messages * missingmotd: * Include nick in IRC numeric 422 command, reduce client confusion * modperl: * Provide `operator ""` for `ZNC::String` * Honor `PERL5LIB` env var * Fix functions like `HasPerm()` which accept `char` * When a broken module couldn't be loaded, it couldn't be loaded anymore even if it was fixed later. * Force strings to UTF-8 in modperl to fix double encoding during concatenation/interpolation. * modpython: * Require ZNC to be built with encodings support * Disable legacy encoding mode when modpython is loaded. * Support `CQuery` and `CServer` * nickserv: * Use `/nickserv identify` by default instead of `/msg nickserv`. * Support messages from X3 services * notify_connect: * Show client identification * sasl: * Add web interface * Enable all known mechanisms by default * Make the first requirement for SET actually mandatory, return information about settings if no input for SET * schat: * Require explicit path to certificate. * simple_away: * Use ExpandString for away reason, rename old `%s` to `%awaytime%` * Add `MinClients` option * stickychan: * Save registry on every stick/unstick action, auto-save if channel key changes * Stop checking so often, increase delay to once every 3 minutes * webadmin: * Make server editor and CTCP replies editor more fancy, when JS is enabled * Make tables sortable. * Allow reseting chan buffer size by entering an empty value * Show per-network traffic info * Make the traffic info page visible for non-admins, non-admins can see only their traffic ## Internal * Stop pretending that ZNC ABI is stable, when it's not. Make module version checks more strict and prevent crashes when loading a module which are built for the wrong ZNC version. * Add an integration test * Various HTML changes * Introduce a CMessage class and its subclasses * Add module callbacks which accept CMessage, deprecate old callbacks * Add `OnNumericMessage` module callback, which previously was possible only with `OnRaw`, which could give unexpected results if the message has IRCv3.2 tags. * Modernize code to use more C++11 features * Various code cleanups * Fix CSS of `_default_` skin for Fingerprints section * Add `OnUserQuitMessage()` module hook. * Add `OnPrivBufferStarting()` and `OnPrivBufferEnding()` hooks * `CString::WildCmp()`: add an optional case-sensitivity argument * Do not call `OnAddUser()` hook during ZNC startup * Allow modules to override CSRF protection. * Rehash now reloads only global settings * Remove `CAP CLEAR` * Add `CChan::GetNetwork()` * `CUser`: add API for removing and clearing allowed hosts * `CZNC`: add missing SSL-related getters and setters * Add a possibility (not an "option") to disable launch after --makeconf * Move Unix signal processing to a dedicated thread. * Add clang-format configuration, switch tabs to spaces. * `CString::StripControls()`: Strip background colors when we reset foreground * Make chan modes and permissions to be char instead of unsigned char. ## Cosmetic * Alphabetically sort the modules we compile using autoconf/Makefile * Alphabetically sort output of `znc --help` * Change output during startup to be more compact * Show new server name when reconnecting to a different server with `/znc jump` * Hide passwords in listservers output * Filter out ZNC passwords in output of `znc -D` * Switch znc.in URLs to https # ZNC 1.6.6 (2018-03-05) * Fix use-after-free in `znc --makepem`. It was broken for a long time, but started segfaulting only now. This is a useability fix, not a security fix, because self-signed (or signed by a CA) certificates can be created without using `--makepem`, and then combined into znc.pem. * Fix build on Cygwin. # ZNC 1.6.5 (2017-03-12) ## Fixes * Fixed a regression of 1.6.4 which caused a crash in modperl/modpython. * Fixed the behavior of `verbose` command in the sasl module. # ZNC 1.6.4 (2016-12-10) ## Fixes * Fixed build with OpenSSL 1.1. * Fixed build on Cygwin. * Fixed a segfault after cloning a user. The bug was introduced in ZNC 1.6.0. * Fixed a segfault when deleting a user or network which is waiting for DNS during connection. The bug was introduced in ZNC 1.0. * Fixed a segfault which could be triggered using alias module. * Fixed an error in controlpanel module when setting the bindhost of another user. * Fixed route_replies to not cause client to disconnect by timeout. * Fixed compatibility with the Gitter IRC bridge. ## Internal * Fixed `OnInvite` for modpython and modperl. * Fixed external location of GoogleTest for `make test`. # ZNC 1.6.3 (2016-02-23) ## Core * New character encoding is now applied immediately, without reconnect. * Fixed build with LibreSSL. * Fixed error 404 when accessing the web UI with the configured URI prefix, but without the `/` in the end. * `znc-buildmod` now exits with non-zero exit code when the .cpp file is not found. * Fixed `znc-buildmod` on Cygwin. * ExpandString got expanded. It now expands `%znc%` to `ZNC - http://znc.in`, honoring the global "Hide version" setting. * Default quit message is switched from `ZNC - http://znc.in` to `%znc%`, which is the same, but "automatically" changes the shown version when ZNC gets upgraded. Before, the old version was recorded in the user's quit message, and stayed the same regardless of the current version of ZNC. ## Modules * modperl: * Fixed a memory leak. * sasl: * Added an option to show which mechanisms failed or succeeded. * webadmin: * Fixed an error message on invalid user settings to say what exactly was invalid. * No more autocomplete password in user settings. It led to an error when ZNC thought the user is going to change a password, but the passwords didn't match. # ZNC 1.6.2 (2015-11-15) ## Fixes * Fixed a use-after-delete in webadmin. It was already partially fixed in ZNC 1.4; since 1.4 it has been still possible to trigger, but much harder. * Fixed a startup failure when awaynick and simple_away were both loaded, and simple_away had arguments. * Fixed a build failure when using an ancient OpenSSL version. * Fixed a build failure when using OpenSSL which was built without SSLv3 support. * Bindhost was sometimes used as ident. * `CAP :END` wasn't parsed correctly, causing timeout during login for some clients. * Fixed channel keys if client joined several channels in single command. * Fixed memory leak when reading an invalid config. ## Modules * autovoice: * Check for autovoices when we are opped. * controlpanel: * Fixed `DelCTCPReply` case-insensitivity. * dcc: * Add missing return statement. It was harmless. * modpython: * Fixed a memory leak. * modules_online: * Wrong ident was used before. * stickychan: * Fixed to unstick inaccessible channels to avoid infinite join loops. ## Internal * Fixed the nick passed to `CModule::OnChanMsg()` so it has channel permissions set. * Fixed noisy `-Winconsistent-missing-override` compilation warnings. * Initialized some fields in constructors of modules before `OnLoad()`. ## Cosmetic * Various modules had commands with empty descriptions. * perform: * Say "number" instead of "nr". * route_replies: * Make the timeout error message more clear. # ZNC 1.6.1 (2015-08-04) ## Fixes * Fixed the problem that channels were no longer removed from the config despite of chansaver being loaded. * Fixed query buffer size for users who have the default channel buffer size set to 0. * Fixed a startup failure when simple_away was loaded after awaynick. * Fixed channel matching commands, such as DETACH, to be case insensitive. * Specified the required compiler versions in the configure script. * Fixed a rare conflict of HTTP-Basic auth and cookies. * Hid local IP address from the 404 page. * Fixed a build failure for users who have `-Werror=missing-declarations` in their `CXXFLAGS`. * Fixed `CXXFLAGS=-DVERSION_EXTRA="foo"` which is used by some distros to package ZNC. * Fixed `znc-buildmod` on Cygwin. ## Modules * chansaver: * Fixed random loading behavior due to an uninitialized member variable. * modpython: * Fixed access to `CUser::GetUserClients()` and `CUser::GetAllClients()`. * sasl: * Improved help texts for the SET and REQUIREAUTH commands. * savebuff: * Fixed periodical writes on the disk when the module is loaded after startup. * webadmin: * Fixed module checkboxes not to claim that all networks/users have loaded a module when there are no networks/users. * Added an explanation that ZNC was built without ICU support, when encoding settings are disabled for that reason. * Improved the breadcrumbs. * Mentioned ExpandString in CTCP replies. * Added an explanation how to delete port which is used to access webadmin. ## Internal * Fixed `CThreadPool` destructor to handle spurious wakeups. * Fixed `make distclean` to remove `zncconfig.h`. * Improved the error message about `--datadir`. * Fixed a compilation warning when `HAVE_LIBSSL` is not defined. * Fixed 'comparision' typos in CString documentation. * Added a non-minified version of the jQuery source code to make Linux distributions (Debian) happy, even though the jQuery license does not require this. # ZNC 1.6.0 (2015-02-12) ## New * Switch versioning scheme to ... * Add settings for which SSL/TLS protocols to use (SSLProtocols), which ciphers to enable (SSLCiphers). By default TLSv1+ are enabled, SSLv2/3 are disabled. Default ciphers are what Mozilla advices: https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29 * Validate SSL certificates. * Allow clients to specify an ID as part of username (user[@identifier][/network]). Currently not used, but modules can use it. * Add alias module for ZNC-side command interception and processing. * Support character encodings with separate settings for networks, and for clients. It replaces older charset module, which didn't work well with webadmin, log and other modules. * Support X-Forwarded-For HTTP header, used with new TrustedProxy setting. * Add URIPrefix option for HTTP listeners, used with reverse proxy. * Store query buffers per query the same way it's done for channels, add new option AutoClearQueryBuffer. * Add DisableChan command to *status, it was available only in webadmin before. * Allow wildcards in arguments of Help commands of *status and various modules. * Support IRCv3.2 batches, used for buffer playbacks. * Support IRCv3.2 self-message. * Remove awaynick module. It's considered bad etiquette. * Add JoinDelay setting, which allows a delay between connection to server, and joining first channel. By default it joins immediately after connect. * Make Detach, EnableChan and DisableChan commands of *status accept multiple channels. * znc-buildmod: Build output to the current working directory. * Wrap long lines in tables (e.g. in Help or ListAvailMods commands). * Support ECDHE if available in OpenSSL. * Report ZNC version more consistently, add HideVersion setting, which hides ZNC version from public. * Bump compiler requirements to support C++11. This means GCC 4.7+, Clang 3.2+, SWIG 3.0.0+. ## Fixes * Disable TLS compression. * Disallow setting ConnectDelay to zero, don't hammer server with our failed connects. * Simplify --makeconf. * Fix logic to find an available nick when connecting to server. * Fix handling of CTCP flood. * Allow network specific quit messages. * Make various text labels gender-neutral. * Fix finding SWIG 3 on FreeBSD. * Handle multi-receiver NOTICE and PRIVMSG. * Make channels follow user-level settings when appropriate. * Write disabled status to config for disabled channels. * Fix double output in messages from modules. * Fix memory leak in gzip compression in HTTP server. * Use random DNS result instead of choosing the same one every time. * Fix HTTP basic auth. * Mention network in message shown if client didn't send PASS. ## Modules * autoattach: * Make it also a network module. * autoreply: * Use NOTICE instead of PRIVMSG. * autoop: * Add support for multiple hostmasks per user. * awaystore: * Store CTCP ACTIONs too. * Reset timer and return from away when a client does a CTCP ACTION. * Allows use of strftime formatting in away messages. * bouncedcc: * Fix quotes in file names. * Fix check for "Connected" state. * buffextras: * Make it also a network module. * chansaver: * Fix saving channel keys. * Add support for loading as a global module. * controlpanel: * Add AddChan, DelChan commands, useful for admins to edit other users' channels, was available only in webadmin before. * Check if adding a new channel succeeded. * Revise Help output. * Allow wildcards for GetChan and SetChan. * flooddetach: * Show current value in Lines and Secs commands. * Add Silent [yes|no] command, similar to route_replies. * listsockets: * Show traffic stats. * log: * Use only lower case characters in log filenames. * Use directories and YYYY-MM-DD filename by default. * Add support for logging rules. E.g. /msg *log setrules #znc !#* * modperl: * Fix some int_t types. * modpython: * Fix calling overloaded methods with parameter CString&. * Support CZNC::GetUserMap(). * Set has_args and args_help_text from module. * Release python/swig ownership when adding object created in python to ZNC container. * Fix some int_t types. * Enable default arguments feature of SWIG 3.0.4. No functionality change, it just makes generated code a bit more beautiful. * nickserv: * Support tddirc.net. * Remove commands Ghost, Recover, Release, Group. The same functionality is available via new alias module. * q: * Add JoinOnInvite, JoinAfterCloaked options. * Don't cloak host on first module load if already connected to IRC. * Add web configuration. * Use HMAC-SHA-256 instead of HMAC-MD5. * route_replies: * Handle numerics 307 and 379 in /whois reply. Handle IRCv3.2 METADATA numerics. * sample: * Make it a network module, which are easier to write. * sasl: * Remove DH-BLOWFISH and DH-AES. See http://nullroute.eu.org/~grawity/irc-sasl-dh.html and http://kaniini.dereferenced.org/2014/12/26/do-not-use-DH-AES-or-DH-BLOWFISH.html for details. * savebuff: * Do not skip channels with AutoClearChanBuffer=true. * Handle empty password in SetPass the same way as during startup. * simple_away: * Apply auto-away on load if no user is connected. * stickychan: * Don't join channels when not connected. * watch: * Add support for detached-only clients, and detached-only channels. * webadmin: * Combine "List Users" and "Add User". * Module argument autocomplete="off", for nickserv module, which contains password in argument before first save. * For every module show in which other levels that module is loaded (global/user/network). * Open links to wiki pages about modules in separate window/tab. * Support renaming a network (it was already possible outside of webadmin, via /znc MoveNetwork). However, it doesn't support moving networks between users yet, for that use /znc command. * Add missing page title on Traffic page. * Improve navigation: "Save and continue". * Clarify that timestamp format is useless with server-time. ## Internal * Move Csocket to git submodule. * Unit tests, via GTest. * Allow lambdas for module command callbacks. * New modules hooks: OnSendToClient, OnSendToIRC, OnJoining, OnMode2, OnChanBufferPlayLine2, OnPrivBufferPlayLine2. * Add methods to CString: StartsWith, EndsWith, Join, Find, Contains, and Convert. * Add limited support for using threads in modules: CModuleJob class. * Inherit CClient and CIRCSock from a common class CIRCSocket. * Add CZNC::CreateInstance to make porting ZNC to MSVC a bit easier. * Add CUtils::Get/SetMessageTags(). * Add CIRCNetwork::FindChans(). * Add CChan::SendBuffer(client, buffer) overload. * Add CIRCNetwork::LoadModule() helper. * Add CClient::IsPlaybackActive(). * Web: Discard sessions in LRU order. * Introduce CaseSensitivity enum class. * Fix CNick::Parse(). * Remove redundant CWebSocket::GetModule(). * Switch from CSmartPtr to std::shared_ptr. * Fix GetClients() const correctness. * Make self-signed cert with SHA-256, provide DH parameters in --makepem. * Use override keyword. * Show username of every http request in -D output. * Split CUserTimer into CIRCNetworkPingTimer and CIRCNetworkJoinTimer. * Give a reason for disabled features during ./configure, where it makes sense. * Use make-tarball.sh for nightlies too. * Revise CChan::JoinUser() & AttachUser(). * Modules: use public API. * Modules: use AddCommand(). * Add ChangeLog.md. # ZNC 1.4 (2014-05-08) This release is done to fix a denial of service attack through webadmin. After authentication, users can crash ZNC through a use-after-delete. Additionally, a number of fixes and nice, low-risk additions from our development branch is included. In detail, these are: ## New * Reduce users' confusion during --makeconf. * Warn people that making ZNC listen on port 6667 might cause problems with some web browsers. * Always generate a SSL certificate during --makeconf. * Stop asking for a bind host / listen host in --makeconf. People who don't want wildcard binds can configure this later. * Don't create ~/.znc/modules if it doesn't exist yet. ## Fixes * Fix a use-after-delete in webadmin. CVE-2014-9403 * Honor the BindHost whitelist when configuring BindHosts in controlpanel module. * Ignore trailing whitespace in /znc jump arguments. * Change formatting of startup messages so that we never overwrite part of a message when printing the result of an action. * Fix configure on non-bash shells. * Send the correct error for invalid CAP subcommands. * Make sure znc-buildmod includes zncconfig.h at the beginning of module code. ## Modules * Make awaystore automatically call the Ping command when the Back command is used. * Add SSL information and port number to servers in network list in webadmin. * Disable password autocompletion when editing users in webadmin. * Make nickserv module work on StarChat.net and ircline.org. * Remove accidental timeout for run commands in shell module. * certauth now uses a case insensitive comparison on hexadecimal fingerprints. ### controlpanel * Correct double output. * Add support for the MaxNetworks global setting. * Add support for the BindHost per-network setting. ### modperl and modpython * Make OnAddNetwork and OnDeleteNetwork module hooks work. * Don't create .pyc files during compilation. * Fix modperl on MacOS X. Twice. * Require at least SWIG 2.0.12 on MacOS X. ## Internal * Don't redefine _FORTIFY_SOURCE if compiler already defines it. * Cache list of available timezones instead of re-reading it whenever it is needed. * Improve const-correctness. * Fix various low-priority compiler warnings. * Change in-memory storage format for ServerThrottle. * Use native API on Win32 to replace a file with another file. * Add src/version.cpp to .gitignore. # ZNC 1.2 (2013-11-04) ## New * ZNC has been relicensed to Apache 2.0 * Show password block in --makepass in new format * Return MaxJoins setting, it helps against server sending ZNC too many lines at once and disconnecting with "Max SendQ exceeded" * Make /znc detach case insensitive, allow "/detach #chan1,#chan2" syntax * No longer store 381 in the buffer ## Fixes * CModule::OnMode(): Fix a stupid NULL pointer dereference * Fix NULL pointer dereference in webadmin. * Fix a crash when you delete a user with more than one attached client * Fix a random crash with module hooks * Revert "Rewrite the JOIN channel logic, dropping MaxJoins" * Fix build on some systems * Fix build of shallow git clone * Fix build of git tags * Fix OOT builds with swig files in source dir * Don't send NAMES and TOPIC for detached channels when a client connects * Fix memory leak * Consistency between Del* and Rem* in command names * Fix changing client nick when client connects. * Timezone GMT+N is really GMT+N now. It behaved like -N before. * Escape special characters in debug output (znc --debug) * Don't disconnect networkless users without PINGing them first. * Don't lose dlerror() message. * Fix use-after-free which may happen during shutdown * Fix "Error: Success" message in SSL * Fixed double forward slashes and incorrect active module highlighting. * make clean: Only delete files that can be regenerated * Don't make backup of znc.conf readable by everyone. * makepem: create pem only rw for the user, on non-win32 * Don't ever try to overwrite /usr/bin/git * Fix user modes * Request secure cookie transmission for HTTPS * "make clean" removes .depend/ * Fix support for /msg @#chan :hi * Fix saving config on some cygwin installations * Fix error message for invalid network name ## Modules * Return old fakeonline module (accidentally removed in 1.0) as modules_online * autoattach: add string searching * autocycle: Convert to a network module * chansaver: Fix chansaver to not rewrite the config each time a user joins a channel on startup * cert: Make default type of cert mod to be network. * watch: Don't handle multiple matching patterns for each target * route_replies: Add some WHOIS numerics * block_motd: Allow block_motd to be loaded per-network and globally * notify_connect: Fixed syntax on attach/detach messages to be more consistent * cyrusauth: Fix user creation ### controlpanel * Support network module manipulation * Increases general verbosity of command results. * Fix bug for "Disconnect" help * Standardize error wordings ### webadmin * Allow loading webadmin as user module. * Show instructions on how to use networks in Add Network too * clarify that + is SSL * Show example timezone in webadmin * Enable embedding network modules. * Enable embedding modules to network pages. * Change save network to show the network and not redirect user ### sasl * Implement DH-AES encrypted password scheme. * Add missing length check * Description line for DH-BLOWFISH * Fixing unaligned accesses ### awaystore * Fix loading old configs which referred to "away" module * Fix displaying IPv6 addresses ### crypt * Add time stamp to buffered messages * Use ASCII for nick prefix and make it configurable ### nickserv * Make NickServ nickname configurable. * Add support for NickServ on wenet.ru and Azzurra * nickserv: don't confuse people so much ### log * Add -sanitize option to log module. * Convert / and \ character to - in nicks for filenames. * Create files with the same permissions as the whole log directory. ### charset * Don't try to build charset module if iconv is not found * Fix: Converted raw string include NULL character in charset module ### modperl * A bit more debug output on modperl * Fix perl modules being shown incorrectly in the webadmin ### partyline * Fix PartyLine so that forced channels may not be left at all - users will be rejoined at once. * Fix partyline rejoin on user deletion ## Internal * Require SWIG 2.0.8 for modperl/modpython (removes hacks to make older SWIG work) * Web interface now supports gzip compression * Update server-time to new specs with ISO 8601 * Add a generic threads abstraction * Add CString::StripControls to strip controls (Colors, C0) from strings * Change PutModule to handle multiple lines * Debug output: Only print queued lines if they are really just queued * Add initial unit tests, runnable by "make test" * Add nick comparison function CNick::NickEquals * Force including zncconfig.h at the beginning of every .cpp * Add OnAddNetwork, OnDeleteNetwork module hooks # ZNC 1.0 (2012-11-07) ## The Big News Multiple networks per user Think about new users as "user groups", while new networks are similar to old users. To login to ZNC, use user/network:password as password, or user/network as username. Also, you can switch between different networks on the fly using the /znc JumpNetwork command. When you first run ZNC 1.0, it will automatically convert your config and create a network called "default" for each user. Settings from each user are moved into these "default" networks. When you log into ZNC without setting a network, the "default" network will automatically be chosen for you. Users can create new networks up to an admin-configurable limit. By default, this limit is one network per user. Existing user-per-network setups can be migrated to the new multinetwork setup using the /znc MoveNetwork command. You can see a list of networks via /znc ListNetworks and /znc ListAllUserNetworks. ## Timezones Timezone can now be configured by name, e.g. "GMT-9", or "Europe/Madrid". Old TimezoneOffset setting (which was the number of hours between the server's timezone and the user's timezone) is deprecated and should not be used anymore. Its old value is lost. The reason for this change is that the old TimezoneOffset was not trivial to count and often broke during switches to/from daylight savings time. So if you previously used the TimezoneOffset option, you now have to configure your timezone again (via the webadmin or controlpanel module). ## No more ZNC-Extra Most modules from ZNC-Extra are now enabled in the usual installation. It was pointless to have them shipped in the tarball, but requiring user to add some weird flags to ./configure. Antiidle, fakeonline and motdfile modules are dropped. Away module is renamed to awaystore to better explain its meaning. ## Fixes * Don't try IPv6 servers when IPv6 isn't available. Use threads for non-blocking DNS instead of c-ares. * Fix debug output of identfile. * Don't forward WHO replies with multi-prefix to clients which don't support multi-prefix * Send nick changes to clients before we call the OnNick module hook * Don't connect to SSLed IRC servers when ZNC is compiled without SSL support * Fix check for visibility support in the compiler * Fix compilation on cygwin again, including modperl and modpython * Support parting several channels at once * Fix a crash in admin (now controlpanel) module * Fix webadmin to deny setting a bindhost that is not on the global list of allowed bindhosts. * Fix using empty value for defaults in user page in webadmin. ## Minor Stuff * Rename admin module to controlpanel to make it clearer that it's not the same as admin flag of a user. * Add protection from flood. If you send multiple lines at once, they will be slowed down, so that the server will not disconnect ZNC due to flood. It can be configured and can be completely turned off. Default settings are: 1 line per second, first 4 lines are sent at once. * Modules can support several types now: a module can be loaded as a user module, as a network module and as a global module, if the module supports these types. * Rename (non-)KeepBuffer to AutoClearChanBuffer * Process starttls numeric * Improvements to modperl, modpython, modtcl. * Add timestamps to znc --debug * Listeners editor in webadmin * Add sasl module which uses SASL to authenticate to NickServ. * Rename saslauth to cyrusauth, to make it clearer that it's not needed to do SASL authentication to NickServ. * Modules get a way to describe their arguments. * webadmin: allow editing of the bindhost without global list. * Don't send our password required notice until after CAP negotiation * Rewrite the JOIN channel logic, dropping MaxJoins * Support messages directed to specific user prefixes (like /msg @#channel Hello) * Show link to http://znc.in/ from web as a link. It was plain text before. * Webadmin: use HTML5 numeric inputs for numbers. * Add SSL/IPv6/DNS info to znc --version * Clarify that only admins can load the shell module. * cyrusauth: Allow creating new users on first login * Clear channel buffers when keep buffer is disabled if we're online * send_raw: Add a command to send a line to the current client * webadmin: Implement clone user * autoreply: Honor RFC 2812. * Add 381 to the buffer ("You are now an IRC Operator") * identfile: Pause the connection queue while we have a locked file * Add ShowBindHost command * autoop: Check for autoops when we get op status * Improvements and fixes to the partyline module * partyline Drop support for fixed channels * Check that there're modules available on startup. Check if ZNC is installed or not. * Modified description field for bouncedcc module to explain what the module actually does. * nickserv: add support for nickserv requests on wenet.ru and rusnet. * send 422 event if MOTD buffer is empty * route_replies: Handle much more replies * Clear text colors before appending timestamps to buffer lines, add space before AppendTimestamp for colorless lines. * Don't replace our motd with a different servers motd * webadmin: Add a "Disabled" checkbox for channels * Send a 464 ERR_PASSWDMISMATCH to clients that did not supply a password * Separate compilation and linking for modules. * Trim spaces from end of commands to autoattach. * nickserv: add ghost, recover and release * Warn if config was saved in a newer ZNC version. * Backup znc.conf when upgrading ZNC. ## Internal Stuff * #include instead of #include "...h" * Add string formatting function with named params. * Python, perl: support global, user, network modules. * Csock: able use non-int number of secs for timer. * CString("off").ToBool() shouldn't be true * Python: Override __eq__ to allow comparison of strings * python: Allow iterating over CModules * Add methods to CModule to get the web path * Rework modperl to better integrate with perl. * Store all 005 values in a map. * Python: Use znc.Socket if no socket class is specified in CreateSocket() * CZNC::WriteConfig(): Better --debug output * Slight refactor of CBuffer & CBufLine. * Implemented an OnInvite hook * Allow a client to become "away" * Create a connection queue * Set default TrimPrefix to ":" * Add a config writer * Wrap MODULECALL macros in a do-while * Don't require CTimer's label to be unique if its empty * Allow loading python modules with modpython (ex. modname/__init__.py) * bNoChange in On{,De}{Op,Voice} wast incorrect * Drop znc-config, change znc-buildmod so it doesn't need znc-config # ZNC 0.206 (2012-04-05) ## Fixes * Identfile: don't crash when ZNC is shutting down. * CTCPReplies setting with empty value now blocks those CTCP requests to the client. * Show more sane error messages instead of "Error: Success". * Imapauth: Follow RFC more closely. * "No" is a false value too. ## Minor stuff * Add Show command to identfile, which should help you understand what's going on, if identfile is blocking every connection attempt for some reason. * Make TLS certs valid for 10 years. * Ask for port > 1024 in --makeconf. * Reset JoinTries counter when we enable a channel. # ZNC 0.204 (2012-01-22) This release fixes CVE-2012-0033, http://www.openwall.com/lists/oss-security/2012/01/08/2 https://bugs.gentoo.org/show_bug.cgi?id=CVE-2012-0033 https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2012-0033 ## Fixes * Fix a crash in bouncedcc module with DCC RESUME. * Fix modperl compilation. * Don't use mkdir during install. * Fix compilation failures, which happened sometimes when an older ZNC was already installed. * Check for the swig2.0 binary too, instead of only swig. ## Minor stuff * Unload modules in reverse order. * Don't send server redirects (numeric 010) to clients. * Make it possible to filter the result of the help command. * Drop @DEFS@ from the build system so that we don't force HAVE_CONFIG_H upon others. * Move autocycle to extra. * Handle raw 482 in route_replies. * Improve identfile's debug messages. * Send a MODE request when JOINing. * Block raw 301 in antiidle. # ZNC 0.202 (2011-09-21) This is a bugfix-mostly release. ## Fixes * Fix a crash when a user changes the buffer size of a channel. * Fix a NULL pointer dereference in buffer-related module hooks. * Fix the autocycle module to not fight with ChanServ. * Fix the getchan command in the admin module. * Don't timeout bouncedcc connections so that idling DCC chats are possible. * Fix build error when compiling against uclibc(++). ## Minor stuff * Improve the timeout message in the route_replies module. * Add the -r parameter to the man page of ZNC. * Install .py files along with .pyc. # ZNC 0.200 (2011-08-20) ## The Big News * Move ident spoofing from ZNC core into new identfile module. * Move dcc handling from ZNC core into new modules bouncedcc and dcc. * Remove the obsolete fixfreenode module. * New module: cert * Move away into ZNC-Extra. ## Fixes * In ZNC 0.098 there was a memleak whenever someone JOINs a channel. * Compile even when OpenSSL was built with no-ssl2. * Correctly handle excessive web sessions. * Correctly save non-ASCII characters to the NV. * Fix znc-buildmod when ZNC was compiled out of tree. * Don't always use IPv6 when verifying the listener in --makeconf. ## Minor Things * Remove a pointless MODE request which ZNC sent on every JOIN. * Raise ZNC's timeouts. * Log's logging path becomes configurable. * Add a replay command to away. * Add a get command to notes. * Add -disableNotesOnLogin argument to notes. * Add hostmask handling to autoattach. * Make it possible for modules to provide additional info, e.g. providing a homepage URL. * Various improvements to modpython. * Hardcode a default entry for the CN in znc --makepem. * Work around Mac OS' and Solaris' brokenness. * Make ZNC compile without getopt_long(). This fixes compilation on e.g. Solaris 9 and hopefully Irix. * Check for errors like "no space left on disk" while writing the config file. * Improve the error handling when reading the config. * Move module data files to own directory in the source and in installation prefix. * Handle Listeners after SSLCertFile during startup. * Check for required SWIG version in ./configure. * Make it possible to use ExpandString-stuff in QuitMsg. * znc-buildmod: Print ZNC's version number. * Add config option ProtectWebSessions which makes it possible to disable the IP check for web sessions. ## Internal Stuff * Build modules with hidden symbol visibility. * Clean up includes. This might break external modules. * New CModCommand for simplifiying module commands. * Add the OnIRCConnectionError(CIRCSock *pIRCSock) module hook * Remove config-related module hooks. * Fix CString::Escape_n() * Make the CUser::IsIRCConnected method check if ZNC already successfully logged in to IRC. * and more... # ZNC 0.098 (2011-03-28) ## New stuff * Add a list of features to the output of /znc version. (cce5824) * Add modpython. (a564e2) (88c84ef) (1854e1) (9745dcb) (644632) (4c6d52c) (b7700fe) (0f2265c) * Verify during --makeconf that the specified listener works. (11ffe9d) * Add TimestampFormat and StatusPrefix settings to admin. (853ddc5) * Add DCCBindHost, channel keys and some more to webadmin. (570fab6) (eb26386) (1e0585c) * Add a web interface to listsockets. (144cdf) * Add a web interface to perform. (c8910c) (89edf703) (ba183e46) * Don't reply to CTCP floods. (142eeb) * Accept wildcards for /znc DetachChan, EnableChan, ClearBuffer and SetBuffer. (e66b24) * Added reconnect and disconnect commands to admin. (65ae83) * Moved from SourceForge to GitHub. (daa610) (ed17804) (86c0e97) (e6bff0c) (087f01) * Don't force --foreground when compiling with --enable-debug. (778449) (fbd8d6) * Add functions for managing CTCPReplies to admin. (3f0e200) * Allow omitting user names with some commands in admin. (4faad67) * Some fixed with ISpoofFile and SSLCertFile paths which use "~". (cd7822) (f69aeff) (ce10cee) ## Fixes * Send more than a single channel per JOIN command. (3327a97) (6a1d27) * Properly unload perl modules so that their code is reread on next load. (ce45917) * Make certauth remember its module list across restarts again. (451b7e32) * Ignore dereferenced sockets in listsockets. (50b57b) * Fix a cross-compilation problem in configure. (d9b4ba1) * Bind web sessions to IP addresses. (577a097) (4556cc) * Limit the number of web sessions per IP. (913a3c8) (bf6dc45) * Build on cygwin again. (37b70a) * Allow admins to ignore MaxBufferSize in webadmin. (b37e23) * Fix some compiler warning generated by clang. (c7c12f0) * Call modules for mode-changes done by not-in-channel nicks. (a53306) * Fix installation with checkinstall and the permissions of some static data. (4c7808) (3d3235) ## Minor stuff * Properly report errors in admin's addserver command. (ed924cb) * Improvements of modperl. (1baa019) (12b1cf6) (7237b9) (ece2c88) * Check for modperl that we have at least Perl 5.10. (0bc606) * Verify in configure that tcl actually works. (89bd527) * Add a warning header to znc.conf that warns about editing the file. (2472ea) (8cadb6) * Improve the ISpoof debug output. (128af8e) * Improve HTTP client-side caching for static files. (9ef41ae) (4e5f9e8) * Removed all generated/copied scripts from the repo. (fde73c60) (9574d6) (e2ce2cf) (5a6a7b) * Only allow admins to use email. (81c864) * Make clearbufferonmsg clear the buffer a little less often. (ddd302fbf) * Make the output from /znc help smaller. (0d928c) * Add a web interface to send_raw. (d8b181) (a93a586) ## Internal stuff * Some optimizations with lots of users and channels. (b359f) (5e070e) * Various changes to the Makefiles. (33e1ccc) (0ad5cf) (df3409) (e17348c) (936b43) (18234a) (0cc8beb) (d21a1be) (517307b) (40632f) (afa16df) (4be0572) (452e3f) (9fec8f) (f76f1e7) (6b396f) * Added a third argument to the OnPart module hook. (a0c0b7) (1d10335) (e4b48d5) * Added vim modelines to some files. (dc8a39) * Added an auto-generated zncconfig.h (8a1c2a4) (aeeb1eb3) (f4927709) (40a1bb) (3ecbf13) (87037f) (b6c8e1) * Update to latest Csocket. (cc552f) * Handle paths like "~/foo" in CFile. (cb2e50a) * Add some generic interface for module commands. (ebd7e53) (8e59fb9) (31bbffa) * CUser::m_sUserName was made const. (d44e590) # ZNC 0.096 (2010-11-06) ## New stuff * Added a new module: clearbufferonmsg. (r2107) (r2151) * Added an optional server name argument to /znc jump. (r2109) * Big overhaul for modperl. (r2119) (r2120) (r2122) (r2123) (r2125) (r2127) (r2133) (r2136) (r2138) (r2140) (r2142) (r2143) (r2144) (r2146) (r2147) (r2156) (r2160) * Modules can now directly influence other modules' web pages. (r2128) (r2129) (r2130) (r2131) (r2132) (r2134) (r2135) ## Fixes * The route_replies module now handles "354" who replies. (r2112) * Fixed a bogus "invalid password" error during login with some clients. (r2117) * Reject long input lines on incoming connections. (r2124) * The lastseen module should only link to webadmin if the latter is loaded. (r2126) * Fixed cases where HTTP requests were incorrectly dropped. (r2148) (r2149) * Fixed partyline to work with servers that don't send a 005 CHANTYPES. (r2162) * Fixed error message from configure if dlopen() isn't found. (r2166) ## Minor stuff * Renamed "vhost" to "bindhost" to better describe what the option does. (r2113) * Honor timezone offset in the simple_away module. (r2114) * Load global modules as soon as their config line is read. (r2118) * Use poll() instead of select() by default. (r2153) (r2165) * Ignore the channel key "*" in the chansaver module. (r2155) ## Internal stuff * Fixed some function prototypes. (r2108) * Rearranged ZNC's CAP handling to IRCds. (r2137) * Added more doxygen comments. (r2139) (r2145) (r2150) (r2152) (r2154) (r2157) * Removed some useless typedefs. (r2158) * Clean up the lastseen module. (r2163) (r2164) # ZNC 0.094 (2010-08-20) ## New stuff * Add new global setting MaxBufferSize instead of hardcoding a value. (r2020) (r2025) * Support CAP. (r2022) (r2024) (r2027) (r2041) (r2048) (r2070) (r2071) (r2097) (r2098) (r2099) (r2100) * Add new module certauth which works similar to certfp. (r2029) * route_replies now also supports routing channel ban lists, ban exemptions and invite exceptions. (r2035) * Add a -nostore flag to the away module. (r2044) * Add a new config option SSLCertFile. (r2086) (r2088) ## Fixes * Fix configure to automatically disable modperl if perl is not found. (r2017) * Include the port number in cookie names to make them unique across different znc instances on the same box. (r2030) * Make sure that we have at least c-ares 1.5.0. (r2055) * Make znc work on solaris. (r2064) (r2065) (r2067) (r2068) * Improve configure's and make's output. (r2079) (r2080) (r2094) (r2101) * Complain about truncated config files. (r2083) * Fix some std::out_of_range error triggerable by people with a valid login. (r2087) (r2093) (r2095) * Make fakeonline behave while we are not connected to an IRC server. (r2091) * Always attach to channels when joining them. (r2092) * Fix a NULL pointer dereference in route_replies. (r2102) (r2103) ## Minor stuff * Allow leading and trailing spaces in config entries. (r2010) * Various minor changes. (r2012) (r2014) (r2021) * Use pkg-config for finding openssl, if it's available. We still fall back to the old code if this fails. (r2018) * znc no longer accepts an alternative file name for znc.conf as its argument. (r2037) * Generate correct HTTP status codes in webmods and make sure this doesn't happen again. (r2039) (r2040) * Rewrite our PING/PONG handling. (r2043) * Raise the size of the query buffer to 250. (r2089) * Update to latest Csocket. (r2096) ## Internal stuff * Remove the fake module usage in WebMods. (r2011) * Remove fake modules completely. (r2012) (r2015) * Make CTable more robust. (r2031) * Move the OnKick() module call so it is issued when the nick still is visible in the channel. (r2038) * Remove CZNC::GetUser() since CZNC::FindUser() does the same. (r2046) * Minor changes to webmod skins. (r2061) (r2062) * Add new macros GLOBALMODULECALL and ALLMODULECALL. (r2074) (r2075) (r2076) * Remove a bogus CClient* argument from some module calls. (r2077) * Mark some functions as const. (r2081) (r2082) (r2084) (r2085) # ZNC 0.092 (2010-07-03) This is a bugfix-only release, mainly for fixing CVE-2010-2488. ## Fixes * ZNC wrongly counted outgoing connections towards the AnonIPLimit config option. (r2050) * The traffic stats caused a NULL pointer dereference if there were any unauthenticated connections. CVE-2010-2488 (r2051) * Csocket had a bug where a wrong error message was generated and one that caused busy loops with c-ares. (r2053) # ZNC 0.090 (2010-06-06) ## Upgrading from previous versions ## Errors during start-up The shell, email and imapauth modules have been moved from the regular module set to the "extra" set, you have to use --enable-extra with ./configure to compile them. So, to fix these errors, edit the znc.conf file in ~/.znc/configs and don't load those modules, or recompile znc with extra. ### WebMods While previously only the "webadmin" provided an HTTP server/interface, the HTTP server is now integrated into ZNC's core. This means that all modules (not only webadmin) can now provide web pages. Examples shipping with ZNC are lastseen, stickychan and notes. Old-style module arguments to webadmin will be automatically converted to the new syntax. Please note that the WebMods interface uses session cookies instead of 'Basic' HTTP authentication. All URLs to webadmin's settings pages have changed. Please adjust your scripts etc. if necessary. ### Running without installing If you want to run ZNC without doing make install, i.e. if you want to run it from the source dir, you will have to add --enable-run-from-source as an argument to ./configure. You do not have to care about this if you use a --prefix= or if you install ZNC system-wide. ### I upgraded and WebAdmin/WebMods is acting weird, Log Out does not work. Starting with 0.090, ZNC uses cookies instead of HTTP Basic authentication. If your browser is still sending the Basic credentials to ZNC, e.g. because you have saved them in a bookmark, or password manager, or simply haven't restarted your browser in a while, those will continue to work, even after you click the Log Out button. To fix this, remove any user:pass@host portions from your bookmarks, remove all entries for ZNC's web interface from your password manager, and restart your browser. ## New stuff * Webmods - Every module can now provide its own webpages. (r1784) (r1785) (r1787) (r1788) (r1789) (r1790) (r1791) (r1792) (r1793) (r1795) (r1796) (r1797) (r1800) (r1801) (r1802) (r1804) (r1805) (r1806) (r1824) (r1825) (r1826) (r1827) (r1843) (r1844) (r1868) (r1886) (r1888) (r1915) (r1916) (r1931) (r1934) (r1870) (r1871) (r1872) (r1873) (r1874) (r1875) (r1876) (r1879) (r1887) (r1891) (r1967) (r1982) (r1984) (r1996) (r1997) (r2000) (r2002) (r2003) * Webmods and thus webadmin now use cookies for managing sessions instead of HTTP authentication. (r1799) (r1819) (r1823) (r1839) (r1840) (r1857) (r1858) (r1859) (r1861) (r1862) * WebMod-enabled lastseen, stickychan modules. (r1880) (r1881) (r1889) (r1918) * Partyline now also handles notices, /me and CTCP. (r1758) * Partyline now saves channel topics across restarts. (r1898) (r1901) * Added a "number of channels" column to /znc listusers. (r1769) * Added an optional user name argument to /znc listchans. (r1770) * Support for the general CAP protocol and the multi-prefix and userhost-in-names caps on connections to the IRC server. (r1812) * ZNC can now listen on IPv4-only, IPv6-only or on both-IP sockets. Renamed "Listen" config option to "Listener". (r1816) (r1817) (r1977) * Added LoadModule, UnLoadModule, ListMods commands to the Admin module. (r1845) (r1864) * Added ability to set/get TimezoneOffset to the Admin module. (r1906) * Added "Connect to IRC + automatically re-connect" checkbox to webadmin. (r1851) * Remember "automatically connect + reconnect" flag across restarts by writing it to the config file. (r1852) * Added AddPort, DelPort, ListPorts command to *status. (r1899) (r1913) * Added optional quit message argument to disconnect command. (r1926) * Added new charset module to extra. (r1942) (r1947) (r1977) (r1985) (r1994) * Added a traffic info page to webadmin. (r1958) (r1959) ## Fixes * Don't let ZNC connect to itself. (r1760) * Added a missing error message to /znc updatemod. (r1772) * Generate cryptographically stronger certificates in --makepem. (r1774) * Autoattach now triggers on channel actions. (r1778) * --disable-tcl now really disables TCL instead of enabling it. (r1782) * User name comparison in blockuser is now case-sensitive. (r1786) * Fixed /names when route_replies is loaded. (r1811) * autoreply now ignores messages from self. (r1828) * Don't forward our own QUIT messages to clients. (r1860) * Do not create empty directories if one does ./znc --datadir=NON_EXISTING_DIR. (r1878) * Query to Raw send the command to IRC instead of to the client. (r1892) * Fixed desync in Partyline after addfixchan or delfixchan. (r1904) * Save passwords for Nickserv module as NV instead of keeping them as arguments. (r1914) * CSRF Protection. (r1932) (r1933) (r1935) (r1936) (r1938) (r1940) (r1944) * Fixed a rare configure failure with modperl. (r1946) * disconkick now only sends kicks for channels the client actually joined. (r1952) * More sanity checks while rewriting znc.conf. (r1962) * Fixed static compilation with libcrypto which needs libdl by checking for libdl earlier. (r1969) * Fixed modtcl with newer tcl versions. (r1970) * Better error message if pkg-config is not found. (r1983) * Fixed a possible race condition in autoop which could cause bogous "invalid password" messages. (r1998) ## Minor stuff * Fixed a memory leak and some coding style thanks to cppcheck. (r1761) (r1762) (r1763) (r1764) (r1776) (r1777) * Updated to latest Csocket. (r1766) (r1767) (r1814) (r1905) (r1930) * Cleanup to /znc help. (r1771) * Removed --disable-modules. Modules are now always enabled. (r1794) (r1829) * saslauth: Error out "better" on invalid module arguments. (r1809) * Changed the default ConnectDelay from 30s to 5s. (r1822) * Misc style/skin fixes to webadmin/webmods. (r1853) (r1854) (r1856) (r1883) (r1884) (r1885) (r1890) (r1900) (r1907) (r1908) (r1909) (r1911) (r1912) (r1917) (r1945) (r2005) * Do not expose ZNC's version number through the web interface unless there's an active user session. (r1877) * Updated AUTHORS file. (r1902) (r1910) (r1999) * Moved some modules into/out of extra. (r1919) (r1922) (r1923) * Added ./configure --enable-run-from-script, without it ZNC will no longer look for modules in ./modules/. (r1927) (r1928) (r2001) * Made a dedicated page to confirm user deletion in webadmin. (r1937) (r1939) (r1941) (r1943) * Use spaces for separating ip addresses from ports. (r1955) * ZNC's built-in MOTD now goes through ExpandString. (r1956) * Check for root before generating a new config file. (r1988) * Added a flag for adding irc-only / http-only ports via /znc addport. (r1990) (r1992) ## Internal stuff * Minor cleanup to various places. (r1757) (r1759) (r1846) (r1847) (r1863) (r1865) (r1920) (r1921) (r2004) * Changes in configure. (r1893) (r1894) (r1895) (r1896) (r1897) * Flakes messed with the version number. (r1768) * CString::Split() now Trim()s values before pushing them if bTrimWhiteSpace is true. (r1798) * Added new module hooks for config entries. (r1803) (r1848) (r1849) (r1850) * New module hook OnAddUser(). (r1820) (r1821) * Cleanup to ISUPPORT parser. (r1807) * Use Split() instead of Token() where possible. (r1808) * Modularize CIRCSock::ForwardRaw353(). (r1810) * Use a better seed for srand(). (r1813) * Changes to debug output. (r1815) (r1836) (r1837) (r1855) (r1882) * Support for delayed HTTP request processing. (r1830) (r1833) (r1834) (r1835) (r1838) (r1841) (r1842) * Fixed CSmartPtr's operator==. (r1818) * Better port/listener management exposed through CZNC. (r1866) (r1867) * Move CListener and CRealListener into their own files. (r1924) * Move the HTTP/IRC switching to CIncomingConnection. (r1925) * Add IsIRCAway() to CUser. (r1903) * Move some common pid file code into new InitPidFile(). (r1929) * Templates can now sort loops based on a key. (r1948) (r1949) (r1951) (r1953) (r1954) * A lot of work went into this release, we would like to thank everyone who contributed code, helped testing or provided feedback. # ZNC 0.080 (2010-02-18) ## New stuff * Update to latest Csocket. (r1682) (r1727) (r1735) (r1751) (r1752) (r1753) * Only allow admins to load modtcl unless -DMOD_MODTCL_ALLOW_EVERYONE is used. (r1684) * Include /me's in the query buffer. (r1687) * Some tweaks to savebuff to differentiate it more from buffextras. (r1691) (r1692) * send_raw can now also send to clients. (r1697) * Move the "Another client authenticated as you"-message into new module clientnotify. (r1698) * Imported block_motd module into extra. (r1700) * Imported flooddetach into extra. (r1701) (r1717) * Added new setting ServerThrottle which sets a timeout between connections to the same server. (r1702) (r1705) * Only ask for the more common modules in --makeconf. (r1706) * Use UTF-8 as default charset for webadmin. (r1716) * Revamped default webadmin skin. It's very grayish, but looks way more like 2010 than the old default skin does. (r1720) * New font style for the "ice" webadmin skin. (r1721) * Added a summary line to /znc listchans. (r1729) * The admin module can now handle more settings and got some missing permission checks added. (r1743) (r1744) (r1745) (r1746) (r1747) ## Fixes * Apply new ConnectDelay settings immediately after a rehash. (r1679) * Do a clean shutdown just before a restart. (r1681) * Fix a theoretical crash in modtcl. (r1685) * Users should use the correct save and download path after Clone(). (r1686) * Several improvements to znc-buildmod. (r1688) (r1689) (r1690) (r1695) * Fix a crash with modperl by loading modules differently. (r1714) * Fix HTTP Cache-Control headers for static files served by webadmin. (r1719) * Send the nicklist to a user who is being force-rejoined in partyline. (r1731) * Set the issuer name in CUtils::GenerateCert(). (r1732) * Fixed some inconsistency with /znc reloadmod. (r1749) * Added a work-around for SSL connections which incorrectly errored out during handshake. (r1750) ## Minor stuff * Don't try to catch SIGILL, SIGBUS or SIGSEGV, the default action will do fine. (r1680) * Added IP-address to messages from notify_connect. (r1699) * Switched to Csocket's own c-ares code. (r1704) (r1707) (r1708) (r1709) (r1710) (r1712) (r1713) * Add more doxygen comments. (r1715) (r1718) (r1737) * Removed useless "add your current ip" checkbox from webadmin's edit user page. (r1722) * Don't try to request a MOTD if there is none. (r1728) ## Internal stuff * It's 2010, where's my hoverboard? (r1693) * Got rid of Timers.h. (r1696) * Added a Clone() method to CNick. (r1711) * Call OnChanAction() after OnChanCTCP(). (r1730) * Random cleanups to CFile::Delete(). (r1694) * Other random cleanups. (r1723) (r1724) (r1725) (r1726) (r1736) * Move the implementation of CSocket to Socket.cpp/h. (r1733) # ZNC 0.078 (2009-12-18) ## New stuff * Add a DCCVHost config option which specifies the VHost (IP only!) for DCC bouncing. (r1647) * Users cloned via the admin module no longer automatically connect to IRC. (r1653) * Inform new clients about their /away status. (r1655) * The "BUG" messages from route_replies can now be turned off via /msg *route_replies silent yes. (r1660) * Rewrite znc.conf on SIGUSR1. (r1666) * ISpoofFormat now supports ExpandString. (r1670) ## Fixes * Allow specifing port and password for delserver. (r1640) * Write the config file on restart and shutdown. (r1641) * Disable c-ares if it is not found unless --enable-c-ares was used. (r1644) (r1645) * blockuser was missing an admin check. (r1648) * Sometimes, removing a server caused znc to lose track of which server it is connected to. (r1659) * Include a more portable header for uint32_t in SHA256.h. (r1665) * Fixed cases where ZNC didn't properly block PONG replies to its own PINGs. (r1668) * Fixed a possible crash if a client disconnected before an auth module was able to verify the login. (r1669) * Away allowed to accidentally execute IRC commands. (r1672) * Correctly bind to named hosts if c-ares is enabled. (r1673) * Don't accept only spaces as QuitMsg because this would cause an invalid config to be written out. (r1675) ## Minor stuff * Comment out some weird code in Client.cpp. (r1646) * Remove connect_throttle since it's obsoleted by fail2ban. (r1649) * Remove outdated sample znc.conf. (r1654) * route_replies now got a higher timeout before it generates a "BUG" message. (r1657) * Documented the signals on which znc reacts better. (r1667) ## Internal stuff * New module hook OnIRCConnecting(). (r1638) * Remove obsolete CUtils::GetHashPass(). (r1642) * A module's GetDescription() now returns a C-String. (r1661) (r1662) * When opening a module, check the version number first and don't do anything on a mismatch. (r1663) # ZNC 0.076 (2009-09-24) ## New stuff * Add a make uninstall Makefile target. (r1580) * Imported modules from znc-extra: fixfreenode, buffextras, autoreply, route_replies, adminlog. (r1591) (r1592) (r1593) (r1594) (r1595) * Imported the rest of znc-extra under modules/extra hidden behind configure's --enable-extra. (r1605) (r1606) (r1608) (r1609) (r1610) * ZNC now uses SHA-256 instead of MD5 for hashing passwords. MD5 hashes still work correctly. (r1618) ## Fixes * Don't cache duplicate raw 005 (e.g. due to /version). (r1579) * Send a MODE removing all user modes to clients when we lose the irc connection. (r1583) * Use a nickmask instead of a nick as the source for ZNC-generated MODE commands. (r1584) * Use the right error codes if startup fails. (r1585) * Fix a NULL pointer dereference in some of the ares-specific code. (r1586) * VHost and Motd input boxes in graphiX and dark-clouds in webadmin didn't insert newlines. (r1588) * Generate proper error messages when loading modules. This was broken since znc 0.070. (r1596) * Allow unloading of removed modules. This was broken since znc 0.070. (r1597) * Fix savebuff with KeepBuffer = false. (r1616) * Fix accidental low buffer size for webadmin sockets. (r1617) * AltNicks are no longer truncated to 9 characters. (r1620) * Webadmin can now successfully add new admin users and have them load the shell module. (r1625) * Webadmin no longer includes the znc version in the auth realm. (r1627) * CUser::Clone now handles modules after all other settings, making it work with shell. (r1628) * Some CSS selectors in webadmin's dark-clouds and graphiX skins were wrong. (r1631) * The help of admin was improved. (r1632) (r1633) ## Minor stuff * make distclean now also removes the pkg-config files. (r1581) * Add the autoconf check for large file support. (r1587) * Generic "not enough arguments" support for route_replies and some fix for /lusers. (r1598) (r1600) * ZNC now tries to join channels in random order. (r1601) (r1602) (r1603) * route_replies now handles "No such channel" for /names. (r1614) * Fixes a theoretical crash on shutdown. (r1624) * saslauth was moved to znc-extra. (r1626) ## Internal stuff * Now using autoconf 2.64. (r1604) * Removed unused classes CNoCopy and CSafePtr. (r1607) * Moved CZNC::FindModPath() to CModules. (r1611) * Added CModules::GetModDirs() as a central place for finding module dirs. (r1612) (r1629) * Added CModules::GetModPathInfo() which works like GetModInfo() but which takes the full path to the module. (r1613) * Updated to latest Csocket which adds openssl 1.0 compatibility and fixes some minor bug. (r1615) (r1621) * Merged the internal join and ping timers. (r1622) (r1623) # ZNC 0.074 (2009-07-23) ## Fixes * Fix a regression due to (r1569): Webadmin was broken if the skins were accessed through an absolute path (=almost always). (r1574) * Fix a possible crash if users are deleted while they have active DCC sockets. (r1575) Sorry for breaking your webadmin experience guys. :( # ZNC 0.072 (2009-07-21) All webadmin skins are broken in this release due to a bug in webadmin itself. This is fixed in the next release. High-impact security bugs There was a path traversal bug in ZNC which allowed attackers write access to any place to which ZNC has write access. The attacker only needed a user account (with BounceDCCs enabled). Details are in the commit message. (r1570) This is CVE-2009-2658. Affected versions All ZNC versions since ZNC 0.022 (Initial import in SVN) are affected. ## New stuff * /msg *status uptime is now accessible to everyone. (r1526) * ZNC can now optionally use c-ares for asynchronous DNS resolving. (r1548) (r1549) (r1550) (r1551) (r1552) (r1553) (r1556) (r1565) (r1566) * The new config option AnonIPLimit limits the number of unidentified connections per IP. (r1561) (r1563) (r1567) ## Fixes * znc --no-color --makeconf still used some color codes. (r1519) * Webadmin favicons were broken since (r1481). (r1524) * znc.pc was installed to the wrong directory in multilib systems. (r1530) * Handle flags like e.g. --allow-root for /msg *status restart. (r1531) (r1533) * Fix channel user mode tracking. (r1574) * Fix a possible crash if users are deleted while they are connecting to IRC. (r1557) * Limit HTTP POST data to 1 MiB. (r1559) * OnStatusCommand() wasn't called for commands executed via /znc. (r1562) * On systems where sizeof(off_t) is 4, all ZNC-originated DCCs failed with "File too large (>4 GiB)". (r1568) * ZNC didn't properly verify paths when checking for directory traversal attacks (Low impact). (r1569) ## Minor stuff * Minor speed optimizations. (r1527) (r1532) * stickychan now accepts a channel list as module arguments. (r1534) * Added a clear command to nickserv. (r1554) * Added an execute command to perform. (r1558) * Added a swap command to perform. (r1560) * fail2ban clears all bans on rehash. (r1564) ## Internal stuff * The API for traffic stats changed. (r1521) (r1523) * Some optimizations to CSmartPtr. (r1522) * CString now accepts an optional precision for converting floating point numbers. (r1525) * Made home dir optional in CDir::ChangeDir(). (r1536) * Stuff. (r1537) (r1550) * EMFILE in CSockets is handled by closing the socket. (r1544) * Special thanks to cnu and flakes! # ZNC 0.070 (2009-05-23) ## New stuff * Add a CloneUser command to admin. (r1477) * Make webadmin work better with browser caches in conjunction with changing skins. (r1481) (r1482) * Better error messages if binding a listening port fails. (r1483) * admin module now supports per-channel settings. (r1484) * Fix the KICK that partyline generates when a user is deleted. (r1486) * fail2ban now allows a couple of login attempts before an IP is banned. (r1489) * Fixed a crash bug in stickychan. (r1500) * Install a pkg-config .pc file. (r1503) * Auto-detect globalness in re/un/loadmod commands. (r1505) ## Fixes * Fix a bug where ZNC lost its lock on the config file. (r1457) * Limit DCC transfers to files smaller than 4 GiB. (r1461) * Make znc -D actually work. (r1466) * Make znc --datadir ./meh --makeconf work. The restart used to fail. (r1468) * Fix a crash bug if CNick::GetPermStr() was called on CNick objects from module calls. (r1491) * Some fixes for solaris. (r1496) (r1497) (r1498) * nickserv module now also works on OFTC. (r1502) * Make sure the "Invalid password" message is sent before a client socket is closed. (r1506) * Fix a bug where ZNC would reply with stale cached MODEs for a "MODE #chan" request. (r1507) ## Minor stuff * Man page updates. (r1467) * Make CFile::Close() check close()'s return values if --debug is used. (r1476) * Update to latest Csocket. (r1490) * Improve the error messages generated by /msg *status loadmod. (r1493) * Remove broken znc --encrypt-pem. (r1495) ## Internal stuff * cout and endl are included in Utils.h, not main.h. (r1449) * CFile::Get*Time() now return a time_t. (r1453) (r1479) * Switched some more CFile members to more appropriate return types. (r1454) (r1471) * CFile::Seek() now takes an off_t as its argument. (r1458) * Turn TCacheMap into more of a map. (r1487) (r1488) * Updates to latest Csocket. (r1509) * API breakage: CAuthBase now wants a Csock* instead of just the remote ip. (r1511) (r1512) * New Module hooks (r1494) * OnChanBufferStarting() * OnChanBufferPlayLine() * OnChanBufferEnding() * OnPrivBufferPlayLine() # ZNC 0.068 (2009-03-29) ## New stuff * watch now uses ExpandString on the patterns. (r1402) * A user is now always notified for failed logins to his account. This now also works with auth modules like imapauth. (r1415) (r1416) * Added /msg *status UpdateModule which reloads an user module on all users. (r1418) (r1419) * A module whose version doesn't match the current ZNC version is now marked as such in ListAvailModules and friends. (r1420) * Added a Set password command to admin. (r1423) (r1424) * ZNC no longer uses znc.conf-backup. (r1432) * Two new command line options were added to ZNC: * ZNC --foreground and znc -f stop ZNC from forking into the background. (r1441) * ZNC --debug and znc -D produce output as if ZNC was compiled with --enable-debug. (r1442) (r1443) ## Fixes * cd in shell works again. (r1401) * Make WALLOPS properly honour KeepBuffer. Before this, they were always added to the replay buffer. (r1405) * ZNC now handles raw 432 Illegal Nickname when trying to login to IRC and sends its AltNick. (r1425) * Fix a crash with recursion in module calls. (r1438) * Fixed some compiler warnings with -Wmissing-declarations. (r1439) ## Minor stuff * Allow a leading colon on client's PASS commands. (r1403) * CFile::IsDir() failed on "/". (r1404) * CZNC::AddUser() now always returns a useful error description. (r1406) * Some micro-optimizations. (r1408) (r1409) * The new default for JoinTries is 10. This should help some excess flood problems. (r1411) * All webadmin skins must now reside inside the webadmin skins dir or they are rejected. (r1412) * Watch now saves its module settings as soon as possible, to prevent data loss on unclean shutdown. (r1413) (r1414) * Regenerated configure with autoconf 2.63. (r1426) * Some dead code elimination. (r1435) * Clean up znc -n output a little. (r1437) ## Internal stuff * CString::Base64Decode() now strips newlines. (r1410) * Remove CModInfo::IsSystem() since it was almost unused and quite useless. (r1417) * Some minor changes to CSmartPtr. (r1421) (r1422) * Added CFile::Sync(), a fsync() wrapper. (r1431) # ZNC 0.066 (2009-02-24) There was a privilege escalation bug in webadmin which could allow all ZNC users to write to znc.conf. They could gain shell access through this. (r1395) (r1396) This is CVE-2009-0759. ## Affected versions This bug affects all versions of ZNC which include the webadmin module. Let's just say this affects every ZNC version, ok? ;) ## Who can use this bug? First, ZNC must have the webadmin module loaded and accessible to the outside. Now any user who already has a valid login can exploit this bug. An admin must help (unknowingly) to trigger this bug by reloading the config. ## Impact Through this bug users can write arbitrary strings to the znc.conf file. Unprivileged ZNC users can make themselves admin and load the shell module to gain shell access. Unprivileged ZNC users can temporarily overwrite any file ZNC has write access to via ISpoof. This can be used to overwrite ~/.ssh/authorized_keys and gain shell access. Unprivileged ZNC users can permanently truncate any file to which ZNC has write access via ISpoof. ZNC never saves more than 1kB for restoring the ISpoofFile. ## How can I protect myself? Upgrade to ZNC 0.066 or newer or unload webadmin. ## What happens? Webadmin doesn't properly validate user input. If you send a manipulated POST request to webadmin's edit user page which includes newlines in e.g. the QuitMessage field, this field will be written unmodified to the config. This way you can add new lines to znc.conf. The new lines will not be parsed until the next rehash or restart. This can be done with nearly all input fields in webadmin. Because every user can modify himself via webadmin, every user can exploit this bug. ## Thanks Thanks to cnu for finding and reporting this bug. ## New stuff * Added the admin module. (r1379) (r1386) * savebuff and away no longer ask for a password on startup. (r1388) * Added the fail2ban module. (r1390) ## Fixes * savebuff now also works with KeepBuffer turned off. (r1384) * webadmin did not properly escape module description which could allow XSS attacks. (r1391) * Fix some "use of uninitialized variable" warnings. (r1392) * Check the return value of strftime(). This allowed reading stack memory. (r1394) ## Minor stuff * Some dead code elimination. (r1381) * Don't have two places where the version number is defined. (r1382) ## Internal stuff * Removed some useless and unused CFile members. (r1383) * Removed the DEBUG_ONLY define. (r1385) * OnFailedLogin() is now called for all failed logins, not only failed IRC ones. This changes CAuthBase API. (r1389) # ZNC 0.064 (2009-02-16) ## New stuff * schat now prints a message if a client connects and there are still some active schats. (r1282) * awaynick: Set awaynick on connect, not after some time. (r1291) * Allow adding new servers through /msg *status addserver even if a server with the same name but e.g. a different port is already added. (r1295) (r1296) * Show the current server in /msg *status listservers with a star. (r1308) * /msg *status listmods now displays the module's arguments instead of its description. Use listavailmods for the description. (r1310) * ZNC now updates the channel buffers for detached channels and thus gives a buffer replay when you reattach. (r1325) * watch now adds timestamps to messages it adds to the playback buffers. (r1333) * ZNC should now work on cygwin out of the box (use --disable-ipv6). (r1351) * Webadmin will handle all HTTP requests on the irc ports. (r1368) (r1375) ## Fixes * Handle read errors in CFile::Copy() instead of going into an endless loop. (r1280) (r1287) * Make schat work properly again and clean it up a little. (r1281) (r1303) * Removed all calls to getcwd(). We now no longer depend on PATH_MAX. (r1286) * stickychan: Don't try to join channels if we are not connected to IRC. (r1298) * watch now saved its settings. (r1304) * Don't forward PONG replies that we requested to the user. (r1309) * awaynick evaluated the awaynick multiple times and thus didn't set the nick back. (r1313) * znc-config --version said '@VERSION@' instead of the actual version number. (r1319) * Handle JOIN redirects due to +L. (r1327) * Remove the length restrictions on webadmin's password fields which led to silent password truncation. (r1330) * Webadmin now reloads global modules if you change their arguments. (r1331) * The main binary is no longer built with position independent code. (r1338) * ZNC failed to bounce DCCs if its own ip started with a value above 127. (r1340) * Savebuff no longer reloads old channel buffers if you did /msg *status clearbuffer. (r1345) * Some work has been done to make ZNC work with mingw (It doesn't work out of the box yet). (r1339) (r1341) (r1342) (r1343) (r1344) (r1354) (r1355) (r1356) (r1358) (r1359) * modperl used huge amounts of memory after some time. This is now fixed. (r1357) * shell now generates error messages if e.g. fork() fails. (r1369) * If the allowed buffer size is lowered, the buffer is now automatically shrunk. (r1371) * webadmin now refuses to transfer files bigger than 16 MiB, because it would block ZNC. (r1374) ## Minor stuff * Only reply to /mode requests if we actually know the answer. (r1290) * Lowered some timeouts. (r1297) * Memory usage optimizations. (r1300) (r1301) (r1302) * Allow custom compiler flags in znc-buildmod via the $CXXFLAGS and $LIBS environment flags. (r1312) * Show the client's IP in debug output if no username is available yet. (r1315) * Allow /msg *status setbuffer for channels we are currently not on. (r1323) * Updated the README. (r1326) * Use @includedir@ instead of @prefix@/include in the Makefile. (r1328) * Use RTLD_NOW for loading modules instead of RTLD_LAZY which could take down the bouncer. (r1332) * Use stat() instead of lstat() if the later one isn't available. (r1339) * CExecSock now generates an error message if execvp() fails. (r1362) * Improved some error messages. (r1367) ## Internal stuff * Add traffic tracking support to CSocket. Every module that uses CSocket now automatically gets the traffic it causes tracked. (r1283) * Add VERSION_MINOR and VERSION_MAJOR defines. (r1284) (r1285) * Rework CZNC::Get*Path() a little. (r1289) (r1292) (r1299) * Remove the IPv6 stuff from CServer. It wasn't used at all. (r1294) * Make email use CSocket instead of Csock. (r1305) * Cleaned up and improved CFile::ReadLine() and CChan::AddNicks() a little. (r1306) (r1307) * Replaced most calls to strtoul() and atoi() with calls to the appropriate CString members. (r1320) * Moved the SetArgs() call before the OnLoad() call so that modules can overwrite there arguments in OnLoad(). (r1329) * Let CZNC::AddUser() check if the user name is still free. (r1346) * API stuff * Added CModule::IsGlobal(). (r1283) * Added CModule::BeginTimers(), EndTimers(), BeginSockets() and EndSockets(). (r1293) * Added CModule::ClearNV(). (r1304) * Removed ReadFile(), WriteFile(), ReadLine() (Use CFile instead), Lower(), Upper() (Use CString::AsUpper(), ::ToUpper(), ::*Lower() instead) and added CFile::ReadFile() (r1311) * Added CModules::OnUnknownUserRaw(). (r1314) * Added CUtils::SaltedHash() for computing the salted MD5 hashes ZNC uses. (r1324) * Removed CLockFile and made CFile take over its job. (r1337) (r1352) (r1353) * Change the return type to CUtils::GetPass() to CString. (r1343) * Added a DEBUG(f) define that expands to DEBUG_ONLY(cout << f << endl). (r1348) (r1349) * Removed some unused functions and definitions. (r1360) (r1361) # ZNC 0.062 (2008-12-06) ## New stuff * Add --disable-optimization to configure. (r1206) * New webadmin skin dark-clouds by bigpresh. (r1210) * Added the q module as a replacement for QAuth. (r1217) (r1218) * Added an enhanced /znc command: (r1228) * /znc jump is equal to /msg *status jump * /znc *shell pwd is equal to /msg *shell pwd * Webadmin should generate less traffic, because it now uses client-side caching for static data (images, style sheets, ...). (r1248) * Changes to the vhost interface from *status: (r1256) * New commands: AddVHost, RemVHost and ListVHosts. * SetVHost now only accepts vhosts from the ListVHosts list, if it is non-empty. * ZNC now should compile and work fine on Mac OS. (r1258) * IPv6 is now enabled by default unless you disable it with --disable-ipv6. (r1270) ## Fixes * Make keepnick usable. (r1203) (r1209) * Don't display 'Your message to .. got lost' for our own nick. (r1211) * Fix compile error with GCC 4.3.1 if ssl is disabled. Thanks to sebastinas. (r1212) * Limit the maximum buffer space each socket may use to prevent DoS attacks. (r1233) * Properly clean the cached perms when you are kicked from a channel. (r1236) * Due to changes in rev 1155-1158, modperl crashed on load on some machines. (r1237) (r1239) * Stickychan didn't work with disabled channels. (r1238) * Catch a throw UNLOAD in the OnBoot module hook. (r1249) * Webadmin now accepts symlinks in the skin dir. (r1252) * Fix for partyline if a force-joined user is deleted. (r1263) (r1264) * Revert change from (r1125) so that we compile on fbsd 4 again. (r1273) ## Minor stuff * Recompile everything if configure is run again. (r1205) * Improved the readability of ListMods und ListAvailMods. (r1216) * Accept "y" and "n" as answers to yes/no questions in --makeconf. (r1244) * --makeconf now also generates a ssl certificate if a ssl listening port is configured. (r1246) * Improved and cleaned up the simple_away module. (r1247) * The nickserv module automatically saves the password and never displays it anymore. (r1250) * Use relative instead of absolute URLs in all webadmin skins. (r1253) (r1254) * Add znc-config --cxx and use it in znc-buildmod. (r1255) * Support out-of-tree-builds. (r1257) * Make schat's showsocks command admin-only. (r1260) * Fix compilation with GCC 4.4. (r1269) * Use AC_PATH_PROG instead of which to find the perl binary. (r1274) * New AUTHORS file format. (r1275) ## Internal stuff * Removed redundant checks for NULL pointers (r1220) (r1227) * Renamed String.h and String.cpp to ZNCString. (r1202) * Print a warning in CTable if an unknown column is SetCell()'d (r1223) * Update to latest Csocket (r1225) * Remove CSocket::m_sLabel and its accessor functions. Use the socket name Csocket provides instead. (r1229) * modules Makefile: Small cleanup, one defines less and no compiler flags passed multiple times. (r1235) * Webadmin now uses CSocket instead of using Csock and keeping a list of sockets itself. (r1240) * Mark some global and static variables as const. (r1241) * Cleanup perform, no feature changes. (r1242) * Some tweaking to configure.in. Among other things, we now honour CPPFLAGS and don't check for a C compiler anymore. (r1251) * On rare occasions webadmin generated weird error messages. (r1259) * OnStatusCommand now doesn't have the const attribute on its argument. (r1262) * Some new functions: * some CString constructors (e.g. CString(true) results in "true") (r1243) (r1245) * CString::TrimPrefix() and CString::TrimSuffix() (r1224) (r1226) * CString::Equals() (r1232) (r1234) * CTable::Clear() (r1230) * CClient::PutStatus(const CTable&) (r1222) * CGlobalModule::OnClientConnect() (r1266) (r1268) * CModule::OnIRCRegistration() (r1271) * CModule::OnTimerAutoJoin() (r1272) * Renames: * CModule::OnUserAttached() is now known as CModules::OnClientLogin(). (r1266) * CModule::OnUserDetached() is now known as CModules::OnClientDisconnect(). (r1266) # ZNC 0.060 (2008-09-13) * Print a message when SIGHUP is caught. (r1197) * Moved autocycle into a module. (r1191) (r1192) * New module call OnMode(). (r1189) * Added MaxJoins and JoinTries to webadmin. (r1187) * Fix channel keyword (+k) related mess up on Quakenet (RFC, anyone?). (r1186) (r1190) * Added new module call OnUserTopicRequest(). (r1185) * Also add traffic generated by modules to the traffic stats. (r1183) * Don't use znc.com but znc.in everywhere (hostname of *status etc). (r1181) (r1195) * Close the listening port if we ran out of free FDs. (r1180) * Add a config option MaxJoins which limits the number of joins ZNC sends in one burst. (r1177) * Bug fix where WriteToDisk() didn't made sure a fail was empty. (r1176) * Add ShowMOTD and reorder the HELP output of *status. (r1175) * Add /msg *status restart . Thanks to kroimon. (r1174) * Make --makeconf more userfriendly. Thanks to kroimon. (r1173) * Don't start a new znc process after --makeconf. Thanks to kroimon. (r1171) * Add CModule::PutModule(const CTable&). (r1168) (r1169) * Unify some preprocessor macros in Modules.cpp. (r1166) * Catch a throw UNLOAD from CModule::OnLoad(). (r1164) * A couple of bugs with OnModCTCP(), OnModCommand() and OnModNotice() where fixed. (r1162) * Quicker connects and reconnects to IRC. (r1161) * Speedup the CTable class. (r1160) * Update our bundled Csocket. (r1159) * Some fixes to modperl for hppa. * Move keepnick into a module. (r1151) (r1152) (r1153) * Split up some big functions and files. (r1148) (r1149) (r1150) * modperl now fails to load if it can't find modperl.pm. (r1147) * Handle nick prefixes and such stuff from clients correctly. (r1144) (r1145) * Simplify the code connecting users a little. (r1143) * Fix partyline for users who are not connected to IRC. (r1141) * We are in a channel when we receive a join for it, not an 'end of /names'. (r1140) * Enable some more debug flags with --enable-debug. (r1138) * Don't ever throw exceptions in CModules::LoadModule(). (r1137) * Don't give any stdin to commands executed from the shell module. (r1136) * Fix some over-the-end iterator dereference on parting empty channels. (r1133) * Replace usage of getresuid() with getuid() and geteuid(). (r1132) * Use salted hashes for increased security. (r1127) (r1139) * Don't mention any libraries in znc-config. (r1126) * Don't define __GNU_LIBRARY__ for FreeBSD. (r1125) # ZNC 0.058 (2008-07-10) * Fix a crash with NAMESX-enabled IRC servers. (r1118) * Fix a privilege escalation bug in webadmin if auth modules are used. (r1113) * Remove -D_GNU_SOURCE from our CXXFLAGS. (r1110) * CUtils::GetInput() now kills the process if reading from stdin fails. (r1109) * Properly include limits.h for PATH_MAX. (r1108) * Don't allow running ZNC as root unless --allow-root is given. (r1102) * Add more possibilities for ExpandString(). (r1101) * Autoattach doesn't allow you adding an entry twice now. (r1100) * Print a warning if PATH_MAX is undefined. (r1099) * Use ExpandString() for CTCPReply. (r1096) * Add Uptime command to *status. (r1095) (r1107) * Make --makeconf clearer. (r1093) * Add man pages for znc, znc-buildmod and znc-config. (r1091) * Perl modules are no longer installed with executable bit set. (r1090) * Crypt now forwards messages to other connected clients. (r1088) * Fix a theoretical crash bug in the DNS resolving code. (r1087) * Modules now get their module name as ident, not 'znc'. (r1084) * Handle channel CTCPs the same way private CTCPs are handled. (r1082) * Webadmin: Add support for timezone offset. (r1079) * Webadmin: Remove the *.de webadmin skins. (r1078) * Webadmin: Don't reset all channel settings when a user page is saved. (r1074) * Fix a possible crash when rehashing failed to open the config file. (r1073) * The instructions at the end of makeconf showed a wrong port. (r1072) * Throttle DCC transfers to the speed of the sending side. (r1069) * De-bashify znc-buildmod. (r1068) * Time out unauthed clients after 60 secs. (r1067) * Don't fail with conspire as IRC client. (r1066) * Replace CString::Token() with a much faster version. (r1065) * DenyLoadMod users no longer can use ListAvailMods. (r1063) * Add a VERSION_EXTRA define which can be influenced via CXXFLAGS and which is appended to ZNC's version number. (r1062) # ZNC 0.056 (2008-05-24) * Rehashing also handles channel settings. (r1058) * Make znc-buildmod work with prefixes. (r1054) * Greatly speed up CUser::GetIRCSock(). Thanks to w00t. (r1053) * Don't link the ZNC binary against libsasl2. (r1050) * Make CString::RandomString() produce a more random string (this is used for autoop and increases its security). (r1047) * Remove OnRehashDone() and add OnPreRehash() and OnPostRehash(). (r1046) * Show traffic stats in a readable unit instead of lots of bytes. (r1038) * Fixed a bug were nick changes where silently dropped if we are in no channels. (r1037) * Remove the /watch command from watch. (r1035) * znc-buildmod now reports errors via exit code. (r1034) * Display a better error message if znc.conf cannot be opened. (r1033) * Print a warning from *status if some message or notice is lost because we are not connected to IRC. (r1032) * Make ./configure --bindir=DIR work. (r1031) * Always track header dependencies. This means we require a compile supporting -MMD and -MF. (r1026) * Improve some error messages if we can't connect to IRC. (r1023) * Use \n instead of \r\n for znc.conf. (r1022) * Fix some invalid replies from the shell module. (r1021) * Support chans other than #chan and &chan. (r1019) * Make chansaver add all channels to the config on load. (r1018) * Reply to PINGs if we are not connected to a server. (r1016) * Fix some bugs with Csocket, one caused segfaults when connecting to special SSL hosts. (r1015) * Use MODFLAGS instead of CXXFLAGS for modules. Do MODFLAGS=something ./configure if you want a flag that is used only by modules. (r1012) * Add OnTopic() module call. (r1011) * Don't create empty .registry files for modules. See find ~/.znc -iname ".registry" -size 0 for a list of files you can delete. (r1010) * Only allow admins to load the shell module. (r1007) * Fix CModule::DelNV()'s return value. (r1006) * Fix CUser::Clone() to handle all the settings. (r1005) * Mark all our FDs as close-on-exec. (r1004) # ZNC 0.054 (2008-04-01) * Forward /names replies for unknown channels. * Global modules can no longer hook into every config line, but only those prefixed with 'GM:'. * Don't forward topic changes for detached channels. * Remove ~/.znc/configs/backups and instead only keep one backup under znc.conf-backup. * Update /msg *status help. * Add --datadir to znc-config. * Update bundled Csocket to the latest version. This fixes some bugs (e.g. not closing SSL connections properly). * Use $HOME if possible to get the user's home (No need to read /etc/passwd anymore). * Use -Wshadow and fix all those warnings. * Add /msg *status ListAvailMods. Thanks to SilverLeo. * Add OnRehashDone() module call. * Add rehashing (SIGHUP and /msg *status rehash). * Also write a pid file if we are compiled with --enable-debug. Thanks to SilverLeo. * Add ClearVHost and 'fix' SetVHost. Thanks to SilverLeo. * Increase the connect timeout for IRC connections to 2 mins. * Add a user's vhost to the list on the user page in webadmin. * Add --no-color switch and only use colors if we are on a terminal. * Add DenySetVHost config option. Thanks to Veit Wahlich aka cru. * Change --makeconf's default for KeepNick and KeepBuffer to false. * Add simple_away module. This sets you away some time after you disconnect from ZNC. * Don't write unneeded settings to the section. Thanks to SilverLeo. * Remove OnFinishedConfig() module call. Use OnBoot() instead. * Fix some GCC 4.3 warnings. Thanks to darix again. * Move the static data (webadmin's skins) to /usr/share/znc per default. Thanks to Marcus Rueckert aka darix. * New znc-buildmod which works on shells other than bash. * Add ClearAllChannelBuffers to *status. * Handle CTCPs to *status. * autoattach now saves and reloads its settings. * Let webadmin use the user's defaults for new chans. Thanks to SilverLeo. # ZNC 0.052 (2007-12-02) * Added saslauth module. * Add del command to autoattach. * Make awaynick save its settings and restore them when it is loaded again. * Added disconnect and connect commands to *status. * CTCPReply = VERSION now ignores ctcp version requests (as long as no client is attached). This works for every CTCP request. * Add -W to our default CXXFLAGS. * Remove save command from perform, it wasn't needed. * Add list command to stickychan. * --with-module-prefix=x now really uses x and not x/znc (Inspired by CNU :) ). * Use a dynamic select timeout (sleep until next cron runs). This should save some CPU time. * Fix NAMESX / UHNAMES, round two (multi-client breakage). * Module API change (without any breakage): OnLoad gets sMessage instead of sErrorMsg. * Fix a mem-leak. * Disable auto-rejoin on kick and add module kickrejoin. * Respect $CXXFLAGS env var in configure. * Removed some executable bits on graphiX' images. * Added README file and removed docs/. * Removed the antiidle module. * Fixes for GCC 4.3 (Debian bug #417793). * Some dead code / code duplications removed. * Rewrote Makefile.ins and don't strip binaries anymore by default. # ZNC 0.050 (2007-08-11) * fixed UHNAMES bug (ident was messed up, wrong joins were sent) * fixed /lusers bug (line was cached more than once) * added disabled chans to the core * send out a notice asking for the server password if client doesn't send one * added ConnectDelay config option * added timestamps on the backlog * added some module calls * added basic traffic stats * added usermodes support * API breakage (CModule::OnLoad got an extra param) * added fixed channels to the partyline module * fixed partyline bugs introduced by last item * fixed a NULL pointer dereference if /nick command was received from a client while not connected to IRC * added a JoinTries per-user config option which specifies how often we try to rejoin a channel (default: 0 -> unlimited) * make configure fail if it can't find openssl (or perl, ...) * new modules: antiidle, nickserv # ZNC 0.047 (2007-05-15) * NULL pointer dereference when a user uses webadmin while not on irc * A logged in user could access any file with /msg *status send/get * znc --makeconf now restarts znc correctly * New webadmin skin (+ german translations) * Updated to new Csocket version * Allow @ and . in user names which now can also be longer * Added crox and psychon to AUTHORS * Relay messages to other clients of the current user (for the crypt module) * Added chansaver Module * Moved awaynick functionality into a module * Added perform module from psychon * fixed bug when compiling without module support * Added a configurable Timer to the away module * Added support for Topics in the partyline module * Added support for reloading global modules * Added a timer to ping inactive clients * Migrated away from CString::ToString() in favor of explicit constructors * IMAP Authentication Module added * Fixed issues with gcc 4.1 * Added concept of default channels that a user is automatically joined to every time they attach * Added SetVHost command * Added error reporting and quit msgs as *status output * Added a server ping for idle connections - Thanks zparta * added -ldl fix for openssl crypto package. fixes static lib link requirement * Explicitly set RTLD_LOCAL, some systems require it - thanks x-x * Added SendBuffer and ClearBuffer client commands * Added support for to talk unencrypted * added with-modules-prefix and moved modules by default to PREFIX/libexec * Added license and contact info * remove compression initialization until standard has normalized a bit # ZNC 0.045 (2006-02-20) * Added +o/v -o/v for when users attach/detach - partyline module * Changed internal naming of CUserSock to CClient for consistency * Fixed some issues with older bsd boxes * Added ListenHost for binding to a specific ip instead of inaddr_any * Allow - and _ as valid username chars * respect compiler, we don't force you to use g++ anymore, don't include system includes for deps * Added Replace_n() and fixed internal loop bug in Replace() (thanks psycho for finding it) * Don't allow .. in GET * Added autoop module * Added support for buffering of /me actions * Added Template support in webadmin now you can write your own skins easily :) * Added ipv6 support * Support for multiple Listen Ports (note the config option "ListenPort" changed to "Listen") # ZNC 0.044 (2005-10-14) * Fixed issue where pipe between client and irc sockets would get out of sync, this was introduced in 0.043 * Added *status commands to list users and clients connected # ZNC 0.043 (2005-10-13) * Added Multi-Client support * Added Global partyline module * Added MOTD config option * Added Admin permission * Added SaveConfig admin-only *status command * Added Broadcast admin-only *status command # ZNC 0.041 (2005-09-08) * This release fixes some issues with 64bit systems. # ZNC 0.040 (2005-09-07) This release contains a lot of features/bugfixes and a great new global module called admin.cpp which will allow you to add/remove/edit users and settings on the fly via a web browser. # ZNC 0.039 (2005-09-07) This release contains a lot of features/bugfixes and a great new global module called admin.cpp which will allow you to add/remove/edit users and settings on the fly via a web browser. # ZNC 0.038 (2005-09-07) This release contains a lot of bugfixes and a great new global module called admin.cpp which will allow you to add/remove/edit users and settings on the fly via a web browser. # ZNC 0.037 (2005-05-22) # ZNC 0.036 (2005-05-15) # ZNC 0.035 (2005-05-14) # ZNC 0.034 (2005-05-01) # ZNC 0.033 (2005-04-26) # ZNC 0.030 (2005-04-21) # ZNC 0.029 (2005-04-12) # ZNC 0.028 (2005-04-04) # ZNC 0.027 (2005-04-04) # ZNC 0.025 (2005-04-03) # ZNC 0.023 (2005-03-10) znc-1.9.1/Dockerfile0000644000175000017500000000233414641222733014540 0ustar somebodysomebodyFROM alpine:3.19 ARG VERSION_EXTRA="" ARG CMAKEFLAGS="-DVERSION_EXTRA=${VERSION_EXTRA} -DCMAKE_INSTALL_PREFIX=/opt/znc -DWANT_CYRUS=YES -DWANT_PERL=YES -DWANT_PYTHON=YES -DWANT_ARGON=YES" ARG MAKEFLAGS="" LABEL org.label-schema.schema-version="1.0" LABEL org.label-schema.vcs-url="https://github.com/znc/znc" LABEL org.label-schema.url="https://znc.in" COPY . /znc-src RUN set -x \ && adduser -S znc \ && addgroup -S znc RUN apk add --no-cache \ argon2-libs \ boost \ build-base \ ca-certificates \ cmake \ cyrus-sasl \ gettext \ icu-dev \ icu-data-full \ openssl-dev \ perl \ python3 \ su-exec \ tini \ tzdata RUN apk add --no-cache --virtual build-dependencies \ argon2-dev \ boost-dev \ cyrus-sasl-dev \ perl-dev \ python3-dev \ swig \ && cd /znc-src \ && mkdir build && cd build \ && cmake .. ${CMAKEFLAGS} \ && make $MAKEFLAGS \ && make install \ && apk del build-dependencies \ && cd / && rm -rf /znc-src COPY docker/slim/entrypoint.sh / COPY docker/*/??-*.sh /startup-sequence/ VOLUME /znc-data ENTRYPOINT ["/entrypoint.sh"] znc-1.9.1/Doxyfile0000644000175000017500000022552614641222733014266 0ustar somebodysomebody# Doxyfile 1.8.1.2 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" "). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or sequence of words) that should # identify the project. Note that if you do not use Doxywizard you need # to put quotes around the project name if it contains spaces. PROJECT_NAME = ZNC # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = trunk # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer # a quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify an logo or icon that is # included in the documentation. The maximum height of the logo should not # exceed 55 pixels and the maximum width should not exceed 200 pixels. # Doxygen will copy the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = doc # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, # Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English # messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, # Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, # Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = YES # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = NO # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful if your file system # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = YES # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding # "class=itcl::class" will allow you to use the command class in the # itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this # tag. The format is ext=language, where ext is a file extension, and language # is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, # C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make # doxygen treat .inc files as Fortran files (default is PHP), and .f files as C # (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions # you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. EXTENSION_MAPPING = # If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all # comments according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you # can mix doxygen, HTML, and XML commands with Markdown formatting. # Disable only in case of backward compatibilities issues. MARKDOWN_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also makes the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and # unions are shown inside the group in which they are included (e.g. using # @ingroup) instead of on a separate page (for HTML and Man pages) or # section (for LaTeX and RTF). INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and # unions with only public data fields will be shown inline in the documentation # of the scope in which they are defined (i.e. file, namespace, or group # documentation), provided this scope is documented. If set to NO (the default), # structs, classes, and unions are shown on a separate page (for HTML and Man # pages) or section (for LaTeX and RTF). INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penalty. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will roughly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols. SYMBOL_CACHE_SIZE = 0 # Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be # set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given # their name and scope. Since this can be an expensive process and often the # same symbol appear multiple times in the code, doxygen keeps a cache of # pre-resolved symbols. If the cache is too small doxygen will become slower. # If the cache is too large, memory is wasted. The cache size is given by this # formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespaces are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen # will list include files with double quotes in the documentation # rather than with sharp brackets. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen # will sort the (brief and detailed) documentation of class members so that # constructors and destructors are listed first. If set to NO (the default) # the constructors will appear in the respective orders defined by # SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. # This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO # and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. SORT_MEMBERS_CTORS_1ST = YES # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to # do proper type resolution of all parameters of a function it will reject a # match between the prototype and the implementation of a member function even # if there is only one candidate or it is obvious which candidate to choose # by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen # will still accept a match between prototype and implementation in such cases. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or macro consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and macros in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. # This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. # You can optionally specify a file name after the option, if omitted # DoxygenLayout.xml will be used as the name of the layout file. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files # containing the references data. This must be a list of .bib files. The # .bib extension is automatically appended if omitted. Using this command # requires the bibtex tool to be installed. See also # http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style # of the bibliography can be controlled using LATEX_BIB_STYLE. To use this # feature you need bibtex and perl available in the search path. CITE_BIB_FILES = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # The WARN_NO_PARAMDOC option can be enabled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = include src third_party/Csocket # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh # *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py # *.f90 *.f *.for *.vhd *.vhdl FILE_PATTERNS = *.h # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. # If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. # Doxygen will compare the file name with each pattern and apply the # filter if there is a match. # The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty or if # non of the patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) # and it is also possible to disable source filtering for a specific pattern # using *.ext= (so without naming a filter). This option only has effect when # FILTER_SOURCE_FILES is enabled. FILTER_SOURCE_PATTERNS = #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C, C++ and Fortran comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. # Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. Note that when using a custom header you are responsible # for the proper inclusion of any scripts and style sheets that doxygen # needs, which is dependent on the configuration options used. # It is advised to generate a default header using "doxygen -w html # header.html footer.html stylesheet.css YourConfigFile" and then modify # that header. Note that the header is subject to change so you typically # have to redo this when upgrading to a newer version of doxygen or when # changing the value of configuration settings such as GENERATE_TREEVIEW! HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # style sheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that # the files will be copied as-is; there are no commands or markers available. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. # Doxygen will adjust the colors in the style sheet and background images # according to this color. Hue is specified as an angle on a color wheel, # see http://en.wikipedia.org/wiki/Hue for more information. # For instance the value 0 represents red, 60 is yellow, 120 is green, # 180 is cyan, 240 is blue, 300 purple, and 360 is red again. # The allowed range is 0 to 359. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of # the colors in the HTML output. For a value of 0 the output will use # grayscales only. A value of 255 will produce the most vivid colors. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to # the luminance component of the colors in the HTML output. Values below # 100 gradually make the output lighter, whereas values above 100 make # the output darker. The value divided by 100 is the actual gamma applied, # so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, # and 100 does not change the gamma. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. HTML_DYNAMIC_SECTIONS = YES # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of # entries shown in the various tree structured indices initially; the user # can expand and collapse entries dynamically later on. Doxygen will expand # the tree to such a level that at most the specified number of entries are # visible (unless a fully collapsed tree already exceeds this amount). # So setting the number of entries 1 will produce a full collapsed tree by # default. 0 is a special value representing an infinite number of entries # and will result in a full expanded tree by default. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated # that can be used as input for Qt's qhelpgenerator to generate a # Qt Compressed Help (.qch) of the generated HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to # add. For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see # # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's # filter section matches. # # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files # will be generated, which together with the HTML files, form an Eclipse help # plugin. To install this plugin and make it available under the help contents # menu in Eclipse, the contents of the directory containing the HTML and XML # files needs to be copied into the plugins directory of eclipse. The name of # the directory within the plugins directory should be the same as # the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before # the help appears. GENERATE_ECLIPSEHELP = NO # A unique identifier for the eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have # this name. ECLIPSE_DOC_ID = org.doxygen.Project # The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) # at top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. Since the tabs have the same information as the # navigation tree you can set this option to NO if you already set # GENERATE_TREEVIEW to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to YES, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). # Windows users are probably better off using the HTML help feature. # Since the tree basically has the same information as the tab index you # could consider to set DISABLE_INDEX to NO when enabling this option. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values # (range [0,1..20]) that doxygen will group on one line in the generated HTML # documentation. Note that a value of 0 will completely suppress the enum # values from appearing in the overview section. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open # links to external symbols imported via tag files in a separate window. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are # not supported properly for IE 6.0, but are supported on all modern browsers. # Note that when changing this option you need to delete any form_*.png files # in the HTML output before the changes have effect. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax # (see http://www.mathjax.org) which uses client side Javascript for the # rendering instead of using prerendered bitmaps. Use this if you do not # have LaTeX installed or if you want to formulas look prettier in the HTML # output. When enabled you may also need to install MathJax separately and # configure the path to it using the MATHJAX_RELPATH option. USE_MATHJAX = NO # When MathJax is enabled you need to specify the location relative to the # HTML output directory using the MATHJAX_RELPATH option. The destination # directory should contain the MathJax.js script. For instance, if the mathjax # directory is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to # the MathJax Content Delivery Network so you can quickly see the result without # installing MathJax. # However, it is strongly recommended to install a local # copy of MathJax from http://www.mathjax.org before deployment. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension # names that should be enabled during MathJax rendering. MATHJAX_EXTENSIONS = # When the SEARCHENGINE tag is enabled doxygen will generate a search box # for the HTML output. The underlying search engine uses javascript # and DHTML and should work on any modern browser. Note that when using # HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets # (GENERATE_DOCSET) there is already a search function so this one should # typically be disabled. For large projects the javascript based search engine # can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a PHP enabled web server instead of at the web client # using Javascript. Doxygen will generate the search PHP script and index # file to put on the web server. The advantage of the server # based approach is that it scales better to large projects and allows # full text search. The disadvantages are that it is more difficult to setup # and does not have live searching capabilities. SERVER_BASED_SEARCH = NO #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. # Note that when enabling USE_PDFLATEX this option is only used for # generating bitmaps for formulas in the HTML output, but not in the # Makefile that is written to the output directory. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for # the generated latex document. The footer should contain everything after # the last chapter. If it is left blank doxygen will generate a # standard footer. Notice: only use this tag if you know what you are doing! LATEX_FOOTER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = NO # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = NO # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # If LATEX_SOURCE_CODE is set to YES then doxygen will include # source code with syntax highlighting in the LaTeX output. # Note that which sources are shown also depends on other settings # such as SOURCE_BROWSER. LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See # http://en.wikipedia.org/wiki/BibTeX for more info. LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load style sheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. # This is useful # if you want to understand what is going on. # On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # pointed to by INCLUDE_PATH will be searched when a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = HAVE_IPV6 \ HAVE_LIBSSL \ HAVE_LSTAT \ HAVE_PTHREAD \ HAVE_THREADED_DNS \ HAVE_ICU \ _NO_CSOCKET_NS \ _MODULES # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition that # overrules the definition found in the source code. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all references to function-like macros # that are alone on a line, have an all uppercase name, and do not end with a # semicolon, because these will confuse the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. For each # tag file the location of the external documentation should be added. The # format of a tag file without this location is as follows: # # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths # or URLs. Note that each tag file must have a unique name (where the name does # NOT include the path). If a tag file is not located in the directory in which # doxygen is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option also works with HAVE_DOT disabled, but it is recommended to # install and use dot, since it yields more powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is # allowed to run in parallel. When set to 0 (the default) doxygen will # base this on the number of processors available in the system. You can set it # explicitly to a value larger than 0 to get control over the balance # between CPU load and processing speed. DOT_NUM_THREADS = 0 # By default doxygen will use the Helvetica font for all dot files that # doxygen generates. When you want a differently looking font you can specify # the font name using DOT_FONTNAME. You need to make sure dot is able to find # the font, which can be done by putting it in a standard location or by setting # the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the # directory containing the font. DOT_FONTNAME = FreeSans # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the Helvetica font. # If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to # set the path where dot can find it. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If the UML_LOOK tag is enabled, the fields and methods are shown inside # the class node. If there are many fields or methods and many nodes the # graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS # threshold limits the number of items for each type to make the size more # manageable. Set this to 0 for no limit. Note that the threshold may be # exceeded by 50% before the limit is enforced. UML_LIMIT_NUM_FIELDS = 10 # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will generate a graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are svg, png, jpg, or gif. # If left blank png will be used. If you choose svg you need to set # HTML_FILE_EXTENSION to xhtml in order to make the SVG files # visible in IE 9+ (other browsers do not have this requirement). DOT_IMAGE_FORMAT = png # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. # Note that this requires a modern browser other than Internet Explorer. # Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you # need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files # visible. Older versions of IE do not have SVG support. INTERACTIVE_SVG = NO # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the # \mscfile command). MSCFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = YES # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES znc-1.9.1/Jenkinsfile0000644000175000017500000000220714641222733014731 0ustar somebodysomebody#!groovy timestamps { node('freebsd') { // freebsd 13.1 + pkg install git openjdk17 cmake icu pkgconf swig python3 boost-libs gettext-tools qt5-buildtools qt5-network qt5-qmake // Then fill known_hosts with https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints // (needed for crowdin cron job in jenkins) timeout(time: 30, unit: 'MINUTES') { def wsdir = pwd() stage('Checkout') { step([$class: 'WsCleanup']) checkout scm sh 'git submodule update --init --recursive' } dir("$wsdir/build") { stage('Build') { sh "cmake $wsdir -DWANT_PERL=ON -DWANT_PYTHON=ON -DCMAKE_INSTALL_PREFIX=$wsdir/build/install-prefix" sh 'make VERBOSE=1 all' } stage('Unit test') { withEnv(['GTEST_OUTPUT=xml:unit-test.xml']) { sh 'make unittest' } } stage('Integration test') { withEnv(['GTEST_OUTPUT=xml:integration-test.xml']) { sh 'make install' sh 'make inttest' } } junit '**/*test.xml' } } } } znc-1.9.1/LICENSE0000644000175000017500000002613614641222733013561 0ustar somebodysomebody Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. znc-1.9.1/NOTICE0000644000175000017500000000612314641222733013452 0ustar somebodysomebodyZNC === ZNC includes code from Csocket (https://github.com/jimloco/Csocket), licensed under the Sleepycat-alike License. ZNC includes code generated by SWIG (http://www.swig.org), not governed by SWIG's license. ZNC includes code of autoconf macro AX_CXX_COMPILE_STDCXX_11, its permissive license is inside the file. ZNC includes modified code of autoconf macro AX_PTHREAD, licensed under the GPLv3+. ZNC includes modified code of autoconf macro AC_PROG_SWIG, licensed under the GPLv2+ with Autoconf Exception. ZNC includes modified code of autoconf macro AM_ICONV, licensed under the FSF Unlimited License. ZNC includes modified code of autoconf macro gl_VISIBILITY, licensed under the FSF Unlimited License. ZNC includes modified code of MD5 implementation by Christophe Devine, licensed under the GPLv2+. ZNC includes resized External Wikipedia icon (https://commons.wikimedia.org/wiki/File:External.svg), which is in the public domain. ZNC includes modified code for SSL verification by Alban Diquet (https://github.com/iSECPartners/ssl-conservatory/) and Daniel Stenberg (https://github.com/bagder/curl/blob/master/lib/), licensed under the MIT License. ZNC includes code from jQuery (http://jquery.com/), licensed under the MIT License. ZNC includes code from jQuery UI (http://jqueryui.com/), licensed under the MIT License. ZNC includes code from Selectize (http://brianreavis.github.io/selectize.js/), licensed under the Apache License 2.0. ZNC includes modified code from CMakeFindFrameworks.cmake by Kitware, Inc., licensed under BSD License. ZNC includes modified code from TestLargeFiles.cmake, licensed under Boost Software License, Version 1.0. ZNC includes code from cctz (https://github.com/google/cctz), licensed under the Apache License 2.0. ZNC is developed by these people: Prozac Jim Hull Uli Schlachter SilverLeo kroimon flakes Alexey "DarthGandalf" Sokolov Kyle Fuller These people, in no particular order, have helped develop ZNC, for example by sending in patches, writing new modules or finding significant bugs: Kuja - runs and pays for znc.in derblubber toby Zack3000 d4n13L - graphiX webadmin skin Veit "Cru" Wahlich crox Freman (http://fremnet.net/contact) Sebastian Ramacher cnu - master of destruction (security issues) Ingmar "KiNgMaR" Runge Michael "Svedrin" Ziegler Robert Lacroix (http://www.robertlacroix.com) Martin "Nirjen" Martimeo Reed Loden Brian Campbell (bcampbell@splafinga.com) Joshua M. Clulow (http://sysmgr.org) evaryont Michael "adgar" Edgar Jens-Andre "vain" Koch Heiko Hund - cyrusauth module Philippe (http://sourceforge.net/users/cycomate) - kickrejoin module J-P Nurmi Thomas Ward If you did something useful and want to be listed here too, add yourself and submit the patch. znc-1.9.1/README.md0000644000175000017500000001375314641222733014034 0ustar somebodysomebody# [![ZNC](logo.png)](https://znc.in) - An advanced IRC bouncer [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/znc/znc/build.yml?branch=master&label=linux)](https://github.com/znc/znc/actions/workflows/build.yml) [![Jenkins Build Status](https://img.shields.io/jenkins/s/https/jenkins.znc.in/job/znc/job/znc/job/master.svg?label=freebsd)](https://jenkins.znc.in/job/znc/job/znc/job/master/) [![AppVeyor Build status](https://img.shields.io/appveyor/ci/DarthGandalf/znc/master.svg?label=windows)](https://ci.appveyor.com/project/DarthGandalf/znc/branch/master) [![Coverage Status](https://img.shields.io/codecov/c/github/znc/znc.svg)](https://codecov.io/gh/znc/znc) ## Table of contents - [Minimal Requirements](#minimal-requirements) - [Optional Requirements](#optional-requirements) - [Installing ZNC](#installing-znc) - [Setting up znc.conf](#setting-up-zncconf) - [Special config options](#special-config-options) - [Using ZNC](#using-znc) - [File Locations](#file-locations) - [ZNC's config file](#zncs-config-file) - [Writing own modules](#writing-own-modules) - [Further information](#further-information) ## Minimal Requirements Core: * GNU make * pkg-config * GCC 8 or clang 5 * CMake 3.13 ## Optional Requirements SSL/TLS support: * openssl 0.9.7d or later * try installing openssl-dev, openssl-devel or libssl-dev * macOS: OpenSSL from Homebrew is preferred over system modperl: * perl and its bundled libperl * SWIG if building from git modpython: * python 3.4+ and its bundled libpython * perl is a build dependency * macOS: Python from Homebrew is preferred over system version * SWIG if building from git cyrusauth: * This module needs cyrus-sasl2 Character Encodings: * To get proper character encoding and charsets install ICU (`libicu4-dev`) I18N (UI translation): * Boost.Locale * gettext is a build dependency Argon2 password hash: * libargon2 ## Installing ZNC Installation from source code is performed using the CMake toolchain. ```shell mkdir build cd build cmake .. make make install ``` You can use `cmake-gui` or `ccmake` for more interactiveness. There is also `configure.sh` which should make migration to CMake easier: it accepts the same parameters as old `./configure`, but calls CMake with CMake-style parameters. Note for FreeBSD users: By default base OpenSSL is selected. If you want the one from ports, use `-DOPENSSL_ROOT_DIR=/usr/local`. For troubleshooting, `cmake --system-information` will show you details. ## Setting up znc.conf For setting up a configuration file in `~/.znc` you can simply do `znc --makeconf` or `./znc --makeconf` for in-place execution. If you are using SSL you should do `znc --makepem` ## Special config options When you create your ZNC configuration file via --makeconf, you are asked two questions which might not be easy to understand. > Number of lines to buffer per channel How many messages should be buffered for each channel. When you connect to ZNC you get a buffer replay for each channel which shows what was said last. This option selects the number of lines this replay should consist of. Increasing this can greatly increase ZNC's memory usage if you are hosting many users. The default value should be fine for most setups. > Would you like to keep buffers after replay? If this is disabled, you get the buffer playback only once and then it is deleted. If this is enabled, the buffer is not deleted. This may be useful if you regularly use more than one client to connect to ZNC. ## Using ZNC Once you have started ZNC you can connect with your favorite IRC-client to ZNC. You should use `username:password` as the server password (e.g. `/pass user:pass`). Once you are connected you can do `/msg *status help` for some commands. Every module you have loaded (`/msg *status listmods`) should additionally provide `/msg *modulename help` ## File Locations In its data dir (`~/.znc` is default) ZNC saves most of its data. The only exception are modules and module data, which are saved in `/lib/znc` and `/share/znc`, and the znc binary itself. More modules (e.g. if you install some later) can be saved in `/modules` (-> `~/.znc/modules`). In the datadir is only one file: - `znc.pem` - This is the server certificate ZNC uses for listening and is created with `znc --makepem`. These directories are also in there: - configs - Contains `znc.conf` (ZNC's config file) and backups of older configs. - modules - ZNC also looks in here for a module. - moddata - Global modules save their settings here. (e.g. webadmin saves the current skin name in here) - users - This is per-user data and mainly contains just a moddata directory and a directory for each network configured. ## ZNC's config file This file shouldn't be too hard too understand. An explanation of all the items can be found on the [Configuration](https://wiki.znc.in/Configuration) page. **Warning: it is better not to edit config while ZNC is running.** Use the [webadmin] and [controlpanel] modules instead. [webadmin]:https://wiki.znc.in/Webadmin [controlpanel]:https://wiki.znc.in/Controlpanel If you changed some settings while ZNC is running, a simple `pkill -SIGUSR1 znc` will make ZNC rewrite its config file. Alternatively you can use `/msg *status saveconfig` ## Writing own modules You can write your own modules in either C++, python or perl. C++ modules are compiled by either saving them in the modules source dir and running make or with the `znc-buildmod` shell script. For additional info look in the wiki: - [Writing modules](https://wiki.znc.in/Writing_modules) Perl modules are loaded through the global module [ModPerl](https://wiki.znc.in/Modperl). Python modules are loaded through the global module [ModPython](https://wiki.znc.in/Modpython). ## Further information Please visit https://znc.in/ or #znc on Libera.Chat if you still have questions: - [ircs://irc.libera.chat:6697/#znc](ircs://irc.libera.chat:6697/#znc) You can get the latest development version with git: `git clone https://github.com/znc/znc.git --recursive` znc-1.9.1/TRANSLATORS.md0000644000175000017500000000166314641222733014710 0ustar somebodysomebodyThese people helped translating ZNC to various languages: * Alcahest ([X] Alcahest) * Altay * aycanuAydemir * bashgeek (Daniel) * CaPaCuL * casmo (Casper) * ChaosEngine (Andrzej Pauli) * cirinho (Ciro Moniz) * CJSStryker * DarthGandalf * dgw * Dreiundachzig * Dremski * eggoez (Baguz Ach) * freonesuka (Андрей Вальтер) * gremax * hypech * JakaMedia (Jaka Media Teknologi) * Jay2k1 * kloun (Victor Kukshiev) * leon-th (Leon T.) * LiteHell * lorenzosu * M0onshadow (Maelan) * MikkelDK * mkgeeky (mkgeeky) * moonlightzzz (moonlightz) * natinaum (natinaum) * PauloHeaven (Paul) * peterstanke (MAGIC) * psychon * remhaze * shillos5 (Nicholas Kyriakides) * simos (filippo.cortigiani) * sukien * SunOS * tojestzart (tojestzart) * Un1matr1x (Falk Seidel) * unavailable (style) * Vimart * Wollino * Xaris_ (Xaris) * xAtlas (Atlas) * Xinayder (Alexandre Oliveira) * Zarthus (Jos Ahrens) Generated from https://crowdin.com/project/znc-bouncer znc-1.9.1/ZNCConfig.cmake.in0000644000175000017500000000327314641222733015740 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # include(CMakeFindDependencyMacro) include("${CMAKE_CURRENT_LIST_DIR}/CMakeFindDependencyMacroPC.cmake") include("${CMAKE_CURRENT_LIST_DIR}/use_homebrew.cmake") @ZNC_CMAKE_FIND_DEPS@ include("${CMAKE_CURRENT_LIST_DIR}/znc_internal.cmake") include("${CMAKE_CURRENT_LIST_DIR}/znc_public.cmake") include(CMakeParseArguments) # For some reason cygwin fails to build modules if Threads::Threads # is not found. if(NOT CYGWIN) set(ZNC_NO_INCLUDE_THREADS true) endif() if(NOT ZNC_NO_INCLUDE_THREADS) set(CMAKE_THREAD_PREFER_PTHREAD true) set(THREADS_PREFER_PTHREAD_FLAG true) find_package(Threads REQUIRED) if(NOT CMAKE_USE_PTHREADS_INIT) message(FATAL_ERROR "This compiler/OS doesn't seem " "to support pthreads.") endif() endif() function(znc_setup_module) cmake_parse_arguments(znc_mod "" "TARGET;NAME" "" ${ARGN}) set_target_properties("${znc_mod_TARGET}" PROPERTIES OUTPUT_NAME "${znc_mod_NAME}" PREFIX "" SUFFIX ".so" NO_SONAME true CXX_VISIBILITY_PRESET "hidden") target_link_libraries("${znc_mod_TARGET}" PRIVATE ZNC::ZNC) endfunction() message(STATUS "Found ZNC @ZNC_VERSION@") znc-1.9.1/cmake/0000755000175000017500000000000014641222733013624 5ustar somebodysomebodyznc-1.9.1/cmake/CMakeFindDependencyMacroPC.cmake0000644000175000017500000000772714641222733021630 0ustar somebodysomebody# This is directly based on CMake's CMakeFindDependencyMacro from 3.26.5, the only change is that it uses pkg_check_modules instead of find_package # Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. #[=======================================================================[.rst: CMakeFindDependencyMacro ------------------------ .. command:: find_dependency The ``find_dependency()`` macro wraps a :command:`find_package` call for a package dependency:: find_dependency( [...]) It is designed to be used in a :ref:`Package Configuration File ` (``Config.cmake``). ``find_dependency`` forwards the correct parameters for ``QUIET`` and ``REQUIRED`` which were passed to the original :command:`find_package` call. Any additional arguments specified are forwarded to :command:`find_package`. If the dependency could not be found it sets an informative diagnostic message and calls :command:`return` to end processing of the calling package configuration file and return to the :command:`find_package` command that loaded it. .. note:: The call to :command:`return` makes this macro unsuitable to call from :ref:`Find Modules`. Package Dependency Search Optimizations ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If ``find_dependency`` is called with arguments identical to a previous call in the same directory, perhaps due to diamond-shaped package dependencies, the underlying call to :command:`find_package` is optimized out. This optimization is important to support large package dependency graphs while avoiding a combinatorial explosion of repeated searches. However, the heuristic cannot account for ambient variables that affect package behavior, such as ``_USE_STATIC_LIBS``, offered by some packages. Therefore package configuration files should avoid setting such variables before their calls to ``find_dependency``. .. versionchanged:: 3.15 Previously, the underlying call to :command:`find_package` was always optimized out if the package had already been found. CMake 3.15 removed the optimization to support cases in which ``find_dependency`` call arguments request different components. .. versionchanged:: 3.26 The pre-3.15 optimization was restored, but with the above-described heuristic to account for varying ``find_dependency`` call arguments. #]=======================================================================] macro(find_dependency_pc dep) find_dependency(PkgConfig) string(SHA256 cmake_fd_call_hash "${dep};${ARGN};${${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED}") if(_CMAKE_${dep}_${cmake_fd_call_hash}_FOUND) unset(cmake_fd_call_hash) else() list(APPEND _CMAKE_${dep}_HASH_STACK ${cmake_fd_call_hash}) set(cmake_fd_quiet_arg) if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) set(cmake_fd_quiet_arg QUIET) endif() set(cmake_fd_required_arg) if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED) set(cmake_fd_required_arg REQUIRED) endif() get_property(cmake_fd_alreadyTransitive GLOBAL PROPERTY _CMAKE_${dep}_TRANSITIVE_DEPENDENCY ) pkg_check_modules(${dep} ${cmake_fd_quiet_arg} ${cmake_fd_required_arg} IMPORTED_TARGET ${ARGN} ) list(POP_BACK _CMAKE_${dep}_HASH_STACK cmake_fd_call_hash) set("_CMAKE_${dep}_${cmake_fd_call_hash}_FOUND" "${${dep}_FOUND}") if(NOT DEFINED cmake_fd_alreadyTransitive OR cmake_fd_alreadyTransitive) set_property(GLOBAL PROPERTY _CMAKE_${dep}_TRANSITIVE_DEPENDENCY TRUE) endif() unset(cmake_fd_alreadyTransitive) unset(cmake_fd_call_hash) unset(cmake_fd_quiet_arg) unset(cmake_fd_required_arg) if (NOT ${dep}_FOUND) set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE "${CMAKE_FIND_PACKAGE_NAME} could not be found because dependency ${dep} could not be found.") set(${CMAKE_FIND_PACKAGE_NAME}_FOUND False) return() endif() endif() endmacro() znc-1.9.1/cmake/CMakeFindFrameworks_fixed.cmake0000644000175000017500000000260714641222733021634 0ustar somebodysomebody# Modification from upstream CMakeFindFrameworks.cmake from version 3.3.1: # - add support of ${fwk}_FRAMEWORKS_ADDITIONAL input variable #.rst: # CMakeFindFrameworks # ------------------- # # helper module to find OSX frameworks #============================================================================= # Copyright 2003-2009 Kitware, Inc. # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # (To distribute this file outside of CMake, substitute the full # License text for the above reference.) if(NOT CMAKE_FIND_FRAMEWORKS_INCLUDED) set(CMAKE_FIND_FRAMEWORKS_INCLUDED 1) macro(CMAKE_FIND_FRAMEWORKS fwk) set(${fwk}_FRAMEWORKS) if(APPLE) foreach(dir ${${fwk}_FRAMEWORKS_ADDITIONAL} ~/Library/Frameworks/${fwk}.framework /Library/Frameworks/${fwk}.framework /System/Library/Frameworks/${fwk}.framework /Network/Library/Frameworks/${fwk}.framework) if(EXISTS ${dir}) set(${fwk}_FRAMEWORKS ${${fwk}_FRAMEWORKS} ${dir}) endif() endforeach() endif() endmacro() endif() znc-1.9.1/cmake/FindPerlLibs.cmake0000644000175000017500000000614514641222733017151 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # perl 5.20 will fix this warning: # https://rt.perl.org/Public/Bug/Display.html?id=120670 set(PERL_CFLAGS -Wno-reserved-user-defined-literal -Wno-literal-suffix CACHE STRING "Perl compiler flags") if(PerlLibs_FIND_VERSION_EXACT) set(_PerlLibs_exact EXACT) else() set(_PerlLibs_exact) endif() find_package(Perl ${PerlLibs_FIND_VERSION} ${PerlLibs_exact} QUIET) if(PERL_FOUND) if(PERL_INCLUDE_DIR AND PERL_LIBRARIES) else() execute_process( COMMAND "${PERL_EXECUTABLE}" -MExtUtils::Embed -e perl_inc OUTPUT_VARIABLE _PerlLibs_inc_output OUTPUT_STRIP_TRAILING_WHITESPACE) string(REGEX REPLACE "^ *-I" "" _PerlLibs_include "${_PerlLibs_inc_output}") execute_process( COMMAND "${PERL_EXECUTABLE}" -MExtUtils::Embed -e ldopts OUTPUT_VARIABLE _PerlLibs_ld_output OUTPUT_STRIP_TRAILING_WHITESPACE) separate_arguments(_PerlLibs_ld_output) set(_PerlLibs_ldflags) set(_PerlLibs_libraries) foreach(_PerlLibs_i ${_PerlLibs_ld_output}) if("${_PerlLibs_i}" MATCHES "^-l") list(APPEND _PerlLibs_libraries "${_PerlLibs_i}") else() set(_PerlLibs_ldflags "${_PerlLibs_ldflags} ${_PerlLibs_i}") endif() endforeach() get_filename_component(_PerlLibs_dir "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) try_compile(_PerlLibs_try "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/perl_check" "${_PerlLibs_dir}/perl_check" perl_check CMAKE_FLAGS "-DPERL_INCLUDE_DIRS=${_PerlLibs_include}" "-DPERL_LDFLAGS=${_PerlLibs_ldflags}" "-DPERL_LIBRARIES=${_PerlLibs_libraries}" "-DPERL_CFLAGS=${PERL_CFLAGS}" OUTPUT_VARIABLE _PerlLibs_tryout) if(_PerlLibs_try) set(PERL_INCLUDE_DIR "${_PerlLibs_include}" CACHE PATH "Perl include dir") set(PERL_LDFLAGS "${_PerlLibs_ldflags}" CACHE STRING "Perl linker flags") set(PERL_LIBRARIES "${_PerlLibs_libraries}" CACHE STRING "Perl libraries") file(APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log" "Output using Perl:\n${_PerlLibs_tryout}\n") else() set(_PerlLibs_failmsg FAIL_MESSAGE "Attempt to use Perl failed") file(APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log" "Error using Perl:\n${_PerlLibs_tryout}\n") endif() endif() else() set(_PerlLibs_failmsg FAIL_MESSAGE "Perl not found") endif() find_package_handle_standard_args(PerlLibs REQUIRED_VARS PERL_INCLUDE_DIR PERL_LIBRARIES VERSION_VAR PERL_VERSION_STRING ${_PerlLibs_failmsg}) set(PERL_INCLUDE_DIRS "${PERL_INCLUDE_DIR}") mark_as_advanced(PERL_INCLUDE_DIR PERL_LDFLAGS PERL_LIBRARIES PERL_CFLAGS) znc-1.9.1/cmake/TestCXX17.cmake0000644000175000017500000000276214641222733016307 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # if(NOT DEFINED cxx17check) message(STATUS "Checking for C++17 support") get_filename_component(_CXX17Check_dir "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) try_compile(cxx17_supported "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cxx17check" "${_CXX17Check_dir}/cxx17check" cxx17check OUTPUT_VARIABLE _CXX17Check_tryout) if(cxx17_supported) message(STATUS "Checking for C++17 support - supported") SET(cxx17check 1 CACHE INTERNAL "C++17 support") file(APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log" "Output of C++17 check:\n${_CXX17Check_tryout}\n") else() file(APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log" "Error in C++17 check:\n${_CXX17Check_tryout}\n") message(STATUS "Checking for C++17 support - not supported") message(FATAL_ERROR " Upgrade your compiler.\n" " GCC 8+ and Clang 5+ should work.") endif() endif() znc-1.9.1/cmake/TestFileOffsetBits.cpp0000644000175000017500000000055514641222733020045 0ustar somebodysomebody// See TestLargeFiles.cmake for the origin of this file #include int main(int argc, char **argv) { /* Cause a compile-time error if off_t is smaller than 64 bits */ #define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[ (LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1 ]; return 0; } znc-1.9.1/cmake/TestLargeFiles.cmake0000644000175000017500000001141614641222733017506 0ustar somebodysomebody# Downloaded from https://panthema.net/2012/1119-eSAIS-Inducing-Suffix-and-LCP-Arrays-in-External-Memory/eSAIS-DC3-LCP-0.5.4/stxxl/misc/cmake/TestLargeFiles.cmake.html # That project's license is Boost Software License, Version 1.0. # - Define macro to check large file support # # TEST_LARGE_FILES(VARIABLE) # # VARIABLE will be set to true if off_t is 64 bits, and fseeko/ftello present. # This macro will also set defines necessary enable large file support, for instance # _LARGE_FILES # _LARGEFILE_SOURCE # _FILE_OFFSET_BITS 64 # HAVE_FSEEKO # # However, it is YOUR job to make sure these defines are set in a cmakedefine so they # end up in a config.h file that is included in your source if necessary! set(_test_large_files_dir "${CMAKE_CURRENT_LIST_DIR}") MACRO(TEST_LARGE_FILES VARIABLE) IF(NOT DEFINED ${VARIABLE}) # On most platforms it is probably overkill to first test the flags for 64-bit off_t, # and then separately fseeko. However, in the future we might have 128-bit filesystems # (ZFS), so it might be dangerous to indiscriminately set e.g. _FILE_OFFSET_BITS=64. MESSAGE(STATUS "Checking for 64-bit off_t") # First check without any special flags TRY_COMPILE(FILE64_OK "${PROJECT_BINARY_DIR}" "${_test_large_files_dir}/TestFileOffsetBits.cpp") if(FILE64_OK) MESSAGE(STATUS "Checking for 64-bit off_t - present") endif(FILE64_OK) if(NOT FILE64_OK) # Test with _FILE_OFFSET_BITS=64 TRY_COMPILE(FILE64_OK "${PROJECT_BINARY_DIR}" "${_test_large_files_dir}/TestFileOffsetBits.cpp" COMPILE_DEFINITIONS "-D_FILE_OFFSET_BITS=64" ) if(FILE64_OK) MESSAGE(STATUS "Checking for 64-bit off_t - present with _FILE_OFFSET_BITS=64") set(_FILE_OFFSET_BITS 64) endif(FILE64_OK) endif(NOT FILE64_OK) if(NOT FILE64_OK) # Test with _LARGE_FILES TRY_COMPILE(FILE64_OK "${PROJECT_BINARY_DIR}" "${_test_large_files_dir}/TestFileOffsetBits.cpp" COMPILE_DEFINITIONS "-D_LARGE_FILES" ) if(FILE64_OK) MESSAGE(STATUS "Checking for 64-bit off_t - present with _LARGE_FILES") set(_LARGE_FILES 1) endif(FILE64_OK) endif(NOT FILE64_OK) if(NOT FILE64_OK) # Test with _LARGEFILE_SOURCE TRY_COMPILE(FILE64_OK "${PROJECT_BINARY_DIR}" "${_test_large_files_dir}/TestFileOffsetBits.cpp" COMPILE_DEFINITIONS "-D_LARGEFILE_SOURCE" ) if(FILE64_OK) MESSAGE(STATUS "Checking for 64-bit off_t - present with _LARGEFILE_SOURCE") set(_LARGEFILE_SOURCE 1) endif(FILE64_OK) endif(NOT FILE64_OK) if(NOT FILE64_OK) # now check for Windows stuff TRY_COMPILE(FILE64_OK "${PROJECT_BINARY_DIR}" "${_test_large_files_dir}/TestWindowsFSeek.cpp") if(FILE64_OK) MESSAGE(STATUS "Checking for 64-bit off_t - present with _fseeki64") set(HAVE__FSEEKI64 1) endif(FILE64_OK) endif(NOT FILE64_OK) if(NOT FILE64_OK) MESSAGE(STATUS "Checking for 64-bit off_t - not present") else(NOT FILE64_OK) # Set the flags we might have determined to be required above configure_file("${_test_large_files_dir}/TestLargeFiles.cpp.cmakein" "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestLargeFiles.cpp") MESSAGE(STATUS "Checking for fseeko/ftello") # Test if ftello/fseeko are available TRY_COMPILE(FSEEKO_COMPILE_OK "${PROJECT_BINARY_DIR}" "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestLargeFiles.cpp") if(FSEEKO_COMPILE_OK) MESSAGE(STATUS "Checking for fseeko/ftello - present") endif(FSEEKO_COMPILE_OK) if(NOT FSEEKO_COMPILE_OK) # glibc 2.2 neds _LARGEFILE_SOURCE for fseeko (but not 64-bit off_t...) TRY_COMPILE(FSEEKO_COMPILE_OK "${PROJECT_BINARY_DIR}" "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestLargeFiles.cpp" COMPILE_DEFINITIONS "-D_LARGEFILE_SOURCE" ) if(FSEEKO_COMPILE_OK) MESSAGE(STATUS "Checking for fseeko/ftello - present with _LARGEFILE_SOURCE") set(_LARGEFILE_SOURCE 1) endif(FSEEKO_COMPILE_OK) endif(NOT FSEEKO_COMPILE_OK) endif(NOT FILE64_OK) if(FSEEKO_COMPILE_OK) SET(${VARIABLE} 1 CACHE INTERNAL "Result of test for large file support" FORCE) set(HAVE_FSEEKO 1) else(FSEEKO_COMPILE_OK) if (HAVE__FSEEKI64) SET(${VARIABLE} 1 CACHE INTERNAL "Result of test for large file support" FORCE) SET(HAVE__FSEEKI64 1 CACHE INTERNAL "Windows 64-bit fseek" FORCE) else (HAVE__FSEEKI64) MESSAGE(STATUS "Checking for fseeko/ftello - not found") SET(${VARIABLE} 0 CACHE INTERNAL "Result of test for large file support" FORCE) endif (HAVE__FSEEKI64) endif(FSEEKO_COMPILE_OK) ENDIF(NOT DEFINED ${VARIABLE}) ENDMACRO(TEST_LARGE_FILES VARIABLE) znc-1.9.1/cmake/TestLargeFiles.cpp.cmakein0000644000175000017500000000130414641222733020611 0ustar somebodysomebody// See TestLargeFiles.cmake for the origin of this file #cmakedefine _LARGEFILE_SOURCE #cmakedefine _LARGEFILE64_SOURCE #cmakedefine _LARGE_FILES #cmakedefine _FILE_OFFSET_BITS ${_FILE_OFFSET_BITS} #include #include #include int main(int argc, char **argv) { /* Cause a compile-time error if off_t is smaller than 64 bits, * and make sure we have ftello / fseeko. */ #define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[ (LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1 ]; FILE *fp = fopen(argv[0],"r"); off_t offset = ftello( fp ); fseeko( fp, offset, SEEK_CUR ); fclose(fp); return 0; } znc-1.9.1/cmake/TestWindowsFSeek.cpp0000644000175000017500000000025114641222733017536 0ustar somebodysomebody// See TestLargeFiles.cmake for the origin of this file #include int main() { __int64 off=0; _fseeki64(NULL, off, SEEK_SET); return 0; } znc-1.9.1/cmake/copy_csocket.cmake0000644000175000017500000000175514641222733017323 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function(copy_csocket tgt source destination) add_custom_command(OUTPUT "${destination}" COMMAND "${CMAKE_COMMAND}" -D "source=${source}" -D "destination=${destination}" -P "${PROJECT_SOURCE_DIR}/cmake/copy_csocket_cmd.cmake" DEPENDS "${source}" "${PROJECT_SOURCE_DIR}/cmake/copy_csocket_cmd.cmake" VERBATIM) add_custom_target("${tgt}" DEPENDS "${destination}") endfunction() znc-1.9.1/cmake/copy_csocket_cmd.cmake0000644000175000017500000000154214641222733020140 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # file(READ "${source}" content) string(REPLACE "#include \"defines.h\"" "#include " content "${content}") string(REPLACE "#include \"Csocket.h\"" "#include " content "${content}") file(WRITE "${destination}" "${content}") znc-1.9.1/cmake/cxx17check/0000755000175000017500000000000014641222733015574 5ustar somebodysomebodyznc-1.9.1/cmake/cxx17check/CMakeLists.txt0000644000175000017500000000145714641222733020343 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # cmake_minimum_required(VERSION 3.13) project(cxx11check LANGUAGES CXX) set(CMAKE_VERBOSE_MAKEFILE true) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED true) add_executable(main main.cpp) znc-1.9.1/cmake/cxx17check/main.cpp0000644000175000017500000000307314641222733017227 0ustar somebodysomebody// This file uses code from http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html (serial 5) // Copyright (c) 2008 Benjamin Kosnik // Copyright (c) 2012 Zack Weinberg // Copyright (c) 2013 Roy Stogner // Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov // // Copying and distribution of this file, with or without modification, are // permitted in any medium without royalty provided the copyright notice // and this notice are preserved. This file is offered as-is, without any // warranty. #include #include template struct check { static_assert(sizeof(int) <= sizeof(T), "not big enough"); }; struct Base { virtual void f() {} }; struct Child : public Base { virtual void f() override {} }; typedef check> right_angle_brackets; int a; decltype(a) b; typedef check check_type; check_type c; check_type&& cr = static_cast(c); auto d = a; auto l = []() {}; // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function // because of this namespace test_template_alias_sfinae { struct foo {}; template using member = typename T::member_type; template void func(...) {} template void func(member*) {} void test(); void test() { func(0); } } int main() { std::map m; m.emplace(2, 4); auto [x, y] = *m.begin(); return 0; } znc-1.9.1/cmake/gen_version.cmake0000644000175000017500000000450114641222733017144 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # if(nightly) set(git_info "${nightly}") elseif(append_git_version) set(git ${gitcmd} "--git-dir=${srcdir}/.git") execute_process(COMMAND ${git} describe --abbrev=0 HEAD RESULT_VARIABLE git_result ERROR_QUIET OUTPUT_VARIABLE latest_tag) string(STRIP "${latest_tag}" latest_tag) if(git_result EQUAL 0) execute_process(COMMAND ${git} rev-parse --short HEAD OUTPUT_VARIABLE short_id) string(STRIP "${short_id}" short_id) if(latest_tag STREQUAL "") # shallow clone set(git_info "-git-${short_id}") else() # One character "x" per commit, remove newlines from output execute_process(COMMAND ${git} log --format=tformat:x "${latest_tag}..HEAD" OUTPUT_VARIABLE commits_since) string(REGEX REPLACE "[^x]" "" commits_since "${commits_since}") string(LENGTH "${commits_since}" commits_since) if(commits_since EQUAL 0) # If this commit is tagged, don't print anything # (the assumption here is: this is a release) set(git_info "") # However, this shouldn't happen, as for releases # append_git_version should be set to false else() set(git_info "-git-${commits_since}-${short_id}") endif() endif() execute_process(COMMAND ${gitcmd} status --ignore-submodules=dirty --porcelain -- third_party/Csocket WORKING_DIRECTORY "${srcdir}" OUTPUT_VARIABLE franken) string(STRIP "${franken}" franken) if(NOT franken STREQUAL "") message(WARNING " Csocket submodule looks outdated.\n" " Run: git submodule update --init --recursive") set(git_info "${git_info}-frankenznc") endif() else() # Probably .git/ or git isn't found, or something set(git_info "-git-unknown") endif() else() set(git_info "") endif() configure_file("${srcfile}" "${destfile}") znc-1.9.1/cmake/perl_check/0000755000175000017500000000000014641222733015723 5ustar somebodysomebodyznc-1.9.1/cmake/perl_check/CMakeLists.txt0000644000175000017500000000214414641222733020464 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # cmake_minimum_required(VERSION 3.13) project(perl_check LANGUAGES CXX) set(CMAKE_VERBOSE_MAKEFILE true) if(APPLE) set(CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS "${CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS} -undefined dynamic_lookup") endif() add_library(main MODULE main.cpp) target_include_directories(main PRIVATE ${PERL_INCLUDE_DIRS}) target_compile_options(main PRIVATE "${PERL_CFLAGS}") target_link_libraries(main PRIVATE ${PERL_LIBRARIES}) set_target_properties(main PROPERTIES LINK_FLAGS "${PERL_LDFLAGS}") znc-1.9.1/cmake/perl_check/main.cpp0000644000175000017500000000160114641222733017351 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include int perlcheck(int argc, char** argv, char** env) { PERL_SYS_INIT3(&argc, &argv, &env); PerlInterpreter* p = perl_alloc(); perl_construct(p); perl_destruct(p); perl_free(p); PERL_SYS_TERM(); return 0; } znc-1.9.1/cmake/render_framed_multiline.cmake0000644000175000017500000000221014641222733021500 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function(render_framed_multiline lines) set(max_len 0) foreach(l ${lines}) string(LENGTH "${l}" l_len) if(l_len GREATER max_len) set(max_len "${l_len}") endif() endforeach() set(i 0) set(header) while(i LESS max_len) set(header "${header}-") math(EXPR i "${i} + 1") endwhile() message("+-${header}-+") foreach(l ${lines}) string(LENGTH "${l}" l_len) while(l_len LESS max_len) set(l "${l} ") math(EXPR l_len "${l_len} + 1") endwhile() message("| ${l} |") endforeach() message("+-${header}-+") endfunction() znc-1.9.1/cmake/translation.cmake0000644000175000017500000000504514641222733017170 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # include(CMakeParseArguments) function(translation) cmake_parse_arguments(arg "" "FULL;SHORT" "SOURCES;TMPLDIRS" ${ARGN}) set(short "${arg_SHORT}") file(GLOB all_po "${short}.*.po") if(XGETTEXT_EXECUTABLE) set(params) foreach(i ${arg_SOURCES}) list(APPEND params "--explicit_sources=${i}") endforeach() foreach(i ${arg_TMPLDIRS}) list(APPEND params "--tmpl_dirs=${i}") endforeach() add_custom_target("translation_${short}" COMMAND "${PROJECT_SOURCE_DIR}/translation_pot.py" "--include_dir=${CMAKE_CURRENT_SOURCE_DIR}/.." "--strip_prefix=${PROJECT_SOURCE_DIR}/" "--tmp_prefix=${CMAKE_CURRENT_BINARY_DIR}/${short}" "--output=${CMAKE_CURRENT_SOURCE_DIR}/${short}.pot" ${params} VERBATIM) foreach(one_po ${all_po}) add_custom_command(TARGET "translation_${short}" POST_BUILD COMMAND "${GETTEXT_MSGMERGE_EXECUTABLE}" --update --quiet --backup=none "${one_po}" "${short}.pot" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" VERBATIM) endforeach() add_dependencies(translation "translation_${short}") endif() if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${short}.pot") return() endif() znc_add_custom_target("po_${short}") foreach(one_po ${all_po}) get_filename_component(longext "${one_po}" EXT) if(NOT longext MATCHES "^\\.([a-zA-Z_]+)\\.po$") message(WARNING "Unrecognized translation file ${one_po}") continue() endif() set(lang "${CMAKE_MATCH_1}") add_custom_command(OUTPUT "${short}.${lang}.gmo" COMMAND "${GETTEXT_MSGFMT_EXECUTABLE}" -D "${CMAKE_CURRENT_SOURCE_DIR}" -o "${short}.${lang}.gmo" "${short}.${lang}.po" DEPENDS "${short}.${lang}.po" VERBATIM) add_custom_target("po_${short}_${lang}" DEPENDS "${short}.${lang}.gmo") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${short}.${lang}.gmo" DESTINATION "${CMAKE_INSTALL_LOCALEDIR}/${lang}/LC_MESSAGES" RENAME "${arg_FULL}.mo") add_dependencies("po_${short}" "po_${short}_${lang}") endforeach() endfunction() znc-1.9.1/cmake/translation_tmpl.py0000755000175000017500000000364114641222733017577 0ustar somebodysomebody#!/usr/bin/env python3 # # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import argparse import glob import os import re parser = argparse.ArgumentParser( description='Extract translateable strings from .tmpl files') parser.add_argument('--directory', action='store') parser.add_argument('--output', action='store') args = parser.parse_args() pattern = re.compile(r'<\?\s*(?:FORMAT|(PLURAL))\s+(?:CTX="([^"]+?)"\s+)?"([^"]+?)"(?(1)\s+"([^"]+?)"|).*?\?>') result = [] for fname in glob.iglob(args.directory + '/*.tmpl'): fbase = os.path.basename(fname) with open(fname) as f: for linenum, line in enumerate(f): for x in pattern.finditer(line): text, plural, context = x.group(3), x.group(4), x.group(2) result.append('#: {}:{}'.format(fbase, linenum + 1)) if context: result.append('msgctxt "{}"'.format(context)) result.append('msgid "{}"'.format(text)) if plural: result.append('msgid_plural "{}"'.format(plural)) result.append('msgstr[0] ""') result.append('msgstr[1] ""') else: result.append('msgstr ""') result.append('') if result: with open(args.output, 'w') as f: for line in result: print(line, file=f) znc-1.9.1/cmake/use_homebrew.cmake0000644000175000017500000000471314641222733017317 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # if(NOT APPLE) return() endif() include(FindPackageMessage) find_program(brew brew) if(brew) find_package_message(brew "Homebrew found: ${brew}" "1;${brew}") else() find_package_message(brew "Homebrew not found" "0") return() endif() execute_process(COMMAND "${brew}" --prefix icu4c RESULT_VARIABLE brew_icu_f OUTPUT_VARIABLE brew_icu OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) if(brew_icu_f EQUAL 0) find_package_message(brew_icu "ICU via Homebrew: ${brew_icu}" "${brew_icu}") set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${brew_icu}/lib/pkgconfig") endif() execute_process(COMMAND "${brew}" --prefix openssl RESULT_VARIABLE brew_ssl_f OUTPUT_VARIABLE brew_ssl OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) if(brew_ssl_f EQUAL 0) find_package_message(brew_ssl "OpenSSL via Homebrew: ${brew_ssl}" "${brew_ssl}") set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${brew_ssl}/lib/pkgconfig") endif() execute_process(COMMAND "${brew}" --prefix python3 RESULT_VARIABLE brew_python_f OUTPUT_VARIABLE brew_python OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) if(brew_python_f EQUAL 0) find_package_message(brew_python "Python via Homebrew: ${brew_python}" "${brew_python}") list(APPEND Python3_FRAMEWORKS_ADDITIONAL "${brew_python}/Frameworks/Python.framework") endif() execute_process(COMMAND "${brew}" --prefix qt5 RESULT_VARIABLE brew_qt5_f OUTPUT_VARIABLE brew_qt5 OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) if(brew_qt5_f EQUAL 0) find_package_message(brew_qt5 "Qt5 via Homebrew: ${brew_qt5}" "${brew_qt5}") endif() execute_process(COMMAND "${brew}" --prefix gettext RESULT_VARIABLE brew_gettext_f OUTPUT_VARIABLE brew_gettext OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) if(brew_gettext_f EQUAL 0) find_package_message(brew_gettext "Gettext via homebrew: ${brew_gettext}" "${brew_gettext}") set(ENV{PATH} "$ENV{PATH}:${brew_gettext}/bin") endif() znc-1.9.1/configure0000755000175000017500000001116414641222733014456 0ustar somebodysomebody#!/bin/sh # # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # http://stackoverflow.com/questions/18993438/shebang-env-preferred-python-version # http://stackoverflow.com/questions/12070516/conditional-shebang-line-for-different-versions-of-python """:" which python3 >/dev/null 2>&1 && exec python3 "$0" "$@" which python >/dev/null 2>&1 && exec python "$0" "$@" which python2 >/dev/null 2>&1 && exec python2 "$0" "$@" echo "Error: configure wrapper requires python" exec echo "Either install python, or use cmake directly" ":""" import argparse import os import shutil import subprocess import sys import re extra_args = [os.path.dirname(sys.argv[0])] parser = argparse.ArgumentParser() def gnu_install_dir(name, cmake=None): if cmake is None: cmake = name.upper() parser.add_argument('--' + name, action='append', metavar=cmake, dest='cm_args', type=lambda s: '-DCMAKE_INSTALL_{}={}'.format(cmake, s)) gnu_install_dir('prefix') gnu_install_dir('bindir') gnu_install_dir('sbindir') gnu_install_dir('libexecdir') gnu_install_dir('sysconfdir') gnu_install_dir('sharedstatedir') gnu_install_dir('localstatedir') gnu_install_dir('libdir') gnu_install_dir('includedir') gnu_install_dir('oldincludedir') gnu_install_dir('datarootdir') gnu_install_dir('datadir') gnu_install_dir('infodir') gnu_install_dir('localedir') gnu_install_dir('mandir') gnu_install_dir('docdir') group = parser.add_mutually_exclusive_group() group.add_argument('--enable-debug', action='store_const', dest='build_type', const='Debug', default='Release') group.add_argument('--disable-debug', action='store_const', dest='build_type', const='Release', default='Release') def tristate(name, cmake=None): if cmake is None: cmake = name.upper() group = parser.add_mutually_exclusive_group() group.add_argument('--enable-' + name, action='append_const', dest='cm_args', const='-DWANT_{}=YES'.format(cmake)) group.add_argument('--disable-' + name, action='append_const', dest='cm_args', const='-DWANT_{}=NO'.format(cmake)) tristate('ipv6') tristate('openssl') tristate('zlib') tristate('perl') tristate('swig') tristate('cyrus') tristate('charset', 'ICU') tristate('tcl') tristate('i18n') tristate('argon') class HandlePython(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): extra_args.append('-DWANT_PYTHON=YES') if values is not None: extra_args.append('-DWANT_PYTHON_VERSION=' + values) group = parser.add_mutually_exclusive_group() group.add_argument('--enable-python', action=HandlePython, nargs='?', metavar='PYTHON_VERSION') group.add_argument('--disable-python', action='append_const', dest='cm_args', const='-DWANT_PYTHON=NO') parser.add_argument('--with-gtest', action='store', dest='gtest') parser.add_argument('--with-gmock', action='store', dest='gmock') class HandleSystemd(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): extra_args.append('-DWANT_SYSTEMD=YES') if values is not None: extra_args.append('-DWANT_SYSTEMD_DIR=' + values) parser.add_argument('--with-systemdsystemunitdir', action=HandleSystemd, nargs='?', metavar='UNITDIR') def env_type(s): name, value = s.split('=', 1) return (name, value) parser.add_argument('env', nargs='*', type=env_type, metavar='ENV_VAR=VALUE') args = parser.parse_args() if not shutil.which('cmake'): print('CMake is required to proceed. Please install it via package manager,\n' 'or download the binary from https://cmake.org/') sys.exit(1) cm_args = args.cm_args or [] for env_key, env_value in args.env: os.environ[env_key] = env_value if args.gtest: os.environ['GTEST_ROOT'] = args.gtest if args.gmock: os.environ['GMOCK_ROOT'] = args.gmock if os.environ.get('CXX') is not None: extra_args.append('-DCMAKE_CXX_COMPILER=' + os.environ['CXX']) try: os.remove('CMakeCache.txt') except OSError: pass subprocess.check_call(['cmake', '-DCMAKE_BUILD_TYPE=' + args.build_type] + cm_args + extra_args) znc-1.9.1/configure.sh0000777000175000017500000000000014641222733017117 2./configureustar somebodysomebodyznc-1.9.1/docker/0000755000175000017500000000000014641222733014013 5ustar somebodysomebodyznc-1.9.1/include/0000755000175000017500000000000014641222733014167 5ustar somebodysomebodyznc-1.9.1/include/CMakeLists.txt0000644000175000017500000000117514641222733016733 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # add_subdirectory(znc) znc-1.9.1/include/znc/0000755000175000017500000000000014641222733014761 5ustar somebodysomebodyznc-1.9.1/include/znc/Buffer.h0000644000175000017500000000773014641222733016352 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_BUFFER_H #define ZNC_BUFFER_H #include #include #include #include #include // Forward Declarations class CClient; // !Forward Declarations class CBufLine { public: CBufLine() : CBufLine("") { throw 0; } // shouldn't be called, but is needed for compilation CBufLine(const CMessage& Format, const CString& sText = ""); /// @deprecated CBufLine(const CString& sFormat, const CString& sText = "", const timeval* ts = nullptr, const MCString& mssTags = MCString::EmptyMap); ~CBufLine(); CMessage ToMessage(const CClient& Client, const MCString& mssParams) const; /// @deprecated Use ToMessage() instead CString GetLine(const CClient& Client, const MCString& mssParams) const; /// @deprecated void UpdateTime(); bool Equals(const CMessage& Format) const { return m_Message.Equals(Format); } // Setters void SetFormat(const CString& sFormat) { m_Message.Parse(sFormat); } void SetText(const CString& sText) { m_sText = sText; } void SetTime(const timeval& ts) { m_Message.SetTime(ts); } void SetTags(const MCString& mssTags) { m_Message.SetTags(mssTags); } // !Setters // Getters const CString& GetCommand() const { return m_Message.GetCommand(); } CString GetFormat() const { return m_Message.ToString(CMessage::ExcludeTags); } const CString& GetText() const { return m_sText; } timeval GetTime() const { return m_Message.GetTime(); } const MCString& GetTags() const { return m_Message.GetTags(); } // !Getters private: protected: CMessage m_Message; CString m_sText; }; class CBuffer : private std::deque { public: CBuffer(unsigned int uLineCount = 100); ~CBuffer(); size_type AddLine(const CMessage& Format, const CString& sText = ""); size_type UpdateLine(const CString& sCommand, const CMessage& Format, const CString& sText = ""); size_type UpdateExactLine(const CMessage& Format, const CString& sText = ""); size_type AddLine(const CString& sFormat, const CString& sText = "", const timeval* ts = nullptr, const MCString& mssTags = MCString::EmptyMap); /// Same as AddLine, but replaces a line whose format string starts with sMatch if there is one. size_type UpdateLine(const CString& sMatch, const CString& sFormat, const CString& sText = ""); /// Same as UpdateLine, but does nothing if this exact line already exists. /// We need this because "/version" sends us the 005 raws again size_type UpdateExactLine(const CString& sFormat, const CString& sText = ""); const CBufLine& GetBufLine(unsigned int uIdx) const; CString GetLine(size_type uIdx, const CClient& Client, const MCString& msParams = MCString::EmptyMap) const; size_type Size() const { return size(); } bool IsEmpty() const { return empty(); } void Clear() { clear(); } // Setters bool SetLineCount(unsigned int u, bool bForce = false); // !Setters // Getters unsigned int GetLineCount() const { return m_uLineCount; } // !Getters private: protected: unsigned int m_uLineCount; }; #endif // !ZNC_BUFFER_H znc-1.9.1/include/znc/CMakeLists.txt0000644000175000017500000000246114641222733017524 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # copy_csocket(copy_csocket_h "${PROJECT_SOURCE_DIR}/third_party/Csocket/Csocket.h" "${CMAKE_CURRENT_BINARY_DIR}/Csocket.h") # __attribute__((__visibility__("default")) macro comes from # generate_export_header(), which wants a library. # we don't really care which library is that. # Macro defined by this generated header is used in Modules.h # Name of this pseudolibrary matches the define passed to modules. add_library(znc_export_lib SHARED IMPORTED) include(GenerateExportHeader) generate_export_header(znc_export_lib) install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/" "${CMAKE_CURRENT_BINARY_DIR}/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/znc" FILES_MATCHING PATTERN "*.h" PATTERN CMakeFiles EXCLUDE) znc-1.9.1/include/znc/Chan.h0000644000175000017500000001720114641222733016004 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_CHAN_H #define ZNC_CHAN_H #include #include #include #include #include #include // Forward Declarations class CUser; class CIRCNetwork; class CClient; class CConfig; class CFile; // !Forward Declarations class CChan : private CCoreTranslationMixin { public: typedef enum { Voice = '+', HalfOp = '%', Op = '@', Admin = '!', Owner = '*' } EUserPerms; typedef enum { M_Private = 'p', M_Secret = 's', M_Moderated = 'm', M_InviteOnly = 'i', M_NoMessages = 'n', M_OpTopic = 't', M_Limit = 'l', M_Key = 'k', M_Op = 'o', M_Voice = 'v', M_Ban = 'b', M_Except = 'e' } EModes; CChan(const CString& sName, CIRCNetwork* pNetwork, bool bInConfig, CConfig* pConfig = nullptr); ~CChan(); CChan(const CChan&) = delete; CChan& operator=(const CChan&) = delete; void Reset(); CConfig ToConfig() const; void Clone(CChan& chan); void Cycle() const; void JoinUser(const CString& sKey = ""); void AttachUser(CClient* pClient = nullptr); void DetachUser(); void OnWho(const CString& sNick, const CString& sIdent, const CString& sHost); // Modes /// @deprecated Use SetModes(CString, VCString) void SetModes(const CString& s); /** * Set the current modes for this channel * @param sModes The mode characters being changed * @param vsModeParams The parameters for the modes to be set */ void SetModes(const CString& sModes, const VCString& vsModeParams); /// @deprecated Use ModeChange(CString, VCString, CNick*) void ModeChange(const CString& sModes, const CNick* OpNick = nullptr); /** * Handle changing the modes on a channel * @param sModes The mode string (eg. +ovbs-pbo) * @param vsModeParams The parameters for the mode string */ void ModeChange(const CString& sModes,const VCString& vsModeParams, const CNick* OpNick = nullptr); bool AddMode(char cMode, const CString& sArg); bool RemMode(char cMode); CString GetModeString() const; CString GetModeArg(CString& sArgs) const; CString GetModeForNames() const; // !Modes // Nicks void ClearNicks(); const CNick* FindNick(const CString& sNick) const; CNick* FindNick(const CString& sNick); int AddNicks(const CString& sNicks); bool AddNick(const CString& sNick); bool RemNick(const CString& sNick); bool ChangeNick(const CString& sOldNick, const CString& sNewNick); // !Nicks // Buffer const CBuffer& GetBuffer() const { return m_Buffer; } unsigned int GetBufferCount() const { return m_Buffer.GetLineCount(); } bool SetBufferCount(unsigned int u, bool bForce = false) { m_bHasBufferCountSet = true; return m_Buffer.SetLineCount(u, bForce); } void InheritBufferCount(unsigned int u, bool bForce = false) { if (!m_bHasBufferCountSet) m_Buffer.SetLineCount(u, bForce); } void ResetBufferCount(); size_t AddBuffer(const CMessage& Format, const CString& sText = "") { return m_Buffer.AddLine(Format, sText); } /// @deprecated size_t AddBuffer(const CString& sFormat, const CString& sText = "", const timeval* ts = nullptr, const MCString& mssTags = MCString::EmptyMap) { return m_Buffer.AddLine(sFormat, sText, ts, mssTags); } void ClearBuffer() { m_Buffer.Clear(); } void SendBuffer(CClient* pClient); void SendBuffer(CClient* pClient, const CBuffer& Buffer); // !Buffer // m_Nick wrappers /// e.g. '@' for chanop. CString GetPermStr() const { return m_Nick.GetPermStr(); } /// e.g. '@' for chanop. bool HasPerm(char cPerm) const { return m_Nick.HasPerm(cPerm); } /// e.g. '@' for chanop. bool AddPerm(char cPerm) { return m_Nick.AddPerm(cPerm); } /// e.g. '@' for chanop. bool RemPerm(char cPerm) { return m_Nick.RemPerm(cPerm); } // !wrappers // Setters void SetModeKnown(bool b) { m_bModeKnown = b; } void SetIsOn(bool b) { m_bIsOn = b; if (!b) { Reset(); } } void SetKey(const CString& s); void SetTopic(const CString& s) { m_sTopic = s; } void SetTopicOwner(const CString& s) { m_sTopicOwner = s; } void SetTopicDate(unsigned long u) { m_ulTopicDate = u; } void SetDefaultModes(const CString& s) { m_sDefaultModes = s; } void SetAutoClearChanBuffer(bool b); void InheritAutoClearChanBuffer(bool b); void ResetAutoClearChanBuffer(); void SetDetached(bool b = true) { m_bDetached = b; } void SetInConfig(bool b); void SetCreationDate(unsigned long u) { m_ulCreationDate = u; } void Disable() { m_bDisabled = true; } void Enable(); void IncJoinTries() { m_uJoinTries++; } void ResetJoinTries() { m_uJoinTries = 0; } // !Setters // Getters CIRCNetwork* GetNetwork() const { return m_pNetwork; } bool IsModeKnown() const { return m_bModeKnown; } bool HasMode(char cMode) const; CString GetOptions() const; CString GetModeArg(char cMode) const; std::map GetPermCounts() const; bool IsOn() const { return m_bIsOn; } const CString& GetName() const { return m_sName; } const std::map& GetModes() const { return m_mcsModes; } const CString& GetKey() const { return m_sKey; } const CString& GetTopic() const { return m_sTopic; } const CString& GetTopicOwner() const { return m_sTopicOwner; } unsigned long GetTopicDate() const { return m_ulTopicDate; } const CString& GetDefaultModes() const { return m_sDefaultModes; } const std::map& GetNicks() const { return m_msNicks; } size_t GetNickCount() const { return m_msNicks.size(); } bool AutoClearChanBuffer() const { return m_bAutoClearChanBuffer; } bool IsDetached() const { return m_bDetached; } bool InConfig() const { return m_bInConfig; } unsigned long GetCreationDate() const { return m_ulCreationDate; } bool IsDisabled() const { return m_bDisabled; } unsigned int GetJoinTries() const { return m_uJoinTries; } bool HasBufferCountSet() const { return m_bHasBufferCountSet; } bool HasAutoClearChanBufferSet() const { return m_bHasAutoClearChanBufferSet; } // !Getters private: protected: bool m_bDetached; bool m_bIsOn; bool m_bAutoClearChanBuffer; bool m_bInConfig; bool m_bDisabled; bool m_bHasBufferCountSet; bool m_bHasAutoClearChanBufferSet; CString m_sName; CString m_sKey; CString m_sTopic; CString m_sTopicOwner; unsigned long m_ulTopicDate; unsigned long m_ulCreationDate; CIRCNetwork* m_pNetwork; CNick m_Nick; unsigned int m_uJoinTries; CString m_sDefaultModes; std::map m_msNicks; // Todo: make this caseless (irc style) CBuffer m_Buffer; bool m_bModeKnown; std::map m_mcsModes; }; #endif // !ZNC_CHAN_H znc-1.9.1/include/znc/Client.h0000644000175000017500000002645714641222733016366 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_CLIENT_H #define ZNC_CLIENT_H #include #include #include #include #include #include #include // Forward Declarations class CZNC; class CUser; class CIRCNetwork; class CIRCSock; class CClient; class CMessage; class CChan; // !Forward Declarations class CAuthBase : private CCoreTranslationMixin { public: CAuthBase(const CString& sUsername, const CString& sPassword, CZNCSock* pSock) : m_sUsername(sUsername), m_sPassword(sPassword), m_pSock(pSock) {} virtual ~CAuthBase() {} CAuthBase(const CAuthBase&) = delete; CAuthBase& operator=(const CAuthBase&) = delete; virtual void SetLoginInfo(const CString& sUsername, const CString& sPassword, CZNCSock* pSock) { m_sUsername = sUsername; m_sPassword = sPassword; m_pSock = pSock; } void AcceptLogin(CUser& User); void RefuseLogin(const CString& sReason); const CString& GetUsername() const { return m_sUsername; } const CString& GetPassword() const { return m_sPassword; } Csock* GetSocket() const { return m_pSock; } CString GetRemoteIP() const; // Invalidate this CAuthBase instance which means it will no longer use // m_pSock and AcceptLogin() or RefusedLogin() will have no effect. virtual void Invalidate(); protected: virtual void AcceptedLogin(CUser& User) = 0; virtual void RefusedLogin(const CString& sReason) = 0; private: CString m_sUsername; CString m_sPassword; CZNCSock* m_pSock; }; class CClientAuth : public CAuthBase { public: CClientAuth(CClient* pClient, const CString& sUsername, const CString& sPassword); virtual ~CClientAuth() {} CClientAuth(const CClientAuth&) = delete; CClientAuth& operator=(const CClientAuth&) = delete; void Invalidate() override { m_pClient = nullptr; CAuthBase::Invalidate(); } void AcceptedLogin(CUser& User) override; void RefusedLogin(const CString& sReason) override; private: protected: CClient* m_pClient; }; class CClient : public CIRCSocket { public: CClient(); virtual ~CClient(); CClient(const CClient&) = delete; CClient& operator=(const CClient&) = delete; void SendRequiredPasswordNotice(); void AcceptLogin(CUser& User); void RefuseLogin(const CString& sReason); CString GetNick(bool bAllowIRCNick = true) const; CString GetNickMask() const; CString GetIdentifier() const { return m_sIdentifier; } unsigned short int CapVersion() const { return m_uCapVersion; } bool HasCap302() const { return CapVersion() >= 302; } bool HasCapNotify() const { return m_bCapNotify; } bool HasAwayNotify() const { return m_bAwayNotify; } bool HasAccountNotify() const { return m_bAccountNotify; } bool HasExtendedJoin() const { return m_bExtendedJoin; } bool HasNamesx() const { return m_bNamesx; } bool HasUHNames() const { return m_bUHNames; } bool IsAway() const { return m_bAway; } bool HasServerTime() const { return m_bServerTime; } bool HasBatch() const { return m_bBatch; } bool HasEchoMessage() const { return m_bEchoMessage; } bool HasSelfMessage() const { return m_bSelfMessage; } static bool IsValidIdentifier(const CString& sIdentifier); void UserCommand(CString& sLine); void UserPortCommand(CString& sLine); void StatusCTCP(const CString& sCommand); void BouncedOff(); bool IsAttached() const { return m_pUser != nullptr; } bool IsPlaybackActive() const { return m_bPlaybackActive; } void SetPlaybackActive(bool bActive) { m_bPlaybackActive = bActive; } void PutIRC(const CString& sLine); /** Sends a raw data line to the client. * @param sLine The line to be sent. * * The line is first passed \e unmodified to the \ref * CModule::OnSendToClient() module hook. If no module halts the process, * the line is then sent to the client. * * These lines appear in the debug output in the following syntax: * \code [time] (user/network) ZNC -> CLI [line] \endcode * * Prefer \l PutClient() instead. */ bool PutClientRaw(const CString& sLine); /** Sends a message to the client. * See \l PutClient(const CMessage&) for details. */ void PutClient(const CString& sLine); /** Sends a message to the client. * @param Message The message to be sent. * @note Only known and compatible messages and tags are sent. * @return \c true if the message was sent, or \c false if it was ignored. * * This method ensures that only messages and tags, that the client has * explicitly requested, are sent. Not all IRC clients are capable of * handling all messages and tags. For example, some older versions of * popular clients were prepared to parse just one interesting tag, * \c time, and would break if multiple tags were included. Furthermore, * messages that are specific to a certain capability, should not be sent * to a client that has not requested the respective capability. Thus, in * order to stay compatible with a variety of IRC clients, ZNC has to * filter out messages and tags that the client has not explicitly * requested. * * ### Message types * * The following table documents which capabilities the client is required * to have requested in order to receive certain types of messages. * * Message type | Capability * ------------ | ---------- * \c ACCOUNT | \l CClient::HasAccountNotify() (account-notify) * \c AWAY | \l CClient::HasAwayNotify() (away-notify) * * ### Message tags * * The following table documents currently supported message tags, and * which capabilities the client is required to have requested to receive * the respective message tags. * * Message tag | Capability * ----------- | ---------- * \c time | \l CClient::HasServerTime() (server-time) * \c batch | \l CClient::HasBatch() (batch) * * Additional tags can be added via \l CClient::SetTagSupport(). * * @warning Bypassing the filter may cause troubles to some older IRC * clients. * * It is possible to bypass the filter by converting a message to a string * using \l CMessage::ToString(), and passing the resulting raw line to the * \l CClient::PutClientRaw(const CString& sLine): * \code * pClient->PutClientRaw(Message.ToString()); * \endcode */ bool PutClient(const CMessage& Message); unsigned int PutStatus(const CTable& table); void PutStatus(const CString& sLine); void PutStatusNotice(const CString& sLine); void PutModule(const CString& sModule, const CString& sLine); void PutModNotice(const CString& sModule, const CString& sLine); bool IsCapEnabled(const CString& sCap) const { return 1 == m_ssAcceptedCaps.count(sCap); } bool IsTagEnabled(const CString& sTag) const { return 1 == m_ssSupportedTags.count(sTag); } /** Registers a tag as being supported or unsupported by the client. * This doesn't affect tags which the client sends. * @param sTag The tag to register. * @param bState Whether the client supports the tag. */ void SetTagSupport(const CString& sTag, bool bState); /** Notifies client about one specific cap which server has just notified us about. */ void NotifyServerDependentCap(const CString& sCap, bool bValue, const CString& sValue); void ReadLine(const CString& sData) override; bool SendMotd(); void HelpUser(const CString& sFilter = ""); void AuthUser(); void Connected() override; void Timeout() override; void Disconnected() override; void ConnectionRefused() override; void ReachedMaxBuffer() override; void SetNick(const CString& s); void SetAway(bool bAway) { m_bAway = bAway; } CUser* GetUser() const { return m_pUser; } void SetNetwork(CIRCNetwork* pNetwork, bool bDisconnect = true, bool bReconnect = true); CIRCNetwork* GetNetwork() const { return m_pNetwork; } const std::vector& GetClients() const; const CIRCSock* GetIRCSock() const; CIRCSock* GetIRCSock(); CString GetFullName() const; private: void HandleCap(const CMessage& Message); void RespondCap(const CString& sResponse); void ParsePass(const CString& sAuthLine); void ParseUser(const CString& sAuthLine); void ParseIdentifier(const CString& sAuthLine); template void AddBuffer(const T& Message); void EchoMessage(const CMessage& Message); std::set MatchChans(const CString& sPatterns) const; unsigned int AttachChans(const std::set& sChans); unsigned int DetachChans(const std::set& sChans); bool OnActionMessage(CActionMessage& Message); bool OnCTCPMessage(CCTCPMessage& Message); bool OnJoinMessage(CJoinMessage& Message); bool OnModeMessage(CModeMessage& Message); bool OnNoticeMessage(CNoticeMessage& Message); bool OnPartMessage(CPartMessage& Message); bool OnPingMessage(CMessage& Message); bool OnPongMessage(CMessage& Message); bool OnQuitMessage(CQuitMessage& Message); bool OnTextMessage(CTextMessage& Message); bool OnTopicMessage(CTopicMessage& Message); bool OnOtherMessage(CMessage& Message); protected: bool m_bGotPass; bool m_bGotNick; bool m_bGotUser; unsigned short int m_uCapVersion; bool m_bInCap; bool m_bCapNotify; bool m_bAwayNotify; bool m_bAccountNotify; bool m_bExtendedJoin; bool m_bNamesx; bool m_bUHNames; bool m_bAway; bool m_bServerTime; bool m_bBatch; bool m_bEchoMessage; bool m_bSelfMessage; bool m_bPlaybackActive; CUser* m_pUser; CIRCNetwork* m_pNetwork; CString m_sNick; CString m_sPass; CString m_sUser; CString m_sNetwork; CString m_sIdentifier; std::shared_ptr m_spAuth; SCString m_ssAcceptedCaps; SCString m_ssSupportedTags; // The capabilities supported by the ZNC core - capability names mapped to // change handler. Note: this lists caps which don't require support on IRC // server. static const std::map>& CoreCaps(); friend class ClientTest; friend class CCoreCaps; }; #endif // !ZNC_CLIENT_H znc-1.9.1/include/znc/Config.h0000644000175000017500000001213014641222733016334 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_CONFIG_H #define ZNC_CONFIG_H #include #include class CFile; class CConfig; struct CConfigEntry { CConfigEntry(); CConfigEntry(const CConfig& Config); CConfigEntry(const CConfigEntry& other); ~CConfigEntry(); CConfigEntry& operator=(const CConfigEntry& other); CConfig* m_pSubConfig; }; class CConfig { public: CConfig() : m_ConfigEntries(), m_SubConfigs(), m_SubConfigNameSets() {} typedef std::map EntryMap; typedef std::pair SubConfigEntry; typedef std::vector SubConfig; typedef std::map SubConfigMap; typedef EntryMap::const_iterator EntryMapIterator; typedef SubConfigMap::const_iterator SubConfigMapIterator; EntryMapIterator BeginEntries() const { return m_ConfigEntries.begin(); } EntryMapIterator EndEntries() const { return m_ConfigEntries.end(); } SubConfigMapIterator BeginSubConfigs() const { return m_SubConfigs.begin(); } SubConfigMapIterator EndSubConfigs() const { return m_SubConfigs.end(); } void AddKeyValuePair(const CString& sName, const CString& sValue) { if (sName.empty() || sValue.empty()) { return; } m_ConfigEntries[sName].push_back(sValue); } bool AddSubConfig(const CString& sTag, const CString& sName, CConfig Config) { auto& nameset = m_SubConfigNameSets[sTag]; if (nameset.find(sName) != nameset.end()) return false; nameset.insert(sName); m_SubConfigs[sTag].emplace_back(sName, Config); return true; } bool FindStringVector(const CString& sName, VCString& vsList, bool bErase = true) { EntryMap::iterator it = m_ConfigEntries.find(sName); vsList.clear(); if (it == m_ConfigEntries.end()) return false; vsList = it->second; if (bErase) { m_ConfigEntries.erase(it); } return true; } bool FindStringEntry(const CString& sName, CString& sRes, const CString& sDefault = "") { EntryMap::iterator it = m_ConfigEntries.find(sName); sRes = sDefault; if (it == m_ConfigEntries.end() || it->second.empty()) return false; sRes = it->second.front(); it->second.erase(it->second.begin()); if (it->second.empty()) m_ConfigEntries.erase(it); return true; } bool FindBoolEntry(const CString& sName, bool& bRes, bool bDefault = false) { CString s; if (FindStringEntry(sName, s)) { bRes = s.ToBool(); return true; } bRes = bDefault; return false; } bool FindUIntEntry(const CString& sName, unsigned int& uRes, unsigned int uDefault = 0) { CString s; if (FindStringEntry(sName, s)) { uRes = s.ToUInt(); return true; } uRes = uDefault; return false; } bool FindUShortEntry(const CString& sName, unsigned short& uRes, unsigned short uDefault = 0) { CString s; if (FindStringEntry(sName, s)) { uRes = s.ToUShort(); return true; } uRes = uDefault; return false; } bool FindDoubleEntry(const CString& sName, double& fRes, double fDefault = 0) { CString s; if (FindStringEntry(sName, s)) { fRes = s.ToDouble(); return true; } fRes = fDefault; return false; } bool FindSubConfig(const CString& sTag, SubConfig& Config, bool bErase = true) { auto it = m_SubConfigs.find(sTag); if (it == m_SubConfigs.end()) { Config.clear(); return false; } Config = it->second; if (bErase) { m_SubConfigs.erase(it); m_SubConfigNameSets.erase(sTag); } return true; } bool empty() const { return m_ConfigEntries.empty() && m_SubConfigs.empty(); } bool Parse(CFile& file, CString& sErrorMsg); void Write(CFile& file, unsigned int iIndentation = 0); private: typedef SCString SubConfigNameSet; typedef std::map SubConfigNameSetMap; EntryMap m_ConfigEntries; SubConfigMap m_SubConfigs; SubConfigNameSetMap m_SubConfigNameSets; }; #endif // !ZNC_CONFIG_H znc-1.9.1/include/znc/ExecSock.h0000644000175000017500000000275214641222733016644 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_EXECSOCK_H #define ZNC_EXECSOCK_H #include #include #include //! @author imaginos@imaginos.net class CExecSock : public CZNCSock { public: CExecSock() : CZNCSock(0), m_iPid(-1) {} int Execute(const CString& sExec) { int iReadFD, iWriteFD; m_iPid = popen2(iReadFD, iWriteFD, sExec); if (m_iPid != -1) { ConnectFD(iReadFD, iWriteFD, "0.0.0.0:0"); } return (m_iPid); } void Kill(int iSignal) { kill(m_iPid, iSignal); Close(); } virtual ~CExecSock() { close2(m_iPid, GetRSock(), GetWSock()); SetRSock(-1); SetWSock(-1); } int popen2(int& iReadFD, int& iWriteFD, const CString& sCommand); void close2(int iPid, int iReadFD, int iWriteFD); private: int m_iPid; }; #endif // !ZNC_EXECSOCK_H znc-1.9.1/include/znc/FileUtils.h0000644000175000017500000001776514641222733017052 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_FILEUTILS_H #define ZNC_FILEUTILS_H #include #include #include #include #include #include #include #include #include class CFile { public: CFile(); CFile(const CString& sLongName); ~CFile(); enum EFileTypes { FT_REGULAR, FT_DIRECTORY, FT_CHARACTER, FT_BLOCK, FT_FIFO, FT_LINK, FT_SOCK }; void SetFileName(const CString& sLongName); static bool IsReg(const CString& sLongName, bool bUseLstat = false); static bool IsDir(const CString& sLongName, bool bUseLstat = false); static bool IsChr(const CString& sLongName, bool bUseLstat = false); static bool IsBlk(const CString& sLongName, bool bUseLstat = false); static bool IsFifo(const CString& sLongName, bool bUseLstat = false); static bool IsLnk(const CString& sLongName, bool bUseLstat = true); static bool IsSock(const CString& sLongName, bool bUseLstat = false); bool IsReg(bool bUseLstat = false) const; bool IsDir(bool bUseLstat = false) const; bool IsChr(bool bUseLstat = false) const; bool IsBlk(bool bUseLstat = false) const; bool IsFifo(bool bUseLstat = false) const; bool IsLnk(bool bUseLstat = true) const; bool IsSock(bool bUseLstat = false) const; // for gettin file types, using fstat instead static bool FType(const CString& sFileName, EFileTypes eType, bool bUseLstat = false); enum EFileAttr { FA_Name, FA_Size, FA_ATime, FA_MTime, FA_CTime, FA_UID }; // // Functions to retrieve file information // bool Exists() const; off_t GetSize() const; time_t GetATime() const; time_t GetMTime() const; time_t GetCTime() const; uid_t GetUID() const; gid_t GetGID() const; static bool Exists(const CString& sFile); static off_t GetSize(const CString& sFile); static time_t GetATime(const CString& sFile); static time_t GetMTime(const CString& sFile); static time_t GetCTime(const CString& sFile); static uid_t GetUID(const CString& sFile); static gid_t GetGID(const CString& sFile); static int GetInfo(const CString& sFile, struct stat& st); // // Functions to manipulate the file on the filesystem // bool Delete(); bool Move(const CString& sNewFileName, bool bOverwrite = false); bool Copy(const CString& sNewFileName, bool bOverwrite = false); static bool Delete(const CString& sFileName); static bool Move(const CString& sOldFileName, const CString& sNewFileName, bool bOverwrite = false); static bool Copy(const CString& sOldFileName, const CString& sNewFileName, bool bOverwrite = false); bool Chmod(mode_t mode); static bool Chmod(const CString& sFile, mode_t mode); bool Seek(off_t uPos); bool Truncate(); bool Sync(); bool Open(const CString& sFileName, int iFlags = O_RDONLY, mode_t iMode = 0644); bool Open(int iFlags = O_RDONLY, mode_t iMode = 0644); ssize_t Read(char* pszBuffer, int iBytes); bool ReadLine(CString& sData, const CString& sDelimiter = "\n"); bool ReadFile(CString& sData, size_t iMaxSize = 512 * 1024); ssize_t Write(const char* pszBuffer, size_t iBytes); ssize_t Write(const CString& sData); void Close(); void ClearBuffer(); bool TryExLock(const CString& sLockFile, int iFlags = O_RDWR | O_CREAT); bool TryExLock(); bool ExLock(); bool UnLock(); bool IsOpen() const; CString GetLongName() const; CString GetShortName() const; CString GetDir() const; bool HadError() const { return m_bHadError; } void ResetError() { m_bHadError = false; } static void InitHomePath(const CString& sFallback); static const CString& GetHomePath() { return m_sHomePath; } private: // fcntl() locking wrapper bool Lock(short iType, bool bBlocking); CString m_sBuffer; int m_iFD; bool m_bHadError; static CString m_sHomePath; protected: CString m_sLongName; //!< Absolute filename (m_sPath + "/" + m_sShortName) CString m_sShortName; //!< Filename alone, without path }; class CDir : public std::vector { public: CDir(const CString& sDir) : m_eSortAttr(CFile::FA_Name), m_bDesc(false) { Fill(sDir); } CDir() : m_eSortAttr(CFile::FA_Name), m_bDesc(false) {} ~CDir() { CleanUp(); } void CleanUp() { for (unsigned int a = 0; a < size(); a++) { delete (*this)[a]; } clear(); } size_t Fill(const CString& sDir) { return FillByWildcard(sDir, "*"); } size_t FillByWildcard(const CString& sDir, const CString& sWildcard) { CleanUp(); DIR* dir = opendir((sDir.empty()) ? "." : sDir.c_str()); if (!dir) { return 0; } struct dirent* de; while ((de = readdir(dir)) != nullptr) { if ((strcmp(de->d_name, ".") == 0) || (strcmp(de->d_name, "..") == 0)) { continue; } if ((!sWildcard.empty()) && (!CString(de->d_name).WildCmp(sWildcard))) { continue; } CFile* file = new CFile(sDir.TrimSuffix_n("/") + "/" + de->d_name /*, this*/); // @todo need to pass pointer // to 'this' if we want to do // Sort() push_back(file); } closedir(dir); return size(); } static unsigned int Chmod(mode_t mode, const CString& sWildcard, const CString& sDir = ".") { CDir cDir; cDir.FillByWildcard(sDir, sWildcard); return cDir.Chmod(mode); } unsigned int Chmod(mode_t mode) { unsigned int uRet = 0; for (unsigned int a = 0; a < size(); a++) { if ((*this)[a]->Chmod(mode)) { uRet++; } } return uRet; } static unsigned int Delete(const CString& sWildcard, const CString& sDir = ".") { CDir cDir; cDir.FillByWildcard(sDir, sWildcard); return cDir.Delete(); } unsigned int Delete() { unsigned int uRet = 0; for (unsigned int a = 0; a < size(); a++) { if ((*this)[a]->Delete()) { uRet++; } } return uRet; } CFile::EFileAttr GetSortAttr() const { return m_eSortAttr; } bool IsDescending() const { return m_bDesc; } // Check if sPath + "/" + sAdd (~/ is handled) is an absolute path which // resides under sPath. Returns absolute path on success, else "". static CString CheckPathPrefix(const CString& sPath, const CString& sAdd, const CString& sHomeDir = ""); static CString ChangeDir(const CString& sPath, const CString& sAdd, const CString& sHomeDir = ""); static bool MakeDir(const CString& sPath, mode_t iMode = 0700); static CString GetCWD() { CString sRet; char* pszCurDir = getcwd(nullptr, 0); if (pszCurDir) { sRet = pszCurDir; free(pszCurDir); } return sRet; } private: protected: CFile::EFileAttr m_eSortAttr; bool m_bDesc; }; #endif // !ZNC_FILEUTILS_H znc-1.9.1/include/znc/HTTPSock.h0000644000175000017500000001246614641222733016542 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_HTTPSOCK_H #define ZNC_HTTPSOCK_H #include #include #include class CModule; class CHTTPSock : public CSocket { public: CHTTPSock(CModule* pMod, const CString& sURIPrefix); CHTTPSock(CModule* pMod, const CString& sURIPrefix, const CString& sHostname, unsigned short uPort, int iTimeout = 60); virtual ~CHTTPSock(); // Csocket derived members void ReadData(const char* data, size_t len) override; void ReadLine(const CString& sData) override; void Connected() override; Csock* GetSockObj(const CString& sHost, unsigned short uPort) override = 0; // !Csocket derived members // Hooks virtual bool ForceLogin(); virtual bool OnLogin(const CString& sUser, const CString& sPass, bool bBasic); virtual void OnPageRequest(const CString& sURI) = 0; virtual bool PrintFile(const CString& sFileName, CString sContentType = ""); // !Hooks void CheckPost(); bool SentHeader() const; bool PrintHeader(off_t uContentLength, const CString& sContentType = "", unsigned int uStatusId = 200, const CString& sStatusMsg = "OK"); void AddHeader(const CString& sName, const CString& sValue); void SetContentType(const CString& sContentType); bool PrintNotFound(); bool Redirect(const CString& sURL); bool PrintErrorPage(unsigned int uStatusId, const CString& sStatusMsg, const CString& sMessage); static void ParseParams(const CString& sParams, std::map& msvsParams); void ParseURI(); void GetPage(); static CString GetDate(time_t tm = 0); CString GetRemoteIP() const override; // Cookies CString GetRequestCookie(const CString& sKey) const; bool SendCookie(const CString& sKey, const CString& sValue); // Cookies // Setters void SetDocRoot(const CString& s); void SetLoggedIn(bool b) { m_bLoggedIn = b; } // !Setters // Getters CString GetPath() const; bool IsLoggedIn() const { return m_bLoggedIn; } const CString& GetDocRoot() const; const CString& GetUser() const; const CString& GetPass() const; const CString& GetParamString() const; const CString& GetContentType() const; const CString& GetURI() const; const CString& GetURIPrefix() const; bool IsPost() const; // !Getters // Parameter access CString GetParam(const CString& sName, bool bPost = true, const CString& sFilter = "\r\n") const; CString GetRawParam(const CString& sName, bool bPost = true) const; bool HasParam(const CString& sName, bool bPost = true) const; const std::map& GetParams(bool bPost = true) const; size_t GetParamValues(const CString& sName, VCString& vsRet, bool bPost = true, const CString& sFilter = "\r\n") const; size_t GetParamValues(const CString& sName, std::set& ssRet, bool bPost = true, const CString& sFilter = "\r\n") const; // !Parameter access private: static CString GetRawParam(const CString& sName, const std::map& msvsParams); static CString GetParam(const CString& sName, const std::map& msvsParams, const CString& sFilter); static size_t GetParamValues(const CString& sName, VCString& vsRet, const std::map& msvsParams, const CString& sFilter); static size_t GetParamValues(const CString& sName, std::set& ssRet, const std::map& msvsParams, const CString& sFilter); void WriteFileUncompressed(CFile& File); void WriteFileGzipped(CFile& File); protected: void PrintPage(const CString& sPage); void Init(); bool m_bSentHeader; bool m_bGotHeader; bool m_bLoggedIn; bool m_bPost; bool m_bDone; bool m_bBasicAuth; unsigned long m_uPostLen; CString m_sPostData; CString m_sURI; CString m_sUser; CString m_sPass; CString m_sContentType; CString m_sDocRoot; CString m_sForwardedIP; std::map m_msvsPOSTParams; std::map m_msvsGETParams; MCString m_msHeaders; bool m_bHTTP10Client; CString m_sIfNoneMatch; bool m_bAcceptGzip; MCString m_msRequestCookies; MCString m_msResponseCookies; CString m_sURIPrefix; }; #endif // !ZNC_HTTPSOCK_H znc-1.9.1/include/znc/IRCNetwork.h0000644000175000017500000002656114641222733017133 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_IRCNETWORK_H #define ZNC_IRCNETWORK_H #include #include #include #include #include class CModules; class CUser; class CFile; class CConfig; class CClient; class CConfig; class CChan; class CQuery; class CServer; class CIRCSock; class CIRCNetworkPingTimer; class CIRCNetworkJoinTimer; class CMessage; class CIRCNetwork : private CCoreTranslationMixin { public: static bool IsValidNetwork(const CString& sNetwork); CIRCNetwork(CUser* pUser, const CString& sName); CIRCNetwork(CUser* pUser, const CIRCNetwork& Network); ~CIRCNetwork(); CIRCNetwork(const CIRCNetwork&) = delete; CIRCNetwork& operator=(const CIRCNetwork&) = delete; void Clone(const CIRCNetwork& Network, bool bCloneName = true); CString GetNetworkPath() const; void DelServers(); bool ParseConfig(CConfig* pConfig, CString& sError, bool bUpgrade = false); CConfig ToConfig() const; void BounceAllClients(); bool IsUserAttached() const { return !m_vClients.empty(); } bool IsUserOnline() const; void ClientConnected(CClient* pClient); void ClientDisconnected(CClient* pClient); CUser* GetUser() const; const CString& GetName() const; bool IsNetworkAttached() const { return !m_vClients.empty(); } const std::vector& GetClients() const { return m_vClients; } std::vector FindClients(const CString& sIdentifier) const; void SetUser(CUser* pUser); bool SetName(const CString& sName); // Modules CModules& GetModules() { return *m_pModules; } const CModules& GetModules() const { return *m_pModules; } // !Modules bool PutUser(const CString& sLine, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); bool PutUser(const CMessage& Message, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); bool PutStatus(const CString& sLine, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); bool PutModule(const CString& sModule, const CString& sLine, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); const std::vector& GetChans() const; CChan* FindChan(CString sName) const; std::vector FindChans(const CString& sWild) const; bool AddChan(CChan* pChan); bool AddChan(const CString& sName, bool bInConfig); bool DelChan(const CString& sName); bool MoveChan(const CString& sChan, unsigned int index, CString& sError); bool SwapChans(const CString& sChan1, const CString& sChan2, CString& sError); void JoinChans(); void JoinChans(std::set& sChans); const std::vector& GetQueries() const; CQuery* FindQuery(const CString& sName) const; std::vector FindQueries(const CString& sWild) const; CQuery* AddQuery(const CString& sName); bool DelQuery(const CString& sName); const CString& GetChanPrefixes() const { return m_sChanPrefixes; } void SetChanPrefixes(const CString& s) { m_sChanPrefixes = s; } bool IsChan(const CString& sChan) const; const std::vector& GetServers() const; bool HasServers() const { return !m_vServers.empty(); } CServer* FindServer(const CString& sName) const; bool DelServer(const CString& sName, unsigned short uPort, const CString& sPass); bool AddServer(const CString& sName); bool AddServer(const CString& sName, unsigned short uPort, const CString& sPass = "", bool bSSL = false); CServer* GetNextServer(bool bAdvance = true); CServer* GetCurrentServer() const; void SetIRCServer(const CString& s); bool SetNextServer(const CServer* pServer); bool IsLastServer() const; const SCString& GetTrustedFingerprints() const { return m_ssTrustedFingerprints; } void AddTrustedFingerprint(const CString& sFP) { m_ssTrustedFingerprints.insert( sFP.Escape_n(CString::EHEXCOLON, CString::EHEXCOLON)); } void DelTrustedFingerprint(const CString& sFP) { m_ssTrustedFingerprints.erase(sFP); } void ClearTrustedFingerprints() { m_ssTrustedFingerprints.clear(); } void SetIRCConnectEnabled(bool b); bool GetIRCConnectEnabled() const { return m_bIRCConnectEnabled; } CIRCSock* GetIRCSock() { return m_pIRCSock; } const CIRCSock* GetIRCSock() const { return m_pIRCSock; } const CString& GetIRCServer() const; const CNick& GetIRCNick() const; void SetIRCNick(const CNick& n); CString GetCurNick() const; bool IsIRCAway() const { return m_bIRCAway; } void SetIRCAway(bool b) { m_bIRCAway = b; } bool Connect(); /** This method will return whether the user is connected and authenticated * to an IRC server. */ bool IsIRCConnected() const; void SetIRCSocket(CIRCSock* pIRCSock); void IRCConnected(); void IRCDisconnected(); void CheckIRCConnect(); void NotifyClientsAboutServerDependentCap(const CString& sCap, bool bValue); bool IsServerCapAccepted(const CString& sCap) const; bool PutIRC(const CString& sLine); bool PutIRC(const CMessage& Message); // Buffers void AddRawBuffer(const CMessage& Format, const CString& sText = "") { m_RawBuffer.AddLine(Format, sText); } void UpdateRawBuffer(const CString& sCommand, const CMessage& Format, const CString& sText = "") { m_RawBuffer.UpdateLine(sCommand, Format, sText); } void UpdateExactRawBuffer(const CMessage& Format, const CString& sText = "") { m_RawBuffer.UpdateExactLine(Format, sText); } void ClearRawBuffer() { m_RawBuffer.Clear(); } /// @deprecated void AddRawBuffer(const CString& sFormat, const CString& sText = "") { m_RawBuffer.AddLine(sFormat, sText); } /// @deprecated void UpdateRawBuffer(const CString& sMatch, const CString& sFormat, const CString& sText = "") { m_RawBuffer.UpdateLine(sMatch, sFormat, sText); } /// @deprecated void UpdateExactRawBuffer(const CString& sFormat, const CString& sText = "") { m_RawBuffer.UpdateExactLine(sFormat, sText); } void AddMotdBuffer(const CMessage& Format, const CString& sText = "") { m_MotdBuffer.AddLine(Format, sText); } void UpdateMotdBuffer(const CString& sCommand, const CMessage& Format, const CString& sText = "") { m_MotdBuffer.UpdateLine(sCommand, Format, sText); } void ClearMotdBuffer() { m_MotdBuffer.Clear(); } /// @deprecated void AddMotdBuffer(const CString& sFormat, const CString& sText = "") { m_MotdBuffer.AddLine(sFormat, sText); } /// @deprecated void UpdateMotdBuffer(const CString& sMatch, const CString& sFormat, const CString& sText = "") { m_MotdBuffer.UpdateLine(sMatch, sFormat, sText); } void AddNoticeBuffer(const CMessage& Format, const CString& sText = "") { m_NoticeBuffer.AddLine(Format, sText); } void UpdateNoticeBuffer(const CString& sCommand, const CMessage& Format, const CString& sText = "") { m_NoticeBuffer.UpdateLine(sCommand, Format, sText); } void ClearNoticeBuffer() { m_NoticeBuffer.Clear(); } /// @deprecated void AddNoticeBuffer(const CString& sFormat, const CString& sText = "") { m_NoticeBuffer.AddLine(sFormat, sText); } /// @deprecated void UpdateNoticeBuffer(const CString& sMatch, const CString& sFormat, const CString& sText = "") { m_NoticeBuffer.UpdateLine(sMatch, sFormat, sText); } void ClearQueryBuffer(); // !Buffers // la const CString& GetNick(const bool bAllowDefault = true) const; const CString& GetAltNick(const bool bAllowDefault = true) const; const CString& GetIdent(const bool bAllowDefault = true) const; CString GetRealName() const; const CString& GetBindHost() const; const CString& GetEncoding() const; CString GetQuitMsg() const; void SetNick(const CString& s); void SetAltNick(const CString& s); void SetIdent(const CString& s); void SetRealName(const CString& s); void SetBindHost(const CString& s); void SetEncoding(const CString& s); void SetQuitMsg(const CString& s); double GetFloodRate() const { return m_fFloodRate; } unsigned short int GetFloodBurst() const { return m_uFloodBurst; } void SetFloodRate(double fFloodRate) { m_fFloodRate = fFloodRate; } void SetFloodBurst(unsigned short int uFloodBurst) { m_uFloodBurst = uFloodBurst; } unsigned short int GetJoinDelay() const { return m_uJoinDelay; } void SetJoinDelay(unsigned short int uJoinDelay) { m_uJoinDelay = uJoinDelay; } void SetTrustAllCerts(const bool bTrustAll = false) { m_bTrustAllCerts = bTrustAll; } bool GetTrustAllCerts() const { return m_bTrustAllCerts; } void SetTrustPKI(const bool bTrustPKI = true) { m_bTrustPKI = bTrustPKI; } bool GetTrustPKI() const { return m_bTrustPKI; } unsigned long long BytesRead() const { return m_uBytesRead; } unsigned long long BytesWritten() const { return m_uBytesWritten; } void AddBytesRead(unsigned long long u) { m_uBytesRead += u; } void AddBytesWritten(unsigned long long u) { m_uBytesWritten += u; } CString ExpandString(const CString& sStr) const; CString& ExpandString(const CString& sStr, CString& sRet) const; private: bool JoinChan(CChan* pChan); bool LoadModule(const CString& sModName, const CString& sArgs, const CString& sNotice, CString& sError); protected: CString m_sName; CUser* m_pUser; CString m_sNick; CString m_sAltNick; CString m_sIdent; CString m_sRealName; CString m_sBindHost; CString m_sEncoding; CString m_sQuitMsg; SCString m_ssTrustedFingerprints; CModules* m_pModules; std::vector m_vClients; CIRCSock* m_pIRCSock; std::vector m_vChans; std::vector m_vQueries; CString m_sChanPrefixes; bool m_bIRCConnectEnabled; bool m_bTrustAllCerts; bool m_bTrustPKI; CString m_sIRCServer; std::vector m_vServers; size_t m_uServerIdx; ///< Index in m_vServers of our current server + 1 CNick m_IRCNick; bool m_bIRCAway; double m_fFloodRate; ///< Set to -1 to disable protection. unsigned short int m_uFloodBurst; CBuffer m_RawBuffer; CBuffer m_MotdBuffer; CBuffer m_NoticeBuffer; CIRCNetworkPingTimer* m_pPingTimer; CIRCNetworkJoinTimer* m_pJoinTimer; unsigned short int m_uJoinDelay; unsigned long long m_uBytesRead; unsigned long long m_uBytesWritten; }; #endif // !ZNC_IRCNETWORK_H znc-1.9.1/include/znc/IRCSock.h0000644000175000017500000002107014641222733016367 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_IRCSOCK_H #define ZNC_IRCSOCK_H #include #include #include #include #include // Forward Declarations class CChan; class CUser; class CIRCNetwork; class CClient; // !Forward Declarations // TODO: This class needs new name class CIRCSock : public CIRCSocket { public: CIRCSock(CIRCNetwork* pNetwork); virtual ~CIRCSock(); CIRCSock(const CIRCSock&) = delete; CIRCSock& operator=(const CIRCSock&) = delete; typedef enum { // These values must line up with their position in the CHANMODE // argument to raw 005 ListArg = 0, HasArg = 1, ArgWhenSet = 2, NoArg = 3 } EChanModeArgs; void ReadLine(const CString& sData) override; void Connected() override; void Disconnected() override; void ConnectionRefused() override; void SockError(int iErrno, const CString& sDescription) override; void Timeout() override; void ReachedMaxBuffer() override; #ifdef HAVE_LIBSSL void SSLCertError(X509* pCert) override; #endif /** Sends a raw data line to the server. * @param sLine The line to be sent. * * The line is first passed \e unmodified to the \ref * CModule::OnSendToIRC() module hook. If no module halts the process, * the line is then sent to the server. * * Prefer \l PutIRC() instead. */ void PutIRCRaw(const CString& sLine); /** Sends a message to the server. * See \l PutIRC(const CMessage&) for details. */ void PutIRC(const CString& sLine); /** Sends a message to the server. * @param Message The message to be sent. * @note Only known and compatible messages and tags are sent. * * This method can delay the delivery of the message to honor protection * from flood. * * This method ensures that only tags, that were negotiated with CAP REQ * and CAP ACK, are sent. Not all IRC server are capable of handling all * messages and tags. Thus, in order to stay compatible with a variety of * IRC servers, ZNC has to filter out messages and tags that the server * has not explicitly acknowleged. * * Additional tags can be added via \l CIRCSock::SetTagSupport(). * * @warning Bypassing the filter may cause troubles to some older IRC * servers. * * It is possible to bypass the filter by converting a message to a string * using \l CMessage::ToString(), and passing the resulting raw line to the * \l CIRCSock::PutIRCRaw(const CString& sLine): * \code * pServer->PutIRCRaw(Message.ToString()); * \endcode */ void PutIRC(const CMessage& Message); void PutIRCQuick(const CString& sLine); //!< Should be used for PONG only void ResetChans(); void Quit(const CString& sQuitMsg = ""); /** You can call this from CModule::OnServerCapResult to suspend * sending other CAP requests and CAP END for a while. Each * call to PauseCap should be balanced with a call to ResumeCap. */ void PauseCap(); /** If you used PauseCap, call this when CAP negotiation and logging in * should be resumed again. */ void ResumeCap(); bool IsTagEnabled(const CString& sTag) const { return 1 == m_ssSupportedTags.count(sTag); } /** Registers a tag as being supported or unsupported by the server. * This doesn't affect tags which the server sends. * @param sTag The tag to register. * @param bState Whether the client supports the tag. */ void SetTagSupport(const CString& sTag, bool bState); // Setters void SetPass(const CString& s) { m_sPass = s; } // !Setters // Getters unsigned int GetMaxNickLen() const { return m_uMaxNickLen; } EChanModeArgs GetModeType(char cMode) const; char GetPermFromMode(char cMode) const; const std::map& GetChanModes() const { return m_mceChanModes; } bool IsPermChar(const char c) const { return (c != '\0' && GetPerms().find(c) != CString::npos); } bool IsPermMode(const char c) const { return (c != '\0' && GetPermModes().find(c) != CString::npos); } const CString& GetPerms() const { return m_sPerms; } const CString& GetPermModes() const { return m_sPermModes; } CString GetNickMask() const { return m_Nick.GetNickMask(); } const CString& GetNick() const { return m_Nick.GetNick(); } const CString& GetPass() const { return m_sPass; } CIRCNetwork* GetNetwork() const { return m_pNetwork; } bool HasNamesx() const { return m_bNamesx; } bool HasUHNames() const { return m_bUHNames; } bool HasAwayNotify() const { return m_bAwayNotify; } bool HasAccountNotify() const { return m_bAccountNotify; } bool HasExtendedJoin() const { return m_bExtendedJoin; } bool HasServerTime() const { return m_bServerTime; } const std::set& GetUserModes() const { return m_scUserModes; } // This is true if we are past raw 001 bool IsAuthed() const { return m_bAuthed; } const SCString& GetAcceptedCaps() const { return m_ssAcceptedCaps; } bool IsCapAccepted(const CString& sCap) { return 1 == m_ssAcceptedCaps.count(sCap); } CString GetCapLsValue(const CString& sKey, const CString& sDefault = "") const; const MCString& GetISupport() const { return m_mISupport; } CString GetISupport(const CString& sKey, const CString& sDefault = "") const; // !Getters // TODO move this function to CIRCNetwork and make it non-static? static bool IsFloodProtected(double fRate); private: // Message Handlers bool OnAccountMessage(CMessage& Message); bool OnActionMessage(CActionMessage& Message); bool OnAwayMessage(CMessage& Message); bool OnCapabilityMessage(CMessage& Message); bool OnCTCPMessage(CCTCPMessage& Message); bool OnErrorMessage(CMessage& Message); bool OnInviteMessage(CMessage& Message); bool OnJoinMessage(CJoinMessage& Message); bool OnKickMessage(CKickMessage& Message); bool OnModeMessage(CModeMessage& Message); bool OnNickMessage(CNickMessage& Message); bool OnNoticeMessage(CNoticeMessage& Message); bool OnNumericMessage(CNumericMessage& Message); bool OnPartMessage(CPartMessage& Message); bool OnPingMessage(CMessage& Message); bool OnPongMessage(CMessage& Message); bool OnQuitMessage(CQuitMessage& Message); bool OnTextMessage(CTextMessage& Message); bool OnTopicMessage(CTopicMessage& Message); bool OnWallopsMessage(CMessage& Message); bool OnServerCapAvailable(const CString& sCap, const CString& sValue); // !Message Handlers void SetNick(const CString& sNick); void ParseISupport(const CMessage& Message); // This is called when we connect and the nick we want is already taken void SendAltNick(const CString& sBadNick); void SendNextCap(); void TrySend(); protected: bool m_bAuthed; bool m_bNamesx; bool m_bUHNames; bool m_bAwayNotify; bool m_bAccountNotify; bool m_bExtendedJoin; bool m_bServerTime; CString m_sPerms; CString m_sPermModes; std::set m_scUserModes; std::map m_mceChanModes; CIRCNetwork* m_pNetwork; CNick m_Nick; CString m_sPass; std::map m_msChans; unsigned int m_uMaxNickLen; unsigned int m_uCapPaused; SCString m_ssAcceptedCaps; SCString m_ssPendingCaps; MCString m_msCapLsValues; time_t m_lastCTCP; unsigned int m_uNumCTCP; static const time_t m_uCTCPFloodTime; static const unsigned int m_uCTCPFloodCount; MCString m_mISupport; std::deque m_vSendQueue; short int m_iSendsAllowed; unsigned short int m_uFloodBurst; double m_fFloodRate; bool m_bFloodProtection; SCString m_ssSupportedTags; VCString m_vsSSLError; friend class CIRCFloodTimer; friend class CCoreCaps; }; #endif // !ZNC_IRCSOCK_H znc-1.9.1/include/znc/Listener.h0000644000175000017500000000620214641222733016717 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_LISTENER_H #define ZNC_LISTENER_H #include #include // Forward Declarations class CRealListener; // !Forward Declarations class CListener { public: typedef enum { ACCEPT_IRC, ACCEPT_HTTP, ACCEPT_ALL } EAcceptType; CListener(unsigned short uPort, const CString& sBindHost, const CString& sURIPrefix, bool bSSL, EAddrType eAddr, EAcceptType eAccept) : m_bSSL(bSSL), m_eAddr(eAddr), m_uPort(uPort), m_sBindHost(sBindHost), m_sURIPrefix(sURIPrefix), m_pListener(nullptr), m_eAcceptType(eAccept) {} ~CListener(); CListener(const CListener&) = delete; CListener& operator=(const CListener&) = delete; // Getters bool IsSSL() const { return m_bSSL; } EAddrType GetAddrType() const { return m_eAddr; } unsigned short GetPort() const { return m_uPort; } const CString& GetBindHost() const { return m_sBindHost; } CRealListener* GetRealListener() const { return m_pListener; } const CString& GetURIPrefix() const { return m_sURIPrefix; } EAcceptType GetAcceptType() const { return m_eAcceptType; } // !Getters // It doesn't make sense to change any of the settings after Listen() // except this one, so don't add other setters! void SetAcceptType(EAcceptType eType) { m_eAcceptType = eType; } bool Listen(); void ResetRealListener(); private: protected: bool m_bSSL; EAddrType m_eAddr; unsigned short m_uPort; CString m_sBindHost; CString m_sURIPrefix; CRealListener* m_pListener; EAcceptType m_eAcceptType; }; class CRealListener : public CZNCSock { public: CRealListener(CListener& listener) : CZNCSock(), m_Listener(listener) {} virtual ~CRealListener(); bool ConnectionFrom(const CString& sHost, unsigned short uPort) override; Csock* GetSockObj(const CString& sHost, unsigned short uPort) override; void SockError(int iErrno, const CString& sDescription) override; private: CListener& m_Listener; }; class CIncomingConnection : public CZNCSock { public: CIncomingConnection(const CString& sHostname, unsigned short uPort, CListener::EAcceptType eAcceptType, const CString& sURIPrefix); virtual ~CIncomingConnection() {} void ReadLine(const CString& sData) override; void ReachedMaxBuffer() override; private: CListener::EAcceptType m_eAcceptType; const CString m_sURIPrefix; }; #endif // !ZNC_LISTENER_H znc-1.9.1/include/znc/MD5.h0000644000175000017500000000177214641222733015526 0ustar somebodysomebody/* C implementation by Christophe Devine, C++ "class-ified" by [T3] */ #ifndef ZNC_MD5_H #define ZNC_MD5_H #include #include using std::string; #ifndef uint8 #define uint8 unsigned char #endif #ifndef uint32 #define uint32 unsigned long int #endif typedef struct { uint32 total[2]; uint32 state[4]; uint8 buffer[64]; } md5_context; class CMD5 { protected: char m_szMD5[33]; public: CMD5(); CMD5(const string& sText); CMD5(const char* szText, uint32 nTextLen); ~CMD5(); operator string() const { return (string)m_szMD5; } operator char*() const { return (char*)m_szMD5; } char* MakeHash(const char* szText, uint32 nTextLen); protected: void md5_starts(md5_context* ctx) const; void md5_update(md5_context* ctx, const uint8* input, uint32 length) const; void md5_finish(md5_context* ctx, uint8 digest[16]) const; private: void md5_process(md5_context* ctx, const uint8 data[64]) const; }; #endif /* ZNC_MD5_H */ znc-1.9.1/include/znc/Message.h0000644000175000017500000002557614641222733016535 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_MESSAGE_H #define ZNC_MESSAGE_H #ifdef SWIG #define ZNC_MSG_DEPRECATED(msg) #else #define ZNC_MSG_DEPRECATED(msg) __attribute__((deprecated(msg))) #endif #include #include #include #include class CChan; class CClient; class CIRCNetwork; /** * Here is a small explanation of how messages on IRC work, and how you can use * this class to get useful information from the parsed message. The output * varies greatly and this advice may not apply to every message type, but this * will hopefully help you understand how it works more accurately. * * @t=some-tag :server.network.net 366 something #channel :End of /NAMES list. * tags nick cmd 0 1 2 * * - `tags` is the IRCv3 tags associated with the message, obtained with * GetTag("t"). the @time tag can also be obtained with GetTime(), some * messages have other tags with them. Tags may not always be present. Refer * to IRCv3 for documentation on tags. * - `nick` is the sender, which can be obtained with GetNick() * - `cmd` is command, which is obtained via GetCommand() * - `0`, `1`, ... are parameters, available via GetParam(n), which removes the * leading colon (:). If you don't want to remove the colon, use * GetParamsColon(). * * For certain events, like a PRIVMSG, convienience commands like GetChan() and * GetNick() are available, this is not true for all CMessage extensions. */ class CMessage { public: explicit CMessage(const CString& sMessage = ""); CMessage(const CNick& Nick, const CString& sCommand, const VCString& vsParams = VCString(), const MCString& mssTags = MCString::EmptyMap); enum class Type { Unknown, Account, Action, Away, Capability, CTCP, Error, Invite, Join, Kick, Mode, Nick, Notice, Numeric, Part, Ping, Pong, Quit, Text, Topic, Wallops, }; Type GetType() const { return m_eType; } bool Equals(const CMessage& Other) const; void Clone(const CMessage& Other); // ZNC <-> IRC CIRCNetwork* GetNetwork() const { return m_pNetwork; } void SetNetwork(CIRCNetwork* pNetwork) { m_pNetwork = pNetwork; } // ZNC <-> CLI CClient* GetClient() const { return m_pClient; } void SetClient(CClient* pClient) { m_pClient = pClient; } CChan* GetChan() const { return m_pChan; } void SetChan(CChan* pChan) { m_pChan = pChan; } CNick& GetNick() { return m_Nick; } const CNick& GetNick() const { return m_Nick; } void SetNick(const CNick& Nick) { m_Nick = Nick; } const CString& GetCommand() const { return m_sCommand; } void SetCommand(const CString& sCommand); const VCString& GetParams() const { return m_vsParams; } /** * Get a subset of the message parameters * * This allows accessing a vector of a specific range of parameters, * allowing easy inline use, such as `pChan->SetModes(Message.GetParam(2), Message.GetParamsSplit(3));` * * @param uIdx The index of the first parameter to retrieve * @param uLen How many parameters to retrieve * @return A VCString containing the retrieved parameters */ VCString GetParamsSplit(unsigned int uIdx, unsigned int uLen = -1) const; void SetParams(const VCString& vsParams); /// @deprecated use GetParamsColon() instead. CString GetParams(unsigned int uIdx, unsigned int uLen = -1) const ZNC_MSG_DEPRECATED("Use GetParamsColon() instead") { return GetParamsColon(uIdx, uLen); } CString GetParamsColon(unsigned int uIdx, unsigned int uLen = -1) const; CString GetParam(unsigned int uIdx) const; void SetParam(unsigned int uIdx, const CString& sParam); const timeval& GetTime() const { return m_time; } void SetTime(const timeval& ts) { m_time = ts; } const MCString& GetTags() const { return m_mssTags; } void SetTags(const MCString& mssTags) { m_mssTags = mssTags; } CString GetTag(const CString& sKey) const; void SetTag(const CString& sKey, const CString& sValue); enum FormatFlags { IncludeAll = 0x0, ExcludePrefix = 0x1, ExcludeTags = 0x2 }; CString ToString(unsigned int uFlags = IncludeAll) const; void Parse(const CString& sMessage); // Implicit and explicit conversion to a subclass reference. #ifndef SWIG template M& As() & { static_assert(std::is_base_of{}, "Must be subclass of CMessage"); static_assert(sizeof(M) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); return static_cast(*this); } template const M& As() const& { static_assert(std::is_base_of{}, "Must be subclass of CMessage"); static_assert(sizeof(M) == sizeof(CMessage), "No data members allowed in CMessage subclasses."); return static_cast(*this); } template {}>::type> operator M&() & { return As(); } template {}>::type> operator const M&() const& { return As(); } // REGISTER_ZNC_MESSAGE allows SWIG to instantiate correct .As<> calls. #define REGISTER_ZNC_MESSAGE(M) #else // SWIG (as of 3.0.7) doesn't parse ref-qualifiers, and doesn't // differentiate constness. template M& As(); #endif private: void InitTime(); void InitType(); CNick m_Nick; CString m_sCommand; VCString m_vsParams; MCString m_mssTags; timeval m_time; CIRCNetwork* m_pNetwork = nullptr; CClient* m_pClient = nullptr; CChan* m_pChan = nullptr; Type m_eType = Type::Unknown; bool m_bColon = false; }; // For gtest #ifdef GTEST_FAIL template {}>::type> inline ::std::ostream& operator<<(::std::ostream& os, const M& msg) { return os << msg.ToString().Escape_n(CString::EDEBUG); } #endif // The various CMessage subclasses are "mutable views" to the data held by // CMessage. // They provide convenient access to message type speficic attributes, but are // not // allowed to hold extra data of their own. class CTargetMessage : public CMessage { public: CString GetTarget() const { return GetParam(0); } void SetTarget(const CString& sTarget) { SetParam(0, sTarget); } }; REGISTER_ZNC_MESSAGE(CTargetMessage); class CActionMessage : public CTargetMessage { public: CString GetText() const { return GetParam(1).TrimPrefix_n("\001ACTION ").TrimSuffix_n("\001"); } void SetText(const CString& sText) { SetParam(1, "\001ACTION " + sText + "\001"); } }; REGISTER_ZNC_MESSAGE(CActionMessage); class CCTCPMessage : public CTargetMessage { public: bool IsReply() const { return GetCommand().Equals("NOTICE"); } CString GetText() const { return GetParam(1).TrimPrefix_n("\001").TrimSuffix_n("\001"); } void SetText(const CString& sText) { SetParam(1, "\001" + sText + "\001"); } }; REGISTER_ZNC_MESSAGE(CCTCPMessage); class CJoinMessage : public CTargetMessage { public: CString GetKey() const { return GetParam(1); } void SetKey(const CString& sKey) { SetParam(1, sKey); } }; REGISTER_ZNC_MESSAGE(CJoinMessage); class CModeMessage : public CTargetMessage { public: /// @deprecated Use GetModeList() and GetModeParams() CString GetModes() const { return GetParamsColon(1).TrimPrefix_n(":"); } CString GetModeList() const { return GetParam(1); }; VCString GetModeParams() const { return GetParamsSplit(2); }; bool HasModes() const { return !GetModeList().empty(); }; }; REGISTER_ZNC_MESSAGE(CModeMessage); class CNickMessage : public CMessage { public: CString GetOldNick() const { return GetNick().GetNick(); } CString GetNewNick() const { return GetParam(0); } void SetNewNick(const CString& sNick) { SetParam(0, sNick); } }; REGISTER_ZNC_MESSAGE(CNickMessage); class CNoticeMessage : public CTargetMessage { public: CString GetText() const { return GetParam(1); } void SetText(const CString& sText) { SetParam(1, sText); } }; REGISTER_ZNC_MESSAGE(CNoticeMessage); class CNumericMessage : public CMessage { public: unsigned int GetCode() const { return GetCommand().ToUInt(); } }; REGISTER_ZNC_MESSAGE(CNumericMessage); class CKickMessage : public CTargetMessage { public: CString GetKickedNick() const { return GetParam(1); } void SetKickedNick(const CString& sNick) { SetParam(1, sNick); } CString GetReason() const { return GetParam(2); } void SetReason(const CString& sReason) { SetParam(2, sReason); } CString GetText() const { return GetReason(); } void SetText(const CString& sText) { SetReason(sText); } }; REGISTER_ZNC_MESSAGE(CKickMessage); class CPartMessage : public CTargetMessage { public: CString GetReason() const { return GetParam(1); } void SetReason(const CString& sReason) { SetParam(1, sReason); } CString GetText() const { return GetReason(); } void SetText(const CString& sText) { SetReason(sText); } }; REGISTER_ZNC_MESSAGE(CPartMessage); class CQuitMessage : public CMessage { public: CString GetReason() const { return GetParam(0); } void SetReason(const CString& sReason) { SetParam(0, sReason); } CString GetText() const { return GetReason(); } void SetText(const CString& sText) { SetReason(sText); } }; REGISTER_ZNC_MESSAGE(CQuitMessage); class CTextMessage : public CTargetMessage { public: CString GetText() const { return GetParam(1); } void SetText(const CString& sText) { SetParam(1, sText); } }; REGISTER_ZNC_MESSAGE(CTextMessage); class CTopicMessage : public CTargetMessage { public: CString GetTopic() const { return GetParam(1); } void SetTopic(const CString& sTopic) { SetParam(1, sTopic); } CString GetText() const { return GetTopic(); } void SetText(const CString& sText) { SetTopic(sText); } }; REGISTER_ZNC_MESSAGE(CTopicMessage); #endif // !ZNC_MESSAGE_H znc-1.9.1/include/znc/Modules.h0000644000175000017500000021776114641222733016560 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_MODULES_H #define ZNC_MODULES_H #include #include #include #include #include #include #include #include #include #include #include #include // Forward Declarations class CAuthBase; class CChan; class CQuery; class CIRCNetwork; class CClient; class CWebSock; class CTemplate; class CIRCSock; class CModule; class CModInfo; // !Forward Declarations #ifdef REQUIRESSL #ifndef HAVE_LIBSSL #error - #error - #error This module only works when ZNC is compiled with OpenSSL support #error - #error - #endif #endif #ifdef BUILD_WITH_CMAKE #include #elif HAVE_VISIBILITY #define ZNC_EXPORT_LIB_EXPORT __attribute__((__visibility__("default"))) #else #define ZNC_EXPORT_LIB_EXPORT #endif /** C-style entry point to the module. * * First, core compares C strings with version and compilation options of core * and module. If they match, assume that C++ classes have the same layout and * proceed to filling CModInfo. * * Most parts of version-extra is explicitly not compared, otherwise all * modules need to be rebuilt for every commit, which is more cumbersome for * ZNC developers. However, the part set by user (e.g. +deb1), is compared. * * If this struct ever changes, the first field (pcVersion) must stay the same. * Otherwise, name of ZNCModuleEntry function must also change. Other fields * can change at will. * * Modules shouldn't care about this struct, it's all managed by ...MODULEDEFS * macro. */ struct CModuleEntry { const char* pcVersion; const char* pcVersionExtra; const char* pcCompileOptions; void (*fpFillModInfo)(CModInfo&); }; #define MODCOMMONDEFS(CLASS, DESCRIPTION, TYPE) \ static void FillModInfo(CModInfo& Info) { \ auto t_s = [&](const CString& sEnglish, \ const CString& sContext = "") { \ return sEnglish.empty() ? "" : Info.t_s(sEnglish, sContext); \ }; \ t_s(CString()); /* Don't warn about unused t_s */ \ Info.SetDescription(DESCRIPTION); \ Info.SetDefaultType(TYPE); \ Info.AddType(TYPE); \ Info.SetLoader(TModLoad); \ TModInfo(Info); \ } \ extern "C" { \ /* A global variable leads to ODR violation when several modules are \ * loaded. But a static variable inside a function works. */ \ ZNC_EXPORT_LIB_EXPORT const CModuleEntry* ZNCModuleEntry(); \ ZNC_EXPORT_LIB_EXPORT const CModuleEntry* ZNCModuleEntry() { \ static const CModuleEntry ThisModule = {VERSION_STR, VERSION_EXTRA, \ ZNC_COMPILE_OPTIONS_STRING, \ FillModInfo}; \ return &ThisModule; \ } \ } /** Instead of writing a constructor, you should call this macro. It accepts all * the necessary arguments and passes them on to CModule's constructor. You * should assume that there are no arguments to the constructor. * * Usage: * \code * class MyModule : public CModule { * MODCONSTRUCTOR(MyModule) { * // Your own constructor's code here * } * } * \endcode * * @param CLASS The name of your module's class. */ #define MODCONSTRUCTOR(CLASS) \ CLASS(ModHandle pDLL, CUser* pUser, CIRCNetwork* pNetwork, \ const CString& sModName, const CString& sModPath, \ CModInfo::EModuleType eType) \ : CModule(pDLL, pUser, pNetwork, sModName, sModPath, eType) // User Module Macros /** This works exactly like MODULEDEFS, but for user modules. */ #define USERMODULEDEFS(CLASS, DESCRIPTION) \ MODCOMMONDEFS(CLASS, DESCRIPTION, CModInfo::UserModule) // !User Module Macros // Global Module Macros /** This works exactly like MODULEDEFS, but for global modules. */ #define GLOBALMODULEDEFS(CLASS, DESCRIPTION) \ MODCOMMONDEFS(CLASS, DESCRIPTION, CModInfo::GlobalModule) // !Global Module Macros // Network Module Macros /** This works exactly like MODULEDEFS, but for network modules. */ #define NETWORKMODULEDEFS(CLASS, DESCRIPTION) \ MODCOMMONDEFS(CLASS, DESCRIPTION, CModInfo::NetworkModule) // !Network Module Macros /** At the end of your source file, you must call this macro in global context. * It defines some static functions which ZNC needs to load this module. * By default the module will be a network module. * @param CLASS The name of your module's class. * @param DESCRIPTION A short description of your module. */ #define MODULEDEFS(CLASS, DESCRIPTION) NETWORKMODULEDEFS(CLASS, DESCRIPTION) // Forward Declarations class CZNC; class CUser; class CNick; class CChan; class CModule; class CFPTimer; class CSockManager; // !Forward Declarations class CCapability { public: virtual ~CCapability() = default; virtual void OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) {} virtual void OnClientChangedSupport(CClient* pClient, bool bState) {} CModule* GetModule() { return m_pModule; } void SetModule(CModule* p) { m_pModule = p; } protected: CModule* m_pModule = nullptr; }; class CTimer : public CCron { public: CTimer(CModule* pModule, unsigned int uInterval, unsigned int uCycles, const CString& sLabel, const CString& sDescription); virtual ~CTimer(); CTimer(const CTimer&) = delete; CTimer& operator=(const CTimer&) = delete; // Setters void SetModule(CModule* p); void SetDescription(const CString& s); // !Setters // Getters CModule* GetModule() const; const CString& GetDescription() const; // !Getters private: protected: CModule* m_pModule; CString m_sDescription; }; typedef void (*FPTimer_t)(CModule*, CFPTimer*); class CFPTimer : public CTimer { public: CFPTimer(CModule* pModule, unsigned int uInterval, unsigned int uCycles, const CString& sLabel, const CString& sDescription) : CTimer(pModule, uInterval, uCycles, sLabel, sDescription), m_pFBCallback(nullptr) {} virtual ~CFPTimer() {} void SetFPCallback(FPTimer_t p) { m_pFBCallback = p; } protected: void RunJob() override { if (m_pFBCallback) { m_pFBCallback(m_pModule, this); } } private: FPTimer_t m_pFBCallback; }; #ifdef HAVE_PTHREAD /// A CJob version which can be safely used in modules. The job will be /// cancelled when the module is unloaded. class CModuleJob : public CJob { public: CModuleJob(CModule* pModule, const CString& sName, const CString& sDesc) : CJob(), m_pModule(pModule), m_sName(sName), m_sDescription(sDesc) {} virtual ~CModuleJob(); CModuleJob(const CModuleJob&) = delete; CModuleJob& operator=(const CModuleJob&) = delete; // Getters CModule* GetModule() const { return m_pModule; } const CString& GetName() const { return m_sName; } const CString& GetDescription() const { return m_sDescription; } // !Getters protected: CModule* m_pModule; const CString m_sName; const CString m_sDescription; }; #endif typedef void* ModHandle; class CModInfo { public: typedef enum { GlobalModule, UserModule, NetworkModule } EModuleType; typedef CModule* (*ModLoader)(ModHandle p, CUser* pUser, CIRCNetwork* pNetwork, const CString& sModName, const CString& sModPath, EModuleType eType); CModInfo() : CModInfo("", "", NetworkModule) {} CModInfo(const CString& sName, const CString& sPath, EModuleType eType) : m_seType(), m_eDefaultType(eType), m_sName(sName), m_sPath(sPath), m_sDescription(""), m_sWikiPage(""), m_sArgsHelpText(""), m_bHasArgs(false), m_fLoader(nullptr) {} ~CModInfo() {} bool operator<(const CModInfo& Info) const { return (GetName() < Info.GetName()); } bool SupportsType(EModuleType eType) const { return m_seType.find(eType) != m_seType.end(); } void AddType(EModuleType eType) { m_seType.insert(eType); } static CString ModuleTypeToString(EModuleType eType) { switch (eType) { case GlobalModule: return "Global"; case UserModule: return "User"; case NetworkModule: return "Network"; default: return "UNKNOWN"; } } // Getters const CString& GetName() const { return m_sName; } const CString& GetPath() const { return m_sPath; } const CString& GetDescription() const { return m_sDescription; } const CString& GetWikiPage() const { return m_sWikiPage; } const CString& GetArgsHelpText() const { return m_sArgsHelpText; } bool GetHasArgs() const { return m_bHasArgs; } ModLoader GetLoader() const { return m_fLoader; } EModuleType GetDefaultType() const { return m_eDefaultType; } // !Getters // Setters void SetName(const CString& s) { m_sName = s; } void SetPath(const CString& s) { m_sPath = s; } void SetDescription(const CString& s) { m_sDescription = s; } void SetWikiPage(const CString& s) { m_sWikiPage = s; } void SetArgsHelpText(const CString& s) { m_sArgsHelpText = s; } void SetHasArgs(bool b = false) { m_bHasArgs = b; } void SetLoader(ModLoader fLoader) { m_fLoader = fLoader; } void SetDefaultType(EModuleType eType) { m_eDefaultType = eType; } // !Setters CString t_s(const CString& sEnglish, const CString& sContext = "") const; private: protected: std::set m_seType; EModuleType m_eDefaultType; CString m_sName; CString m_sPath; CString m_sDescription; CString m_sWikiPage; CString m_sArgsHelpText; bool m_bHasArgs; ModLoader m_fLoader; }; template void TModInfo(CModInfo& Info) {} template CModule* TModLoad(ModHandle p, CUser* pUser, CIRCNetwork* pNetwork, const CString& sModName, const CString& sModPath, CModInfo::EModuleType eType) { return new M(p, pUser, pNetwork, sModName, sModPath, eType); } /** A helper class for handling commands in modules. */ class CModCommand : private CCoreTranslationMixin { public: /// Type for the callback function that handles the actual command. typedef void (CModule::*ModCmdFunc)(const CString& sLine); typedef std::function CmdFunc; /// Default constructor, needed so that this can be saved in a std::map. CModCommand(); /** Construct a new CModCommand. * @param sCmd The name of the command. * @param func The command's callback function. * @param sArgs Help text describing the arguments to this command. * @param sDesc Help text describing what this command does. */ CModCommand(const CString& sCmd, CModule* pMod, ModCmdFunc func, const CString& sArgs, const CString& sDesc); CModCommand(const CString& sCmd, CmdFunc func, const COptionalTranslation& Args, const COptionalTranslation& Desc); /** Copy constructor, needed so that this can be saved in a std::map. * @param other Object to copy from. */ CModCommand(const CModCommand& other) = default; /** Assignment operator, needed so that this can be saved in a std::map. * @param other Object to copy from. */ CModCommand& operator=(const CModCommand& other) = default; /** Initialize a CTable so that it can be used with AddHelp(). * @param Table The instance of CTable to initialize. */ static void InitHelp(CTable& Table); /** Add this command to the CTable instance. * @param Table Instance of CTable to which this should be added. * @warning The Table should be initialized via InitHelp(). */ void AddHelp(CTable& Table) const; const CString& GetCommand() const { return m_sCmd; } CmdFunc GetFunction() const { return m_pFunc; } CString GetArgs() const { return m_Args.Resolve(); } CString GetDescription() const { return m_Desc.Resolve(); } void Call(const CString& sLine) const { m_pFunc(sLine); } private: CString m_sCmd; CmdFunc m_pFunc; COptionalTranslation m_Args; COptionalTranslation m_Desc; }; /** The base class for your own ZNC modules. * * If you want to write a module for ZNC, you will have to implement a class * which inherits from this class. You should override some of the "On*" * functions in this class. These function will then be called by ZNC when the * associated event happens. * * If such a module hook is called with a non-const reference to e.g. a * CString, then it is free to modify that argument to influence ZNC's * behavior. * * @see MODCONSTRUCTOR and MODULEDEFS */ class CModule { public: CModule( ModHandle pDLL, CUser* pUser, CIRCNetwork* pNetwork, const CString& sModName, const CString& sDataDir, CModInfo::EModuleType eType = CModInfo::NetworkModule); // TODO: remove default value in ZNC 2.x virtual ~CModule(); CModule(const CModule&) = delete; CModule& operator=(const CModule&) = delete; /** This enum is just used for return from module hooks. Based on this * return, ZNC then decides what to do with the event which caused the * module hook. */ typedef enum { /** ZNC will continue event processing normally. This is what * you should return normally. */ CONTINUE = 1, /** This is the same as both CModule::HALTMODS and * CModule::HALTCORE together. */ HALT = 2, /** Stop sending this even to other modules which were not * called yet. Internally, the event is handled normally. */ HALTMODS = 3, /** Continue calling other modules. When done, ignore the event * in the ZNC core. (For most module hooks this means that a * given event won't be forwarded to the connected users) */ HALTCORE = 4 } EModRet; typedef enum { /** Your module can throw this enum at any given time. When this * is thrown, the module will be unloaded. */ UNLOAD } EModException; void SetUser(CUser* pUser); void SetNetwork(CIRCNetwork* pNetwork); void SetClient(CClient* pClient); /** This function throws CModule::UNLOAD which causes this module to be unloaded. */ void Unload() { throw UNLOAD; } /** This module hook is called when a module is loaded * @param sArgsi The arguments for the modules. * @param sMessage A message that may be displayed to the user after * loading the module. Useful for returning error messages. * @return true if the module loaded successfully, else false. */ virtual bool OnLoad(const CString& sArgsi, CString& sMessage); /** This module hook is called during ZNC startup. Only modules loaded * from znc.conf get this call. * @return false to abort ZNC startup. */ virtual bool OnBoot(); /** Modules which can only be used with an active user session have to return true here. * @return false for modules that can do stuff for non-logged in web users as well. */ virtual bool WebRequiresLogin() { return true; } /** Return true if this module should only be usable for admins on the web. * @return false if normal users can use this module's web pages as well. */ virtual bool WebRequiresAdmin() { return false; } /** Return the title of the module's section in the web interface's side bar. * @return The Title. */ virtual CString GetWebMenuTitle() { return ""; } virtual CString GetWebPath(); virtual CString GetWebFilesPath(); /** For WebMods: Called before the list of registered SubPages will be checked. * Important: If you return true, you need to take care of calling WebSock.Close! * This allows for stuff like returning non-templated data, long-polling and other fun. * @param WebSock The active request. * @param sPageName The name of the page that has been requested. * @return true if you handled the page request or false if the name is to be checked * against the list of registered SubPages and their permission settings. */ virtual bool OnWebPreRequest(CWebSock& WebSock, const CString& sPageName); /** If OnWebPreRequest returned false, and the RequiresAdmin/IsAdmin check has been passed, * this method will be called with the page name. It will also be called for pages that * have NOT been specifically registered with AddSubPage. * @param WebSock The active request. * @param sPageName The name of the page that has been requested. * @param Tmpl The active template. You can add variables, loops and stuff to it. * @return You MUST return true if you want the template to be evaluated and sent to the browser. * Return false if you called Redirect() or PrintErrorPage(). If you didn't, a 404 page will be sent. */ virtual bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl); /** If ValidateWebRequestCSRFCheck returned false, a CSRF error will be printed. * @param WebSock The active request. * @param sPageName The name of the page that has been requested. * @return You MUST return true if the CSRF token is valid. */ virtual bool ValidateWebRequestCSRFCheck(CWebSock& WebSock, const CString& sPageName); /** Registers a sub page for the sidebar. * @param spSubPage The SubPage instance. */ virtual void AddSubPage(TWebSubPage spSubPage) { m_vSubPages.push_back(spSubPage); } /** Removes all registered (AddSubPage'd) SubPages. */ virtual void ClearSubPages() { m_vSubPages.clear(); } /** Returns a list of all registered SubPages. Don't mess with it too much. * @return The List. */ virtual VWebSubPages& GetSubPages() { return m_vSubPages; } /** Using this hook, module can embed web stuff directly to different places. * This method is called whenever embededded modules I/O happens. * Name of used .tmpl file (if any) is up to caller. * @param WebSock Socket for web connection, don't do bad things with it. * @param sPageName Describes the place where web stuff is embedded to. * @param Tmpl Template. Depending on context, you can do various stuff with it. * @return If you don't need to embed web stuff to the specified place, just return false. * Exact meaning of return value is up to caller, and depends on context. */ virtual bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl); /** Called just before znc.conf is rehashed */ virtual void OnPreRehash(); /** This module hook is called after a successful rehash. */ virtual void OnPostRehash(); /** This module hook is called when a user gets disconnected from IRC. */ virtual void OnIRCDisconnected(); /** This module hook is called after a successful login to IRC. */ virtual void OnIRCConnected(); /** This module hook is called just before ZNC tries to establish a * connection to an IRC server. * @param pIRCSock The socket that will be used for the connection. * @return See CModule::EModRet. */ virtual EModRet OnIRCConnecting(CIRCSock* pIRCSock); /** This module hook is called when a CIRCSock fails to connect or * a module returned HALTCORE from OnIRCConnecting. * @param pIRCSock The socket that failed to connect. */ virtual void OnIRCConnectionError(CIRCSock* pIRCSock); /** This module hook is called before loging in to the IRC server. The * low-level connection is established at this point, but SSL * handshakes didn't necessarily finish yet. * @param sPass The server password that will be used. * @param sNick The nick that will be used. * @param sIdent The protocol identity that will be used. This is not * the ident string that is transferred via e.g. oidentd! * @param sRealName The real name that will be used. * @return See CModule::EModRet. */ virtual EModRet OnIRCRegistration(CString& sPass, CString& sNick, CString& sIdent, CString& sRealName); /** This module hook is called when a message is broadcasted to all users. * @param sMessage The message that is broadcasted. * @return see CModule::EModRet */ virtual EModRet OnBroadcast(CString& sMessage); /** This module hook is called when a user mode on a channel changes. * @param pOpNick The nick who sent the mode change, or nullptr if set by server. * @param Nick The nick whose channel mode changes. * @param Channel The channel on which the user mode is changed. * @param cMode The mode character that is changed, e.g. '@' for op. * @param bAdded True if the mode is added, else false. * @param bNoChange true if this mode change doesn't change anything * because the nick already had this permission. * @see CIRCSock::GetModeType() for converting uMode into a mode (e.g. * 'o' for op). */ virtual void OnChanPermission3(const CNick* pOpNick, const CNick& Nick, CChan& Channel, char cMode, bool bAdded, bool bNoChange); /// @deprecated. Use OnChanPermission3. virtual void OnChanPermission2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, unsigned char uMode, bool bAdded, bool bNoChange); /// @deprecated. Use OnChanPermission3. virtual void OnChanPermission(const CNick& OpNick, const CNick& Nick, CChan& Channel, unsigned char uMode, bool bAdded, bool bNoChange); /** Called when a nick is opped on a channel */ virtual void OnOp2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange); virtual void OnOp(const CNick& OpNick, const CNick& Nick, CChan& Channel, bool bNoChange); /** Called when a nick is deopped on a channel */ virtual void OnDeop2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange); virtual void OnDeop(const CNick& OpNick, const CNick& Nick, CChan& Channel, bool bNoChange); /** Called when a nick is voiced on a channel */ virtual void OnVoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange); virtual void OnVoice(const CNick& OpNick, const CNick& Nick, CChan& Channel, bool bNoChange); /** Called when a nick is devoiced on a channel */ virtual void OnDevoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange); virtual void OnDevoice(const CNick& OpNick, const CNick& Nick, CChan& Channel, bool bNoChange); /** Called on an individual channel mode change. * @param pOpNick The nick who changes the channel mode, or nullptr if set by server. * @param Channel The channel whose mode is changed. * @param uMode The mode character that is changed. * @param sArg The argument to the mode character, if any. * @param bAdded True if this mode is added ("+"), else false. * @param bNoChange True if this mode was already effective before. */ virtual void OnMode2(const CNick* pOpNick, CChan& Channel, char uMode, const CString& sArg, bool bAdded, bool bNoChange); virtual void OnMode(const CNick& OpNick, CChan& Channel, char uMode, const CString& sArg, bool bAdded, bool bNoChange); /** Called on any channel mode change. This is called before the more * detailed mode hooks like e.g. OnOp() and OnMode(). * @param pOpNick The nick who changes the channel mode, or nullptr if set by server. * @param Channel The channel whose mode is changed. * @param sModes The raw mode change, e.g. "+s-io". * @param sArgs All arguments to the mode change from sModes. */ virtual void OnRawMode2(const CNick* pOpNick, CChan& Channel, const CString& sModes, const CString& sArgs); virtual void OnRawMode(const CNick& OpNick, CChan& Channel, const CString& sModes, const CString& sArgs); /** Called on any raw IRC line received from the IRC server. * @param sLine The line read from the server. * @note The line does not include message tags. Use OnRawMessage() to access them. * @return See CModule::EModRet. */ virtual EModRet OnRaw(CString& sLine); /** Called on any raw message received from the IRC server. * @since 1.7.0 * @param Message The received message. * @return See CModule::EModRet. */ virtual EModRet OnRawMessage(CMessage& Message); /** Called when a numeric message is received from the IRC server. * @since 1.7.0 * @param Message The received message. * @return See CModule::EModRet. */ virtual EModRet OnNumericMessage(CNumericMessage& Message); /** Called when a command to *status is sent. * @param sCommand The command sent. * @return See CModule::EModRet. */ virtual EModRet OnStatusCommand(CString& sCommand); /** Called when a command to your module is sent, e.g. query to *modname. * @param sCommand The command that was sent. */ virtual void OnModCommand(const CString& sCommand); /** This is similar to OnModCommand(), but it is only called if * HandleCommand didn't find any that wants to handle this. This is only * called if HandleCommand() is called, which practically means that * this is only called if you don't overload OnModCommand(). * @param sCommand The command that was sent. */ virtual void OnUnknownModCommand(const CString& sCommand); /** Called when a your module nick was sent a notice. * @param sMessage The message which was sent. */ virtual void OnModNotice(const CString& sMessage); /** Called when your module nick was sent a CTCP message. OnModCommand() * won't be called for this message. * @param sMessage The message which was sent. */ virtual void OnModCTCP(const CString& sMessage); /** Called when a nick quit from IRC. * @since 1.7.0 * @param Message The quit message. * @param vChans List of channels which you and nick share. */ virtual void OnQuitMessage(CQuitMessage& Message, const std::vector& vChans); /// @deprecated Use OnQuitMessage() instead. virtual void OnQuit(const CNick& Nick, const CString& sMessage, const std::vector& vChans); /** Called when a nickname change occurs. * @since 1.7.0 * @param Message The nick message. * @param vChans Channels which we and nick share. */ virtual void OnNickMessage(CNickMessage& Message, const std::vector& vChans); /// @deprecated Use OnNickMessage() instead. virtual void OnNick(const CNick& Nick, const CString& sNewNick, const std::vector& vChans); /** Called when a nick is kicked from a channel. * @since 1.7.0 * @param Message The kick message. */ virtual void OnKickMessage(CKickMessage& Message); /// @deprecated Use OnKickMessage() instead. virtual void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage); /** This module hook is called just before ZNC tries to join an IRC channel. * @param Chan The channel which is about to get joined. * @return See CModule::EModRet. */ virtual EModRet OnJoining(CChan& Channel); /** Called when a nick joins a channel. * @since 1.7.0 * @param Message The join message. */ virtual void OnJoinMessage(CJoinMessage& Message); /// @deprecated Use OnJoinMessage() instead. virtual void OnJoin(const CNick& Nick, CChan& Channel); /** Called when a nick parts a channel. * @since 1.7.0 * @param Message The part message. */ virtual void OnPartMessage(CPartMessage& Message); /// @deprecated Use OnPartMessage() instead. virtual void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage); /** Called when user is invited into a channel * @param Nick The nick who invited you. * @param sChan The channel the user got invited into * @return See CModule::EModRet. * @todo Add OnInviteMessage() hook */ virtual EModRet OnInvite(const CNick& Nick, const CString& sChan); /** Called before a channel buffer is played back to a client. * @param Chan The channel which will be played back. * @param Client The client the buffer will be played back to. * @return See CModule::EModRet. */ virtual EModRet OnChanBufferStarting(CChan& Chan, CClient& Client); /** Called after a channel buffer was played back to a client. * @param Chan The channel which was played back. * @param Client The client the buffer was played back to. * @return See CModule::EModRet. */ virtual EModRet OnChanBufferEnding(CChan& Chan, CClient& Client); /** Called for each message during a channel's buffer play back. * @since 1.7.0 * @param Message The playback message. * @return See CModule::EModRet. */ virtual EModRet OnChanBufferPlayMessage(CMessage& Message); /// @deprecated Use OnChanBufferPlayMessage() instead. virtual EModRet OnChanBufferPlayLine2(CChan& Chan, CClient& Client, CString& sLine, const timeval& tv); /// @deprecated Use OnChanBufferPlayMessage() instead. virtual EModRet OnChanBufferPlayLine(CChan& Chan, CClient& Client, CString& sLine); /** Called before a query buffer is played back to a client. * @since 1.7.0 * @param Query The query which will be played back. * @param Client The client the buffer will be played back to. * @return See CModule::EModRet. */ virtual EModRet OnPrivBufferStarting(CQuery& Query, CClient& Client); /** Called after a query buffer was played back to a client. * @since 1.7.0 * @param Query The query which was played back. * @param Client The client the buffer was played back to. * @return See CModule::EModRet. */ virtual EModRet OnPrivBufferEnding(CQuery& Query, CClient& Client); /** Called for each message during a query's buffer play back. * @since 1.7.0 * @param Message The playback message. * @return See CModule::EModRet. */ virtual EModRet OnPrivBufferPlayMessage(CMessage& Message); /// @deprecated Use OnPrivBufferPlayMessage() instead. virtual EModRet OnPrivBufferPlayLine2(CClient& Client, CString& sLine, const timeval& tv); /// @deprecated Use OnPrivBufferPlayMessage() instead. virtual EModRet OnPrivBufferPlayLine(CClient& Client, CString& sLine); /** Called when a client successfully logged in to ZNC. */ virtual void OnClientLogin(); /** Called when a client disconnected from ZNC. */ virtual void OnClientDisconnect(); /** This module hook is called when a client sends a raw traffic line to ZNC. * @param sLine The raw traffic line sent. * @return See CModule::EModRet. */ virtual EModRet OnUserRaw(CString& sLine); /** This module hook is called when a client sends any message to ZNC. * @since 1.7.0 * @param Message The message sent. * @return See CModule::EModRet. */ virtual EModRet OnUserRawMessage(CMessage& Message); /** This module hook is called when a client sends a CTCP reply. * @since 1.7.0 * @param Message The CTCP reply message. * @return See CModule::EModRet. */ virtual EModRet OnUserCTCPReplyMessage(CCTCPMessage& Message); /// @deprecated Use OnUserCTCPReplyMessage() instead. virtual EModRet OnUserCTCPReply(CString& sTarget, CString& sMessage); /** This module hook is called when a client sends a CTCP request. * @since 1.7.0 * @param Message The CTCP request message. * @return See CModule::EModRet. * @note This is not called for CTCP ACTION messages, use * CModule::OnUserActionMessage() instead. */ virtual EModRet OnUserCTCPMessage(CCTCPMessage& Message); /// @deprecated Use OnUserCTCPMessage() instead. virtual EModRet OnUserCTCP(CString& sTarget, CString& sMessage); /** Called when a client sends a CTCP ACTION request ("/me"). * @since 1.7.0 * @param Message The action message. * @return See CModule::EModRet. * @note CModule::OnUserCTCPMessage() will not be called for this message. */ virtual EModRet OnUserActionMessage(CActionMessage& Message); /// @deprecated Use OnUserActionMessage() instead. virtual EModRet OnUserAction(CString& sTarget, CString& sMessage); /** This module hook is called when a user sends a PRIVMSG message. * @since 1.7.0 * @param Message The message which was sent. * @return See CModule::EModRet. */ virtual EModRet OnUserTextMessage(CTextMessage& Message); /// @deprecated Use OnUserTextMessage() instead. virtual EModRet OnUserMsg(CString& sTarget, CString& sMessage); /** This module hook is called when a user sends a NOTICE message. * @since 1.7.0 * @param Message The message which was sent. * @return See CModule::EModRet. */ virtual EModRet OnUserNoticeMessage(CNoticeMessage& Message); /// @deprecated Use OnUserNoticeMessage() instead. virtual EModRet OnUserNotice(CString& sTarget, CString& sMessage); /** This hooks is called when a user sends a JOIN message. * @since 1.7.0 * @param Message The join message. * @return See CModule::EModRet. */ virtual EModRet OnUserJoinMessage(CJoinMessage& Message); /// @deprecated Use OnUserJoinMessage() instead. virtual EModRet OnUserJoin(CString& sChannel, CString& sKey); /** This hooks is called when a user sends a PART message. * @since 1.7.0 * @param Message The part message. * @return See CModule::EModRet. */ virtual EModRet OnUserPartMessage(CPartMessage& Message); /// @deprecated Use OnUserPartMessage() instead. virtual EModRet OnUserPart(CString& sChannel, CString& sMessage); /** This module hook is called when a user wants to change a channel topic. * @since 1.7.0 * @param Message The topic message. * @return See CModule::EModRet. */ virtual EModRet OnUserTopicMessage(CTopicMessage& Message); /// @deprecated Use OnUserTopicMessage() instead. virtual EModRet OnUserTopic(CString& sChannel, CString& sTopic); /** This hook is called when a user requests a channel's topic. * @param sChannel The channel for which the request is. * @return See CModule::EModRet. */ virtual EModRet OnUserTopicRequest(CString& sChannel); /** This module hook is called when a client quits ZNC. * @since 1.7.0 * @param Message The quit message the client sent. * @return See CModule::EModRet. */ virtual EModRet OnUserQuitMessage(CQuitMessage& Message); /// @deprecated Use OnUserQuitMessage() instead. virtual EModRet OnUserQuit(CString& sMessage); /** Called when we receive a CTCP reply from IRC. * @since 1.7.0 * @param Message The CTCP reply message. * @return See CModule::EModRet. */ virtual EModRet OnCTCPReplyMessage(CCTCPMessage& Message); /// @deprecated Use OnCTCPReplyMessage() instead. virtual EModRet OnCTCPReply(CNick& Nick, CString& sMessage); /** Called when we receive a private CTCP request from IRC. * @since 1.7.0 * @param Message The CTCP request message. * @return See CModule::EModRet. */ virtual EModRet OnPrivCTCPMessage(CCTCPMessage& Message); /// @deprecated Use OnPrivCTCPMessage() instead. virtual EModRet OnPrivCTCP(CNick& Nick, CString& sMessage); /** Called when we receive a channel CTCP request from IRC. * @since 1.7.0 * @param Message The CTCP request message. * @return See CModule::EModRet. */ virtual EModRet OnChanCTCPMessage(CCTCPMessage& Message); /// @deprecated Use OnChanCTCPMessage() instead. virtual EModRet OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage); /** Called when we receive a private CTCP ACTION ("/me" in query) from IRC. * @since 1.7.0 * @param Message The action message * @return See CModule::EModRet. */ virtual EModRet OnPrivActionMessage(CActionMessage& Message); /// @deprecated Use OnPrivActionMessage() instead. virtual EModRet OnPrivAction(CNick& Nick, CString& sMessage); /** Called when we receive a channel CTCP ACTION ("/me" in a channel) from IRC. * @since 1.7.0 * @param Message The action message * @return See CModule::EModRet. */ virtual EModRet OnChanActionMessage(CActionMessage& Message); /// @deprecated Use OnChanActionMessage() instead. virtual EModRet OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage); /** Called when we receive a private PRIVMSG message from IRC. * @since 1.7.0 * @param Message The private message. * @return See CModule::EModRet. */ virtual EModRet OnPrivTextMessage(CTextMessage& Message); /// @deprecated Use OnPrivTextMessage() instead. virtual EModRet OnPrivMsg(CNick& Nick, CString& sMessage); /** Called when we receive a channel PRIVMSG message from IRC. * @since 1.7.0 * @param Message The channel message. * @return See CModule::EModRet. */ virtual EModRet OnChanTextMessage(CTextMessage& Message); /// @deprecated Use OnChanTextMessage() instead. virtual EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage); /** Called when we receive a private NOTICE message from IRC. * @since 1.7.0 * @param Message The notice message. * @return See CModule::EModRet. */ virtual EModRet OnPrivNoticeMessage(CNoticeMessage& Message); /// @deprecated Use OnPrivNoticeMessage() instead. virtual EModRet OnPrivNotice(CNick& Nick, CString& sMessage); /** Called when we receive a channel NOTICE message from IRC. * @since 1.7.0 * @param Message The notice message. * @return See CModule::EModRet. */ virtual EModRet OnChanNoticeMessage(CNoticeMessage& Message); /// @deprecated Use OnChanNoticeMessage() instead. virtual EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage); /** Called when we receive a channel topic change from IRC. * @since 1.7.0 * @param Message The topic message. * @return See CModule::EModRet. */ virtual EModRet OnTopicMessage(CTopicMessage& Message); /// @deprecated Use OnTopicMessage() instead. virtual EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic); /** Called for every CAP received via CAP LS from server. * If you need to also advertise the cap to clients, use * AddServerDependentCapability() instead. * @param sCap capability supported by server. * @return true if your module supports this CAP and * needs to turn it on with CAP REQ. */ virtual bool OnServerCapAvailable(const CString& sCap); /** Called for every CAP received via CAP LS from server. * By default just calls OnServerCapAvailable() without sValue, so * overriding one of the two is enough. * If you need to also advertise the cap to clients, use * AddServerDependentCapability() instead. * @param sCap capability name supported by server. * @param sValue value. * @return true if your module supports this CAP and * needs to turn it on with CAP REQ. */ virtual bool OnServerCap302Available(const CString& sCap, const CString& sValue); /** Called for every CAP accepted or rejected by server * (with CAP ACK or CAP NAK after our CAP REQ). * If you need to also advertise the cap to clients, use * AddServerDependentCapability() instead. * @param sCap capability accepted/rejected by server. * @param bSuccess true if capability was accepted, false if rejected. */ virtual void OnServerCapResult(const CString& sCap, bool bSuccess); /** This module hook is called just before ZNC tries to join a channel * by itself because it's in the config but wasn't joined yet. * @param Channel The channel which will be joined. * @return See CModule::EModRet. */ virtual EModRet OnTimerAutoJoin(CChan& Channel); /** This module hook is called when a network is being added. * @param Network The new IRC network. * @param sErrorRet A message that may be displayed to the user if * the module stops adding the network. * @return See CModule::EModRet. */ virtual EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet); /** This module hook is called when a network is deleted. * @param Network The IRC network which is going to be deleted. * @return See CModule::EModRet. */ virtual EModRet OnDeleteNetwork(CIRCNetwork& Network); /** Called immediately before ZNC sends a raw traffic line to a client. * @since 1.7.0 * @param Message The message being sent to the client. * @warning Calling PutUser() from within this hook leads to infinite recursion. * @return See CModule::EModRet. */ virtual EModRet OnSendToClientMessage(CMessage& Message); /// @deprecated Use OnSendToClientMessage() instead. virtual EModRet OnSendToClient(CString& sLine, CClient& Client); /** Called immediately before ZNC sends a raw traffic line to the IRC server. * @since 1.7.0 * @param Message The message being sent to the IRC server. * @warning Calling PutIRC() from within this hook leads to infinite recursion. * @return See CModule::EModRet. */ virtual EModRet OnSendToIRCMessage(CMessage& Message); /// @deprecated Use OnSendToIRCMessage() instead. virtual EModRet OnSendToIRC(CString& sLine); ModHandle GetDLL() { return m_pDLL; } /** This function sends a given IRC line to the IRC server, if we * are connected to one. Else this line is discarded. * @param sLine The line which should be sent. * @return true if the line was queued for sending. */ virtual bool PutIRC(const CString& sLine); /** This function sends a given IRC message to the IRC server, if we * are connected to one. Else this message is discarded. * @param Message The message which should be sent. * @return true if the message was queued for sending. */ virtual bool PutIRC(const CMessage& Message); /** This function sends a given raw IRC line to a client. * If we are in a module hook which is called for a specific client, * only that client will get the line, else all connected clients will * receive this line. * @param sLine The line which should be sent. * @return true if the line was sent to at least one client. */ virtual bool PutUser(const CString& sLine); /** This function generates a query from *status. If we are in a module * hook for a specific client, only that client gets this message, else * all connected clients will receive it. * @param sLine The message which should be sent from *status. * @return true if the line was sent to at least one client. */ virtual bool PutStatus(const CString& sLine); /** This function sends a query from your module nick. If we are in a * module hook for a specific client, only that client gets this * message, else all connected clients will receive it. * @param sLine The message which should be sent. * @return true if the line was sent to at least one client. */ virtual bool PutModule(const CString& sLine); /** This function calls CModule::PutModule(const CString&, const * CString&, const CString&) for each line in the table. * @param table The table which should be send. * @return The number of lines sent. */ virtual unsigned int PutModule(const CTable& table); /** Send a notice from your module nick. If we are in a module hook for * a specific client, only that client gets this notice, else all * clients will receive it. * @param sLine The line which should be sent. * @return true if the line was sent to at least one client. */ virtual bool PutModNotice(const CString& sLine); /** @returns The name of the module. */ const CString& GetModName() const { return m_sModName; } /** @returns The nick of the module. This is just the module name * prefixed by the status prefix. */ CString GetModNick() const; /** Get the module's data dir. * Modules can be accompanied by static data, e.g. skins for webadmin. * These function will return the path to that data. */ const CString& GetModDataDir() const { return m_sDataDir; } // Timer stuff bool AddTimer(CTimer* pTimer); bool AddTimer(FPTimer_t pFBCallback, const CString& sLabel, u_int uInterval, u_int uCycles = 0, const CString& sDescription = ""); bool RemTimer(CTimer* pTimer); bool RemTimer(const CString& sLabel); bool UnlinkTimer(CTimer* pTimer); CTimer* FindTimer(const CString& sLabel); std::set::const_iterator BeginTimers() const { return m_sTimers.begin(); } std::set::const_iterator EndTimers() const { return m_sTimers.end(); } virtual void ListTimers(); // !Timer stuff // Socket stuff bool AddSocket(CSocket* pSocket); bool RemSocket(CSocket* pSocket); bool RemSocket(const CString& sSockName); bool UnlinkSocket(CSocket* pSocket); CSocket* FindSocket(const CString& sSockName); std::set::const_iterator BeginSockets() const { return m_sSockets.begin(); } std::set::const_iterator EndSockets() const { return m_sSockets.end(); } virtual void ListSockets(); // !Socket stuff #ifdef HAVE_PTHREAD // Job stuff void AddJob(CModuleJob* pJob); void CancelJob(CModuleJob* pJob); bool CancelJob(const CString& sJobName); void CancelJobs(const std::set& sJobs); bool UnlinkJob(CModuleJob* pJob); // !Job stuff #endif // Command stuff /// Register the "Help" command. void AddHelpCommand(); /// @return True if the command was successfully added. bool AddCommand(const CModCommand& Command); /// @return True if the command was successfully added. /// @deprecated Use the variant with COptionalTranslation. bool AddCommand(const CString& sCmd, CModCommand::ModCmdFunc func, const CString& sArgs = "", const CString& sDesc = ""); /// @param dDesc Either a string "", or the result of t_d() /// @return True if the command was successfully added. bool AddCommand(const CString& sCmd, const COptionalTranslation& Args, const COptionalTranslation& Desc, std::function func); /// @return True if the command was successfully removed. bool RemCommand(const CString& sCmd); /// @return The CModCommand instance or nullptr if none was found. const CModCommand* FindCommand(const CString& sCmd) const; /** This function tries to dispatch the given command via the correct * instance of CModCommand. Before this can be called, commands have to * be added via AddCommand(). If no matching commands are found then * OnUnknownModCommand will be called. * @param sLine The command line to handle. * @return True if something was done, else false. */ bool HandleCommand(const CString& sLine); /** Send a description of all registered commands via PutModule(). * @param sLine The help command that is being asked for. */ void HandleHelpCommand(const CString& sLine = ""); // !Command stuff bool LoadRegistry(); bool SaveRegistry() const; bool MoveRegistry(const CString& sPath); bool SetNV(const CString& sName, const CString& sValue, bool bWriteToDisk = true); CString GetNV(const CString& sName) const; bool HasNV(const CString& sName) const { return m_mssRegistry.find(sName) != m_mssRegistry.end(); } bool DelNV(const CString& sName, bool bWriteToDisk = true); MCString::iterator FindNV(const CString& sName) { return m_mssRegistry.find(sName); } MCString::iterator EndNV() { return m_mssRegistry.end(); } MCString::iterator BeginNV() { return m_mssRegistry.begin(); } void DelNV(MCString::iterator it) { m_mssRegistry.erase(it); } bool ClearNV(bool bWriteToDisk = true); const CString& GetSavePath() const; CString ExpandString(const CString& sStr) const; CString& ExpandString(const CString& sStr, CString& sRet) const; // Setters void SetType(CModInfo::EModuleType eType) { m_eType = eType; } void SetDescription(const CString& s) { m_sDescription = s; } void SetModPath(const CString& s) { m_sModPath = s; } void SetArgs(const CString& s) { m_sArgs = s; } // !Setters // Getters CModInfo::EModuleType GetType() const { return m_eType; } const CString& GetDescription() const { return m_sDescription; } const CString& GetArgs() const { return m_sArgs; } const CString& GetModPath() const { return m_sModPath; } /** @returns For user modules this returns the user for which this * module was loaded. For global modules this returns nullptr, * except when we are in a user-specific module hook in which * case this is the user pointer. */ CUser* GetUser() const { return m_pUser; } /** @returns nullptr except when we are in a network-specific module hook in * which case this is the network for which the hook is called. */ CIRCNetwork* GetNetwork() const { return m_pNetwork; } /** @returns nullptr except when we are in a client-specific module hook in * which case this is the client for which the hook is called. */ CClient* GetClient() const { return m_pClient; } CSockManager* GetManager() const { return m_pManager; } // !Getters // Global Modules /** This module hook is called when a user is being added. * @param User The user which will be added. * @param sErrorRet A message that may be displayed to the user if * the module stops adding the user. * @return See CModule::EModRet. */ virtual EModRet OnAddUser(CUser& User, CString& sErrorRet); /** This module hook is called when a user is deleted. * @param User The user which will be deleted. * @return See CModule::EModRet. */ virtual EModRet OnDeleteUser(CUser& User); /** This module hook is called when there is an incoming connection on * any of ZNC's listening sockets. * @param pSock The incoming client socket. * @param sHost The IP the client is connecting from. * @param uPort The port the client is connecting from. */ virtual void OnClientConnect(CZNCSock* pSock, const CString& sHost, unsigned short uPort); /** This module hook is called when a client tries to login. If your * module wants to handle the login attempt, it must return * CModule::EModRet::HALT; * @param Auth The necessary authentication info for this login attempt. * @return See CModule::EModRet. */ virtual EModRet OnLoginAttempt(std::shared_ptr Auth); /** Called after a client login was rejected. * @param sUsername The username that tried to log in. * @param sRemoteIP The IP address from which the client tried to login. */ virtual void OnFailedLogin(const CString& sUsername, const CString& sRemoteIP); /** This function behaves like CModule::OnUserRaw(), but is also called * before the client successfully logged in to ZNC. You should always * prefer to use CModule::OnUserRaw() if possible. * @param pClient The client which send this line. * @param sLine The raw traffic line which the client sent. */ virtual EModRet OnUnknownUserRaw(CClient* pClient, CString& sLine); virtual EModRet OnUnknownUserRawMessage(CMessage& Message); /** Called after login, and also during JumpNetwork. */ virtual void OnClientAttached(); /** Called upon disconnect, and also during JumpNetwork. */ virtual void OnClientDetached(); #ifndef SWIG /** Simple API to support client capabilities which depend on server to support that capability. * It is built on top of other CAP related API, but removes boilerplate, * and handles some tricky cases related to cap-notify and JumpNetwork. To * use, create a subclass of CCapability, and pass to this function; it * will automatically set the module pointer, then call the callbacks to * notify you when server and client accepted support of the capability, or * stopped supporting it. Note that it's not a strict toggle: e.g. * sometimes client will disable the cap even when it was already disabled * for that client. * For perl and python modules, this function accepts 3 parameters: * name, server callback, client callback; signatures of the callbacks are * the same as of the virtual functions you'd implement in C++. */ void AddServerDependentCapability(const CString& sName, std::unique_ptr pCap); #endif /** Called when a client told us CAP LS. Use ssCaps.insert("cap-name") * for announcing capabilities which your module supports. * If you need to adverite the cap to clients only when it's also supported * by the server, use AddServerDependentCapability() instead. * @param pClient The client which requested the list. * @param ssCaps set of caps which will be sent to client. */ virtual void OnClientCapLs(CClient* pClient, SCString& ssCaps); /** Called only to check if your module supports turning on/off named capability. * @param pClient The client which wants to enable/disable a capability. * @param sCap name of capability. * @param bState On or off, depending on which case is interesting for client. * @return true if your module supports this capability in the specified state. */ virtual bool IsClientCapSupported(CClient* pClient, const CString& sCap, bool bState); /** Called when we actually need to turn a capability on or off for a client. * If you need to adverite the cap to clients only when it's also supported * by the server, use AddServerDependentCapability() instead. * If implementing a custom capability, make sure to call * pClient->SetTagSupport("tag-name", bState) for each tag that the * capability provides. * @param pClient The client which requested the capability. * @param sCap name of wanted capability. * @param bState On or off, depending on which case client needs. * @see CClient::SetTagSupport() */ virtual void OnClientCapRequest(CClient* pClient, const CString& sCap, bool bState); /** Called when a module is going to be loaded. * @param sModName name of the module. * @param eType wanted type of the module (user/global). * @param sArgs arguments of the module. * @param[out] bSuccess the module was loaded successfully * as result of this module hook? * @param[out] sRetMsg text about loading of the module. * @return See CModule::EModRet. */ virtual EModRet OnModuleLoading(const CString& sModName, const CString& sArgs, CModInfo::EModuleType eType, bool& bSuccess, CString& sRetMsg); /** Called when a module is going to be unloaded. * @param pModule the module. * @param[out] bSuccess the module was unloaded successfully * as result of this module hook? * @param[out] sRetMsg text about unloading of the module. * @return See CModule::EModRet. */ virtual EModRet OnModuleUnloading(CModule* pModule, bool& bSuccess, CString& sRetMsg); /** Called when info about a module is needed. * @param[out] ModInfo put result here, if your module knows it. * @param sModule name of the module. * @param bSuccess this module provided info about the module. * @param sRetMsg text describing possible issues. * @return See CModule::EModRet. */ virtual EModRet OnGetModInfo(CModInfo& ModInfo, const CString& sModule, bool& bSuccess, CString& sRetMsg); /** Called when list of available mods is requested. * @param ssMods put new modules here. * @param bGlobal true if global modules are needed. */ virtual void OnGetAvailableMods(std::set& ssMods, CModInfo::EModuleType eType); // !Global Modules #ifndef SWIG // Translation CString t_s(const CString& sEnglish, const CString& sContext = "") const; CInlineFormatMessage t_f(const CString& sEnglish, const CString& sContext = "") const; CInlineFormatMessage t_p(const CString& sEnglish, const CString& sEnglishes, int iNum, const CString& sContext = "") const; CDelayedTranslation t_d(const CString& sEnglish, const CString& sContext = "") const; #endif // Default implementations of several callbacks to make // AddServerDependentCapability work in modpython/modperl. // Don't worry about existence of these functions. bool InternalServerDependentCapsOnServerCap302Available( const CString& sCap, const CString& sValue); void InternalServerDependentCapsOnServerCapResult(const CString& sCap, bool bSuccess); void InternalServerDependentCapsOnClientCapLs(CClient* pClient, SCString& ssCaps); bool InternalServerDependentCapsIsClientCapSupported(CClient* pClient, const CString& sCap, bool bState); void InternalServerDependentCapsOnClientCapRequest(CClient* pClient, const CString& sCap, bool bState); void InternalServerDependentCapsOnClientAttached(); void InternalServerDependentCapsOnClientDetached(); void InternalServerDependentCapsOnIRCConnected(); void InternalServerDependentCapsOnIRCDisconnected(); protected: CModInfo::EModuleType m_eType; CString m_sDescription; std::set m_sTimers; std::set m_sSockets; #ifdef HAVE_PTHREAD std::set m_sJobs; #endif ModHandle m_pDLL; CSockManager* m_pManager; CUser* m_pUser; CIRCNetwork* m_pNetwork; CClient* m_pClient; CString m_sModName; CString m_sDataDir; CString m_sSavePath; CString m_sArgs; CString m_sModPath; CTranslationDomainRefHolder m_Translation; std::map> m_mServerDependentCaps; private: MCString m_mssRegistry; //!< way to save name/value pairs. Note there is no encryption involved in this VWebSubPages m_vSubPages; std::map m_mCommands; }; class CModules : public std::vector, private CCoreTranslationMixin { public: CModules(); ~CModules(); CModules(const CModules&) = default; CModules& operator=(const CModules&) = default; void SetUser(CUser* pUser) { m_pUser = pUser; } void SetNetwork(CIRCNetwork* pNetwork) { m_pNetwork = pNetwork; } void SetClient(CClient* pClient) { m_pClient = pClient; } CUser* GetUser() const { return m_pUser; } CIRCNetwork* GetNetwork() const { return m_pNetwork; } CClient* GetClient() const { return m_pClient; } void UnloadAll(); bool OnBoot(); bool OnPreRehash(); bool OnPostRehash(); bool OnIRCDisconnected(); bool OnIRCConnected(); bool OnIRCConnecting(CIRCSock* pIRCSock); bool OnIRCConnectionError(CIRCSock* pIRCSock); bool OnIRCRegistration(CString& sPass, CString& sNick, CString& sIdent, CString& sRealName); bool OnBroadcast(CString& sMessage); bool OnChanPermission3(const CNick* pOpNick, const CNick& Nick, CChan& Channel, char cMode, bool bAdded, bool bNoChange); bool OnChanPermission2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, unsigned char uMode, bool bAdded, bool bNoChange); bool OnChanPermission(const CNick& OpNick, const CNick& Nick, CChan& Channel, unsigned char uMode, bool bAdded, bool bNoChange); bool OnOp2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange); bool OnOp(const CNick& OpNick, const CNick& Nick, CChan& Channel, bool bNoChange); bool OnDeop2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange); bool OnDeop(const CNick& OpNick, const CNick& Nick, CChan& Channel, bool bNoChange); bool OnVoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange); bool OnVoice(const CNick& OpNick, const CNick& Nick, CChan& Channel, bool bNoChange); bool OnDevoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange); bool OnDevoice(const CNick& OpNick, const CNick& Nick, CChan& Channel, bool bNoChange); bool OnRawMode2(const CNick* pOpNick, CChan& Channel, const CString& sModes, const CString& sArgs); bool OnRawMode(const CNick& OpNick, CChan& Channel, const CString& sModes, const CString& sArgs); bool OnMode2(const CNick* pOpNick, CChan& Channel, char uMode, const CString& sArg, bool bAdded, bool bNoChange); bool OnMode(const CNick& OpNick, CChan& Channel, char uMode, const CString& sArg, bool bAdded, bool bNoChange); bool OnRaw(CString& sLine); bool OnRawMessage(CMessage& Message); bool OnNumericMessage(CNumericMessage& Message); bool OnStatusCommand(CString& sCommand); bool OnModCommand(const CString& sCommand); bool OnModNotice(const CString& sMessage); bool OnModCTCP(const CString& sMessage); bool OnQuit(const CNick& Nick, const CString& sMessage, const std::vector& vChans); bool OnQuitMessage(CQuitMessage& Message, const std::vector& vChans); bool OnNick(const CNick& Nick, const CString& sNewNick, const std::vector& vChans); bool OnNickMessage(CNickMessage& Message, const std::vector& vChans); bool OnKick(const CNick& Nick, const CString& sOpNick, CChan& Channel, const CString& sMessage); bool OnKickMessage(CKickMessage& Message); bool OnJoining(CChan& Channel); bool OnJoin(const CNick& Nick, CChan& Channel); bool OnJoinMessage(CJoinMessage& Message); bool OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage); bool OnPartMessage(CPartMessage& Message); bool OnInvite(const CNick& Nick, const CString& sChan); bool OnChanBufferStarting(CChan& Chan, CClient& Client); bool OnChanBufferEnding(CChan& Chan, CClient& Client); bool OnChanBufferPlayLine2(CChan& Chan, CClient& Client, CString& sLine, const timeval& tv); bool OnChanBufferPlayLine(CChan& Chan, CClient& Client, CString& sLine); bool OnPrivBufferStarting(CQuery& Query, CClient& Client); bool OnPrivBufferEnding(CQuery& Query, CClient& Client); bool OnPrivBufferPlayLine2(CClient& Client, CString& sLine, const timeval& tv); bool OnPrivBufferPlayLine(CClient& Client, CString& sLine); bool OnChanBufferPlayMessage(CMessage& Message); bool OnPrivBufferPlayMessage(CMessage& Message); bool OnClientLogin(); bool OnClientDisconnect(); bool OnUserRaw(CString& sLine); bool OnUserRawMessage(CMessage& Message); bool OnUserCTCPReply(CString& sTarget, CString& sMessage); bool OnUserCTCPReplyMessage(CCTCPMessage& Message); bool OnUserCTCP(CString& sTarget, CString& sMessage); bool OnUserCTCPMessage(CCTCPMessage& Message); bool OnUserAction(CString& sTarget, CString& sMessage); bool OnUserActionMessage(CActionMessage& Message); bool OnUserMsg(CString& sTarget, CString& sMessage); bool OnUserTextMessage(CTextMessage& Message); bool OnUserNotice(CString& sTarget, CString& sMessage); bool OnUserNoticeMessage(CNoticeMessage& Message); bool OnUserJoin(CString& sChannel, CString& sKey); bool OnUserJoinMessage(CJoinMessage& Message); bool OnUserPart(CString& sChannel, CString& sMessage); bool OnUserPartMessage(CPartMessage& Message); bool OnUserTopic(CString& sChannel, CString& sTopic); bool OnUserTopicMessage(CTopicMessage& Message); bool OnUserTopicRequest(CString& sChannel); bool OnUserQuit(CString& sMessage); bool OnUserQuitMessage(CQuitMessage& Message); bool OnCTCPReply(CNick& Nick, CString& sMessage); bool OnCTCPReplyMessage(CCTCPMessage& Message); bool OnPrivCTCP(CNick& Nick, CString& sMessage); bool OnPrivCTCPMessage(CCTCPMessage& Message); bool OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage); bool OnChanCTCPMessage(CCTCPMessage& Message); bool OnPrivAction(CNick& Nick, CString& sMessage); bool OnPrivActionMessage(CActionMessage& Message); bool OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage); bool OnChanActionMessage(CActionMessage& Message); bool OnPrivMsg(CNick& Nick, CString& sMessage); bool OnPrivTextMessage(CTextMessage& Message); bool OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage); bool OnChanTextMessage(CTextMessage& Message); bool OnPrivNotice(CNick& Nick, CString& sMessage); bool OnPrivNoticeMessage(CNoticeMessage& Message); bool OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage); bool OnChanNoticeMessage(CNoticeMessage& Message); bool OnTopic(CNick& Nick, CChan& Channel, CString& sTopic); bool OnTopicMessage(CTopicMessage& Message); bool OnTimerAutoJoin(CChan& Channel); bool OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet); bool OnDeleteNetwork(CIRCNetwork& Network); bool OnSendToClient(CString& sLine, CClient& Client); bool OnSendToClientMessage(CMessage& Message); bool OnSendToIRC(CString& sLine); bool OnSendToIRCMessage(CMessage& Message); bool OnClientAttached(); bool OnClientDetached(); bool OnServerCapAvailable(const CString& sCap, const CString& sValue); bool OnServerCapResult(const CString& sCap, bool bSuccess); CModule* FindModule(const CString& sModule) const; bool LoadModule(const CString& sModule, const CString& sArgs, CModInfo::EModuleType eType, CUser* pUser, CIRCNetwork* pNetwork, CString& sRetMsg); bool UnloadModule(const CString& sModule); bool UnloadModule(const CString& sModule, CString& sRetMsg); bool ReloadModule(const CString& sModule, const CString& sArgs, CUser* pUser, CIRCNetwork* pNetwork, CString& sRetMsg); static bool GetModInfo(CModInfo& ModInfo, const CString& sModule, CString& sRetMsg); static bool GetModPathInfo(CModInfo& ModInfo, const CString& sModule, const CString& sModPath, CString& sRetMsg); static void GetAvailableMods( std::set& ssMods, CModInfo::EModuleType eType = CModInfo::UserModule); static void GetDefaultMods( std::set& ssMods, CModInfo::EModuleType eType = CModInfo::UserModule); // This returns the path to the .so and to the data dir // which is where static data (webadmin skins) are saved static bool FindModPath(const CString& sModule, CString& sModPath, CString& sDataPath); // Return a list of pairs for directories in // which modules can be found. typedef std::queue> ModDirList; static ModDirList GetModDirs(); // Global Modules bool OnAddUser(CUser& User, CString& sErrorRet); bool OnDeleteUser(CUser& User); bool OnClientConnect(CZNCSock* pSock, const CString& sHost, unsigned short uPort); bool OnLoginAttempt(std::shared_ptr Auth); bool OnFailedLogin(const CString& sUsername, const CString& sRemoteIP); bool OnUnknownUserRaw(CClient* pClient, CString& sLine); bool OnUnknownUserRawMessage(CMessage& Message); bool OnClientCapLs(CClient* pClient, SCString& ssCaps); bool IsClientCapSupported(CClient* pClient, const CString& sCap, bool bState); bool OnClientCapRequest(CClient* pClient, const CString& sCap, bool bState); bool OnModuleLoading(const CString& sModName, const CString& sArgs, CModInfo::EModuleType eType, bool& bSuccess, CString& sRetMsg); bool OnModuleUnloading(CModule* pModule, bool& bSuccess, CString& sRetMsg); bool OnGetModInfo(CModInfo& ModInfo, const CString& sModule, bool& bSuccess, CString& sRetMsg); bool OnGetAvailableMods(std::set& ssMods, CModInfo::EModuleType eType); // !Global Modules private: static ModHandle OpenModule(const CString& sModule, const CString& sModPath, CModInfo& Info, CString& sRetMsg); static bool ValidateModuleName(const CString& sModule, CString& sRetMsg); protected: CUser* m_pUser; CIRCNetwork* m_pNetwork; CClient* m_pClient; }; #endif // !ZNC_MODULES_H znc-1.9.1/include/znc/Nick.h0000644000175000017500000000407314641222733016022 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_NICK_H #define ZNC_NICK_H #include #include #include // Forward Decl class CIRCNetwork; class CChan; // !Forward Decl class CNick { public: CNick(); CNick(const CString& sNick); ~CNick(); CNick(const CNick&) = default; CNick& operator=(const CNick&) = default; void Reset(); void Parse(const CString& sNickMask); CString GetHostMask() const; size_t GetCommonChans(std::vector& vChans, CIRCNetwork* pNetwork) const; bool NickEquals(const CString& nickname) const; // Setters void SetNetwork(CIRCNetwork* pNetwork); void SetNick(const CString& s); void SetIdent(const CString& s); void SetHost(const CString& s); /// e.g. '@' for chanop. bool AddPerm(char cPerm); /// e.g. '@' for chanop. bool RemPerm(char cPerm); // !Setters // Getters /// e.g. '@' for chanop. CString GetPermStr() const; /// e.g. '@' for chanop. char GetPermChar() const; /// e.g. '@' for chanop. bool HasPerm(char cPerm) const; const CString& GetNick() const; const CString& GetIdent() const; const CString& GetHost() const; CString GetNickMask() const; // !Getters void Clone(const CNick& SourceNick); private: protected: CString m_sChanPerms; CIRCNetwork* m_pNetwork; CString m_sNick; CString m_sIdent; CString m_sHost; }; #endif // !ZNC_NICK_H znc-1.9.1/include/znc/Query.h0000644000175000017500000000400214641222733016233 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_QUERY_H #define ZNC_QUERY_H #include #include #include // Forward Declarations class CClient; class CIRCNetwork; // !Forward Declarations class CQuery { public: CQuery(const CString& sName, CIRCNetwork* pNetwork); ~CQuery(); CQuery(const CQuery&) = delete; CQuery& operator=(const CQuery&) = delete; // Buffer const CBuffer& GetBuffer() const { return m_Buffer; } unsigned int GetBufferCount() const { return m_Buffer.GetLineCount(); } bool SetBufferCount(unsigned int u, bool bForce = false) { return m_Buffer.SetLineCount(u, bForce); } size_t AddBuffer(const CMessage& Format, const CString& sText = "") { return m_Buffer.AddLine(Format, sText); } /// @deprecated size_t AddBuffer(const CString& sFormat, const CString& sText = "", const timeval* ts = nullptr, const MCString& mssTags = MCString::EmptyMap) { return m_Buffer.AddLine(sFormat, sText, ts, mssTags); } void ClearBuffer() { m_Buffer.Clear(); } void SendBuffer(CClient* pClient); void SendBuffer(CClient* pClient, const CBuffer& Buffer); // !Buffer // Getters const CString& GetName() const { return m_sName; } // !Getters private: CString m_sName; CIRCNetwork* m_pNetwork; CBuffer m_Buffer; }; #endif // !ZNC_QUERY_H znc-1.9.1/include/znc/SHA256.h0000644000175000017500000000477614641222733016020 0ustar somebodysomebody/* * FIPS 180-2 SHA-224/256/384/512 implementation * Last update: 02/02/2007 * Issue date: 04/30/2005 * * Copyright (C) 2005, 2007 Olivier Gay * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef ZNC_SHA2_H #define ZNC_SHA2_H #define SHA256_DIGEST_SIZE (256 / 8) #define SHA256_BLOCK_SIZE (512 / 8) #include // C99 defines stdint.h which defines a uint32_t and uint8_t type. // But the other kids didn't want to play with poor little Solaris 9 and so he // just defines these in inttypes.h which is also part of C99 and is supposed to // include stdint.h. Solaris 9 is a weirdo. :( #include #include typedef struct { size_t tot_len; size_t len; unsigned char block[2 * SHA256_BLOCK_SIZE]; uint32_t h[8]; } sha256_ctx; void sha256_init(sha256_ctx* ctx); void sha256_update(sha256_ctx* ctx, const unsigned char* message, size_t len); void sha256_final(sha256_ctx* ctx, unsigned char* digest); void sha256(const unsigned char* message, size_t len, unsigned char* digest); #endif /* !ZNC_SHA2_H */ znc-1.9.1/include/znc/SSLVerifyHost.h0000644000175000017500000000163614641222733017624 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_SSLVERIFYHOST_H #define ZNC_SSLVERIFYHOST_H #ifdef HAVE_LIBSSL #include #include bool ZNC_SSLVerifyHost(const CString& sHost, const X509* pCert, CString& sError); #endif /* HAVE_LIBSSL */ #endif /* ZNC_SSLVERIFYHOST_H */ znc-1.9.1/include/znc/Server.h0000644000175000017500000000240514641222733016401 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_SERVER_H #define ZNC_SERVER_H #include #include class CServer { public: CServer(const CString& sName, unsigned short uPort = 6667, const CString& sPass = "", bool bSSL = false); ~CServer(); const CString& GetName() const; unsigned short GetPort() const; const CString& GetPass() const; bool IsSSL() const; CString GetString(bool bIncludePassword = true) const; static bool IsValidHostName(const CString& sHostName); private: protected: CString m_sName; unsigned short m_uPort; CString m_sPass; bool m_bSSL; }; #endif // !ZNC_SERVER_H znc-1.9.1/include/znc/Socket.h0000644000175000017500000002620614641222733016370 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_SOCKET_H #define ZNC_SOCKET_H #include #include #include #include class CModule; class CZNCSock : public Csock, protected CCoreTranslationMixin { public: CZNCSock(int timeout = 60); CZNCSock(const CString& sHost, u_short port, int timeout = 60); ~CZNCSock() {} int ConvertAddress(const struct sockaddr_storage* pAddr, socklen_t iAddrLen, CString& sIP, u_short* piPort) const override; #ifdef HAVE_LIBSSL int VerifyPeerCertificate(int iPreVerify, X509_STORE_CTX* pStoreCTX) override; void SSLHandShakeFinished() override; bool CheckSSLCert(X509* pCert); virtual void SSLCertError(X509* pCert) {} bool SNIConfigureClient(CString& sHostname) override; CString GetSSLPeerFingerprint(X509* pCert = nullptr) const; #else CString GetSSLPeerFingerprint() const { return ""; } #endif void SetHostToVerifySSL(const CString& sHost) { m_sHostToVerifySSL = sHost; } void SetSSLTrustedPeerFingerprints(const SCString& ssFPs) { m_ssTrustedFingerprints = ssFPs; } void SetTrustAllCerts(bool bTrustAll) { m_bTrustAllCerts = bTrustAll; } bool GetTrustAllCerts() const { return m_bTrustAllCerts; } void SetTrustPKI(bool bTrustPKI) { m_bTrustPKI = bTrustPKI; } bool GetTrustPKI() const { return m_bTrustPKI; } void SetEncoding(const CString&); virtual CString GetRemoteIP() const { return Csock::GetRemoteIP(); } protected: // All existing errno codes seem to be in range 1-300 enum { errnoBadSSLCert = 12569, }; private: CString m_sHostToVerifySSL; SCString m_ssTrustedFingerprints; SCString m_ssCertVerificationErrors; bool m_bTrustAllCerts = false; bool m_bTrustPKI = true; }; enum EAddrType { ADDR_IPV4ONLY, ADDR_IPV6ONLY, ADDR_ALL }; class CSockManager : public TSocketManager, private CCoreTranslationMixin { public: CSockManager(); virtual ~CSockManager(); bool ListenHost(u_short iPort, const CString& sSockName, const CString& sBindHost, bool bSSL = false, int iMaxConns = SOMAXCONN, CZNCSock* pcSock = nullptr, u_int iTimeout = 0, EAddrType eAddr = ADDR_ALL) { CSListener L(iPort, sBindHost); L.SetSockName(sSockName); L.SetIsSSL(bSSL); L.SetTimeout(iTimeout); L.SetMaxConns(iMaxConns); #ifdef HAVE_IPV6 switch (eAddr) { case ADDR_IPV4ONLY: L.SetAFRequire(CSSockAddr::RAF_INET); break; case ADDR_IPV6ONLY: L.SetAFRequire(CSSockAddr::RAF_INET6); break; case ADDR_ALL: L.SetAFRequire(CSSockAddr::RAF_ANY); break; } #endif return Listen(L, pcSock); } bool ListenAll(u_short iPort, const CString& sSockName, bool bSSL = false, int iMaxConns = SOMAXCONN, CZNCSock* pcSock = nullptr, u_int iTimeout = 0, EAddrType eAddr = ADDR_ALL) { return ListenHost(iPort, sSockName, "", bSSL, iMaxConns, pcSock, iTimeout, eAddr); } u_short ListenRand(const CString& sSockName, const CString& sBindHost, bool bSSL = false, int iMaxConns = SOMAXCONN, CZNCSock* pcSock = nullptr, u_int iTimeout = 0, EAddrType eAddr = ADDR_ALL) { unsigned short uPort = 0; CSListener L(0, sBindHost); L.SetSockName(sSockName); L.SetIsSSL(bSSL); L.SetTimeout(iTimeout); L.SetMaxConns(iMaxConns); #ifdef HAVE_IPV6 switch (eAddr) { case ADDR_IPV4ONLY: L.SetAFRequire(CSSockAddr::RAF_INET); break; case ADDR_IPV6ONLY: L.SetAFRequire(CSSockAddr::RAF_INET6); break; case ADDR_ALL: L.SetAFRequire(CSSockAddr::RAF_ANY); break; } #endif Listen(L, pcSock, &uPort); return uPort; } u_short ListenAllRand(const CString& sSockName, bool bSSL = false, int iMaxConns = SOMAXCONN, CZNCSock* pcSock = nullptr, u_int iTimeout = 0, EAddrType eAddr = ADDR_ALL) { return (ListenRand(sSockName, "", bSSL, iMaxConns, pcSock, iTimeout, eAddr)); } void Connect(const CString& sHostname, u_short iPort, const CString& sSockName, int iTimeout = 60, bool bSSL = false, const CString& sBindHost = "", CZNCSock* pcSock = nullptr); unsigned int GetAnonConnectionCount(const CString& sIP) const; void DelSockByAddr(Csock* pcSock) override; private: void FinishConnect(const CString& sHostname, u_short iPort, const CString& sSockName, int iTimeout, bool bSSL, const CString& sBindHost, CZNCSock* pcSock); std::map m_InFlightDnsSockets; #ifdef HAVE_PTHREAD class CThreadMonitorFD; friend class CThreadMonitorFD; #endif #ifdef HAVE_THREADED_DNS struct TDNSTask { TDNSTask() : sHostname(""), iPort(0), sSockName(""), iTimeout(0), bSSL(false), sBindhost(""), pcSock(nullptr), bDoneTarget(false), bDoneBind(false), aiTarget(nullptr), aiBind(nullptr) {} TDNSTask(const TDNSTask&) = delete; TDNSTask& operator=(const TDNSTask&) = delete; CString sHostname; u_short iPort; CString sSockName; int iTimeout; bool bSSL; CString sBindhost; CZNCSock* pcSock; bool bDoneTarget; bool bDoneBind; addrinfo* aiTarget; addrinfo* aiBind; }; class CDNSJob : public CJob { public: CDNSJob() : sHostname(""), task(nullptr), pManager(nullptr), bBind(false), iRes(0), aiResult(nullptr) {} CDNSJob(const CDNSJob&) = delete; CDNSJob& operator=(const CDNSJob&) = delete; CString sHostname; TDNSTask* task; CSockManager* pManager; bool bBind; int iRes; addrinfo* aiResult; void runThread() override; void runMain() override; }; void StartTDNSThread(TDNSTask* task, bool bBind); void SetTDNSThreadFinished(TDNSTask* task, bool bBind, addrinfo* aiResult); static void* TDNSThread(void* argument); #endif protected: }; /** * @class CSocket * @brief Base Csock implementation to be used by modules * * By all means, this class should be used as a base for sockets originating from modules. It handles removing instances of itself * from the module as it unloads, and simplifies use in general. * - EnableReadLine is default to true in this class * - MaxBuffer for readline is set to 10240, in the event this is reached the socket is closed (@see ReachedMaxBuffer) */ class CSocket : public CZNCSock { public: /** * @brief ctor * @param pModule the module this sock instance is associated to */ CSocket(CModule* pModule); /** * @brief ctor * @param pModule the module this sock instance is associated to * @param sHostname the hostname being connected to * @param uPort the port being connected to * @param iTimeout the timeout period for this specific sock */ CSocket(CModule* pModule, const CString& sHostname, unsigned short uPort, int iTimeout = 60); virtual ~CSocket(); CSocket(const CSocket&) = delete; CSocket& operator=(const CSocket&) = delete; using Csock::Connect; using Csock::Listen; //! This defaults to closing the socket, feel free to override void ReachedMaxBuffer() override; void SockError(int iErrno, const CString& sDescription) override; //! This limits the global connections from this IP to defeat DoS attacks, feel free to override. The ACL used is provided by the main interface @see CZNC::AllowConnectionFrom bool ConnectionFrom(const CString& sHost, unsigned short uPort) override; //! Ease of use Connect, assigns to the manager and is subsequently tracked bool Connect(const CString& sHostname, unsigned short uPort, bool bSSL = false, unsigned int uTimeout = 60); //! Ease of use Listen, assigned to the manager and is subsequently tracked bool Listen(unsigned short uPort, bool bSSL, unsigned int uTimeout = 0); // Getters CModule* GetModule() const; // !Getters #ifndef SWIG // Translation. As opposed to CCoreTranslationMixin, this one uses module.mo CString t_s(const CString& sEnglish, const CString& sContext = "") const; CInlineFormatMessage t_f(const CString& sEnglish, const CString& sContext = "") const; CInlineFormatMessage t_p(const CString& sEnglish, const CString& sEnglishes, int iNum, const CString& sContext) const; CDelayedTranslation t_d(const CString& sEnglish, const CString& sContext = "") const; #endif private: protected: CModule* m_pModule; //!< pointer to the module that this sock instance belongs to }; /** * @class CIRCSocket * @brief Base IRC socket for client<->ZNC, and ZNC<->server */ class CIRCSocket : public CZNCSock { public: #ifdef HAVE_ICU /** * @brief Allow IRC control characters to appear even if protocol encoding explicitly disallows them. * * E.g. ISO-2022-JP disallows 0x0F, which in IRC means "reset format", * so by default it gets replaced with U+FFFD ("replacement character"). * https://code.google.com/p/chromium/issues/detail?id=277062#c3 * * In case if protocol encoding uses these code points for something else, the encoding takes preference, * and they are not IRC control characters anymore. */ void IcuExtToUCallback(UConverterToUnicodeArgs* toArgs, const char* codeUnits, int32_t length, UConverterCallbackReason reason, UErrorCode* err) override; void IcuExtFromUCallback(UConverterFromUnicodeArgs* fromArgs, const UChar* codeUnits, int32_t length, UChar32 codePoint, UConverterCallbackReason reason, UErrorCode* err) override; #endif }; #endif /* ZNC_SOCKET_H */ znc-1.9.1/include/znc/Template.h0000644000175000017500000001615514641222733016715 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_TEMPLATE_H #define ZNC_TEMPLATE_H #include #include #include #include #include class CTemplate; class CTemplateTagHandler { public: CTemplateTagHandler() {} virtual ~CTemplateTagHandler() {} virtual bool HandleVar(CTemplate& Tmpl, const CString& sName, const CString& sArgs, CString& sOutput) { return false; } virtual bool HandleTag(CTemplate& Tmpl, const CString& sName, const CString& sArgs, CString& sOutput) { return false; } virtual bool HandleIf(CTemplate& Tmpl, const CString& sName, const CString& sArgs, CString& sOutput) { return HandleVar(Tmpl, sName, sArgs, sOutput); } virtual bool HandleValue(CTemplate& Tmpl, CString& sValue, const MCString& msOptions) { return false; } private: }; class CTemplate; class CTemplateOptions { public: CTemplateOptions() : m_eEscapeFrom(CString::EASCII), m_eEscapeTo(CString::EASCII) {} virtual ~CTemplateOptions() {} void Parse(const CString& sLine); // Getters CString::EEscape GetEscapeFrom() const { return m_eEscapeFrom; } CString::EEscape GetEscapeTo() const { return m_eEscapeTo; } // !Getters private: CString::EEscape m_eEscapeFrom; CString::EEscape m_eEscapeTo; }; class CTemplateLoopContext { public: CTemplateLoopContext(unsigned long uFilePos, const CString& sLoopName, bool bReverse, std::vector* pRows) : m_bReverse(bReverse), m_bHasData(false), m_sName(sLoopName), m_uRowIndex(0), m_uFilePosition(uFilePos), m_pvRows(pRows) {} virtual ~CTemplateLoopContext() {} CTemplateLoopContext(const CTemplateLoopContext&) = default; CTemplateLoopContext& operator=(const CTemplateLoopContext&) = default; // Setters void SetHasData(bool b = true) { m_bHasData = b; } void SetName(const CString& s) { m_sName = s; } void SetRowIndex(unsigned int u) { m_uRowIndex = u; } unsigned int IncRowIndex() { return ++m_uRowIndex; } unsigned int DecRowIndex() { if (m_uRowIndex == 0) { return 0; } return --m_uRowIndex; } void SetFilePosition(unsigned int u) { m_uFilePosition = u; } // !Setters // Getters bool HasData() const { return m_bHasData; } const CString& GetName() const { return m_sName; } unsigned long GetFilePosition() const { return m_uFilePosition; } unsigned int GetRowIndex() const { return m_uRowIndex; } size_t GetRowCount() { return m_pvRows->size(); } std::vector* GetRows() { return m_pvRows; } CTemplate* GetNextRow() { return GetRow(IncRowIndex()); } CTemplate* GetCurRow() { return GetRow(m_uRowIndex); } CTemplate* GetRow(unsigned int uIndex); CString GetValue(const CString& sName, bool bFromIf = false); // !Getters private: bool m_bReverse; //!< Iterate through this loop in reverse order bool m_bHasData; //!< Tells whether this loop has real data or not CString m_sName; //!< The name portion of the tag unsigned int m_uRowIndex; //!< The index of the current row we're on unsigned long m_uFilePosition; //!< The file position of the opening tag std::vector* m_pvRows; //!< This holds pointers to the templates associated with this loop }; class CTemplate : public MCString { public: CTemplate() : CTemplate("") {} CTemplate(const CString& sFileName) : MCString(), m_pParent(nullptr), m_sFileName(sFileName), m_lsbPaths(), m_mvLoops(), m_vLoopContexts(), m_spOptions(new CTemplateOptions), m_vspTagHandlers() {} CTemplate(const std::shared_ptr& Options, CTemplate* pParent = nullptr) : MCString(), m_pParent(pParent), m_sFileName(""), m_lsbPaths(), m_mvLoops(), m_vLoopContexts(), m_spOptions(Options), m_vspTagHandlers() {} virtual ~CTemplate(); CTemplate(const CTemplate& other) = default; CTemplate& operator=(const CTemplate& other) = default; //! Class for implementing custom tags in subclasses void AddTagHandler(std::shared_ptr spTagHandler) { m_vspTagHandlers.push_back(spTagHandler); } std::vector>& GetTagHandlers() { if (m_pParent) { return m_pParent->GetTagHandlers(); } return m_vspTagHandlers; } CString ResolveLiteral(const CString& sString); void Init(); CTemplate* GetParent(bool bRoot); CString ExpandFile(const CString& sFilename, bool bFromInc = false); bool SetFile(const CString& sFileName); void SetPath(const CString& sPath); // Sets the dir:dir:dir type path to // look at for templates, as of right // now no ../../.. protection CString MakePath(const CString& sPath) const; void PrependPath(const CString& sPath, bool bIncludesOnly = false); void AppendPath(const CString& sPath, bool bIncludesOnly = false); void RemovePath(const CString& sPath); void ClearPaths(); bool PrintString(CString& sRet); bool Print(std::ostream& oOut); bool Print(const CString& sFileName, std::ostream& oOut); bool ValidIf(const CString& sArgs); bool ValidExpr(const CString& sExpr); bool IsTrue(const CString& sName); bool HasLoop(const CString& sName); CString GetValue(const CString& sName, bool bFromIf = false); CTemplate& AddRow(const CString& sName); CTemplate* GetRow(const CString& sName, unsigned int uIndex); std::vector* GetLoop(const CString& sName); void DelCurLoopContext(); CTemplateLoopContext* GetCurLoopContext(); CTemplate* GetCurTemplate(); // Getters const CString& GetFileName() const { return m_sFileName; } // !Getters private: CTemplate* m_pParent; CString m_sFileName; std::list> m_lsbPaths; std::map> m_mvLoops; std::vector m_vLoopContexts; std::shared_ptr m_spOptions; std::vector> m_vspTagHandlers; }; #endif // !ZNC_TEMPLATE_H znc-1.9.1/include/znc/Threads.h0000644000175000017500000001156414641222733016533 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_THREADS_H #define ZNC_THREADS_H #include #ifdef HAVE_PTHREAD #include #include #include #include #include #include #include #include #include /** * This class represents a non-recursive mutex. Only a single thread may own the * mutex at any point in time. */ using CMutex = std::mutex; /** * A mutex locker should always be used as an automatic variable. This * class makes sure that the mutex is unlocked when this class is destructed. * For example, this makes it easier to make code exception-safe. */ using CMutexLocker = std::unique_lock; /** * A condition variable makes it possible for threads to wait until some * condition is reached at which point the thread can wake up again. */ using CConditionVariable = std::condition_variable_any; /** * A job is a task which should run without blocking the main thread. You do * this by inheriting from this class and implementing the pure virtual methods * runThread(), which gets executed in a separate thread and does not block the * main thread, and runMain() which gets automatically called from the main * thread after runThread() finishes. * * After you create a new instance of your class, you can pass it to * CThreadPool()::Get().addJob(job) to start it. The thread pool automatically * deletes your class after it finished. * * For modules you should use CModuleJob instead. */ class CJob { public: friend class CThreadPool; enum EJobState { READY, RUNNING, DONE, CANCELLED }; CJob() : m_eState(READY) {} /// Destructor, always called from the main thread. virtual ~CJob() {} /// This function is called in a separate thread and can do heavy, blocking work. virtual void runThread() = 0; /// This function is called from the main thread after runThread() /// finishes. It can be used to handle the results from runThread() /// without needing synchronization primitives. virtual void runMain() = 0; /// This can be used to check if the job was cancelled. For example, /// runThread() can return early if this returns true. bool wasCancelled() const; private: // Undefined copy constructor and assignment operator CJob(const CJob&); CJob& operator=(const CJob&); // Synchronized via the thread pool's mutex! Do not access without that // mutex! EJobState m_eState; }; class CThreadPool { private: friend class CJob; CThreadPool(); ~CThreadPool(); public: static CThreadPool& Get(); /// Add a job to the thread pool and run it. The job will be deleted when done. void addJob(CJob* job); /// Cancel a job that was previously passed to addJob(). This *might* /// mean that runThread() and/or runMain() will not be called on the job. /// This function BLOCKS until the job finishes! void cancelJob(CJob* job); /// Cancel some jobs that were previously passed to addJob(). This *might* /// mean that runThread() and/or runMain() will not be called on some of /// the jobs. This function BLOCKS until all jobs finish! void cancelJobs(const std::set& jobs); int getReadFD() const { return m_iJobPipe[0]; } void handlePipeReadable() const; private: void jobDone(CJob* pJob); // Check if the calling thread is still needed, must be called with m_mutex // held bool threadNeeded() const; CJob* getJobFromPipe() const; void finishJob(CJob*) const; void threadFunc(); // mutex protecting all of these members CMutex m_mutex; // condition variable for waiting idle threads CConditionVariable m_cond; // condition variable for reporting finished cancellation CConditionVariable m_cancellationCond; // condition variable for waiting running threads == 0 CConditionVariable m_exit_cond; // when this is true, all threads should exit bool m_done; // total number of running threads size_t m_num_threads; // number of idle threads waiting on the condition variable size_t m_num_idle; // pipe for waking up the main thread int m_iJobPipe[2]; // list of pending jobs std::list m_jobs; }; #endif // HAVE_PTHREAD #endif // !ZNC_THREADS_H znc-1.9.1/include/znc/Translation.h0000644000175000017500000000735114641222733017436 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_TRANSLATION_H #define ZNC_TRANSLATION_H #include #include #include struct CTranslationInfo { static std::map GetTranslations(); CString sSelfName; }; // All instances of modules share single message map using this class stored in // CZNC. class CTranslation { public: static CTranslation& Get(); CString Singular(const CString& sDomain, const CString& sContext, const CString& sEnglish); CString Plural(const CString& sDomain, const CString& sContext, const CString& sEnglish, const CString& sEnglishes, int iNum); void PushLanguage(const CString& sLanguage); void PopLanguage(); void NewReference(const CString& sDomain); void DelReference(const CString& sDomain); private: // Domain is either "znc" or "znc-foo" where foo is a module name const std::locale& LoadTranslation(const CString& sDomain); std::unordered_map> m_Translations; VCString m_sLanguageStack; std::unordered_map m_miReferences; }; struct CLanguageScope { explicit CLanguageScope(const CString& sLanguage); ~CLanguageScope(); }; struct CTranslationDomainRefHolder { explicit CTranslationDomainRefHolder(const CString& sDomain); ~CTranslationDomainRefHolder(); private: const CString m_sDomain; }; // This is inspired by boost::locale::message, but without boost class CDelayedTranslation { public: CDelayedTranslation() = default; CDelayedTranslation(const CString& sDomain, const CString& sContext, const CString& sEnglish) : m_sDomain(sDomain), m_sContext(sContext), m_sEnglish(sEnglish) {} CString Resolve() const; private: CString m_sDomain; CString m_sContext; CString m_sEnglish; }; class COptionalTranslation { public: COptionalTranslation(const CString& sText) : m_text(sText) {} COptionalTranslation(const char* s) : COptionalTranslation(CString(s)) {} COptionalTranslation(const CDelayedTranslation& dTranslation) : m_text(dTranslation) {} CString Resolve() const { if (m_text.index() == 0) { return std::get<0>(m_text); } return std::get<1>(m_text).Resolve(); } private: std::variant m_text; }; // Used by everything except modules. // CModule defines its own version of these functions. class CCoreTranslationMixin { protected: static CString t_s(const CString& sEnglish, const CString& sContext = ""); static CInlineFormatMessage t_f(const CString& sEnglish, const CString& sContext = ""); static CInlineFormatMessage t_p(const CString& sEnglish, const CString& sEnglishes, int iNum, const CString& sContext = ""); static CDelayedTranslation t_d(const CString& sEnglish, const CString& sContext = ""); }; #endif znc-1.9.1/include/znc/User.h0000644000175000017500000002620114641222733016051 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_USER_H #define ZNC_USER_H #include #include #include #include #include #include #include class CModules; class CChan; class CClient; class CConfig; class CFile; class CIRCNetwork; class CIRCSock; class CUserTimer; class CServer; class CUser : private CCoreTranslationMixin { public: CUser(const CString& sUsername); ~CUser(); CUser(const CUser&) = delete; CUser& operator=(const CUser&) = delete; bool ParseConfig(CConfig* Config, CString& sError); enum eHashType { HASH_NONE, HASH_MD5, HASH_SHA256, HASH_ARGON2ID, // This should be kept in sync with CUtils::SaltedHash #if ZNC_HAVE_ARGON HASH_DEFAULT = HASH_ARGON2ID, #else HASH_DEFAULT = HASH_SHA256, #endif }; static CString SaltedHash(const CString& sPass, const CString& sSalt) { return CUtils::SaltedHash(sPass, sSalt); } CConfig ToConfig() const; /** Checks password, may upgrade the hash method. */ bool CheckPass(const CString& sPass); bool AddAllowedHost(const CString& sHostMask); bool RemAllowedHost(const CString& sHostMask); void ClearAllowedHosts(); bool IsHostAllowed(const CString& sHost) const; bool IsValid(CString& sErrMsg, bool bSkipPass = false) const; static bool IsValidUsername(const CString& sUsername); /** @deprecated Use IsValidUsername() instead. */ static bool IsValidUserName(const CString& sUsername); static CString MakeCleanUsername(const CString& sUsername); /** @deprecated Use MakeCleanUsername() instead. */ static CString MakeCleanUserName(const CString& sUsername); // Modules CModules& GetModules() { return *m_pModules; } const CModules& GetModules() const { return *m_pModules; } // !Modules // Networks CIRCNetwork* AddNetwork(const CString& sNetwork, CString& sErrorRet); bool DeleteNetwork(const CString& sNetwork); bool AddNetwork(CIRCNetwork* pNetwork); void RemoveNetwork(CIRCNetwork* pNetwork); CIRCNetwork* FindNetwork(const CString& sNetwork) const; const std::vector& GetNetworks() const; bool HasSpaceForNewNetwork() const; // !Networks bool PutUser(const CString& sLine, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); bool PutAllUser(const CString& sLine, CClient* pClient = nullptr, CClient* pSkipClient = nullptr) ZNC_MSG_DEPRECATED("Use PutUser() instead") { return PutUser(sLine, pClient, pSkipClient); } bool PutStatus(const CString& sLine, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); bool PutStatusNotice(const CString& sLine, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); bool PutModule(const CString& sModule, const CString& sLine, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); bool PutModNotice(const CString& sModule, const CString& sLine, CClient* pClient = nullptr, CClient* pSkipClient = nullptr); bool IsUserAttached() const; void UserConnected(CClient* pClient); void UserDisconnected(CClient* pClient); CString GetLocalDCCIP() const; CString ExpandString(const CString& sStr) const; CString& ExpandString(const CString& sStr, CString& sRet) const; CString AddTimestamp(const CString& sStr) const; CString AddTimestamp(time_t tm, const CString& sStr) const; CString AddTimestamp(timeval tv, const CString& sStr) const; void CloneNetworks(const CUser& User); bool Clone(const CUser& User, CString& sErrorRet, bool bCloneNetworks = true); void BounceAllClients(); void AddBytesRead(unsigned long long u) { m_uBytesRead += u; } void AddBytesWritten(unsigned long long u) { m_uBytesWritten += u; } // Setters void SetNick(const CString& s); void SetAltNick(const CString& s); void SetIdent(const CString& s); void SetRealName(const CString& s); void SetBindHost(const CString& s); void SetDCCBindHost(const CString& s); void SetPass(const CString& s, eHashType eHash, const CString& sSalt = ""); void SetMultiClients(bool b); void SetDenyLoadMod(bool b); void SetAdmin(bool b); void SetDenySetBindHost(bool b); void SetDenySetIdent(bool b); void SetDenySetNetwork(bool b); void SetDenySetRealName(bool b); void SetDenySetQuitMsg(bool b); void SetDenySetCTCPReplies(bool b); bool SetStatusPrefix(const CString& s); void SetDefaultChanModes(const CString& s); void SetClientEncoding(const CString& s); void SetQuitMsg(const CString& s); bool AddCTCPReply(const CString& sCTCP, const CString& sReply); bool DelCTCPReply(const CString& sCTCP); /** @deprecated Use SetChanBufferSize() or SetQueryBufferSize() instead. */ bool SetBufferCount(unsigned int u, bool bForce = false); bool SetChanBufferSize(unsigned int u, bool bForce = false); bool SetQueryBufferSize(unsigned int u, bool bForce = false); void SetAutoClearChanBuffer(bool b); void SetAutoClearQueryBuffer(bool b); bool SetLanguage(const CString& s); void SetBeingDeleted(bool b) { m_bBeingDeleted = b; } void SetTimestampFormat(const CString& s) { m_sTimestampFormat = s; } void SetTimestampAppend(bool b) { m_bAppendTimestamp = b; } void SetTimestampPrepend(bool b) { m_bPrependTimestamp = b; } void SetAuthOnlyViaModule(bool b) { m_bAuthOnlyViaModule = b; } void SetTimezone(const CString& s) { m_sTimezone = s; } void SetJoinTries(unsigned int i) { m_uMaxJoinTries = i; } void SetMaxJoins(unsigned int i) { m_uMaxJoins = i; } void SetSkinName(const CString& s) { m_sSkinName = s; } void SetMaxNetworks(unsigned int i) { m_uMaxNetworks = i; } void SetMaxQueryBuffers(unsigned int i) { m_uMaxQueryBuffers = i; } void SetNoTrafficTimeout(unsigned int i) { m_uNoTrafficTimeout = i; } // !Setters // Getters const std::vector& GetUserClients() const { return m_vClients; } std::vector GetAllClients() const; /** @deprecated Use GetUsername() instead. */ const CString& GetUserName() const; const CString& GetUsername() const; const CString& GetCleanUserName() const; const CString& GetNick(bool bAllowDefault = true) const; const CString& GetAltNick(bool bAllowDefault = true) const; const CString& GetIdent(bool bAllowDefault = true) const; CString GetRealName() const; const CString& GetBindHost() const; const CString& GetDCCBindHost() const; const CString& GetPass() const; eHashType GetPassHashType() const; const CString& GetPassSalt() const; const std::set& GetAllowedHosts() const; const CString& GetTimestampFormat() const; const CString& GetClientEncoding() const; bool GetTimestampAppend() const; bool GetTimestampPrepend() const; const CString& GetUserPath() const; bool DenyLoadMod() const; bool IsAdmin() const; bool DenySetBindHost() const; bool DenySetIdent() const; bool DenySetNetwork() const; bool DenySetRealName() const; bool DenySetQuitMsg() const; bool DenySetCTCPReplies() const; bool MultiClients() const; bool AuthOnlyViaModule() const; const CString& GetStatusPrefix() const; const CString& GetDefaultChanModes() const; /** How long must an IRC connection be idle before ZNC sends a ping */ unsigned int GetPingFrequency() const { return m_uNoTrafficTimeout / 2; } /** Time between checks if PINGs need to be sent */ unsigned int GetPingSlack() const { return m_uNoTrafficTimeout / 6; } /** Timeout after which IRC connections are closed. Must * obviously be greater than GetPingFrequency() + GetPingSlack(). */ unsigned int GetNoTrafficTimeout() const { return m_uNoTrafficTimeout; } CString GetQuitMsg() const; const MCString& GetCTCPReplies() const; /** @deprecated Use GetChanBufferSize() or GetQueryBufferSize() instead. */ unsigned int GetBufferCount() const; unsigned int GetChanBufferSize() const; unsigned int GetQueryBufferSize() const; bool AutoClearChanBuffer() const; bool AutoClearQueryBuffer() const; bool IsBeingDeleted() const { return m_bBeingDeleted; } CString GetTimezone() const { return m_sTimezone; } unsigned long long BytesRead() const; unsigned long long BytesWritten() const; unsigned int JoinTries() const { return m_uMaxJoinTries; } unsigned int MaxJoins() const { return m_uMaxJoins; } CString GetSkinName() const; CString GetLanguage() const; unsigned int MaxNetworks() const { return m_uMaxNetworks; } unsigned int MaxQueryBuffers() const { return m_uMaxQueryBuffers; } // !Getters protected: const CString m_sUsername; const CString m_sCleanUsername; CString m_sNick; CString m_sAltNick; CString m_sIdent; CString m_sRealName; CString m_sBindHost; CString m_sDCCBindHost; CString m_sPass; CString m_sPassSalt; CString m_sStatusPrefix; CString m_sDefaultChanModes; CString m_sClientEncoding; CString m_sQuitMsg; MCString m_mssCTCPReplies; CString m_sTimestampFormat; CString m_sTimezone; eHashType m_eHashType; // Paths CString m_sUserPath; // !Paths bool m_bMultiClients; bool m_bDenyLoadMod; bool m_bAdmin; bool m_bDenySetBindHost; bool m_bDenySetIdent; bool m_bDenySetNetwork; bool m_bDenySetRealName; bool m_bDenySetQuitMsg; bool m_bDenySetCTCPReplies; bool m_bAutoClearChanBuffer; bool m_bAutoClearQueryBuffer; bool m_bBeingDeleted; bool m_bAppendTimestamp; bool m_bPrependTimestamp; bool m_bAuthOnlyViaModule; CUserTimer* m_pUserTimer; std::vector m_vIRCNetworks; std::vector m_vClients; std::set m_ssAllowedHosts; unsigned int m_uChanBufferSize; unsigned int m_uQueryBufferSize; unsigned long long m_uBytesRead; unsigned long long m_uBytesWritten; unsigned int m_uMaxJoinTries; unsigned int m_uMaxNetworks; unsigned int m_uMaxQueryBuffers; unsigned int m_uMaxJoins; unsigned int m_uNoTrafficTimeout; CString m_sSkinName; CString m_sLanguage; CModules* m_pModules; private: void SetKeepBuffer(bool b) { SetAutoClearChanBuffer(!b); } // XXX compatibility crap, added in 0.207 bool LoadModule(const CString& sModName, const CString& sArgs, const CString& sNotice, CString& sError); }; #endif // !ZNC_USER_H znc-1.9.1/include/znc/Utils.h0000644000175000017500000003205314641222733016235 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_UTILS_H #define ZNC_UTILS_H #include #include #include #include #include #include #include #include #include #include static inline void SetFdCloseOnExec(int fd) { int flags = fcntl(fd, F_GETFD, 0); if (flags < 0) return; // Ignore errors // When we execve() a new process this fd is now automatically closed. fcntl(fd, F_SETFD, flags | FD_CLOEXEC); } static const char g_HexDigits[] = "0123456789abcdef"; class CUtils { public: CUtils(); ~CUtils(); static CString GetIP(unsigned long addr); static unsigned long GetLongIP(const CString& sIP); static void PrintError(const CString& sMessage); static void PrintMessage(const CString& sMessage, bool bStrong = false); static void PrintPrompt(const CString& sMessage); static void PrintAction(const CString& sMessage); static void PrintStatus(bool bSuccess, const CString& sMessage = ""); /** Asks password from stdin, with confirmation. * * @returns Piece of znc.conf with block * */ static CString AskSaltedHashPassForConfig(); static CString GetSalt(); static CString SaltedMD5Hash(const CString& sPass, const CString& sSalt); static CString SaltedSHA256Hash(const CString& sPass, const CString& sSalt); static CString SaltedHash(const CString& sPass, const CString& sSalt); static CString GetPass(const CString& sPrompt); static bool GetInput(const CString& sPrompt, CString& sRet, const CString& sDefault = "", const CString& sHint = ""); static bool GetBoolInput(const CString& sPrompt, bool bDefault); static bool GetBoolInput(const CString& sPrompt, bool* pbDefault = nullptr); static bool GetNumInput(const CString& sPrompt, unsigned int& uRet, unsigned int uMin = 0, unsigned int uMax = ~0, unsigned int uDefault = ~0); static timeval GetTime(); static unsigned long long GetMillTime(); #ifdef HAVE_LIBSSL static void GenerateCert(FILE* pOut, const CString& sHost = ""); #endif /* HAVE_LIBSSL */ static CString CTime(time_t t, const CString& sTZ); static CString FormatTime(time_t t, const CString& sFormat, const CString& sTZ); /** Supports an additional format specifier for formatting sub-second values: * * - %f - sub-second fraction * - %3f - millisecond (default, if no width is specified) * - %6f - microsecond * * However, note that timeval only supports microsecond precision * (thus, formatting with higher-than-microsecond precision will * always result in trailing zeroes), and IRC server-time is specified * in millisecond precision (thus formatting received timestamps with * higher-than-millisecond precision will always result in trailing * zeroes). */ static CString FormatTime(const timeval& tv, const CString& sFormat, const CString& sTZ); static CString FormatServerTime(const timeval& tv); static timeval ParseServerTime(const CString& sTime); static SCString GetTimezones(); static SCString GetEncodings(); /** CIDR notation checker, e.g. "192.0.2.0/24" or "2001:db8::/32" * * For historical reasons also allows wildcards, e.g. "192.168.*" */ static bool CheckCIDR(const CString& sIP, const CString& sRange); /// @deprecated Use CMessage instead static MCString GetMessageTags(const CString& sLine); /// @deprecated Use CMessage instead static void SetMessageTags(CString& sLine, const MCString& mssTags); private: protected: }; class CException { public: typedef enum { EX_Shutdown, EX_Restart } EType; CException(EType e) : m_eType(e) {} virtual ~CException() {} EType GetType() const { return m_eType; } private: protected: EType m_eType; }; /** Generate a grid-like or list-like output from a given input. * * @code * CTable table; * table.AddColumn("a"); * table.AddColumn("b"); * table.AddRow(); * table.SetCell("a", "hello"); * table.SetCell("b", "world"); * * unsigned int idx = 0; * CString tmp; * while (table.GetLine(idx++, tmp)) { * // Output tmp somehow * } * @endcode * * The above code would generate the following output: * @verbatim +-------+-------+ | a | b | +-------+-------+ | hello | world | +-------+-------+@endverbatim * * If the table has at most two columns, one can switch to ListStyle output * like so: * @code * CTable table; * table.AddColumn("a"); * table.AddColumn("b"); * table.SetStyle(CTable::ListStyle); * // ... * @endcode * * This will yield the following (Note that the header is omitted; asterisks * denote bold text): * @verbatim *hello*: world@endverbatim */ class CTable : protected std::vector> { public: enum EStyle { GridStyle, ListStyle }; CTable() {} virtual ~CTable() {} /** Adds a new column to the table. * Please note that you should add all columns before starting to fill * the table! * @param sName The name of the column. * @return false if a column by that name already existed or the current * style does not allow this many columns. */ bool AddColumn(const CString& sName); /** Selects the output style of the table. * Select between different styles for printing. Default is GridStyle. * @param eNewStyle Table style type. * @return false if the style cannot be applied (usually too many columns). */ bool SetStyle(EStyle eNewStyle); /** Adds a new row to the table. * After calling this you can fill the row with content. * @return The index of this row */ size_type AddRow(); /** Sets a given cell in the table to a value. * @param sColumn The name of the column you want to fill. * @param sValue The value to write into that column. * @param uRowIdx The index of the row to use as returned by AddRow(). * If this is not given, the last row will be used. * @return True if setting the cell was successful. */ bool SetCell(const CString& sColumn, const CString& sValue, size_type uRowIdx = ~0); /** Get a line of the table's output * @param uIdx The index of the line you want. * @param sLine This string will receive the output. * @return True unless uIdx is past the end of the table. */ bool GetLine(unsigned int uIdx, CString& sLine) const; /** Return the width of the given column. * Please note that adding and filling new rows might change the * result of this function! * @param uIdx The index of the column you are interested in. * @return The width of the column. */ CString::size_type GetColumnWidth(unsigned int uIdx) const; /// Completely clear the table. void Clear(); /// @return The number of rows in this table, not counting the header. using std::vector>::size; /// @return True if this table doesn't contain any rows. using std::vector>::empty; private: unsigned int GetColumnIndex(const CString& sName) const; protected: std::vector m_vsHeaders; // Used to cache the width of a column std::map m_msuWidths; EStyle eStyle = GridStyle; }; #ifdef HAVE_LIBSSL #include #include #include //! does Blowfish w/64 bit feedback, no padding class CBlowfish { public: /** * @param sPassword key to encrypt with * @param iEncrypt encrypt method (BF_DECRYPT or BF_ENCRYPT) * @param sIvec what to set the ivector to start with, default sets it all 0's */ CBlowfish(const CString& sPassword, int iEncrypt, const CString& sIvec = ""); ~CBlowfish(); CBlowfish(const CBlowfish&) = default; CBlowfish& operator=(const CBlowfish&) = default; //! output must be freed static unsigned char* MD5(const unsigned char* input, unsigned int ilen); //! returns an md5 of the CString (not hex encoded) static CString MD5(const CString& sInput, bool bHexEncode = false); //! output must be the same size as input void Crypt(unsigned char* input, unsigned char* output, unsigned int ibytes); //! must free result unsigned char* Crypt(unsigned char* input, unsigned int ibytes); CString Crypt(const CString& sData); private: unsigned char* m_ivec; BF_KEY m_bkey; int m_iEncrypt, m_num; }; #endif /* HAVE_LIBSSL */ /** * @class TCacheMap * @author prozac * @brief Insert an object with a time-to-live and check later if it still exists */ template class TCacheMap { public: TCacheMap(unsigned int uTTL = 5000) : m_mItems(), m_uTTL(uTTL) {} virtual ~TCacheMap() {} /** * @brief This function adds an item to the cache using the default time-to-live value * @param Item the item to add to the cache */ void AddItem(const K& Item) { AddItem(Item, m_uTTL); } /** * @brief This function adds an item to the cache using a custom time-to-live value * @param Item the item to add to the cache * @param uTTL the time-to-live for this specific item */ void AddItem(const K& Item, unsigned int uTTL) { AddItem(Item, V(), uTTL); } /** * @brief This function adds an item to the cache using the default time-to-live value * @param Item the item to add to the cache * @param Val The value associated with the key Item */ void AddItem(const K& Item, const V& Val) { AddItem(Item, Val, m_uTTL); } /** * @brief This function adds an item to the cache using a custom time-to-live value * @param Item the item to add to the cache * @param Val The value associated with the key Item * @param uTTL the time-to-live for this specific item */ void AddItem(const K& Item, const V& Val, unsigned int uTTL) { if (!uTTL) { // If time-to-live is zero we don't want to waste our time adding // it RemItem(Item); // Remove the item incase it already exists return; } m_mItems[Item] = value(CUtils::GetMillTime() + uTTL, Val); } /** * @brief Performs a Cleanup() and then checks to see if your item exists * @param Item The item to check for * @return true if item exists */ bool HasItem(const K& Item) { Cleanup(); return (m_mItems.find(Item) != m_mItems.end()); } /** * @brief Performs a Cleanup() and returns a pointer to the object, or nullptr * @param Item The item to check for * @return Pointer to the item or nullptr if there is no suitable one */ V* GetItem(const K& Item) { Cleanup(); iterator it = m_mItems.find(Item); if (it == m_mItems.end()) return nullptr; return &it->second.second; } /** * @brief Removes a specific item from the cache * @param Item The item to be removed * @return true if item existed and was removed, false if it never existed */ bool RemItem(const K& Item) { return (m_mItems.erase(Item) != 0); } /** * @brief Cycles through the queue removing all of the stale entries */ void Cleanup() { iterator it = m_mItems.begin(); while (it != m_mItems.end()) { if (CUtils::GetMillTime() > (it->second.first)) { m_mItems.erase(it++); } else { ++it; } } } /** * @brief Clear all entries */ void Clear() { m_mItems.clear(); } /** * @brief Returns all entries */ std::map GetItems() { Cleanup(); std::map mItems; for (const auto& it : m_mItems) { mItems[it.first] = it.second.second; } return mItems; } // Setters void SetTTL(unsigned int u) { m_uTTL = u; } // !Setters // Getters unsigned int GetTTL() const { return m_uTTL; } // !Getters protected: typedef std::pair value; typedef typename std::map::iterator iterator; std::map m_mItems; //!< Map of cached items. The value portion of the map is for the expire time unsigned int m_uTTL; //!< Default time-to-live duration }; #endif // !ZNC_UTILS_H znc-1.9.1/include/znc/WebModules.h0000644000175000017500000001335614641222733017210 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_WEBMODULES_H #define ZNC_WEBMODULES_H #include #include #include #include #include class CAuthBase; class CUser; class CWebSock; class CModule; class CWebSubPage; typedef std::shared_ptr TWebSubPage; typedef std::vector VWebSubPages; class CZNCTagHandler : public CTemplateTagHandler { public: CZNCTagHandler(CWebSock& pWebSock); virtual ~CZNCTagHandler() {} bool HandleTag(CTemplate& Tmpl, const CString& sName, const CString& sArgs, CString& sOutput) override; private: CWebSock& m_WebSock; }; class CWebSession { public: CWebSession(const CString& sId, const CString& sIP); ~CWebSession(); CWebSession(const CWebSession&) = delete; CWebSession& operator=(const CWebSession&) = delete; const CString& GetId() const { return m_sId; } const CString& GetIP() const { return m_sIP; } CUser* GetUser() const { return m_pUser; } time_t GetLastActive() const { return m_tmLastActive; } bool IsLoggedIn() const { return m_pUser != nullptr; } bool IsAdmin() const; void UpdateLastActive(); CUser* SetUser(CUser* p) { m_pUser = p; return m_pUser; } void ClearMessageLoops(); void FillMessageLoops(CTemplate& Tmpl); size_t AddError(const CString& sMessage); size_t AddSuccess(const CString& sMessage); private: CString m_sId; CString m_sIP; CUser* m_pUser; VCString m_vsErrorMsgs; VCString m_vsSuccessMsgs; time_t m_tmLastActive; }; class CWebSubPage { public: CWebSubPage(const CString& sName, const CString& sTitle = "", unsigned int uFlags = 0) : m_uFlags(uFlags), m_sName(sName), m_Title(sTitle), m_vParams() {} CWebSubPage(const CString& sName, const COptionalTranslation& Title, const VPair& vParams, unsigned int uFlags = 0) : m_uFlags(uFlags), m_sName(sName), m_Title(Title), m_vParams(vParams) {} virtual ~CWebSubPage() {} enum { F_ADMIN = 1 }; void SetName(const CString& s) { m_sName = s; } void SetTitle(const COptionalTranslation& s) { m_Title = s; } void AddParam(const CString& sName, const CString& sValue) { m_vParams.push_back(make_pair(sName, sValue)); } bool RequiresAdmin() const { return m_uFlags & F_ADMIN; } const CString& GetName() const { return m_sName; } CString GetTitle() const { return m_Title.Resolve(); } const VPair& GetParams() const { return m_vParams; } private: unsigned int m_uFlags; CString m_sName; COptionalTranslation m_Title; VPair m_vParams; }; class CWebSessionMap : public TCacheMap> { public: CWebSessionMap(unsigned int uTTL = 5000) : TCacheMap>(uTTL) {} void FinishUserSessions(const CUser& User); }; class CWebSock : public CHTTPSock { public: enum EPageReqResult { PAGE_NOTFOUND, // print 404 and Close() PAGE_PRINT, // print page contents and Close() PAGE_DEFERRED, // async processing, Close() will be called from a // different place PAGE_DONE // all stuff has been done }; CWebSock(const CString& sURIPrefix); virtual ~CWebSock(); bool ForceLogin() override; bool OnLogin(const CString& sUser, const CString& sPass, bool bBasic) override; void OnPageRequest(const CString& sURI) override; EPageReqResult PrintTemplate(const CString& sPageName, CString& sPageRet, CModule* pModule = nullptr); EPageReqResult PrintStaticFile(const CString& sPath, CString& sPageRet, CModule* pModule = nullptr); CString FindTmpl(CModule* pModule, const CString& sName); void PrintErrorPage(const CString& sMessage); std::shared_ptr GetSession(); Csock* GetSockObj(const CString& sHost, unsigned short uPort) override; static CString GetSkinPath(const CString& sSkinName); void GetAvailSkins(VCString& vRet) const; CString GetSkinName(); CString GetRequestCookie(const CString& sKey); bool SendCookie(const CString& sKey, const CString& sValue); static void FinishUserSessions(const CUser& User); CString GetCSRFCheck(); bool ValidateCSRFCheck(const CString& sURI); protected: using CHTTPSock::PrintErrorPage; bool AddModLoop(const CString& sLoopName, CModule& Module, CTemplate* pTemplate = nullptr); VCString GetDirs(CModule* pModule, bool bIsTemplate); void SetPaths(CModule* pModule, bool bIsTemplate = false); void SetVars(); private: EPageReqResult OnPageRequestInternal(const CString& sURI, CString& sPageRet); bool m_bPathsSet; CTemplate m_Template; std::shared_ptr m_spAuth; CString m_sModName; CString m_sPath; CString m_sPage; std::shared_ptr m_spSession; static const unsigned int m_uiMaxSessions; }; #endif // !ZNC_WEBMODULES_H znc-1.9.1/include/znc/ZNCDebug.h0000644000175000017500000000341714641222733016540 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNCDEBUG_H #define ZNCDEBUG_H #include #include #include /** Output a debug info if debugging is enabled. * If ZNC was compiled with --enable-debug or was started with * --debug, the given argument will be sent to stdout. * * You can use all the features of C++ streams: * @code * DEBUG("I had " << errors << " errors"); * @endcode * * @param f The expression you want to display. */ #define DEBUG(f) \ do { \ if (CDebug::Debug()) { \ CDebugStream sDebug; \ sDebug << f; \ } \ } while (0) class CDebug { public: static void SetStdoutIsTTY(bool b) { stdoutIsTTY = b; } static bool StdoutIsTTY() { return stdoutIsTTY; } static void SetDebug(bool b) { debug = b; } static bool Debug() { return debug; } static CString Filter(const CString& sUnfilteredLine); protected: static bool stdoutIsTTY; static bool debug; }; class CDebugStream : public std::ostringstream { public: ~CDebugStream(); }; #endif // !ZNCDEBUG_H znc-1.9.1/include/znc/ZNCString.h0000644000175000017500000007145514641222733016767 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNCSTRING_H #define ZNCSTRING_H #include #include #include #include #include #include #include #include #define _SQL(s) CString("'" + CString(s).Escape_n(CString::ESQL) + "'") #define _URL(s) CString(s).Escape_n(CString::EURL) #define _HTML(s) CString(s).Escape_n(CString::EHTML) #define _NAMEDFMT(s) CString(s).Escape_n(CString::ENAMEDFMT) class CString; class MCString; typedef std::set SCString; typedef std::vector VCString; typedef std::vector> VPair; static const unsigned char XX = 0xff; static const unsigned char base64_table[256] = { XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, 62, XX, XX, XX, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, XX, XX, XX, XX, XX, XX, XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, XX, XX, XX, XX, XX, XX, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, }; enum class CaseSensitivity { CaseInsensitive, CaseSensitive }; /** * @brief String class that is used inside ZNC. * * All strings that are used in ZNC and its modules should use instances of this * class. It provides helpful functions for parsing input like Token() and * Split(). */ class CString : public std::string { public: typedef enum { EASCII, EURL, EHTML, ESQL, ENAMEDFMT, EDEBUG, EMSGTAG, EHEXCOLON, } EEscape; static const CaseSensitivity CaseSensitive = CaseSensitivity::CaseSensitive; static const CaseSensitivity CaseInsensitive = CaseSensitivity::CaseInsensitive; explicit CString(bool b) : std::string(b ? "true" : "false") {} explicit CString(char c); explicit CString(unsigned char c); explicit CString(short i); explicit CString(unsigned short i); explicit CString(int i); explicit CString(unsigned int i); explicit CString(long i); explicit CString(unsigned long i); explicit CString(long long i); explicit CString(unsigned long long i); explicit CString(double i, int precision = 2); explicit CString(float i, int precision = 2); CString() : std::string() {} CString(const char* c) : std::string(c) {} CString(const char* c, size_t l) : std::string(c, l) {} CString(const std::string& s) : std::string(s) {} CString(size_t n, char c) : std::string(n, c) {} CString(std::initializer_list list) : std::string(list) {} ~CString() {} /** * Casts a CString to another type. Implemented via std::stringstream, you use this * for any class that has an operator<<(std::ostream, YourClass). * @param target The object to cast into. If the cast fails, its state is unspecified. * @return True if the cast succeeds, and false if it fails. */ template bool Convert(T* target) const { std::stringstream ss(*this); ss >> *target; return (bool)ss; // we don't care why it failed, only whether it failed } /** * Joins a collection of objects together, using 'this' as a delimiter. * You can pass either pointers to arrays, or iterators to collections. * @param i_begin An iterator pointing to the beginning of a group of objects. * @param i_end An iterator pointing past the end of a group of objects. * @return The joined string */ template CString Join(Iterator i_start, const Iterator& i_end) const { if (i_start == i_end) return CString(""); std::ostringstream output; output << *i_start; while (true) { ++i_start; if (i_start == i_end) return CString(output.str()); output << *this; output << *i_start; } } /** * Compare this string caselessly to some other string. * @param s The string to compare to. * @param uLen The number of characters to compare. * @return An integer less than, equal to, or greater than zero if this * string smaller, equal.... to the given string. */ int CaseCmp(const CString& s, CString::size_type uLen = CString::npos) const; /** * Compare this string case sensitively to some other string. * @param s The string to compare to. * @param uLen The number of characters to compare. * @return An integer less than, equal to, or greater than zero if this * string smaller, equal.... to the given string. */ int StrCmp(const CString& s, CString::size_type uLen = CString::npos) const; /** * Check if this string is equal to some other string. * @param s The string to compare to. * @param cs CaseSensitive if you want the comparison to be case * sensitive, CaseInsensitive (default) otherwise. * @return True if the strings are equal. */ bool Equals(const CString& s, CaseSensitivity cs = CaseInsensitive) const; /** * @deprecated */ bool Equals(const CString& s, bool bCaseSensitive, CString::size_type uLen = CString::npos) const; /** * Do a wildcard comparison between two strings. * For example, the following returns true: * WildCmp("*!?bar@foo", "I_am!~bar@foo"); * @param sWild The wildcards used for the comparison. * @param sString The string that is used for comparing. * @param cs CaseSensitive (default) if you want the comparison * to be case sensitive, CaseInsensitive otherwise. * @todo Make cs CaseInsensitive by default. * @return true if the wildcard matches. */ static bool WildCmp(const CString& sWild, const CString& sString, CaseSensitivity cs = CaseSensitive); /** * Do a wild compare on this string. * @param sWild The wildcards used to for the comparison. * @param cs CaseSensitive (default) if you want the comparison * to be case sensitive, CaseInsensitive otherwise. * @todo Make cs CaseInsensitive by default. * @return The result of this->WildCmp(sWild, *this);. */ bool WildCmp(const CString& sWild, CaseSensitivity cs = CaseSensitive) const; /** * Turn all characters in this string into their upper-case equivalent. * @returns A reference to *this. */ CString& MakeUpper(); /** * Turn all characters in this string into their lower-case equivalent. * @returns A reference to *this. */ CString& MakeLower(); /** * Return a copy of this string with all characters turned into * upper-case. * @return The new string. */ CString AsUpper() const; /** * Return a copy of this string with all characters turned into * lower-case. * @return The new string. */ CString AsLower() const; static EEscape ToEscape(const CString& sEsc); CString Escape_n(EEscape eFrom, EEscape eTo) const; CString Escape_n(EEscape eTo) const; CString& Escape(EEscape eFrom, EEscape eTo); CString& Escape(EEscape eTo); /** Replace all occurrences in a string. * * You can specify a "safe zone" via sLeft and sRight. Anything inside * of such a zone will not be replaced. This does not do recursion, so * e.g. with Replace("(a()a)", "a", "b", "(", ")", true) * you would get "a(b)" as result. The second opening brace and the * second closing brace would not be seen as a delimitered and thus * wouldn't be removed. The first a is inside a "safe zone" and thus is * left alone, too. * * @param sStr The string to do the replacing on. This will also contain * the result when this function returns. * @param sReplace The string that should be replaced. * @param sWith The replacement to use. * @param sLeft The string that marks the begin of the "safe zone". * @param sRight The string that marks the end of the "safe zone". * @param bRemoveDelims If this is true, all matches for sLeft and * sRight are removed. * @returns The number of replacements done. */ static unsigned int Replace(CString& sStr, const CString& sReplace, const CString& sWith, const CString& sLeft = "", const CString& sRight = "", bool bRemoveDelims = false); /** Replace all occurrences in the current string. * @see CString::Replace * @param sReplace The string to look for. * @param sWith The replacement to use. * @param sLeft The delimiter at the beginning of a safe zone. * @param sRight The delimiter at the end of a safe zone. * @param bRemoveDelims If true, all matching delimiters are removed. * @return The result of the replacing. The current string is left * unchanged. */ CString Replace_n(const CString& sReplace, const CString& sWith, const CString& sLeft = "", const CString& sRight = "", bool bRemoveDelims = false) const; /** Replace all occurrences in the current string. * @see CString::Replace * @param sReplace The string to look for. * @param sWith The replacement to use. * @param sLeft The delimiter at the beginning of a safe zone. * @param sRight The delimiter at the end of a safe zone. * @param bRemoveDelims If true, all matching delimiters are removed. * @returns The number of replacements done. */ unsigned int Replace(const CString& sReplace, const CString& sWith, const CString& sLeft = "", const CString& sRight = "", bool bRemoveDelims = false); /** Ellipsize the current string. * For example, ellipsizing "Hello, I'm Bob" to the length 9 would * result in "Hello,...". * @param uLen The length to ellipsize to. * @return The ellipsized string. */ CString Ellipsize(unsigned int uLen) const; /** Return the left part of the string. * @param uCount The number of characters to keep. * @return The resulting string. */ CString Left(size_type uCount) const; /** Return the right part of the string. * @param uCount The number of characters to keep. * @return The resulting string. */ CString Right(size_type uCount) const; /** Get the first line of this string. * @return The first line of text. */ CString FirstLine() const { return Token(0, false, "\n"); } /** Get a token out of this string. For example in the string "a bc d e", * each of "a", "bc", "d" and "e" are tokens. * @param uPos The number of the token you are interested. The first * token has a position of 0. * @param bRest If false, only the token you asked for is returned. Else * you get the substring starting from the beginning of * your token. * @param sSep Seperator between tokens. * @param bAllowEmpty If this is true, empty tokens are allowed. In the * example from above this means that there is a * token "" before the "e" token. * @return The token you asked for and, if bRest is true, everything * after it. * @see Split() if you need a string split into all of its tokens. */ CString Token(size_t uPos, bool bRest = false, const CString& sSep = " ", bool bAllowEmpty = false) const; /** Get a token out of this string. This function behaves much like the * other Token() function in this class. The extra arguments are * handled similarly to Split(). */ CString Token(size_t uPos, bool bRest, const CString& sSep, bool bAllowEmpty, const CString& sLeft, const CString& sRight, bool bTrimQuotes = true) const; size_type URLSplit(MCString& msRet) const; size_type OptionSplit(MCString& msRet, bool bUpperKeys = false) const; size_type QuoteSplit(VCString& vsRet) const; /** Split up this string into tokens. * Via sLeft and sRight you can define "markers" like with Replace(). * Anything in such a marked section is treated as a single token. All * occurrences of sDelim in such a block are ignored. * @param sDelim Delimiter between tokens. * @param vsRet Vector for returning the result. * @param bAllowEmpty Do empty tokens count as a valid token? * @param sLeft Left delimiter like with Replace(). * @param sRight Right delimiter like with Replace(). * @param bTrimQuotes Should sLeft and sRight be removed from the token * they mark? * @param bTrimWhiteSpace If this is true, CString::Trim() is called on * each token. * @return The number of tokens found. */ size_type Split(const CString& sDelim, VCString& vsRet, bool bAllowEmpty = true, const CString& sLeft = "", const CString& sRight = "", bool bTrimQuotes = true, bool bTrimWhiteSpace = false) const; /** Split up this string into tokens. * This function is identical to the other CString::Split(), except that * the result is returned as a SCString instead of a VCString. */ size_type Split(const CString& sDelim, SCString& ssRet, bool bAllowEmpty = true, const CString& sLeft = "", const CString& sRight = "", bool bTrimQuotes = true, bool bTrimWhiteSpace = false) const; /** Build a string from a format string, replacing values from a map. * The format specification can contain simple named parameters that match * keys in the given map. For example in the string "a {b} c", the key "b" * is looked up in the map, and inserted for "{b}". * @param sFormat The format specification. * @param msValues A map of named parameters to their values. * @return The string with named parameters replaced. */ static CString NamedFormat(const CString& sFormat, const MCString& msValues); /** Produces a random string. * @param uLength The length of the resulting string. * @return A random string. */ static CString RandomString(unsigned int uLength); /** @return The MD5 hash of this string. */ CString MD5() const; /** @return The SHA256 hash of this string. */ CString SHA256() const; /** Treat this string as base64-encoded data and decode it. * @param sRet String to which the result of the decode is safed. * @return The length of the resulting string. */ unsigned long Base64Decode(CString& sRet) const; /** Treat this string as base64-encoded data and decode it. * The result is saved in this CString instance. * @return The length of the resulting string. */ unsigned long Base64Decode(); /** Treat this string as base64-encoded data and decode it. * @return The decoded string. */ CString Base64Decode_n() const; /** Base64-encode the current string. * @param sRet String where the result is saved. * @param uWrap A boolean(!?!) that decides if the result should be * wrapped after everywhere 57 characters. * @return true unless this code is buggy. * @todo WTF @ uWrap. * @todo This only returns false if some formula we use was wrong?! */ bool Base64Encode(CString& sRet, unsigned int uWrap = 0) const; /** Base64-encode the current string. * This string is overwritten with the result of the encode. * @todo return value and param are as with Base64Encode() from above. */ bool Base64Encode(unsigned int uWrap = 0); /** Base64-encode the current string * @todo uWrap is as broken as Base64Encode()'s uWrap. * @return The encoded string. */ CString Base64Encode_n(unsigned int uWrap = 0) const; #ifdef HAVE_LIBSSL CString Encrypt_n(const CString& sPass, const CString& sIvec = "") const; CString Decrypt_n(const CString& sPass, const CString& sIvec = "") const; void Encrypt(const CString& sPass, const CString& sIvec = ""); void Decrypt(const CString& sPass, const CString& sIvec = ""); void Crypt(const CString& sPass, bool bEncrypt, const CString& sIvec = ""); #endif /** Pretty-print a percent value. * @param d The percent value. This should be in range 0-100. * @return The "pretty" string. */ static CString ToPercent(double d); /** Pretty-print a number of bytes. * @param d The number of bytes. * @return A string describing the number of bytes. */ static CString ToByteStr(unsigned long long d); /** Pretty-print a time span. * @param s Number of seconds to print. * @return A string like "4w 6d 4h 3m 58s". */ static CString ToTimeStr(unsigned long s); /** @return True if this string is not "false". */ bool ToBool() const; /** @return The numerical value of this string similar to atoi(). */ short ToShort() const; /** @return The numerical value of this string similar to atoi(). */ unsigned short ToUShort() const; /** @return The numerical value of this string similar to atoi(). */ int ToInt() const; /** @return The numerical value of this string similar to atoi(). */ long ToLong() const; /** @return The numerical value of this string similar to atoi(). */ unsigned int ToUInt() const; /** @return The numerical value of this string similar to atoi(). */ unsigned long ToULong() const; /** @return The numerical value of this string similar to atoi(). */ unsigned long long ToULongLong() const; /** @return The numerical value of this string similar to atoi(). */ long long ToLongLong() const; /** @return The numerical value of this string similar to atoi(). */ double ToDouble() const; /** Trim this string. All leading/trailing occurrences of characters from * s are removed. * @param s A list of characters that should be trimmed. * @return true if this string was modified. */ bool Trim(const CString& s = " \t\r\n"); /** Trim this string. All leading occurrences of characters from s are * removed. * @param s A list of characters that should be trimmed. * @return true if this string was modified. */ bool TrimLeft(const CString& s = " \t\r\n"); /** Trim this string. All trailing occurrences of characters from s are * removed. * @param s A list of characters that should be trimmed. * @return true if this string was modified. */ bool TrimRight(const CString& s = " \t\r\n"); /** Trim this string. All leading/trailing occurrences of characters from * s are removed. This CString instance is not modified. * @param s A list of characters that should be trimmed. * @return The trimmed string. */ CString Trim_n(const CString& s = " \t\r\n") const; /** Trim this string. All leading occurrences of characters from s are * removed. This CString instance is not modified. * @param s A list of characters that should be trimmed. * @return The trimmed string. */ CString TrimLeft_n(const CString& s = " \t\r\n") const; /** Trim this string. All trailing occurrences of characters from s are * removed. This CString instance is not modified. * @param s A list of characters that should be trimmed. * @return The trimmed string. */ CString TrimRight_n(const CString& s = " \t\r\n") const; /** Trim a given prefix. * @param sPrefix The prefix that should be removed. * @return True if this string was modified. */ bool TrimPrefix(const CString& sPrefix = ":"); /** Trim a given suffix. * @param sSuffix The suffix that should be removed. * @return True if this string was modified. */ bool TrimSuffix(const CString& sSuffix); /** Trim a given prefix. * @param sPrefix The prefix that should be removed. * @return A copy of this string without the prefix. */ CString TrimPrefix_n(const CString& sPrefix = ":") const; /** Trim a given suffix. * @param sSuffix The suffix that should be removed. * @return A copy of this string without the prefix. */ CString TrimSuffix_n(const CString& sSuffix) const; /** Find the position of the given substring. * @param s The substring to search for. * @param cs CaseSensitive if you want the comparison to be case * sensitive, CaseInsensitive (default) otherwise. * @return The position of the substring if found, CString::npos otherwise. */ size_t Find(const CString& s, CaseSensitivity cs = CaseInsensitive) const; /** Check whether the string starts with a given prefix. * @param sPrefix The prefix. * @param cs CaseSensitive if you want the comparison to be case * sensitive, CaseInsensitive (default) otherwise. * @return True if the string starts with prefix, false otherwise. */ bool StartsWith(const CString& sPrefix, CaseSensitivity cs = CaseInsensitive) const; /** Check whether the string ends with a given suffix. * @param sSuffix The suffix. * @param cs CaseSensitive if you want the comparison to be case * sensitive, CaseInsensitive (default) otherwise. * @return True if the string ends with suffix, false otherwise. */ bool EndsWith(const CString& sSuffix, CaseSensitivity cs = CaseInsensitive) const; /** * Check whether the string contains a given string. * @param s The string to search. * @param bCaseSensitive Whether the search is case sensitive. * @return True if this string contains the other string, falser otherwise. */ bool Contains(const CString& s, CaseSensitivity cs = CaseInsensitive) const; /** Remove characters from the beginning of this string. * @param uLen The number of characters to remove. * @return true if this string was modified. */ bool LeftChomp(size_type uLen = 1); /** Remove characters from the end of this string. * @param uLen The number of characters to remove. * @return true if this string was modified. */ bool RightChomp(size_type uLen = 1); /** Remove characters from the beginning of this string. * This string object isn't modified. * @param uLen The number of characters to remove. * @return The result of the conversion. */ CString LeftChomp_n(size_type uLen = 1) const; /** Remove characters from the end of this string. * This string object isn't modified. * @param uLen The number of characters to remove. * @return The result of the conversion. */ CString RightChomp_n(size_type uLen = 1) const; /** Remove controls characters from this string. * Controls characters are color codes, and those in C0 set * See https://en.wikipedia.org/wiki/C0_and_C1_control_codes * @return The result of the conversion. */ CString& StripControls(); /** Remove controls characters from this string. * Controls characters are color codes, and those in C0 set * See https://en.wikipedia.org/wiki/C0_and_C1_control_codes * This string object isn't modified. * @return The result of the conversion. */ CString StripControls_n() const; private: protected: unsigned char* strnchr(const unsigned char* src, unsigned char c, unsigned int iMaxBytes, unsigned char* pFill = nullptr, unsigned int* piCount = nullptr) const; }; /** * @brief A dictionary for strings. * @todo Replace with "using MCString = std::map;" in ZNC 2.0 * * This class maps strings to other strings. */ class MCString : public std::map { public: /** Construct an empty MCString. */ MCString() : std::map() {} /** Construct a MCString using an initializer list eg. MCString m = { {"key1", "val1"}, {"key2", "val2"} }; */ MCString(std::initializer_list> list) : std::map(list) {} /** Destruct this MCString. */ virtual ~MCString() { clear(); } /** A static instance of an empty map. */ static const MCString EmptyMap; /** Status codes that can be returned by WriteToDisk() and * ReadFromDisk(). */ enum status_t { /// No errors. MCS_SUCCESS = 0, /// Opening the file failed. MCS_EOPEN = 1, /// Writing to the file failed. MCS_EWRITE = 2, /// WriteFilter() failed. MCS_EWRITEFIL = 3, /// ReadFilter() failed. MCS_EREADFIL = 4 }; /** Write this map to a file. * @param sPath The file name to write to. * @param iMode The mode for the file. * @return The result of the operation. * @see WriteFilter. */ enum status_t WriteToDisk(const CString& sPath, mode_t iMode = 0644) const; /** Read a map from a file. * @param sPath The file name to read from. * @return The result of the operation. * @see ReadFilter. */ enum status_t ReadFromDisk(const CString& sPath); /** Filter used while writing this map. This function is called by * WriteToDisk() for each pair that is going to be written. This * function has the chance to modify the data that will be written. * @param sKey The key that will be written. Can be modified. * @param sValue The value that will be written. Can be modified. * @return true unless WriteToDisk() should fail with MCS_EWRITEFIL. */ virtual bool WriteFilter(CString& sKey, CString& sValue) const { return true; } /** Filter used while reading this map. This function is called by * ReadFromDisk() for each pair that is beging read. This function has * the chance to modify the data that is being read. * @param sKey The key that was read. Can be modified. * @param sValue The value that was read. Can be modified. * @return true unless ReadFromDisk() should fail with MCS_EWRITEFIL. */ virtual bool ReadFilter(CString& sKey, CString& sValue) const { return true; } /** Encode a value so that it can safely be parsed by ReadFromDisk(). * This is an internal function. */ virtual CString& Encode(CString& sValue) const; /** Undo the effects of Encode(). This is an internal function. */ virtual CString& Decode(CString& sValue) const; }; namespace std { template <> struct hash : hash {}; } // Make translateable messages easy to write: // t_f("Foo is {1}")(foo) class CInlineFormatMessage { public: explicit CInlineFormatMessage(const CString& sFormat) : m_sFormat(sFormat) {} explicit CInlineFormatMessage(CString&& sFormat) : m_sFormat(std::move(sFormat)) {} template CString operator()(const Args&... args) const { MCString values; apply(values, 1, args...); return CString::NamedFormat(m_sFormat, values); } private: template void apply(MCString& values, int index, const Arg& arg, const Rest&... rest) const { values[CString(index)] = CString(arg); apply(values, index + 1, rest...); } void apply(MCString& values, int index) const {} CString m_sFormat; }; // For gtest #ifdef GTEST_FAIL inline void PrintTo(const CString& s, std::ostream* os) { *os << '"' << s.Escape_n(CString::EDEBUG) << '"'; } #endif #endif // !ZNCSTRING_H znc-1.9.1/include/znc/defines.h0000644000175000017500000000227214641222733016552 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_DEFINES_H #define ZNC_DEFINES_H #include // This header file is just for Csocket #include #include #define CS_STRING CString #define _NO_CSOCKET_NS #ifdef _DEBUG #define __DEBUG__ #endif // Redefine some Csocket debugging mechanisms to use ZNC's #define CS_DEBUG(f) DEBUG(__FILE__ << ":" << __LINE__ << " " << f) #define PERROR(f) \ DEBUG(__FILE__ << ":" << __LINE__ << " " << f << ": " \ << strerror(GetSockError())) #endif // !ZNC_DEFINES_H znc-1.9.1/include/znc/main.h0000644000175000017500000002136114641222733016061 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_MAIN_H #define ZNC_MAIN_H #include #include extern bool ZNC_NO_NEED_TO_DO_ANYTHING_ON_MODULE_CALL_EXITER; #define NOTHING &ZNC_NO_NEED_TO_DO_ANYTHING_ON_MODULE_CALL_EXITER #define ALLMODULECALL(macFUNC, macEXITER) \ do { \ CModules& GMods = CZNC::Get().GetModules(); \ bool bAllExit = false; \ if (GMods.macFUNC) { \ bAllExit = true; \ } else { \ const map& mUsers = CZNC::Get().GetUserMap(); \ map::const_iterator it; \ for (it = mUsers.begin(); it != mUsers.end(); ++it) { \ CModules& UMods = it->second->GetModules(); \ if (UMods.macFUNC) { \ bAllExit = true; \ break; \ } \ const vector& mNets = it->second->GetNetworks(); \ vector::const_iterator it2; \ for (it2 = mNets.begin(); it2 != mNets.end(); ++it2) { \ CModules& NMods = (*it2)->GetModules(); \ if (NMods.macFUNC) { \ bAllExit = true; \ break; \ } \ } \ if (bAllExit) break; \ } \ } \ if (bAllExit) *macEXITER = true; \ } while (false) #define _GLOBALMODULECALL(macFUNC, macUSER, macNETWORK, macCLIENT, macEXITER) \ do { \ CModules& GMods = CZNC::Get().GetModules(); \ CUser* pOldGUser = GMods.GetUser(); \ CIRCNetwork* pOldGNetwork = GMods.GetNetwork(); \ CClient* pOldGClient = GMods.GetClient(); \ GMods.SetUser(macUSER); \ GMods.SetNetwork(macNETWORK); \ GMods.SetClient(macCLIENT); \ if (GMods.macFUNC) { \ GMods.SetUser(pOldGUser); \ GMods.SetNetwork(pOldGNetwork); \ GMods.SetClient(pOldGClient); \ *macEXITER = true; \ } \ GMods.SetUser(pOldGUser); \ GMods.SetNetwork(pOldGNetwork); \ GMods.SetClient(pOldGClient); \ } while (false) #define _USERMODULECALL(macFUNC, macUSER, macNETWORK, macCLIENT, macEXITER) \ do { \ bool bGlobalExited = false; \ _GLOBALMODULECALL(macFUNC, macUSER, macNETWORK, macCLIENT, \ &bGlobalExited); \ if (bGlobalExited) { \ *macEXITER = true; \ break; \ } \ if (macUSER != nullptr) { \ CModules& UMods = macUSER->GetModules(); \ CIRCNetwork* pOldUNetwork = UMods.GetNetwork(); \ CClient* pOldUClient = UMods.GetClient(); \ UMods.SetNetwork(macNETWORK); \ UMods.SetClient(macCLIENT); \ if (UMods.macFUNC) { \ UMods.SetNetwork(pOldUNetwork); \ UMods.SetClient(pOldUClient); \ *macEXITER = true; \ } \ UMods.SetNetwork(pOldUNetwork); \ UMods.SetClient(pOldUClient); \ } \ } while (false) #define NETWORKMODULECALL(macFUNC, macUSER, macNETWORK, macCLIENT, macEXITER) \ do { \ bool bUserExited = false; \ _USERMODULECALL(macFUNC, macUSER, macNETWORK, macCLIENT, \ &bUserExited); \ if (bUserExited) { \ *macEXITER = true; \ break; \ } \ if (macNETWORK != nullptr) { \ CModules& NMods = macNETWORK->GetModules(); \ CClient* pOldNClient = NMods.GetClient(); \ NMods.SetClient(macCLIENT); \ if (NMods.macFUNC) { \ NMods.SetClient(pOldNClient); \ *macEXITER = true; \ } \ NMods.SetClient(pOldNClient); \ } \ } while (false) #define GLOBALMODULECALL(macFUNC, macEXITER) \ _GLOBALMODULECALL(macFUNC, nullptr, nullptr, nullptr, macEXITER) #define USERMODULECALL(macFUNC, macUSER, macCLIENT, macEXITER) \ _USERMODULECALL(macFUNC, macUSER, nullptr, macCLIENT, macEXITER) /** @mainpage * Welcome to the API documentation for ZNC. * * To write your own module, you should start with writing a new class which * inherits from CModule. Use #MODCONSTRUCTOR for the module's constructor and * call #MODULEDEFS at the end of your source file. * Congratulations, you just wrote your first module.
* For global modules, the procedure is similar. Instead of #MODULEDEFS call * #GLOBALMODULEDEFS. * * If you want your module to actually do something, you should override some * of the hooks from CModule. These are the functions whose names start with * "On". They are called when the associated event happens. * * Feel free to also look at existing modules. */ #endif // !ZNC_MAIN_H znc-1.9.1/include/znc/version.h0000644000175000017500000000356714641222733016632 0ustar somebodysomebody/* Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #ifndef ZNC_VERSION_H #define ZNC_VERSION_H // Don't use this one #define VERSION (VERSION_MAJOR + VERSION_MINOR / 10.0) // CMake: You can add -DVERSION_EXTRA=stuff to cmake! #ifndef VERSION_EXTRA #define VERSION_EXTRA "" #endif extern const char* ZNC_VERSION_EXTRA; // Compilation options which affect ABI #ifdef HAVE_IPV6 #define ZNC_VERSION_TEXT_IPV6 "yes" #else #define ZNC_VERSION_TEXT_IPV6 "no" #endif #ifdef HAVE_LIBSSL #define ZNC_VERSION_TEXT_SSL "yes" #else #define ZNC_VERSION_TEXT_SSL "no" #endif #ifdef HAVE_THREADED_DNS #define ZNC_VERSION_TEXT_DNS "threads" #else #define ZNC_VERSION_TEXT_DNS "blocking" #endif #ifdef HAVE_ICU #define ZNC_VERSION_TEXT_ICU "yes" #else #define ZNC_VERSION_TEXT_ICU "no" #endif #ifdef HAVE_I18N #define ZNC_VERSION_TEXT_I18N "yes" #else #define ZNC_VERSION_TEXT_I18N "no" #endif // This is only here because HASH_DEFAULT has different value #ifdef ZNC_HAVE_ARGON #define ZNC_VERSION_TEXT_ARGON "yes" #else #define ZNC_VERSION_TEXT_ARGON "no" #endif #define ZNC_COMPILE_OPTIONS_STRING \ "IPv6: " ZNC_VERSION_TEXT_IPV6 ", SSL: " ZNC_VERSION_TEXT_SSL \ ", DNS: " ZNC_VERSION_TEXT_DNS ", charset: " ZNC_VERSION_TEXT_ICU \ ", i18n: " ZNC_VERSION_TEXT_I18N ", Argon2: " ZNC_VERSION_TEXT_ARGON #endif // !ZNC_VERSION_H znc-1.9.1/include/znc/znc.h0000644000175000017500000002731414641222733015733 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNC_H #define ZNC_H #include #include #include #include #include #include #include #include #include class CListener; class CUser; class CIRCNetwork; class CConnectQueueTimer; class CConfigWriteTimer; class CConfig; class CFile; class CZNC : private CCoreTranslationMixin { public: CZNC(); ~CZNC(); CZNC(const CZNC&) = delete; CZNC& operator=(const CZNC&) = delete; enum ConfigState { ECONFIG_NOTHING, ECONFIG_NEED_REHASH, ECONFIG_NEED_WRITE, ECONFIG_NEED_VERBOSE_WRITE, ECONFIG_DELAYED_WRITE, ECONFIG_NEED_QUIT, // Not really config... }; void DeleteUsers(); void Loop(); bool WritePidFile(int iPid); bool DeletePidFile(); bool WaitForChildLock(); bool IsHostAllowed(const CString& sHostMask) const; // This returns false if there are too many anonymous connections from this // ip bool AllowConnectionFrom(const CString& sIP) const; void InitDirs(const CString& sArgvPath, const CString& sDataDir); bool OnBoot(); CString ExpandConfigPath(const CString& sConfigFile, bool bAllowMkDir = true); bool WriteNewConfig(const CString& sConfigFile); bool WriteConfig(); bool ParseConfig(const CString& sConfig, CString& sError); bool RehashConfig(CString& sError); void BackupConfigOnce(const CString& sSuffix); static CString GetVersion(); static CString GetTag(bool bIncludeVersion = true, bool bHTML = false); static CString GetCompileOptionsString(); CString GetUptime() const; /** @deprecated Since 1.7.0. List of allowed bind hosts was a flawed design. */ void ClearBindHosts() {} /** @deprecated Since 1.7.0. List of allowed bind hosts was a flawed design. */ bool AddBindHost(const CString& sHost) { return false; } /** @deprecated Since 1.7.0. List of allowed bind hosts was a flawed design. */ bool RemBindHost(const CString& sHost) { return false; } void ClearTrustedProxies(); bool AddTrustedProxy(const CString& sHost); bool RemTrustedProxy(const CString& sHost); void Broadcast(const CString& sMessage, bool bAdminOnly = false, CUser* pSkipUser = nullptr, CClient* pSkipClient = nullptr); void AddBytesRead(unsigned long long u) { m_uBytesRead += u; } void AddBytesWritten(unsigned long long u) { m_uBytesWritten += u; } unsigned long long BytesRead() const { return m_uBytesRead; } unsigned long long BytesWritten() const { return m_uBytesWritten; } // Traffic fun typedef std::pair TrafficStatsPair; typedef std::map TrafficStatsMap; // Returns a map which maps user names to // while also providing the traffic of all users together, traffic which // couldn't be accounted to any particular user and the total traffic // generated through ZNC. TrafficStatsMap GetTrafficStats(TrafficStatsPair& Users, TrafficStatsPair& ZNC, TrafficStatsPair& Total); TrafficStatsMap GetNetworkTrafficStats(const CString& sUsername, TrafficStatsPair& Total); // Authenticate a user. // The result is passed back via callbacks to CAuthBase. void AuthUser(std::shared_ptr AuthClass); // Setters void SetConfigState(enum ConfigState e) { std::lock_guard guard(m_mutexConfigState); m_eConfigState = e; } void SetSkinName(const CString& s) { m_sSkinName = s; } void SetStatusPrefix(const CString& s) { m_sStatusPrefix = (s.empty()) ? "*" : s; } void SetMaxBufferSize(unsigned int i) { m_uiMaxBufferSize = i; } void SetAnonIPLimit(unsigned int i) { m_uiAnonIPLimit = i; } void SetServerThrottle(unsigned int i) { m_sConnectThrottle.SetTTL(i * 1000); } void SetProtectWebSessions(bool b) { m_bProtectWebSessions = b; } void SetHideVersion(bool b) { m_bHideVersion = b; } void SetAuthOnlyViaModule(bool b) { m_bAuthOnlyViaModule = b; } void SetConnectDelay(unsigned int i); void SetSSLCiphers(const CString& sCiphers) { m_sSSLCiphers = sCiphers; } bool SetSSLProtocols(const CString& sProtocols); void SetSSLCertFile(const CString& sFile) { m_sSSLCertFile = sFile; } void SetConfigWriteDelay(unsigned int i) { m_uiConfigWriteDelay = i; } // !Setters // Getters enum ConfigState GetConfigState() { std::lock_guard guard(m_mutexConfigState); return m_eConfigState; } CSockManager& GetManager() { return m_Manager; } const CSockManager& GetManager() const { return m_Manager; } CModules& GetModules() { return *m_pModules; } CString GetSkinName() const { return m_sSkinName; } const CString& GetStatusPrefix() const { return m_sStatusPrefix; } const CString& GetCurPath() const; const CString& GetHomePath() const; const CString& GetZNCPath() const; CString GetConfPath(bool bAllowMkDir = true) const; CString GetUserPath() const; CString GetModPath() const; CString GetPemLocation() const; CString GetKeyLocation() const; CString GetDHParamLocation() const; const CString& GetConfigFile() const { return m_sConfigFile; } bool WritePemFile(); /** @deprecated Since 1.7.0. List of allowed bind hosts was a flawed design. */ const VCString& GetBindHosts() const { return m_vsBindHosts; } const VCString& GetTrustedProxies() const { return m_vsTrustedProxies; } const std::vector& GetListeners() const { return m_vpListeners; } time_t TimeStarted() const { return m_TimeStarted; } unsigned int GetMaxBufferSize() const { return m_uiMaxBufferSize; } unsigned int GetAnonIPLimit() const { return m_uiAnonIPLimit; } unsigned int GetServerThrottle() const { return m_sConnectThrottle.GetTTL() / 1000; } unsigned int GetConnectDelay() const { return m_uiConnectDelay; } bool GetProtectWebSessions() const { return m_bProtectWebSessions; } bool GetHideVersion() const { return m_bHideVersion; } bool GetAuthOnlyViaModule() const { return m_bAuthOnlyViaModule; } CString GetSSLCiphers() const { return m_sSSLCiphers; } CString GetSSLProtocols() const { return m_sSSLProtocols; } Csock::EDisableProtocol GetDisabledSSLProtocols() const { return static_cast(m_uDisabledSSLProtocols); } CString GetSSLCertFile() const { return m_sSSLCertFile; } static VCString GetAvailableSSLProtocols(); unsigned int GetConfigWriteDelay() const { return m_uiConfigWriteDelay; } // !Getters // Static allocator static void CreateInstance(); static CZNC& Get(); static void DestroyInstance(); CUser* FindUser(const CString& sUsername); CModule* FindModule(const CString& sModName, const CString& sUsername); CModule* FindModule(const CString& sModName, CUser* pUser); /** Reload a module everywhere * * This method will unload a module globally, for a user and for each * network. It will then reload them all again. * * @param sModule The name of the module to reload */ bool UpdateModule(const CString& sModule); bool DeleteUser(const CString& sUsername); bool AddUser(CUser* pUser, CString& sErrorRet, bool bStartup = false); const std::map& GetUserMap() const { return (m_msUsers); } // Listener yummy CListener* FindListener(u_short uPort, const CString& BindHost, EAddrType eAddr); bool AddListener(CListener*); bool AddListener(unsigned short uPort, const CString& sBindHost, const CString& sURIPrefix, bool bSSL, EAddrType eAddr, CListener::EAcceptType eAccept, CString& sError); bool DelListener(CListener*); // Message of the Day void SetMotd(const CString& sMessage) { ClearMotd(); AddMotd(sMessage); } void AddMotd(const CString& sMessage) { if (!sMessage.empty()) { m_vsMotd.push_back(sMessage); } } void ClearMotd() { m_vsMotd.clear(); } const VCString& GetMotd() const { return m_vsMotd; } // !MOTD void AddServerThrottle(CString sName) { m_sConnectThrottle.AddItem(sName, true); } bool GetServerThrottle(CString sName) { bool* b = m_sConnectThrottle.GetItem(sName); return (b && *b); } void AddNetworkToQueue(CIRCNetwork* pNetwork); std::list& GetConnectionQueue() { return m_lpConnectQueue; } // This creates a CConnectQueueTimer if we haven't got one yet void EnableConnectQueue(); void DisableConnectQueue(); void PauseConnectQueue(); void ResumeConnectQueue(); void ForceEncoding(); void UnforceEncoding(); bool IsForcingEncoding() const; CString FixupEncoding(const CString& sEncoding) const; // Never call this unless you are CConnectQueueTimer::~CConnectQueueTimer() void LeakConnectQueueTimer(CConnectQueueTimer* pTimer); void DisableConfigTimer(); static void DumpConfig(const CConfig* Config); private: static CString FormatBindError(); CFile* InitPidFile(); bool ReadConfig(CConfig& config, CString& sError); bool LoadGlobal(CConfig& config, CString& sError); bool LoadUsers(CConfig& config, CString& sError); bool LoadListeners(CConfig& config, CString& sError); void UnloadRemovedModules(const MCString& msModules); bool HandleUserDeletion(); CString MakeConfigHeader(); bool AddListener(const CString& sLine, CString& sError); bool AddListener(CConfig* pConfig, CString& sError); protected: time_t m_TimeStarted; enum ConfigState m_eConfigState; std::mutex m_mutexConfigState; std::vector m_vpListeners; std::map m_msUsers; std::map m_msDelUsers; CSockManager m_Manager; CString m_sCurPath; CString m_sZNCPath; CString m_sConfigFile; CString m_sSkinName; CString m_sStatusPrefix; CString m_sPidFile; CString m_sSSLCertFile; CString m_sSSLKeyFile; CString m_sSSLDHParamFile; CString m_sSSLCiphers; CString m_sSSLProtocols; VCString m_vsBindHosts; // TODO: remove (deprecated in 1.7.0) VCString m_vsTrustedProxies; VCString m_vsMotd; CFile* m_pLockFile; unsigned int m_uiConnectDelay; unsigned int m_uiAnonIPLimit; unsigned int m_uiMaxBufferSize; unsigned int m_uDisabledSSLProtocols; CModules* m_pModules; unsigned long long m_uBytesRead; unsigned long long m_uBytesWritten; std::list m_lpConnectQueue; CConnectQueueTimer* m_pConnectQueueTimer; unsigned int m_uiConnectPaused; unsigned int m_uiForceEncoding; TCacheMap m_sConnectThrottle; bool m_bProtectWebSessions; bool m_bHideVersion; bool m_bAuthOnlyViaModule; CTranslationDomainRefHolder m_Translation; unsigned int m_uiConfigWriteDelay; CConfigWriteTimer* m_pConfigTimer; }; #endif // !ZNC_H znc-1.9.1/include/znc/zncconfig.h.cmake.in0000644000175000017500000000334114641222733020577 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ZNCCONFIG_H #define ZNCCONFIG_H #define BUILD_WITH_CMAKE 1 #define VERSION_MAJOR @PROJECT_VERSION_MAJOR@ #define VERSION_MINOR @PROJECT_VERSION_MINOR@ #define VERSION_PATCH @PROJECT_VERSION_PATCH@ #define VERSION_STR "@PROJECT_VERSION@" #define VERSION_EXTRA "@VERSION_EXTRA@" #cmakedefine _GLIBCXX_DEBUG 1 #cmakedefine _GLIBCXX_DEBUG_PEDANTIC 1 #define HAVE_PTHREAD 1 #cmakedefine HAVE_THREADED_DNS 1 #cmakedefine HAVE_LIBSSL 1 #cmakedefine HAVE_SSL_SESSION_get0_cipher 1 #cmakedefine HAVE_IPV6 1 #cmakedefine HAVE_ZLIB 1 #cmakedefine HAVE_I18N 1 #cmakedefine ZNC_HAVE_ARGON 1 #cmakedefine CSOCK_USE_POLL 1 #cmakedefine HAVE_GETOPT_LONG 1 #cmakedefine HAVE_LSTAT 1 #cmakedefine HAVE_TCSETATTR 1 #cmakedefine HAVE_GETPASSPHRASE 1 #cmakedefine HAVE_CLOCK_GETTIME 1 #cmakedefine HAVE_ICU 1 #define U_USING_ICU_NAMESPACE 1 #cmakedefine _LARGE_FILES 1 #cmakedefine _LARGEFILE_SOURCE 1 #cmakedefine _FILE_OFFSET_BITS @_FILE_OFFSET_BITS@ #define _MODDIR_ "@CMAKE_INSTALL_FULL_LIBDIR@/znc" #define _DATADIR_ "@CMAKE_INSTALL_FULL_DATADIR@/znc" #define LOCALE_DIR "@CMAKE_INSTALL_FULL_LOCALEDIR@" #endif /* ZNCCONFIG_H */ znc-1.9.1/logo.png0000644000175000017500000001126214641222733014214 0ustar somebodysomebodyPNG  IHDRv?.̇PLTE"! *%"+'&++$0*%.,!20$52'75)843<4)57497+=9(A8.?<0D<2C?.B?3FA0GD8LC8ID?KF5QF7OJ9PICUJ;SN=VMBUP?TRE[P@WPLYTBZVDcWG^ZHaYM`ZTa\Jc\E\][`\Zh\LgbPibKcb`hb[ocSmfOqdOqiRuhRqh]piduiXxjUvoWspc{nXvq^{o^vpjzs[~q[|rgs]}u^tc|unzwi|wdw`x[v`zb~yx{hyhyc|_{t}d~}u|p}f}a}lgbido{vfglrgjiu{kqqwmm~yuppv|rstv}x}zz~z{ôƴķ·ĹùżǼƼžʽͽƾĤñƱĽƬķǧʪȻʰɷͭ˾ϯжμԺյܻx 2>qmIDATXV}Te~\R3[M[ a|Al\W<Q h!0@,6bw;3Ascsx~wFk+b|Đ  a0<1d+ ?Sk`31<888M ?G P0EQ, oi[LQT Ib7>8Ot$3F&fz`4ϐc{;w ?!.p]8#WhJOx۝sFuuweI $`ni||aHÔojkkz?&Ƈl{{{G/i`7njHJ{qxtl|z[ ÏHH+XL{ƌ^ܓnj3ѱa3 پg%d߃@u[cc;hMl 36^on`}!%1RCؠmE>HoW0L"M`36tanPQW;Ehq+L;MᐒIVTVL%9 SBxT`4Tw o)FT66rݝ &`ș 0V[h =Agb2f$-cTW$t7TTcm,1 v/B[n4_RZ\\\Zhlq4/?|5S(9#ڀ FS_NF[ftg A/hf~UڊQmA<2y"h s9Jb͔ Ti4-+Z膜($-T[l dS@'$ ~G' Sڷpzj9}]ݳ=q$' '8rC̚5{֚$VD2\:4y9v$sd`Fn6Bh<7 @?kάY7I4l(:t,CRjFVֱ\һ:Ӽ61..?.YǎCE s4E*csfjjr(B 77رc WVG%&'g/^-Y}uT|@dz xd0 дL),AYY}_\y!4*J(=ͭuuwh`?J$R֟8dGM >UsY'gucՁãlsܺxpux큁n]~]I2NLDIc_{qS,R5ΌH .==99[?ō\G1#vf# q߮]靟r,jߑ7>=q7?]@Vuo&( eE KIr|_2( 1ㆧD,\%yD(!6O?z{]MX "Uʵe6h^2ws-;Py^/uNpŀpʥ4nv9)+h'ƈ5uKnF'uNt+6:tE6nJW'~;x`Y)'Z ¥-_4rmj5J؇4*vDd"W^YK/Z)"(UT#cP84`^+E&g#XP2ϷWI$ Y'jdBI$С- |5* NT1j6᫄ly.XTW!D*B.lj6EDbd`|*0I^A-lޫn! ƨ޻wwld|"[l=jA( CP8l8=U^P|#CxKIُ|p/w"2Nki\&SlddMu@(n!Z4!H @KK=vK=U۰7[tkelְؕ@\· 46%aatIҘ͛z/_v3bbvB^5Jbܼ [O\ļ/F>5P.}Ş9988oP\ZZڻ6AK`w>pD:}³2W=.{ &(-2Cǻ w;V(SI԰ҊmJK-3`nBI}~F4E?O"58~wEOꚦ#κmt\P@@/ogDzKVd6N9޳K[dg"''{ _0/xlނ/,zryO(4/9̂F8+Vz饗u֭]r66/'gtVL, #include #include #include #include class CAdminDebugMod : public CModule { private: CString m_sEnabledBy; public: MODCONSTRUCTOR(CAdminDebugMod) { AddHelpCommand(); AddCommand("Enable", "", t_d("Enable Debug Mode"), [=](const CString& sLine) { CommandEnable(sLine); }); AddCommand("Disable", "", t_d("Disable Debug Mode"), [=](const CString& sLine) { CommandDisable(sLine); }); AddCommand("Status", "", t_d("Show the Debug Mode status"), [=](const CString& sLine) { CommandStatus(sLine); }); } void CommandEnable(const CString& sCommand) { if (!GetUser()->IsAdmin()) { PutModule(t_s("Access denied!")); return; } ToggleDebug(true, GetUser()->GetNick()); } void CommandDisable(const CString& sCommand) { if (!GetUser()->IsAdmin()) { PutModule(t_s("Access denied!")); return; } ToggleDebug(false, m_sEnabledBy); } bool ToggleDebug(bool bEnable, const CString& sEnabledBy) { if (!CDebug::StdoutIsTTY()) { PutModule(t_s("Failure. We need to be running with a TTY. (is ZNC running with --foreground?)")); return false; } bool bValue = CDebug::Debug(); if (bEnable == bValue) { if (bEnable) { PutModule(t_s("Already enabled.")); } else { PutModule(t_s("Already disabled.")); } return false; } CDebug::SetDebug(bEnable); CString sEnabled = bEnable ? "on" : "off"; CZNC::Get().Broadcast( "An administrator has just turned Debug Mode \02" + sEnabled + "\02. It was enabled by \02" + sEnabledBy + "\02."); if (bEnable) { CZNC::Get().Broadcast( "Messages, credentials, and other sensitive data may become " "exposed to the host during this period."); m_sEnabledBy = sEnabledBy; } else { m_sEnabledBy = ""; } return true; } void CommandStatus(const CString& sCommand) { if (CDebug::Debug()) { PutModule(t_s("Debugging mode is \02on\02.")); } else { PutModule(t_s("Debugging mode is \02off\02.")); } PutModule(t_s("Logging to: \02stdout\02.")); } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("admindebug"); } GLOBALMODULEDEFS(CAdminDebugMod, t_s("Enable Debug mode dynamically.")) znc-1.9.1/modules/adminlog.cpp0000644000175000017500000001565614641222733016527 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include class CAdminLogMod : public CModule { public: MODCONSTRUCTOR(CAdminLogMod) { AddHelpCommand(); AddCommand("Show", "", t_d("Show the logging target"), [=](const CString& sLine) { OnShowCommand(sLine); }); AddCommand("Target", t_d(" [path]"), t_d("Set the logging target"), [=](const CString& sLine) { OnTargetCommand(sLine); }); openlog("znc", LOG_PID, LOG_DAEMON); } ~CAdminLogMod() override { Log("Logging ended."); closelog(); } bool OnLoad(const CString& sArgs, CString& sMessage) override { CString sTarget = GetNV("target"); if (sTarget.Equals("syslog")) m_eLogMode = LOG_TO_SYSLOG; else if (sTarget.Equals("both")) m_eLogMode = LOG_TO_BOTH; else if (sTarget.Equals("file")) m_eLogMode = LOG_TO_FILE; else m_eLogMode = LOG_TO_FILE; SetLogFilePath(GetNV("path")); Log("Logging started. ZNC PID[" + CString(getpid()) + "] UID/GID[" + CString(getuid()) + ":" + CString(getgid()) + "]"); return true; } void OnIRCConnected() override { Log("[" + GetUser()->GetUsername() + "/" + GetNetwork()->GetName() + "] connected to IRC: " + GetNetwork()->GetCurrentServer()->GetName()); } void OnIRCDisconnected() override { Log("[" + GetUser()->GetUsername() + "/" + GetNetwork()->GetName() + "] disconnected from IRC"); } EModRet OnRawMessage(CMessage& Message) override { if (Message.GetCommand().Equals("ERROR")) { // ERROR :Closing Link: nick[24.24.24.24] (Excess Flood) // ERROR :Closing Link: nick[24.24.24.24] Killer (Local kill by // Killer (reason)) Log("[" + GetUser()->GetUsername() + "/" + GetNetwork()->GetName() + "] disconnected from IRC: " + GetNetwork()->GetCurrentServer()->GetName() + " [" + Message.GetParamsColon(0) + "]", LOG_NOTICE); } return CONTINUE; } void OnClientLogin() override { Log("[" + GetUser()->GetUsername() + "] connected to ZNC from " + GetClient()->GetRemoteIP()); } void OnClientDisconnect() override { Log("[" + GetUser()->GetUsername() + "] disconnected from ZNC from " + GetClient()->GetRemoteIP()); } void OnFailedLogin(const CString& sUsername, const CString& sRemoteIP) override { Log("[" + sUsername + "] failed to login from " + sRemoteIP, LOG_WARNING); } void SetLogFilePath(CString sPath) { if (sPath.empty()) { sPath = GetSavePath() + "/znc.log"; } CFile LogFile(sPath); CString sLogDir = LogFile.GetDir(); struct stat ModDirInfo; CFile::GetInfo(GetSavePath(), ModDirInfo); if (!CFile::Exists(sLogDir)) { CDir::MakeDir(sLogDir, ModDirInfo.st_mode); } m_sLogFile = sPath; SetNV("path", sPath); } void Log(CString sLine, int iPrio = LOG_INFO) { if (m_eLogMode & LOG_TO_SYSLOG) syslog(iPrio, "%s", sLine.c_str()); if (m_eLogMode & LOG_TO_FILE) { time_t curtime; tm* timeinfo; char buf[23]; time(&curtime); timeinfo = localtime(&curtime); strftime(buf, sizeof(buf), "[%Y-%m-%d %H:%M:%S] ", timeinfo); CFile LogFile(m_sLogFile); if (LogFile.Open(O_WRONLY | O_APPEND | O_CREAT)) LogFile.Write(buf + sLine + "\n"); else DEBUG("Failed to write to [" << m_sLogFile << "]: " << strerror(errno)); } } void OnModCommand(const CString& sCommand) override { if (!GetUser()->IsAdmin()) { PutModule(t_s("Access denied")); } else { HandleCommand(sCommand); } } void OnTargetCommand(const CString& sCommand) { CString sArg = sCommand.Token(1, false); CString sTarget; CString sMessage; LogMode mode; if (sArg.Equals("file")) { sTarget = "file"; sMessage = t_s("Now logging to file"); mode = LOG_TO_FILE; } else if (sArg.Equals("syslog")) { sTarget = "syslog"; sMessage = t_s("Now only logging to syslog"); mode = LOG_TO_SYSLOG; } else if (sArg.Equals("both")) { sTarget = "both"; sMessage = t_s("Now logging to syslog and file"); mode = LOG_TO_BOTH; } else { if (sArg.empty()) { PutModule(t_s("Usage: Target [path]")); } else { PutModule(t_s("Unknown target")); } return; } if (mode != LOG_TO_SYSLOG) { CString sPath = sCommand.Token(2, true); SetLogFilePath(sPath); sMessage += " [" + sPath + "]"; } Log(sMessage); SetNV("target", sTarget); m_eLogMode = mode; PutModule(sMessage); } void OnShowCommand(const CString& sCommand) { CString sTarget; switch (m_eLogMode) { case LOG_TO_FILE: sTarget = t_s("Logging is enabled for file"); break; case LOG_TO_SYSLOG: sTarget = t_s("Logging is enabled for syslog"); break; case LOG_TO_BOTH: sTarget = t_s("Logging is enabled for both, file and syslog"); break; } PutModule(sTarget); if (m_eLogMode != LOG_TO_SYSLOG) PutModule(t_f("Log file will be written to {1}")(m_sLogFile)); } private: enum LogMode { LOG_TO_FILE = 1 << 0, LOG_TO_SYSLOG = 1 << 1, LOG_TO_BOTH = LOG_TO_FILE | LOG_TO_SYSLOG }; LogMode m_eLogMode = LOG_TO_FILE; CString m_sLogFile; }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("adminlog"); } GLOBALMODULEDEFS(CAdminLogMod, t_s("Log ZNC events to file and/or syslog.")) znc-1.9.1/modules/alias.cpp0000644000175000017500000003477614641222733016032 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include using std::vector; using std::stringstream; class CAlias { private: CModule* parent; CString name; VCString alias_cmds; public: // getters/setters const CString& GetName() const { return name; } // name should be a single, all uppercase word void SetName(const CString& newname) { name = newname.Token(0, false, " "); name.MakeUpper(); } // combined getter/setter for command list VCString& AliasCmds() { return alias_cmds; } // check registry if alias exists static bool AliasExists(CModule* module, CString alias_name) { alias_name = alias_name.Token(0, false, " ").MakeUpper(); return (module->FindNV(alias_name) != module->EndNV()); } // populate alias from stored settings in registry, or return false if none // exists static bool AliasGet(CAlias& alias, CModule* module, CString line) { line = line.Token(0, false, " ").MakeUpper(); MCString::iterator i = module->FindNV(line); if (i == module->EndNV()) return false; alias.parent = module; alias.name = line; i->second.Split("\n", alias.alias_cmds, false); return true; } // constructors CAlias() : parent(nullptr) {} CAlias(CModule* new_parent, const CString& new_name) : parent(new_parent) { SetName(new_name); } // produce a command string from this alias' command list CString GetCommands() const { return CString("\n").Join(alias_cmds.begin(), alias_cmds.end()); } // write this alias to registry void Commit() const { if (!parent) return; parent->SetNV(name, GetCommands()); } // delete this alias from regisrty void Delete() const { if (!parent) return; parent->DelNV(name); } private: // this function helps imprint out. it checks if there is a substitution // token at 'caret' in 'alias_data' // and if it finds one, pulls the appropriate token out of 'line' and // appends it to 'output', and updates 'caret'. // 'skip' is updated based on the logic that we should skip the % at the // caret if we fail to parse the token. void ParseToken(const CString& alias_data, const CString& line, CString& output, size_t& caret, size_t& skip) const { bool optional = false; bool subsequent = false; size_t index = caret + 1; int token = -1; skip = 1; // try to read optional flag if (alias_data.length() > index && alias_data[index] == '?') { optional = true; ++index; } // try to read integer if (alias_data.length() > index && CString(alias_data.substr(index)).Convert(&token)) { // skip any numeric digits in string (supposed to fail if // whitespace precedes integer) while (alias_data.length() > index && alias_data[index] >= '0' && alias_data[index] <= '9') ++index; } else { // token was malformed. leave caret unchanged, and flag first // character for skipping return; } // try to read subsequent flag if (alias_data.length() > index && alias_data[index] == '+') { subsequent = true; ++index; } // try to read end-of-substitution marker if (alias_data.length() > index && alias_data[index] == '%') { ++index; } else return; // if we get here, we're definitely dealing with a token, so get the // token's value CString stok = line.Token(token, subsequent, " "); if (stok.empty() && !optional) // blow up if token is required and also empty throw std::invalid_argument( parent->t_f("missing required parameter: {1}")(CString(token))); // write token value to output output.append(stok); // since we're moving the cursor after the end of the token, skip no // characters skip = 0; // advance the cursor forward by the size of the token caret = index; } public: // read an IRC line and do token substitution // throws an exception if a required parameter is missing, and might also // throw if you manage to make it bork CString Imprint(CString line) const { CString output; CString alias_data = GetCommands(); alias_data = parent->ExpandString(alias_data); size_t lastfound = 0, skip = 0; // it would be very inefficient to attempt to blindly replace every // possible token // so let's just parse the line and replace when we find them // token syntax: // %[?]n[+]% // adding ? makes the substitution optional (you'll get "" if there are // insufficient tokens, otherwise the alias will fail) // adding + makes the substitution contain all tokens from the nth to // the end of the line while (true) { // if (found >= (int) alias_data.length()) break; // ^ shouldn't be possible. size_t found = alias_data.find("%", lastfound + skip); // if we found nothing, break if (found == CString::npos) break; // capture everything between the last stopping point and here output.append(alias_data.substr(lastfound, found - lastfound)); // attempt to read a token, updates indices based on // success/failure ParseToken(alias_data, line, output, found, skip); lastfound = found; } // append from the final output += alias_data.substr(lastfound); return output; } }; class CAliasMod : public CModule { private: bool sending_lines; public: void CreateCommand(const CString& sLine) { CString name = sLine.Token(1, false, " "); if (!CAlias::AliasExists(this, name)) { CAlias na(this, name); na.Commit(); PutModule(t_f("Created alias: {1}")(na.GetName())); } else PutModule(t_s("Alias already exists.")); } void DeleteCommand(const CString& sLine) { CString name = sLine.Token(1, false, " "); CAlias delete_alias; if (CAlias::AliasGet(delete_alias, this, name)) { PutModule(t_f("Deleted alias: {1}")(delete_alias.GetName())); delete_alias.Delete(); } else PutModule(t_s("Alias does not exist.")); } void AddCmd(const CString& sLine) { CString name = sLine.Token(1, false, " "); CAlias add_alias; if (CAlias::AliasGet(add_alias, this, name)) { add_alias.AliasCmds().push_back(sLine.Token(2, true, " ")); add_alias.Commit(); PutModule(t_s("Modified alias.")); } else PutModule(t_s("Alias does not exist.")); } void InsertCommand(const CString& sLine) { CString name = sLine.Token(1, false, " "); CAlias insert_alias; int index; if (CAlias::AliasGet(insert_alias, this, name)) { // if Convert succeeds, then i has been successfully read from user // input if (!sLine.Token(2, false, " ").Convert(&index) || index < 0 || index > (int)insert_alias.AliasCmds().size()) { PutModule(t_s("Invalid index.")); return; } insert_alias.AliasCmds().insert( insert_alias.AliasCmds().begin() + index, sLine.Token(3, true, " ")); insert_alias.Commit(); PutModule(t_s("Modified alias.")); } else PutModule(t_s("Alias does not exist.")); } void RemoveCommand(const CString& sLine) { CString name = sLine.Token(1, false, " "); CAlias remove_alias; int index; if (CAlias::AliasGet(remove_alias, this, name)) { if (!sLine.Token(2, false, " ").Convert(&index) || index < 0 || index > (int)remove_alias.AliasCmds().size() - 1) { PutModule(t_s("Invalid index.")); return; } remove_alias.AliasCmds().erase(remove_alias.AliasCmds().begin() + index); remove_alias.Commit(); PutModule(t_s("Modified alias.")); } else PutModule(t_s("Alias does not exist.")); } void ClearCommand(const CString& sLine) { CString name = sLine.Token(1, false, " "); CAlias clear_alias; if (CAlias::AliasGet(clear_alias, this, name)) { clear_alias.AliasCmds().clear(); clear_alias.Commit(); PutModule(t_s("Modified alias.")); } else PutModule(t_s("Alias does not exist.")); } void ListCommand(const CString& sLine) { MCString::iterator i = BeginNV(); if (i == EndNV()) { PutModule(t_s("There are no aliases.")); return; } VCString vsAliases; for (; i != EndNV(); ++i) { vsAliases.push_back(i->first); } PutModule(t_f("The following aliases exist: {1}")( CString(t_s(", ", "list|separator")) .Join(vsAliases.begin(), vsAliases.end()))); } void DumpCommand(const CString& sLine) { MCString::iterator i = BeginNV(); if (i == EndNV()) { PutModule(t_s("There are no aliases.")); return; } PutModule("-----------------------"); PutModule("/ZNC-CLEAR-ALL-ALIASES!"); for (; i != EndNV(); ++i) { PutModule("/msg " + GetModNick() + " Create " + i->first); if (!i->second.empty()) { VCString it; uint idx; i->second.Split("\n", it); for (idx = 0; idx < it.size(); ++idx) { PutModule("/msg " + GetModNick() + " Add " + i->first + " " + it[idx]); } } } PutModule("-----------------------"); } void InfoCommand(const CString& sLine) { CString name = sLine.Token(1, false, " "); CAlias info_alias; if (CAlias::AliasGet(info_alias, this, name)) { PutModule(t_f("Actions for alias {1}:")(info_alias.GetName())); for (size_t i = 0; i < info_alias.AliasCmds().size(); ++i) { CString num(i); CString padding(4 - (num.length() > 3 ? 3 : num.length()), ' '); PutModule(num + padding + info_alias.AliasCmds()[i]); } PutModule( t_f("End of actions for alias {1}.")(info_alias.GetName())); } else PutModule(t_s("Alias does not exist.")); } MODCONSTRUCTOR(CAliasMod), sending_lines(false) { AddHelpCommand(); AddCommand("Create", t_d(""), t_d("Creates a new, blank alias called name."), [=](const CString& sLine) { CreateCommand(sLine); }); AddCommand("Delete", t_d(""), t_d("Deletes an existing alias."), [=](const CString& sLine) { DeleteCommand(sLine); }); AddCommand("Add", t_d(" "), t_d("Adds a line to an existing alias."), [=](const CString& sLine) { AddCmd(sLine); }); AddCommand("Insert", t_d(" "), t_d("Inserts a line into an existing alias."), [=](const CString& sLine) { InsertCommand(sLine); }); AddCommand("Remove", t_d(" "), t_d("Removes a line from an existing alias."), [=](const CString& sLine) { RemoveCommand(sLine); }); AddCommand("Clear", t_d(""), t_d("Removes all lines from an existing alias."), [=](const CString& sLine) { ClearCommand(sLine); }); AddCommand("List", "", t_d("Lists all aliases by name."), [=](const CString& sLine) { ListCommand(sLine); }); AddCommand("Info", t_d(""), t_d("Reports the actions performed by an alias."), [=](const CString& sLine) { InfoCommand(sLine); }); AddCommand( "Dump", "", t_d("Generate a list of commands to copy your alias config."), [=](const CString& sLine) { DumpCommand(sLine); }); } EModRet OnUserRaw(CString& sLine) override { CAlias current_alias; if (sending_lines) return CONTINUE; try { if (sLine.Equals("ZNC-CLEAR-ALL-ALIASES!")) { ListCommand(""); PutModule(t_s("Clearing all of them!")); ClearNV(); return HALT; } else if (CAlias::AliasGet(current_alias, this, sLine)) { VCString rawLines; current_alias.Imprint(sLine).Split("\n", rawLines, false); sending_lines = true; for (size_t i = 0; i < rawLines.size(); ++i) { m_pClient->ReadLine(rawLines[i]); } sending_lines = false; return HALT; } } catch (std::exception& e) { CString my_nick = (GetNetwork() == nullptr ? "" : GetNetwork()->GetCurNick()); if (my_nick.empty()) my_nick = "*"; PutUser(CString(":znc.in 461 " + my_nick + " " + current_alias.GetName() + " :ZNC alias error: ") + e.what()); return HALTCORE; } return CONTINUE; } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("alias"); Info.AddType(CModInfo::NetworkModule); } USERMODULEDEFS(CAliasMod, t_s("Provides bouncer-side command alias support.")) znc-1.9.1/modules/autoattach.cpp0000644000175000017500000002232214641222733017056 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include using std::vector; class CAttachMatch { public: CAttachMatch(CModule* pModule, const CString& sChannels, const CString& sSearch, const CString& sHostmasks, bool bNegated) { m_pModule = pModule; m_sChannelWildcard = sChannels; m_sSearchWildcard = sSearch; m_sHostmaskWildcard = sHostmasks; m_bNegated = bNegated; if (m_sChannelWildcard.empty()) m_sChannelWildcard = "*"; if (m_sSearchWildcard.empty()) m_sSearchWildcard = "*"; if (m_sHostmaskWildcard.empty()) m_sHostmaskWildcard = "*!*@*"; } bool IsMatch(const CString& sChan, const CString& sHost, const CString& sMessage) const { if (!sHost.WildCmp(m_sHostmaskWildcard, CString::CaseInsensitive)) return false; if (!sChan.WildCmp(m_sChannelWildcard, CString::CaseInsensitive)) return false; if (!sMessage.WildCmp(m_pModule->ExpandString(m_sSearchWildcard), CString::CaseInsensitive)) return false; return true; } bool IsNegated() const { return m_bNegated; } const CString& GetHostMask() const { return m_sHostmaskWildcard; } const CString& GetSearch() const { return m_sSearchWildcard; } const CString& GetChans() const { return m_sChannelWildcard; } CString ToString() { CString sRes; if (m_bNegated) sRes += "!"; sRes += m_sChannelWildcard; sRes += " "; sRes += m_sSearchWildcard; sRes += " "; sRes += m_sHostmaskWildcard; return sRes; } private: bool m_bNegated; CModule* m_pModule; CString m_sChannelWildcard; CString m_sSearchWildcard; CString m_sHostmaskWildcard; }; class CChanAttach : public CModule { public: typedef vector VAttachMatch; typedef VAttachMatch::iterator VAttachIter; private: void HandleAdd(const CString& sLine) { CString sMsg = sLine.Token(1, true); bool bHelp = false; bool bNegated = sMsg.TrimPrefix("!"); CString sChan = sMsg.Token(0); CString sSearch = sMsg.Token(1); CString sHost = sMsg.Token(2); if (sChan.empty()) { bHelp = true; } else if (Add(bNegated, sChan, sSearch, sHost)) { PutModule(t_s("Added to list")); } else { PutModule(t_f("{1} is already added")(sLine.Token(1, true))); bHelp = true; } if (bHelp) { PutModule(t_s("Usage: Add [!]<#chan> ")); PutModule(t_s("Wildcards are allowed")); } } void HandleDel(const CString& sLine) { CString sMsg = sLine.Token(1, true); bool bNegated = sMsg.TrimPrefix("!"); CString sChan = sMsg.Token(0); CString sSearch = sMsg.Token(1); CString sHost = sMsg.Token(2); if (Del(bNegated, sChan, sSearch, sHost)) { PutModule(t_f("Removed {1} from list")(sChan)); } else { PutModule(t_s("Usage: Del [!]<#chan> ")); } } void HandleList(const CString& sLine) { CTable Table; Table.AddColumn(t_s("Neg")); Table.AddColumn(t_s("Chan")); Table.AddColumn(t_s("Search")); Table.AddColumn(t_s("Host")); VAttachIter it = m_vMatches.begin(); for (; it != m_vMatches.end(); ++it) { Table.AddRow(); Table.SetCell(t_s("Neg"), it->IsNegated() ? "!" : ""); Table.SetCell(t_s("Chan"), it->GetChans()); Table.SetCell(t_s("Search"), it->GetSearch()); Table.SetCell(t_s("Host"), it->GetHostMask()); } if (Table.size()) { PutModule(Table); } else { PutModule(t_s("You have no entries.")); } } public: MODCONSTRUCTOR(CChanAttach) { AddHelpCommand(); AddCommand( "Add", t_d("[!]<#chan> "), t_d("Add an entry, use !#chan to negate and * for wildcards"), [=](const CString& sLine) { HandleAdd(sLine); }); AddCommand("Del", t_d("[!]<#chan> "), t_d("Remove an entry, needs to be an exact match"), [=](const CString& sLine) { HandleDel(sLine); }); AddCommand("List", "", t_d("List all entries"), [=](const CString& sLine) { HandleList(sLine); }); } ~CChanAttach() override {} bool OnLoad(const CString& sArgs, CString& sMessage) override { VCString vsChans; sArgs.Split(" ", vsChans, false); for (VCString::const_iterator it = vsChans.begin(); it != vsChans.end(); ++it) { CString sAdd = *it; bool bNegated = sAdd.TrimPrefix("!"); CString sChan = sAdd.Token(0); CString sSearch = sAdd.Token(1); CString sHost = sAdd.Token(2, true); if (!Add(bNegated, sChan, sSearch, sHost)) { PutModule(t_f("Unable to add [{1}]")(*it)); } } // Load our saved settings, ignore errors MCString::iterator it; for (it = BeginNV(); it != EndNV(); ++it) { CString sAdd = it->first; bool bNegated = sAdd.TrimPrefix("!"); CString sChan = sAdd.Token(0); CString sSearch = sAdd.Token(1); CString sHost = sAdd.Token(2, true); Add(bNegated, sChan, sSearch, sHost); } return true; } void TryAttach(const CNick& Nick, CChan& Channel, CString& Message) { const CString& sChan = Channel.GetName(); const CString& sHost = Nick.GetHostMask(); const CString& sMessage = Message; VAttachIter it; if (!Channel.IsDetached()) return; // Any negated match? for (it = m_vMatches.begin(); it != m_vMatches.end(); ++it) { if (it->IsNegated() && it->IsMatch(sChan, sHost, sMessage)) return; } // Now check for a positive match for (it = m_vMatches.begin(); it != m_vMatches.end(); ++it) { if (!it->IsNegated() && it->IsMatch(sChan, sHost, sMessage)) { Channel.AttachUser(); return; } } } EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) override { TryAttach(Nick, Channel, sMessage); return CONTINUE; } EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) override { TryAttach(Nick, Channel, sMessage); return CONTINUE; } EModRet OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) override { TryAttach(Nick, Channel, sMessage); return CONTINUE; } VAttachIter FindEntry(const CString& sChan, const CString& sSearch, const CString& sHost) { VAttachIter it = m_vMatches.begin(); for (; it != m_vMatches.end(); ++it) { if (sHost.empty() || it->GetHostMask() != sHost) continue; if (sSearch.empty() || it->GetSearch() != sSearch) continue; if (sChan.empty() || it->GetChans() != sChan) continue; return it; } return m_vMatches.end(); } bool Add(bool bNegated, const CString& sChan, const CString& sSearch, const CString& sHost) { CAttachMatch attach(this, sChan, sSearch, sHost, bNegated); // Check for duplicates VAttachIter it = m_vMatches.begin(); for (; it != m_vMatches.end(); ++it) { if (it->GetHostMask() == attach.GetHostMask() && it->GetChans() == attach.GetChans() && it->GetSearch() == attach.GetSearch()) return false; } m_vMatches.push_back(attach); // Also save it for next module load SetNV(attach.ToString(), ""); return true; } bool Del(bool bNegated, const CString& sChan, const CString& sSearch, const CString& sHost) { VAttachIter it = FindEntry(sChan, sSearch, sHost); if (it == m_vMatches.end() || it->IsNegated() != bNegated) return false; DelNV(it->ToString()); m_vMatches.erase(it); return true; } private: VAttachMatch m_vMatches; }; template <> void TModInfo(CModInfo& Info) { Info.AddType(CModInfo::UserModule); Info.SetWikiPage("autoattach"); Info.SetHasArgs(true); Info.SetArgsHelpText(Info.t_s( "List of channel masks and channel masks with ! before them.")); } NETWORKMODULEDEFS(CChanAttach, t_s("Reattaches you to channels on activity.")) znc-1.9.1/modules/autocycle.cpp0000644000175000017500000001511214641222733016710 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include using std::vector; class CAutoCycleMod : public CModule { public: MODCONSTRUCTOR(CAutoCycleMod) { AddHelpCommand(); AddCommand( "Add", t_d("[!]<#chan>"), t_d("Add an entry, use !#chan to negate and * for wildcards"), [=](const CString& sLine) { OnAddCommand(sLine); }); AddCommand("Del", t_d("[!]<#chan>"), t_d("Remove an entry, needs to be an exact match"), [=](const CString& sLine) { OnDelCommand(sLine); }); AddCommand("List", "", t_d("List all entries"), [=](const CString& sLine) { OnListCommand(sLine); }); m_recentlyCycled.SetTTL(15 * 1000); } ~CAutoCycleMod() override {} bool OnLoad(const CString& sArgs, CString& sMessage) override { VCString vsChans; sArgs.Split(" ", vsChans, false); for (const CString& sChan : vsChans) { if (!Add(sChan)) { PutModule(t_f("Unable to add {1}")(sChan)); } } // Load our saved settings, ignore errors MCString::iterator it; for (it = BeginNV(); it != EndNV(); ++it) { Add(it->first); } // Default is auto cycle for all channels if (m_vsChans.empty()) Add("*"); return true; } void OnAddCommand(const CString& sLine) { CString sChan = sLine.Token(1); if (AlreadyAdded(sChan)) { PutModule(t_f("{1} is already added")(sChan)); } else if (Add(sChan)) { PutModule(t_f("Added {1} to list")(sChan)); } else { PutModule(t_s("Usage: Add [!]<#chan>")); } } void OnDelCommand(const CString& sLine) { CString sChan = sLine.Token(1); if (Del(sChan)) PutModule(t_f("Removed {1} from list")(sChan)); else PutModule(t_s("Usage: Del [!]<#chan>")); } void OnListCommand(const CString& sLine) { CTable Table; Table.AddColumn(t_s("Channel")); Table.SetStyle(CTable::ListStyle); for (const CString& sChan : m_vsChans) { Table.AddRow(); Table.SetCell(t_s("Channel"), sChan); } for (const CString& sChan : m_vsNegChans) { Table.AddRow(); Table.SetCell(t_s("Channel"), "!" + sChan); } if (Table.size()) { PutModule(Table); } else { PutModule(t_s("You have no entries.")); } } void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) override { AutoCycle(Channel); } void OnQuit(const CNick& Nick, const CString& sMessage, const vector& vChans) override { for (CChan* pChan : vChans) AutoCycle(*pChan); } void OnKick(const CNick& Nick, const CString& sOpNick, CChan& Channel, const CString& sMessage) override { AutoCycle(Channel); } protected: void AutoCycle(CChan& Channel) { if (!IsAutoCycle(Channel.GetName())) return; // Did we recently annoy opers via cycling of an empty channel? if (m_recentlyCycled.HasItem(Channel.GetName())) return; // Is there only one person left in the channel? if (Channel.GetNickCount() != 1) return; // Is that person us and we don't have op? const CNick& pNick = Channel.GetNicks().begin()->second; if (!pNick.HasPerm(CChan::Op) && pNick.NickEquals(GetNetwork()->GetCurNick())) { Channel.Cycle(); m_recentlyCycled.AddItem(Channel.GetName()); } } bool AlreadyAdded(const CString& sInput) { CString sChan = sInput; if (sChan.TrimPrefix("!")) { for (const CString& s : m_vsNegChans) { if (s.Equals(sChan)) return true; } } else { for (const CString& s : m_vsChans) { if (s.Equals(sChan)) return true; } } return false; } bool Add(const CString& sChan) { if (sChan.empty() || sChan == "!") { return false; } if (sChan.Left(1) == "!") { m_vsNegChans.push_back(sChan.substr(1)); } else { m_vsChans.push_back(sChan); } // Also save it for next module load SetNV(sChan, ""); return true; } bool Del(const CString& sChan) { vector::iterator it, end; if (sChan.empty() || sChan == "!") return false; if (sChan.Left(1) == "!") { CString sTmp = sChan.substr(1); it = m_vsNegChans.begin(); end = m_vsNegChans.end(); for (; it != end; ++it) if (*it == sTmp) break; if (it == end) return false; m_vsNegChans.erase(it); } else { it = m_vsChans.begin(); end = m_vsChans.end(); for (; it != end; ++it) if (*it == sChan) break; if (it == end) return false; m_vsChans.erase(it); } DelNV(sChan); return true; } bool IsAutoCycle(const CString& sChan) { for (const CString& s : m_vsNegChans) { if (sChan.WildCmp(s, CString::CaseInsensitive)) { return false; } } for (const CString& s : m_vsChans) { if (sChan.WildCmp(s, CString::CaseInsensitive)) { return true; } } return false; } private: vector m_vsChans; vector m_vsNegChans; TCacheMap m_recentlyCycled; }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("autocycle"); Info.SetHasArgs(true); Info.SetArgsHelpText(Info.t_s( "List of channel masks and channel masks with ! before them.")); } NETWORKMODULEDEFS( CAutoCycleMod, t_s("Rejoins channels to gain Op if you're the only user left")) znc-1.9.1/modules/autoop.cpp0000644000175000017500000004712714641222733016242 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include using std::map; using std::set; using std::vector; class CAutoOpMod; #define AUTOOP_CHALLENGE_LENGTH 32 class CAutoOpTimer : public CTimer { public: CAutoOpTimer(CAutoOpMod* pModule) : CTimer((CModule*)pModule, 20, 0, "AutoOpChecker", "Check channels for auto op candidates") { m_pParent = pModule; } ~CAutoOpTimer() override {} private: protected: void RunJob() override; CAutoOpMod* m_pParent; }; class CAutoOpUser { public: CAutoOpUser() {} CAutoOpUser(const CString& sLine) { FromString(sLine); } CAutoOpUser(const CString& sUsername, const CString& sUserKey, const CString& sHostmasks, const CString& sChannels) : m_sUsername(sUsername), m_sUserKey(sUserKey) { AddHostmasks(sHostmasks); AddChans(sChannels); } virtual ~CAutoOpUser() {} const CString& GetUsername() const { return m_sUsername; } const CString& GetUserKey() const { return m_sUserKey; } bool ChannelMatches(const CString& sChan) const { for (const CString& s : m_ssChans) { if (sChan.AsLower().WildCmp(s, CString::CaseInsensitive)) { return true; } } return false; } bool HostMatches(const CString& sHostmask) { for (const CString& s : m_ssHostmasks) { if (sHostmask.WildCmp(s, CString::CaseInsensitive)) { return true; } } return false; } CString GetHostmasks() const { return CString(",").Join(m_ssHostmasks.begin(), m_ssHostmasks.end()); } CString GetChannels() const { return CString(" ").Join(m_ssChans.begin(), m_ssChans.end()); } bool DelHostmasks(const CString& sHostmasks) { VCString vsHostmasks; sHostmasks.Split(",", vsHostmasks); for (const CString& s : vsHostmasks) { m_ssHostmasks.erase(s); } return m_ssHostmasks.empty(); } void AddHostmasks(const CString& sHostmasks) { VCString vsHostmasks; sHostmasks.Split(",", vsHostmasks); for (const CString& s : vsHostmasks) { m_ssHostmasks.insert(s); } } void DelChans(const CString& sChans) { VCString vsChans; sChans.Split(" ", vsChans); for (const CString& sChan : vsChans) { m_ssChans.erase(sChan.AsLower()); } } void AddChans(const CString& sChans) { VCString vsChans; sChans.Split(" ", vsChans); for (const CString& sChan : vsChans) { m_ssChans.insert(sChan.AsLower()); } } CString ToString() const { return m_sUsername + "\t" + GetHostmasks() + "\t" + m_sUserKey + "\t" + GetChannels(); } bool FromString(const CString& sLine) { m_sUsername = sLine.Token(0, false, "\t"); // Trim because there was a bug which caused spaces in the hostname sLine.Token(1, false, "\t").Trim_n().Split(",", m_ssHostmasks); m_sUserKey = sLine.Token(2, false, "\t"); sLine.Token(3, false, "\t").Split(" ", m_ssChans); return !m_sUserKey.empty(); } private: protected: CString m_sUsername; CString m_sUserKey; set m_ssHostmasks; set m_ssChans; }; class CAutoOpMod : public CModule { public: MODCONSTRUCTOR(CAutoOpMod) { AddHelpCommand(); AddCommand("ListUsers", "", t_d("List all users"), [=](const CString& sLine) { OnListUsersCommand(sLine); }); AddCommand("AddChans", t_d(" [channel] ..."), t_d("Adds channels to a user"), [=](const CString& sLine) { OnAddChansCommand(sLine); }); AddCommand("DelChans", t_d(" [channel] ..."), t_d("Removes channels from a user"), [=](const CString& sLine) { OnDelChansCommand(sLine); }); AddCommand("AddMasks", t_d(" ,[mask] ..."), t_d("Adds masks to a user"), [=](const CString& sLine) { OnAddMasksCommand(sLine); }); AddCommand("DelMasks", t_d(" ,[mask] ..."), t_d("Removes masks from a user"), [=](const CString& sLine) { OnDelMasksCommand(sLine); }); AddCommand("AddUser", t_d(" [,...] [channels]"), t_d("Adds a user"), [=](const CString& sLine) { OnAddUserCommand(sLine); }); AddCommand("DelUser", t_d(""), t_d("Removes a user"), [=](const CString& sLine) { OnDelUserCommand(sLine); }); } bool OnLoad(const CString& sArgs, CString& sMessage) override { AddTimer(new CAutoOpTimer(this)); // Load the users for (MCString::iterator it = BeginNV(); it != EndNV(); ++it) { const CString& sLine = it->second; CAutoOpUser* pUser = new CAutoOpUser; if (!pUser->FromString(sLine) || FindUser(pUser->GetUsername().AsLower())) { delete pUser; } else { m_msUsers[pUser->GetUsername().AsLower()] = pUser; } } return true; } ~CAutoOpMod() override { for (const auto& it : m_msUsers) { delete it.second; } m_msUsers.clear(); } void OnJoin(const CNick& Nick, CChan& Channel) override { // If we have ops in this chan if (Channel.HasPerm(CChan::Op)) { CheckAutoOp(Nick, Channel); } } void OnQuit(const CNick& Nick, const CString& sMessage, const vector& vChans) override { MCString::iterator it = m_msQueue.find(Nick.GetNick().AsLower()); if (it != m_msQueue.end()) { m_msQueue.erase(it); } } void OnNick(const CNick& OldNick, const CString& sNewNick, const vector& vChans) override { // Update the queue with nick changes MCString::iterator it = m_msQueue.find(OldNick.GetNick().AsLower()); if (it != m_msQueue.end()) { m_msQueue[sNewNick.AsLower()] = it->second; m_msQueue.erase(it); } } EModRet OnPrivNotice(CNick& Nick, CString& sMessage) override { if (!sMessage.Token(0).Equals("!ZNCAO")) { return CONTINUE; } CString sCommand = sMessage.Token(1); if (sCommand.Equals("CHALLENGE")) { ChallengeRespond(Nick, sMessage.Token(2)); } else if (sCommand.Equals("RESPONSE")) { VerifyResponse(Nick, sMessage.Token(2)); } return HALTCORE; } void OnOp2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) override { if (Nick.GetNick() == GetNetwork()->GetIRCNick().GetNick()) { const map& msNicks = Channel.GetNicks(); for (const auto& it : msNicks) { if (!it.second.HasPerm(CChan::Op)) { CheckAutoOp(it.second, Channel); } } } } void OnModCommand(const CString& sLine) override { CString sCommand = sLine.Token(0).AsUpper(); if (sCommand.Equals("TIMERS")) { // for testing purposes - hidden from help ListTimers(); } else { HandleCommand(sLine); } } void OnAddUserCommand(const CString& sLine) { CString sUser = sLine.Token(1); CString sHost = sLine.Token(2); CString sKey = sLine.Token(3); if (sHost.empty()) { PutModule( t_s("Usage: AddUser [,...] " "[channels]")); } else { CAutoOpUser* pUser = AddUser(sUser, sKey, sHost, sLine.Token(4, true)); if (pUser) { SetNV(sUser, pUser->ToString()); } } } void OnDelUserCommand(const CString& sLine) { CString sUser = sLine.Token(1); if (sUser.empty()) { PutModule(t_s("Usage: DelUser ")); } else { DelUser(sUser); DelNV(sUser); } } void OnListUsersCommand(const CString& sLine) { if (m_msUsers.empty()) { PutModule(t_s("There are no users defined")); return; } CTable Table; Table.AddColumn(t_s("User")); Table.AddColumn(t_s("Hostmasks")); Table.AddColumn(t_s("Key")); Table.AddColumn(t_s("Channels")); for (const auto& it : m_msUsers) { VCString vsHostmasks; it.second->GetHostmasks().Split(",", vsHostmasks); for (unsigned int a = 0; a < vsHostmasks.size(); a++) { Table.AddRow(); if (a == 0) { Table.SetCell(t_s("User"), it.second->GetUsername()); Table.SetCell(t_s("Key"), it.second->GetUserKey()); Table.SetCell(t_s("Channels"), it.second->GetChannels()); } else if (a == vsHostmasks.size() - 1) { Table.SetCell(t_s("User"), "`-"); } else { Table.SetCell(t_s("User"), "|-"); } Table.SetCell(t_s("Hostmasks"), vsHostmasks[a]); } } PutModule(Table); } void OnAddChansCommand(const CString& sLine) { CString sUser = sLine.Token(1); CString sChans = sLine.Token(2, true); if (sChans.empty()) { PutModule(t_s("Usage: AddChans [channel] ...")); return; } CAutoOpUser* pUser = FindUser(sUser); if (!pUser) { PutModule(t_s("No such user")); return; } pUser->AddChans(sChans); PutModule(t_f("Channel(s) added to user {1}")(pUser->GetUsername())); SetNV(pUser->GetUsername(), pUser->ToString()); } void OnDelChansCommand(const CString& sLine) { CString sUser = sLine.Token(1); CString sChans = sLine.Token(2, true); if (sChans.empty()) { PutModule(t_s("Usage: DelChans [channel] ...")); return; } CAutoOpUser* pUser = FindUser(sUser); if (!pUser) { PutModule(t_s("No such user")); return; } pUser->DelChans(sChans); PutModule( t_f("Channel(s) Removed from user {1}")(pUser->GetUsername())); SetNV(pUser->GetUsername(), pUser->ToString()); } void OnAddMasksCommand(const CString& sLine) { CString sUser = sLine.Token(1); CString sHostmasks = sLine.Token(2); if (sHostmasks.empty()) { PutModule(t_s("Usage: AddMasks ,[mask] ...")); return; } CAutoOpUser* pUser = FindUser(sUser); if (!pUser) { PutModule(t_s("No such user")); return; } pUser->AddHostmasks(sHostmasks); PutModule(t_f("Hostmasks(s) added to user {1}")(pUser->GetUsername())); SetNV(pUser->GetUsername(), pUser->ToString()); } void OnDelMasksCommand(const CString& sLine) { CString sUser = sLine.Token(1); CString sHostmasks = sLine.Token(2); if (sHostmasks.empty()) { PutModule(t_s("Usage: DelMasks ,[mask] ...")); return; } CAutoOpUser* pUser = FindUser(sUser); if (!pUser) { PutModule(t_s("No such user")); return; } if (pUser->DelHostmasks(sHostmasks)) { PutModule(t_f("Removed user {1} with key {2} and channels {3}")( pUser->GetUsername(), pUser->GetUserKey(), pUser->GetChannels())); DelUser(sUser); DelNV(sUser); } else { PutModule(t_f("Hostmasks(s) Removed from user {1}")( pUser->GetUsername())); SetNV(pUser->GetUsername(), pUser->ToString()); } } CAutoOpUser* FindUser(const CString& sUser) { map::iterator it = m_msUsers.find(sUser.AsLower()); return (it != m_msUsers.end()) ? it->second : nullptr; } CAutoOpUser* FindUserByHost(const CString& sHostmask, const CString& sChannel = "") { for (const auto& it : m_msUsers) { CAutoOpUser* pUser = it.second; if (pUser->HostMatches(sHostmask) && (sChannel.empty() || pUser->ChannelMatches(sChannel))) { return pUser; } } return nullptr; } bool CheckAutoOp(const CNick& Nick, CChan& Channel) { CAutoOpUser* pUser = FindUserByHost(Nick.GetHostMask(), Channel.GetName()); if (!pUser) { return false; } if (pUser->GetUserKey().Equals("__NOKEY__")) { PutIRC("MODE " + Channel.GetName() + " +o " + Nick.GetNick()); } else { // then insert this nick into the queue, the timer does the rest CString sNick = Nick.GetNick().AsLower(); if (m_msQueue.find(sNick) == m_msQueue.end()) { m_msQueue[sNick] = ""; } } return true; } void DelUser(const CString& sUser) { map::iterator it = m_msUsers.find(sUser.AsLower()); if (it == m_msUsers.end()) { PutModule(t_s("No such user")); return; } delete it->second; m_msUsers.erase(it); PutModule(t_f("User {1} removed")(sUser)); } CAutoOpUser* AddUser(const CString& sUser, const CString& sKey, const CString& sHosts, const CString& sChans) { if (m_msUsers.find(sUser) != m_msUsers.end()) { PutModule(t_s("That user already exists")); return nullptr; } CAutoOpUser* pUser = new CAutoOpUser(sUser, sKey, sHosts, sChans); m_msUsers[sUser.AsLower()] = pUser; PutModule(t_f("User {1} added with hostmask(s) {2}")(sUser, sHosts)); return pUser; } bool ChallengeRespond(const CNick& Nick, const CString& sChallenge) { // Validate before responding - don't blindly trust everyone bool bValid = false; bool bMatchedHost = false; CAutoOpUser* pUser = nullptr; for (const auto& it : m_msUsers) { pUser = it.second; // First verify that the person who challenged us matches a user's // host if (pUser->HostMatches(Nick.GetHostMask())) { const vector& Chans = GetNetwork()->GetChans(); bMatchedHost = true; // Also verify that they are opped in at least one of the user's // chans for (CChan* pChan : Chans) { const CNick* pNick = pChan->FindNick(Nick.GetNick()); if (pNick) { if (pNick->HasPerm(CChan::Op) && pUser->ChannelMatches(pChan->GetName())) { bValid = true; break; } } } if (bValid) { break; } } } if (!bValid) { if (bMatchedHost) { PutModule(t_f( "[{1}] sent us a challenge but they are not opped in any " "defined channels.")(Nick.GetHostMask())); } else { PutModule( t_f("[{1}] sent us a challenge but they do not match a " "defined user.")(Nick.GetHostMask())); } return false; } if (sChallenge.length() != AUTOOP_CHALLENGE_LENGTH) { PutModule(t_f("WARNING! [{1}] sent an invalid challenge.")( Nick.GetHostMask())); return false; } CString sResponse = pUser->GetUserKey() + "::" + sChallenge; PutIRC("NOTICE " + Nick.GetNick() + " :!ZNCAO RESPONSE " + sResponse.MD5()); return false; } bool VerifyResponse(const CNick& Nick, const CString& sResponse) { MCString::iterator itQueue = m_msQueue.find(Nick.GetNick().AsLower()); if (itQueue == m_msQueue.end()) { PutModule( t_f("[{1}] sent an unchallenged response. This could be due " "to lag.")(Nick.GetHostMask())); return false; } CString sChallenge = itQueue->second; m_msQueue.erase(itQueue); for (const auto& it : m_msUsers) { if (it.second->HostMatches(Nick.GetHostMask())) { if (sResponse == CString(it.second->GetUserKey() + "::" + sChallenge) .MD5()) { OpUser(Nick, *it.second); return true; } else { PutModule( t_f("WARNING! [{1}] sent a bad response. Please " "verify that you have their correct password.")( Nick.GetHostMask())); return false; } } } PutModule( t_f("WARNING! [{1}] sent a response but did not match any defined " "users.")(Nick.GetHostMask())); return false; } void ProcessQueue() { bool bRemoved = true; // First remove any stale challenges while (bRemoved) { bRemoved = false; for (MCString::iterator it = m_msQueue.begin(); it != m_msQueue.end(); ++it) { if (!it->second.empty()) { m_msQueue.erase(it); bRemoved = true; break; } } } // Now issue challenges for the new users in the queue for (auto& it : m_msQueue) { it.second = CString::RandomString(AUTOOP_CHALLENGE_LENGTH); PutIRC("NOTICE " + it.first + " :!ZNCAO CHALLENGE " + it.second); } } void OpUser(const CNick& Nick, const CAutoOpUser& User) { const vector& Chans = GetNetwork()->GetChans(); for (CChan* pChan : Chans) { if (pChan->HasPerm(CChan::Op) && User.ChannelMatches(pChan->GetName())) { const CNick* pNick = pChan->FindNick(Nick.GetNick()); if (pNick && !pNick->HasPerm(CChan::Op)) { PutIRC("MODE " + pChan->GetName() + " +o " + Nick.GetNick()); } } } } private: map m_msUsers; MCString m_msQueue; }; void CAutoOpTimer::RunJob() { m_pParent->ProcessQueue(); } template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("autoop"); } NETWORKMODULEDEFS(CAutoOpMod, t_s("Auto op the good people")) znc-1.9.1/modules/autoreply.cpp0000644000175000017500000000601514641222733016746 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * Copyright (C) 2008 Michael "Svedrin" Ziegler diese-addy@funzt-halt.net * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include class CAutoReplyMod : public CModule { public: MODCONSTRUCTOR(CAutoReplyMod) { AddHelpCommand(); AddCommand("Set", t_d(""), t_d("Sets a new reply"), [=](const CString& sLine) { OnSetCommand(sLine); }); AddCommand("Show", "", t_d("Displays the current query reply"), [=](const CString& sLine) { OnShowCommand(sLine); }); m_Messaged.SetTTL(1000 * 120); } ~CAutoReplyMod() override {} bool OnLoad(const CString& sArgs, CString& sMessage) override { if (!sArgs.empty()) { SetReply(sArgs); } return true; } void SetReply(const CString& sReply) { SetNV("Reply", sReply); } CString GetReply() { CString sReply = GetNV("Reply"); if (sReply.empty()) { sReply = "%nick% is currently away, try again later"; SetReply(sReply); } return ExpandString(sReply); } void Handle(const CString& sNick) { CIRCSock* pIRCSock = GetNetwork()->GetIRCSock(); if (!pIRCSock) // WTF? return; if (sNick == pIRCSock->GetNick()) return; if (m_Messaged.HasItem(sNick)) return; if (GetNetwork()->IsUserAttached()) return; m_Messaged.AddItem(sNick); PutIRC("NOTICE " + sNick + " :" + GetReply()); } EModRet OnPrivMsg(CNick& Nick, CString& sMessage) override { Handle(Nick.GetNick()); return CONTINUE; } void OnShowCommand(const CString& sCommand) { CString sReply = GetReply(); PutModule(t_f("Current reply is: {1} ({2})")(GetNV("Reply"), sReply)); } void OnSetCommand(const CString& sCommand) { SetReply(sCommand.Token(1, true)); PutModule( t_f("New reply set to: {1} ({2})")(GetNV("Reply"), GetReply())); } private: TCacheMap m_Messaged; }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("autoreply"); Info.AddType(CModInfo::NetworkModule); Info.SetHasArgs(true); Info.SetArgsHelpText(Info.t_s( "You might specify a reply text. It is used when automatically " "answering queries, if you are not connected to ZNC.")); } USERMODULEDEFS(CAutoReplyMod, t_s("Reply to queries when you are away")) znc-1.9.1/modules/autovoice.cpp0000644000175000017500000002514614641222733016726 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include using std::map; using std::set; class CAutoVoiceUser { public: CAutoVoiceUser() {} CAutoVoiceUser(const CString& sLine) { FromString(sLine); } CAutoVoiceUser(const CString& sUsername, const CString& sHostmask, const CString& sChannels) : m_sUsername(sUsername), m_sHostmask(sHostmask) { AddChans(sChannels); } virtual ~CAutoVoiceUser() {} const CString& GetUsername() const { return m_sUsername; } const CString& GetHostmask() const { return m_sHostmask; } bool ChannelMatches(const CString& sChan) const { for (const CString& s : m_ssChans) { if (sChan.AsLower().WildCmp(s, CString::CaseInsensitive)) { return true; } } return false; } bool HostMatches(const CString& sHostmask) { return sHostmask.WildCmp(m_sHostmask, CString::CaseInsensitive); } CString GetChannels() const { CString sRet; for (const CString& sChan : m_ssChans) { if (!sRet.empty()) { sRet += " "; } sRet += sChan; } return sRet; } void DelChans(const CString& sChans) { VCString vsChans; sChans.Split(" ", vsChans); for (const CString& sChan : vsChans) { m_ssChans.erase(sChan.AsLower()); } } void AddChans(const CString& sChans) { VCString vsChans; sChans.Split(" ", vsChans); for (const CString& sChan : vsChans) { m_ssChans.insert(sChan.AsLower()); } } CString ToString() const { CString sChans; for (const CString& sChan : m_ssChans) { if (!sChans.empty()) { sChans += " "; } sChans += sChan; } return m_sUsername + "\t" + m_sHostmask + "\t" + sChans; } bool FromString(const CString& sLine) { m_sUsername = sLine.Token(0, false, "\t"); m_sHostmask = sLine.Token(1, false, "\t"); sLine.Token(2, false, "\t").Split(" ", m_ssChans); return !m_sHostmask.empty(); } private: protected: CString m_sUsername; CString m_sHostmask; set m_ssChans; }; class CAutoVoiceMod : public CModule { public: MODCONSTRUCTOR(CAutoVoiceMod) { AddHelpCommand(); AddCommand("ListUsers", "", t_d("List all users"), [=](const CString& sLine) { OnListUsersCommand(sLine); }); AddCommand("AddChans", t_d(" [channel] ..."), t_d("Adds channels to a user"), [=](const CString& sLine) { OnAddChansCommand(sLine); }); AddCommand("DelChans", t_d(" [channel] ..."), t_d("Removes channels from a user"), [=](const CString& sLine) { OnDelChansCommand(sLine); }); AddCommand("AddUser", t_d(" [channels]"), t_d("Adds a user"), [=](const CString& sLine) { OnAddUserCommand(sLine); }); AddCommand("DelUser", t_d(""), t_d("Removes a user"), [=](const CString& sLine) { OnDelUserCommand(sLine); }); } bool OnLoad(const CString& sArgs, CString& sMessage) override { // Load the chans from the command line unsigned int a = 0; VCString vsChans; sArgs.Split(" ", vsChans, false); for (const CString& sChan : vsChans) { CString sName = "Args"; sName += CString(a); AddUser(sName, "*", sChan); } // Load the saved users for (MCString::iterator it = BeginNV(); it != EndNV(); ++it) { const CString& sLine = it->second; CAutoVoiceUser* pUser = new CAutoVoiceUser; if (!pUser->FromString(sLine) || FindUser(pUser->GetUsername().AsLower())) { delete pUser; } else { m_msUsers[pUser->GetUsername().AsLower()] = pUser; } } return true; } ~CAutoVoiceMod() override { for (const auto& it : m_msUsers) { delete it.second; } m_msUsers.clear(); } void OnJoin(const CNick& Nick, CChan& Channel) override { // If we have ops in this chan if (Channel.HasPerm(CChan::Op) || Channel.HasPerm(CChan::HalfOp)) { for (const auto& it : m_msUsers) { // and the nick who joined is a valid user if (it.second->HostMatches(Nick.GetHostMask()) && it.second->ChannelMatches(Channel.GetName())) { PutIRC("MODE " + Channel.GetName() + " +v " + Nick.GetNick()); break; } } } } void OnOp2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) override { if (Nick.NickEquals(GetNetwork()->GetNick())) { const map& msNicks = Channel.GetNicks(); for (const auto& it : msNicks) { if (!it.second.HasPerm(CChan::Voice)) { CheckAutoVoice(it.second, Channel); } } } } bool CheckAutoVoice(const CNick& Nick, CChan& Channel) { CAutoVoiceUser* pUser = FindUserByHost(Nick.GetHostMask(), Channel.GetName()); if (!pUser) { return false; } PutIRC("MODE " + Channel.GetName() + " +v " + Nick.GetNick()); return true; } void OnAddUserCommand(const CString& sLine) { CString sUser = sLine.Token(1); CString sHost = sLine.Token(2); if (sHost.empty()) { PutModule(t_s("Usage: AddUser [channels]")); } else { CAutoVoiceUser* pUser = AddUser(sUser, sHost, sLine.Token(3, true)); if (pUser) { SetNV(sUser, pUser->ToString()); } } } void OnDelUserCommand(const CString& sLine) { CString sUser = sLine.Token(1); if (sUser.empty()) { PutModule(t_s("Usage: DelUser ")); } else { DelUser(sUser); DelNV(sUser); } } void OnListUsersCommand(const CString& sLine) { if (m_msUsers.empty()) { PutModule(t_s("There are no users defined")); return; } CTable Table; Table.AddColumn(t_s("User")); Table.AddColumn(t_s("Hostmask")); Table.AddColumn(t_s("Channels")); for (const auto& it : m_msUsers) { Table.AddRow(); Table.SetCell(t_s("User"), it.second->GetUsername()); Table.SetCell(t_s("Hostmask"), it.second->GetHostmask()); Table.SetCell(t_s("Channels"), it.second->GetChannels()); } PutModule(Table); } void OnAddChansCommand(const CString& sLine) { CString sUser = sLine.Token(1); CString sChans = sLine.Token(2, true); if (sChans.empty()) { PutModule(t_s("Usage: AddChans [channel] ...")); return; } CAutoVoiceUser* pUser = FindUser(sUser); if (!pUser) { PutModule(t_s("No such user")); return; } pUser->AddChans(sChans); PutModule(t_f("Channel(s) added to user {1}")(pUser->GetUsername())); SetNV(pUser->GetUsername(), pUser->ToString()); } void OnDelChansCommand(const CString& sLine) { CString sUser = sLine.Token(1); CString sChans = sLine.Token(2, true); if (sChans.empty()) { PutModule(t_s("Usage: DelChans [channel] ...")); return; } CAutoVoiceUser* pUser = FindUser(sUser); if (!pUser) { PutModule(t_s("No such user")); return; } pUser->DelChans(sChans); PutModule( t_f("Channel(s) Removed from user {1}")(pUser->GetUsername())); SetNV(pUser->GetUsername(), pUser->ToString()); } CAutoVoiceUser* FindUser(const CString& sUser) { map::iterator it = m_msUsers.find(sUser.AsLower()); return (it != m_msUsers.end()) ? it->second : nullptr; } CAutoVoiceUser* FindUserByHost(const CString& sHostmask, const CString& sChannel = "") { for (const auto& it : m_msUsers) { CAutoVoiceUser* pUser = it.second; if (pUser->HostMatches(sHostmask) && (sChannel.empty() || pUser->ChannelMatches(sChannel))) { return pUser; } } return nullptr; } void DelUser(const CString& sUser) { map::iterator it = m_msUsers.find(sUser.AsLower()); if (it == m_msUsers.end()) { PutModule(t_s("No such user")); return; } delete it->second; m_msUsers.erase(it); PutModule(t_f("User {1} removed")(sUser)); } CAutoVoiceUser* AddUser(const CString& sUser, const CString& sHost, const CString& sChans) { if (m_msUsers.find(sUser) != m_msUsers.end()) { PutModule(t_s("That user already exists")); return nullptr; } CAutoVoiceUser* pUser = new CAutoVoiceUser(sUser, sHost, sChans); m_msUsers[sUser.AsLower()] = pUser; PutModule(t_f("User {1} added with hostmask {2}")(sUser, sHost)); return pUser; } private: map m_msUsers; }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("autovoice"); Info.SetHasArgs(true); Info.SetArgsHelpText(Info.t_s( "Each argument is either a channel you want autovoice for (which can " "include wildcards) or, if it starts with !, it is an exception for " "autovoice.")); } NETWORKMODULEDEFS(CAutoVoiceMod, t_s("Auto voice the good people")) znc-1.9.1/modules/awaynick.cpp0000644000175000017500000000212214641222733016523 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include class CAwayNickMod : public CModule { public: MODCONSTRUCTOR(CAwayNickMod) {} bool OnLoad(const CString&, CString& sMessage) override { sMessage = "retired module - see https://wiki.znc.in/awaynick"; return false; } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("awaynick"); } NETWORKMODULEDEFS(CAwayNickMod, "retired module - see https://wiki.znc.in/awaynick") znc-1.9.1/modules/awaystore.cpp0000644000175000017500000004101014641222733016732 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * Author: imaginos * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Quiet Away and message logger * * I originally wrote this module for when I had multiple clients connected to *ZNC. I would leave work and forget to close my client, arriving at home * and re-attaching there someone may have messaged me in commute and I wouldn't *know it until I would arrive back at work the next day. I wrote it such that * my xchat client would monitor desktop activity and ping the module to let it *know I was active. Within a few minutes of inactivity the pinging stops and * the away module sets the user as away and logging commences. */ #define REQUIRESSL #include #include #include #include #include #include using std::vector; using std::map; #define CRYPT_VERIFICATION_TOKEN "::__:AWAY:__::" class CAway; class CAwayJob : public CTimer { public: CAwayJob(CModule* pModule, unsigned int uInterval, unsigned int uCycles, const CString& sLabel, const CString& sDescription) : CTimer(pModule, uInterval, uCycles, sLabel, sDescription) {} ~CAwayJob() override {} protected: void RunJob() override; }; class CAway : public CModule { void AwayCommand(const CString& sCommand) { CString sReason; timeval curtime; gettimeofday(&curtime, nullptr); if (sCommand.Token(1) != "-quiet") { sReason = CUtils::FormatTime(curtime, sCommand.Token(1, true), GetUser()->GetTimezone()); PutModNotice(t_s("You have been marked as away")); } else { sReason = CUtils::FormatTime(curtime, sCommand.Token(2, true), GetUser()->GetTimezone()); } Away(false, sReason); } void BackCommand(const CString& sCommand) { if ((m_vMessages.empty()) && (sCommand.Token(1) != "-quiet")) PutModNotice(t_s("Welcome back!")); Ping(); Back(); } void MessagesCommand(const CString& sCommand) { for (u_int a = 0; a < m_vMessages.size(); a++) PutModule(m_vMessages[a]); } void ReplayCommand(const CString& sCommand) { CString nick = GetClient()->GetNick(); for (u_int a = 0; a < m_vMessages.size(); a++) { CString sWhom = m_vMessages[a].Token(1, false, ":"); CString sMessage = m_vMessages[a].Token(2, true, ":"); PutUser(":" + sWhom + " PRIVMSG " + nick + " :" + sMessage); } } void DeleteCommand(const CString& sCommand) { CString sWhich = sCommand.Token(1); if (sWhich == "all") { PutModNotice(t_f("Deleted {1} messages")(m_vMessages.size())); for (u_int a = 0; a < m_vMessages.size(); a++) m_vMessages.erase(m_vMessages.begin() + a--); } else if (sWhich.empty()) { PutModNotice(t_s("USAGE: delete ")); return; } else { u_int iNum = sWhich.ToUInt(); if (iNum >= m_vMessages.size()) { PutModNotice(t_s("Illegal message # requested")); return; } else { m_vMessages.erase(m_vMessages.begin() + iNum); PutModNotice(t_s("Message erased")); } SaveBufferToDisk(); } } void SaveCommand(const CString& sCommand) { if (m_saveMessages) { SaveBufferToDisk(); PutModNotice(t_s("Messages saved to disk")); } else { PutModNotice(t_s("There are no messages to save")); } } void PingCommand(const CString& sCommand) { Ping(); if (m_bIsAway) Back(); } void PassCommand(const CString& sCommand) { m_sPassword = sCommand.Token(1); PutModNotice(t_f("Password updated to [{1}]")(m_sPassword)); } void ShowCommand(const CString& sCommand) { map> msvOutput; for (u_int a = 0; a < m_vMessages.size(); a++) { CString sTime = m_vMessages[a].Token(0, false); CString sWhom = m_vMessages[a].Token(1, false); CString sMessage = m_vMessages[a].Token(2, true); if ((sTime.empty()) || (sWhom.empty()) || (sMessage.empty())) { // illegal format PutModule(t_f("Corrupt message! [{1}]")(m_vMessages[a])); m_vMessages.erase(m_vMessages.begin() + a--); continue; } time_t iTime = sTime.ToULong(); char szFormat[64]; struct tm t; localtime_r(&iTime, &t); size_t iCount = strftime(szFormat, 64, "%F %T", &t); if (iCount <= 0) { PutModule(t_f("Corrupt time stamp! [{1}]")(m_vMessages[a])); m_vMessages.erase(m_vMessages.begin() + a--); continue; } CString sTmp = " " + CString(a) + ") ["; sTmp.append(szFormat, iCount); sTmp += "] "; sTmp += sMessage; msvOutput[sWhom].push_back(sTmp); } for (map>::iterator it = msvOutput.begin(); it != msvOutput.end(); ++it) { PutModule(it->first); for (u_int a = 0; a < it->second.size(); a++) PutModule(it->second[a]); } PutModule(t_s("#--- End of messages")); } void EnableTimerCommand(const CString& sCommand) { SetAwayTime(300); PutModule(t_s("Timer set to 300 seconds")); } void DisableTimerCommand(const CString& sCommand) { SetAwayTime(0); PutModule(t_s("Timer disabled")); } void SetTimerCommand(const CString& sCommand) { int iSetting = sCommand.Token(1).ToInt(); SetAwayTime(iSetting); if (iSetting == 0) PutModule(t_s("Timer disabled")); else PutModule(t_f("Timer set to {1} seconds")(iSetting)); } void TimerCommand(const CString& sCommand) { PutModule(t_f("Current timer setting: {1} seconds")(GetAwayTime())); } public: MODCONSTRUCTOR(CAway) { Ping(); m_bIsAway = false; m_bBootError = false; m_saveMessages = true; m_chanMessages = false; SetAwayTime(300); AddTimer( new CAwayJob(this, 60, 0, "AwayJob", "Checks for idle and saves messages every 1 minute")); AddHelpCommand(); AddCommand("Away", static_cast(&CAway::AwayCommand), "[-quiet]"); AddCommand("Back", static_cast(&CAway::BackCommand), "[-quiet]"); AddCommand("Messages", static_cast(&CAway::BackCommand)); AddCommand("Delete", static_cast(&CAway::DeleteCommand), "delete "); AddCommand("Save", static_cast(&CAway::SaveCommand)); AddCommand("Ping", static_cast(&CAway::PingCommand)); AddCommand("Pass", static_cast(&CAway::PassCommand)); AddCommand("Show", static_cast(&CAway::ShowCommand)); AddCommand("Replay", static_cast(&CAway::ReplayCommand)); AddCommand("EnableTimer", static_cast( &CAway::EnableTimerCommand)); AddCommand("DisableTimer", static_cast( &CAway::DisableTimerCommand)); AddCommand("SetTimer", static_cast( &CAway::SetTimerCommand), ""); AddCommand("Timer", static_cast(&CAway::TimerCommand)); } ~CAway() override { if (!m_bBootError) SaveBufferToDisk(); } bool OnLoad(const CString& sArgs, CString& sMessage) override { CString sMyArgs = sArgs; size_t uIndex = 0; if (sMyArgs.Token(0) == "-nostore") { uIndex++; m_saveMessages = false; } if (sMyArgs.Token(uIndex) == "-chans") { uIndex++; m_chanMessages = true; } if (sMyArgs.Token(uIndex) == "-notimer") { SetAwayTime(0); sMyArgs = sMyArgs.Token(uIndex + 1, true); } else if (sMyArgs.Token(uIndex) == "-timer") { SetAwayTime(sMyArgs.Token(uIndex + 1).ToInt()); sMyArgs = sMyArgs.Token(uIndex + 2, true); } if (m_saveMessages) { if (!sMyArgs.empty()) { m_sPassword = CBlowfish::MD5(sMyArgs); } else { sMessage = t_s("This module needs as an argument a keyphrase used for " "encryption"); return false; } if (!BootStrap()) { sMessage = t_s( "Failed to decrypt your saved messages - " "Did you give the right encryption key as an argument to " "this module?"); m_bBootError = true; return false; } } return true; } void OnIRCConnected() override { if (m_bIsAway) { Away(true); // reset away if we are reconnected } else { // ircd seems to remember your away if you killed the client and // came back Back(); } } bool BootStrap() { CString sFile; if (DecryptMessages(sFile)) { VCString vsLines; VCString::iterator it; sFile.Split("\n", vsLines); for (it = vsLines.begin(); it != vsLines.end(); ++it) { CString sLine(*it); sLine.Trim(); AddMessage(sLine); } } else { m_sPassword = ""; CUtils::PrintError("[" + GetModName() + ".so] Failed to Decrypt Messages"); return (false); } return (true); } void SaveBufferToDisk() { if (!m_sPassword.empty()) { CString sFile = CRYPT_VERIFICATION_TOKEN; for (u_int b = 0; b < m_vMessages.size(); b++) sFile += m_vMessages[b] + "\n"; CBlowfish c(m_sPassword, BF_ENCRYPT); sFile = c.Crypt(sFile); CString sPath = GetPath(); if (!sPath.empty()) { CFile File(sPath); if (File.Open(O_WRONLY | O_CREAT | O_TRUNC, 0600)) { File.Chmod(0600); File.Write(sFile); } File.Close(); } } } void OnClientLogin() override { Back(true); } void OnClientDisconnect() override { Away(); } CString GetPath() { CString sBuffer = GetUser()->GetUsername(); CString sRet = GetSavePath(); sRet += "/.znc-away-" + CBlowfish::MD5(sBuffer, true); return (sRet); } void Away(bool bForce = false, const CString& sReason = "") { if ((!m_bIsAway) || (bForce)) { if (!bForce) m_sReason = sReason; else if (!sReason.empty()) m_sReason = sReason; time_t iTime = time(nullptr); char* pTime = ctime(&iTime); CString sTime; if (pTime) { sTime = pTime; sTime.Trim(); } if (m_sReason.empty()) m_sReason = "Auto Away at " + sTime; PutIRC("AWAY :" + m_sReason); m_bIsAway = true; } } void Back(bool bUsePrivMessage = false) { PutIRC("away"); m_bIsAway = false; if (!m_vMessages.empty()) { if (bUsePrivMessage) { PutModule(t_s("Welcome back!")); PutModule(t_f("You have {1} messages!")(m_vMessages.size())); } else { PutModNotice(t_s("Welcome back!")); PutModNotice(t_f("You have {1} messages!")(m_vMessages.size())); } } m_sReason = ""; } EModRet OnPrivMsg(CNick& Nick, CString& sMessage) override { if (m_bIsAway) AddMessage(time(nullptr), Nick, sMessage); return (CONTINUE); } EModRet OnChanMsg(CNick& nick, CChan& channel, CString& sMessage) override { if (m_bIsAway && m_chanMessages && sMessage.AsLower().find(m_pNetwork->GetCurNick().AsLower()) != CString::npos) { AddMessage(time(nullptr), nick, channel.GetName() + " " + sMessage); } return (CONTINUE); } EModRet OnPrivAction(CNick& Nick, CString& sMessage) override { if (m_bIsAway) { AddMessage(time(nullptr), Nick, "* " + sMessage); } return (CONTINUE); } EModRet OnUserNotice(CString& sTarget, CString& sMessage) override { Ping(); if (m_bIsAway) Back(); return (CONTINUE); } EModRet OnUserMsg(CString& sTarget, CString& sMessage) override { Ping(); if (m_bIsAway) Back(); return (CONTINUE); } EModRet OnUserAction(CString& sTarget, CString& sMessage) override { Ping(); if (m_bIsAway) Back(); return (CONTINUE); } time_t GetTimeStamp() const { return (m_iLastSentData); } void Ping() { m_iLastSentData = time(nullptr); } time_t GetAwayTime() { return m_iAutoAway; } void SetAwayTime(time_t u) { m_iAutoAway = u; } bool IsAway() { return (m_bIsAway); } private: CString m_sPassword; bool m_bBootError; bool DecryptMessages(CString& sBuffer) { CString sMessages = GetPath(); CString sFile; sBuffer = ""; CFile File(sMessages); if (sMessages.empty() || !File.Open() || !File.ReadFile(sFile)) { PutModule(t_s("Unable to find buffer")); return (true); // gonna be successful here } File.Close(); if (!sFile.empty()) { CBlowfish c(m_sPassword, BF_DECRYPT); sBuffer = c.Crypt(sFile); if (sBuffer.Left(strlen(CRYPT_VERIFICATION_TOKEN)) != CRYPT_VERIFICATION_TOKEN) { // failed to decode :( PutModule(t_s("Unable to decode encrypted messages")); return (false); } sBuffer.erase(0, strlen(CRYPT_VERIFICATION_TOKEN)); } return (true); } void AddMessage(time_t iTime, const CNick& Nick, const CString& sMessage) { if (Nick.GetNick() == GetNetwork()->GetIRCNick().GetNick()) return; // ignore messages from self AddMessage(CString(iTime) + " " + Nick.GetNickMask() + " " + sMessage); } void AddMessage(const CString& sText) { if (m_saveMessages) { m_vMessages.push_back(sText); } } time_t m_iLastSentData; bool m_bIsAway; time_t m_iAutoAway; vector m_vMessages; CString m_sReason; bool m_saveMessages; bool m_chanMessages; }; void CAwayJob::RunJob() { CAway* p = (CAway*)GetModule(); p->SaveBufferToDisk(); if (!p->IsAway()) { time_t iNow = time(nullptr); if ((iNow - p->GetTimeStamp()) > p->GetAwayTime() && p->GetAwayTime() != 0) p->Away(); } } template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("awaystore"); Info.SetHasArgs(true); Info.SetArgsHelpText(Info.t_s( "[ -notimer | -timer N ] [-chans] passw0rd . N is number of seconds, " "600 by default.")); } NETWORKMODULEDEFS( CAway, t_s("Adds auto-away with logging, useful when you use ZNC from " "different locations")) znc-1.9.1/modules/block_motd.cpp0000644000175000017500000000600414641222733017035 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include using std::set; class CBlockMotd : public CModule { public: MODCONSTRUCTOR(CBlockMotd) { AddHelpCommand(); AddCommand("GetMotd", t_d("[]"), t_d("Override the block with this command. Can optionally " "specify which server to query."), [this](const CString& sLine) { OverrideCommand(sLine); }); } ~CBlockMotd() override {} void OverrideCommand(const CString& sLine) { if (!GetNetwork() || !GetNetwork()->GetIRCSock()) { PutModule(t_s("You are not connected to an IRC Server.")); return; } TemporarilyAcceptMotd(); const CString sServer = sLine.Token(1); if (sServer.empty()) { PutIRC("MOTD"); } else { PutIRC("MOTD " + sServer); } } EModRet OnNumericMessage(CNumericMessage& Message) override { if ((Message.GetCode() == 375 /* begin of MOTD */ || Message.GetCode() == 372 /* MOTD */) && !ShouldTemporarilyAcceptMotd()) return HALT; if (Message.GetCode() == 376 /* End of MOTD */) { if (!ShouldTemporarilyAcceptMotd()) { Message.SetParam(1, t_s("MOTD blocked by ZNC")); } StopTemporarilyAcceptingMotd(); } if (Message.GetCode() == 422) { // Server has no MOTD StopTemporarilyAcceptingMotd(); } return CONTINUE; } void OnIRCDisconnected() override { StopTemporarilyAcceptingMotd(); } bool ShouldTemporarilyAcceptMotd() const { return m_sTemporaryAcceptedMotdSocks.count(GetNetwork()->GetIRCSock()) > 0; } void TemporarilyAcceptMotd() { if (ShouldTemporarilyAcceptMotd()) { return; } m_sTemporaryAcceptedMotdSocks.insert(GetNetwork()->GetIRCSock()); } void StopTemporarilyAcceptingMotd() { m_sTemporaryAcceptedMotdSocks.erase(GetNetwork()->GetIRCSock()); } private: set m_sTemporaryAcceptedMotdSocks; }; template <> void TModInfo(CModInfo& Info) { Info.AddType(CModInfo::NetworkModule); Info.AddType(CModInfo::GlobalModule); Info.SetWikiPage("block_motd"); } USERMODULEDEFS( CBlockMotd, t_s("Block the MOTD from IRC so it's not sent to your client(s).")) znc-1.9.1/modules/blockuser.cpp0000644000175000017500000001671014641222733016716 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include using std::vector; #define MESSAGE \ t_s("Your account has been disabled. Contact your administrator.") class CBlockUser : public CModule { public: MODCONSTRUCTOR(CBlockUser) { AddHelpCommand(); AddCommand("List", "", t_d("List blocked users"), [this](const CString& sLine) { OnListCommand(sLine); }); AddCommand("Block", t_d(""), t_d("Block a user"), [this](const CString& sLine) { OnBlockCommand(sLine); }); AddCommand("Unblock", t_d(""), t_d("Unblock a user"), [this](const CString& sLine) { OnUnblockCommand(sLine); }); } ~CBlockUser() override {} bool OnLoad(const CString& sArgs, CString& sMessage) override { VCString vArgs; VCString::iterator it; MCString::iterator it2; // Load saved settings for (it2 = BeginNV(); it2 != EndNV(); ++it2) { // Ignore errors Block(it2->first); } // Parse arguments, each argument is a user name to block sArgs.Split(" ", vArgs, false); for (it = vArgs.begin(); it != vArgs.end(); ++it) { if (!Block(*it)) { sMessage = t_f("Could not block {1}")(*it); return false; } } return true; } /* If a user is on the blocked list and tries to log in, displays - MESSAGE and stops their log in attempt.*/ EModRet OnLoginAttempt(std::shared_ptr Auth) override { if (IsBlocked(Auth->GetUsername())) { Auth->RefuseLogin(MESSAGE); return HALT; } return CONTINUE; } void OnModCommand(const CString& sCommand) override { if (!GetUser()->IsAdmin()) { PutModule(t_s("Access denied")); } else { HandleCommand(sCommand); } } // Displays all blocked users as a list. void OnListCommand(const CString& sCommand) { if (BeginNV() == EndNV()) { PutModule(t_s("No users are blocked")); return; } PutModule(t_s("Blocked users:")); for (auto it = BeginNV(); it != EndNV(); ++it) { PutModule(it->first); } } /* Blocks a user if possible(ie not self, not already blocked). Displays an error message if not possible. */ void OnBlockCommand(const CString& sCommand) { CString sUser = sCommand.Token(1, true); if (sUser.empty()) { PutModule(t_s("Usage: Block ")); return; } if (GetUser()->GetUsername().Equals(sUser)) { PutModule(t_s("You can't block yourself")); return; } if (Block(sUser)) PutModule(t_f("Blocked {1}")(sUser)); else PutModule(t_f("Could not block {1} (misspelled?)")(sUser)); } // Unblocks a user from the blocked list. void OnUnblockCommand(const CString& sCommand) { CString sUser = sCommand.Token(1, true); if (sUser.empty()) { PutModule(t_s("Usage: Unblock ")); return; } if (DelNV(sUser)) PutModule(t_f("Unblocked {1}")(sUser)); else PutModule(t_s("This user is not blocked")); } // Provides GUI to configure this module by adding a widget to user page in webadmin. bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) override { if (sPageName == "webadmin/user" && WebSock.GetSession()->IsAdmin()) { CString sAction = Tmpl["WebadminAction"]; if (sAction == "display") { Tmpl["Blocked"] = CString(IsBlocked(Tmpl["Username"])); Tmpl["Self"] = CString(Tmpl["Username"].Equals( WebSock.GetSession()->GetUser()->GetUsername())); return true; } if (sAction == "change" && WebSock.GetParam("embed_blockuser_presented").ToBool()) { if (Tmpl["Username"].Equals( WebSock.GetSession()->GetUser()->GetUsername()) && WebSock.GetParam("embed_blockuser_block").ToBool()) { WebSock.GetSession()->AddError( t_s("You can't block yourself")); } else if (WebSock.GetParam("embed_blockuser_block").ToBool()) { if (!WebSock.GetParam("embed_blockuser_old").ToBool()) { if (Block(Tmpl["Username"])) { WebSock.GetSession()->AddSuccess( t_f("Blocked {1}")(Tmpl["Username"])); } else { WebSock.GetSession()->AddError( t_f("Couldn't block {1}")(Tmpl["Username"])); } } } else if (WebSock.GetParam("embed_blockuser_old").ToBool()) { if (DelNV(Tmpl["Username"])) { WebSock.GetSession()->AddSuccess( t_f("Unblocked {1}")(Tmpl["Username"])); } else { WebSock.GetSession()->AddError( t_f("User {1} is not blocked")(Tmpl["Username"])); } } return true; } } return false; } private: /* Iterates through all blocked users and returns true if the specified user (sUser) is blocked, else returns false.*/ bool IsBlocked(const CString& sUser) { MCString::iterator it; for (it = BeginNV(); it != EndNV(); ++it) { if (sUser == it->first) { return true; } } return false; } bool Block(const CString& sUser) { CUser* pUser = CZNC::Get().FindUser(sUser); if (!pUser) return false; // Disconnect all clients vector vpClients = pUser->GetAllClients(); vector::iterator it; for (it = vpClients.begin(); it != vpClients.end(); ++it) { (*it)->PutStatusNotice(MESSAGE); (*it)->Close(Csock::CLT_AFTERWRITE); } // Disconnect all networks from irc vector vNetworks = pUser->GetNetworks(); for (vector::iterator it2 = vNetworks.begin(); it2 != vNetworks.end(); ++it2) { (*it2)->SetIRCConnectEnabled(false); } SetNV(pUser->GetUsername(), ""); return true; } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("blockuser"); Info.SetHasArgs(true); Info.SetArgsHelpText( Info.t_s("Enter one or more user names. Separate them by spaces.")); } GLOBALMODULEDEFS(CBlockUser, t_s("Block certain users from logging in.")) znc-1.9.1/modules/bouncedcc.cpp0000644000175000017500000005100214641222733016643 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include using std::set; class CBounceDCCMod; class CDCCBounce : public CSocket { public: CDCCBounce(CBounceDCCMod* pMod, unsigned long uLongIP, unsigned short uPort, const CString& sFileName, const CString& sRemoteNick, const CString& sRemoteIP, bool bIsChat = false); CDCCBounce(CBounceDCCMod* pMod, const CString& sHostname, unsigned short uPort, const CString& sRemoteNick, const CString& sRemoteIP, const CString& sFileName, int iTimeout = 60, bool bIsChat = false); ~CDCCBounce() override; static unsigned short DCCRequest(const CString& sNick, unsigned long uLongIP, unsigned short uPort, const CString& sFileName, bool bIsChat, CBounceDCCMod* pMod, const CString& sRemoteIP); void ReadLine(const CString& sData) override; void ReadData(const char* data, size_t len) override; void ReadPaused() override; void Timeout() override; void ConnectionRefused() override; void ReachedMaxBuffer() override; void SockError(int iErrno, const CString& sDescription) override; void Connected() override; void Disconnected() override; Csock* GetSockObj(const CString& sHost, unsigned short uPort) override; void Shutdown(); void PutServ(const CString& sLine); void PutPeer(const CString& sLine); bool IsPeerConnected() { return (m_pPeer) ? m_pPeer->IsConnected() : false; } // Setters void SetPeer(CDCCBounce* p) { m_pPeer = p; } void SetRemoteIP(const CString& s) { m_sRemoteIP = s; } void SetRemoteNick(const CString& s) { m_sRemoteNick = s; } void SetRemote(bool b) { m_bIsRemote = b; } // !Setters // Getters unsigned short GetUserPort() const { return m_uRemotePort; } const CString& GetRemoteAddr() const { return m_sRemoteIP; } const CString& GetRemoteNick() const { return m_sRemoteNick; } const CString& GetFileName() const { return m_sFileName; } CDCCBounce* GetPeer() const { return m_pPeer; } bool IsRemote() { return m_bIsRemote; } bool IsChat() { return m_bIsChat; } // !Getters private: protected: CString m_sRemoteNick; CString m_sRemoteIP; CString m_sConnectIP; CString m_sLocalIP; CString m_sFileName; CBounceDCCMod* m_pModule; CDCCBounce* m_pPeer; unsigned short m_uRemotePort; bool m_bIsChat; bool m_bIsRemote; static const unsigned int m_uiMaxDCCBuffer; static const unsigned int m_uiMinDCCBuffer; }; // If we buffer more than this in memory, we will throttle the receiving side const unsigned int CDCCBounce::m_uiMaxDCCBuffer = 10 * 1024; // If less than this is in the buffer, the receiving side continues const unsigned int CDCCBounce::m_uiMinDCCBuffer = 2 * 1024; class CBounceDCCMod : public CModule { public: void ListDCCsCommand(const CString& sLine) { CTable Table; Table.AddColumn(t_s("Type", "list")); Table.AddColumn(t_s("State", "list")); Table.AddColumn(t_s("Speed", "list")); Table.AddColumn(t_s("Nick", "list")); Table.AddColumn(t_s("IP", "list")); Table.AddColumn(t_s("File", "list")); set::const_iterator it; for (it = BeginSockets(); it != EndSockets(); ++it) { CDCCBounce* pSock = (CDCCBounce*)*it; CString sSockName = pSock->GetSockName(); if (!(pSock->IsRemote())) { Table.AddRow(); Table.SetCell(t_s("Nick", "list"), pSock->GetRemoteNick()); Table.SetCell(t_s("IP", "list"), pSock->GetRemoteAddr()); if (pSock->IsChat()) { Table.SetCell(t_s("Type", "list"), t_s("Chat", "list")); } else { Table.SetCell(t_s("Type", "list"), t_s("Xfer", "list")); Table.SetCell(t_s("File", "list"), pSock->GetFileName()); } CString sState = t_s("Waiting"); if ((pSock->IsConnected()) || (pSock->IsPeerConnected())) { sState = t_s("Halfway"); if ((pSock->IsConnected()) && (pSock->IsPeerConnected())) { sState = t_s("Connected"); } } Table.SetCell(t_s("State", "list"), sState); } } if (PutModule(Table) == 0) { PutModule(t_s("You have no active DCCs.")); } } void UseClientIPCommand(const CString& sLine) { CString sValue = sLine.Token(1, true); if (!sValue.empty()) { SetNV("UseClientIP", sValue); } PutModule(t_f("Use client IP: {1}")(GetNV("UseClientIP").ToBool())); } MODCONSTRUCTOR(CBounceDCCMod) { AddHelpCommand(); AddCommand("ListDCCs", "", t_d("List all active DCCs"), [this](const CString& sLine) { ListDCCsCommand(sLine); }); AddCommand("UseClientIP", "", t_d("Change the option to use IP of client"), [this](const CString& sLine) { UseClientIPCommand(sLine); }); } ~CBounceDCCMod() override {} CString GetLocalDCCIP() { return GetUser()->GetLocalDCCIP(); } bool UseClientIP() { return GetNV("UseClientIP").ToBool(); } EModRet OnUserCTCP(CString& sTarget, CString& sMessage) override { if (sMessage.StartsWith("DCC ")) { CString sType = sMessage.Token(1, false, " ", false, "\"", "\"", true); CString sFile = sMessage.Token(2, false, " ", false, "\"", "\"", false); unsigned long uLongIP = sMessage.Token(3, false, " ", false, "\"", "\"", true).ToULong(); unsigned short uPort = sMessage.Token(4, false, " ", false, "\"", "\"", true).ToUShort(); unsigned long uFileSize = sMessage.Token(5, false, " ", false, "\"", "\"", true).ToULong(); CString sIP = GetLocalDCCIP(); if (!UseClientIP()) { uLongIP = CUtils::GetLongIP(GetClient()->GetRemoteIP()); } if (sType.Equals("CHAT")) { unsigned short uBNCPort = CDCCBounce::DCCRequest( sTarget, uLongIP, uPort, "", true, this, ""); if (uBNCPort) { PutIRC("PRIVMSG " + sTarget + " :\001DCC CHAT chat " + CString(CUtils::GetLongIP(sIP)) + " " + CString(uBNCPort) + "\001"); } } else if (sType.Equals("SEND")) { // DCC SEND readme.txt 403120438 5550 1104 unsigned short uBNCPort = CDCCBounce::DCCRequest( sTarget, uLongIP, uPort, sFile, false, this, ""); if (uBNCPort) { PutIRC("PRIVMSG " + sTarget + " :\001DCC SEND " + sFile + " " + CString(CUtils::GetLongIP(sIP)) + " " + CString(uBNCPort) + " " + CString(uFileSize) + "\001"); } } else if (sType.Equals("RESUME")) { // PRIVMSG user :DCC RESUME "znc.o" 58810 151552 unsigned short uResumePort = sMessage.Token(3).ToUShort(); set::const_iterator it; for (it = BeginSockets(); it != EndSockets(); ++it) { CDCCBounce* pSock = (CDCCBounce*)*it; if (pSock->GetLocalPort() == uResumePort) { PutIRC("PRIVMSG " + sTarget + " :\001DCC " + sType + " " + sFile + " " + CString(pSock->GetUserPort()) + " " + sMessage.Token(4) + "\001"); } } } else if (sType.Equals("ACCEPT")) { // Need to lookup the connection by port, filter the port, and // forward to the user set::const_iterator it; for (it = BeginSockets(); it != EndSockets(); ++it) { CDCCBounce* pSock = (CDCCBounce*)*it; if (pSock->GetUserPort() == sMessage.Token(3).ToUShort()) { PutIRC("PRIVMSG " + sTarget + " :\001DCC " + sType + " " + sFile + " " + CString(pSock->GetLocalPort()) + " " + sMessage.Token(4) + "\001"); } } } return HALTCORE; } return CONTINUE; } EModRet OnPrivCTCP(CNick& Nick, CString& sMessage) override { CIRCNetwork* pNetwork = GetNetwork(); if (sMessage.StartsWith("DCC ") && pNetwork->IsUserAttached()) { // DCC CHAT chat 2453612361 44592 CString sType = sMessage.Token(1, false, " ", false, "\"", "\"", true); CString sFile = sMessage.Token(2, false, " ", false, "\"", "\"", false); unsigned long uLongIP = sMessage.Token(3, false, " ", false, "\"", "\"", true).ToULong(); unsigned short uPort = sMessage.Token(4, false, " ", false, "\"", "\"", true).ToUShort(); unsigned long uFileSize = sMessage.Token(5, false, " ", false, "\"", "\"", true).ToULong(); if (sType.Equals("CHAT")) { CNick FromNick(Nick.GetNickMask()); unsigned short uBNCPort = CDCCBounce::DCCRequest( FromNick.GetNick(), uLongIP, uPort, "", true, this, CUtils::GetIP(uLongIP)); if (uBNCPort) { CString sIP = GetLocalDCCIP(); PutUser(":" + Nick.GetNickMask() + " PRIVMSG " + pNetwork->GetCurNick() + " :\001DCC CHAT chat " + CString(CUtils::GetLongIP(sIP)) + " " + CString(uBNCPort) + "\001"); } } else if (sType.Equals("SEND")) { // DCC SEND readme.txt 403120438 5550 1104 unsigned short uBNCPort = CDCCBounce::DCCRequest( Nick.GetNick(), uLongIP, uPort, sFile, false, this, CUtils::GetIP(uLongIP)); if (uBNCPort) { CString sIP = GetLocalDCCIP(); PutUser(":" + Nick.GetNickMask() + " PRIVMSG " + pNetwork->GetCurNick() + " :\001DCC SEND " + sFile + " " + CString(CUtils::GetLongIP(sIP)) + " " + CString(uBNCPort) + " " + CString(uFileSize) + "\001"); } } else if (sType.Equals("RESUME")) { // Need to lookup the connection by port, filter the port, and // forward to the user unsigned short uResumePort = sMessage.Token(3).ToUShort(); set::const_iterator it; for (it = BeginSockets(); it != EndSockets(); ++it) { CDCCBounce* pSock = (CDCCBounce*)*it; if (pSock->GetLocalPort() == uResumePort) { PutUser(":" + Nick.GetNickMask() + " PRIVMSG " + pNetwork->GetCurNick() + " :\001DCC " + sType + " " + sFile + " " + CString(pSock->GetUserPort()) + " " + sMessage.Token(4) + "\001"); } } } else if (sType.Equals("ACCEPT")) { // Need to lookup the connection by port, filter the port, and // forward to the user set::const_iterator it; for (it = BeginSockets(); it != EndSockets(); ++it) { CDCCBounce* pSock = (CDCCBounce*)*it; if (pSock->GetUserPort() == sMessage.Token(3).ToUShort()) { PutUser(":" + Nick.GetNickMask() + " PRIVMSG " + pNetwork->GetCurNick() + " :\001DCC " + sType + " " + sFile + " " + CString(pSock->GetLocalPort()) + " " + sMessage.Token(4) + "\001"); } } } return HALTCORE; } return CONTINUE; } }; CDCCBounce::CDCCBounce(CBounceDCCMod* pMod, unsigned long uLongIP, unsigned short uPort, const CString& sFileName, const CString& sRemoteNick, const CString& sRemoteIP, bool bIsChat) : CSocket(pMod) { m_uRemotePort = uPort; m_sConnectIP = CUtils::GetIP(uLongIP); m_sRemoteIP = sRemoteIP; m_sFileName = sFileName; m_sRemoteNick = sRemoteNick; m_pModule = pMod; m_bIsChat = bIsChat; m_sLocalIP = pMod->GetLocalDCCIP(); m_pPeer = nullptr; m_bIsRemote = false; if (bIsChat) { EnableReadLine(); } else { DisableReadLine(); } } CDCCBounce::CDCCBounce(CBounceDCCMod* pMod, const CString& sHostname, unsigned short uPort, const CString& sRemoteNick, const CString& sRemoteIP, const CString& sFileName, int iTimeout, bool bIsChat) : CSocket(pMod, sHostname, uPort, iTimeout) { m_uRemotePort = 0; m_bIsChat = bIsChat; m_pModule = pMod; m_pPeer = nullptr; m_sRemoteNick = sRemoteNick; m_sFileName = sFileName; m_sRemoteIP = sRemoteIP; m_bIsRemote = false; SetMaxBufferThreshold(10240); if (bIsChat) { EnableReadLine(); } else { DisableReadLine(); } } CDCCBounce::~CDCCBounce() { if (m_pPeer) { m_pPeer->Shutdown(); m_pPeer = nullptr; } } void CDCCBounce::ReadLine(const CString& sData) { CString sLine = sData.TrimRight_n("\r\n"); DEBUG(GetSockName() << " <- [" << sLine << "]"); PutPeer(sLine); } void CDCCBounce::ReachedMaxBuffer() { DEBUG(GetSockName() << " == ReachedMaxBuffer()"); CString sType = m_bIsChat ? t_s("Chat", "type") : t_s("Xfer", "type"); m_pModule->PutModule(t_f("DCC {1} Bounce ({2}): Too long line received")( sType, m_sRemoteNick)); Close(); } void CDCCBounce::ReadData(const char* data, size_t len) { if (m_pPeer) { m_pPeer->Write(data, len); size_t BufLen = m_pPeer->GetInternalWriteBuffer().length(); if (BufLen >= m_uiMaxDCCBuffer) { DEBUG(GetSockName() << " The send buffer is over the " "limit (" << BufLen << "), throttling"); PauseRead(); } } } void CDCCBounce::ReadPaused() { if (!m_pPeer || m_pPeer->GetInternalWriteBuffer().length() <= m_uiMinDCCBuffer) UnPauseRead(); } void CDCCBounce::Timeout() { DEBUG(GetSockName() << " == Timeout()"); CString sType = m_bIsChat ? t_s("Chat", "type") : t_s("Xfer", "type"); if (IsRemote()) { CString sHost = Csock::GetHostName(); if (!sHost.empty()) { m_pModule->PutModule(t_f( "DCC {1} Bounce ({2}): Timeout while connecting to {3} {4}")( sType, m_sRemoteNick, sHost, Csock::GetPort())); } else { m_pModule->PutModule( t_f("DCC {1} Bounce ({2}): Timeout while connecting.")( sType, m_sRemoteNick)); } } else { m_pModule->PutModule(t_f( "DCC {1} Bounce ({2}): Timeout while waiting for incoming " "connection on {3} {4}")(sType, m_sRemoteNick, Csock::GetLocalIP(), Csock::GetLocalPort())); } } void CDCCBounce::ConnectionRefused() { DEBUG(GetSockName() << " == ConnectionRefused()"); CString sType = m_bIsChat ? t_s("Chat", "type") : t_s("Xfer", "type"); CString sHost = Csock::GetHostName(); if (!sHost.empty()) { m_pModule->PutModule( t_f("DCC {1} Bounce ({2}): Connection refused while connecting to " "{3} {4}")(sType, m_sRemoteNick, sHost, Csock::GetPort())); } else { m_pModule->PutModule( t_f("DCC {1} Bounce ({2}): Connection refused while connecting.")( sType, m_sRemoteNick)); } } void CDCCBounce::SockError(int iErrno, const CString& sDescription) { DEBUG(GetSockName() << " == SockError(" << iErrno << ")"); CString sType = m_bIsChat ? t_s("Chat", "type") : t_s("Xfer", "type"); if (IsRemote()) { CString sHost = Csock::GetHostName(); if (!sHost.empty()) { m_pModule->PutModule(t_f( "DCC {1} Bounce ({2}): Socket error on {3} {4}: {5}")( sType, m_sRemoteNick, sHost, Csock::GetPort(), sDescription)); } else { m_pModule->PutModule(t_f("DCC {1} Bounce ({2}): Socket error: {3}")( sType, m_sRemoteNick, sDescription)); } } else { m_pModule->PutModule( t_f("DCC {1} Bounce ({2}): Socket error on {3} {4}: {5}")( sType, m_sRemoteNick, Csock::GetLocalIP(), Csock::GetLocalPort(), sDescription)); } } void CDCCBounce::Connected() { SetTimeout(0); DEBUG(GetSockName() << " == Connected()"); } void CDCCBounce::Disconnected() { DEBUG(GetSockName() << " == Disconnected()"); } void CDCCBounce::Shutdown() { m_pPeer = nullptr; DEBUG(GetSockName() << " == Close(); because my peer told me to"); Close(); } Csock* CDCCBounce::GetSockObj(const CString& sHost, unsigned short uPort) { Close(); if (m_sRemoteIP.empty()) { m_sRemoteIP = sHost; } CDCCBounce* pSock = new CDCCBounce(m_pModule, sHost, uPort, m_sRemoteNick, m_sRemoteIP, m_sFileName, m_bIsChat); CDCCBounce* pRemoteSock = new CDCCBounce(m_pModule, sHost, uPort, m_sRemoteNick, m_sRemoteIP, m_sFileName, m_bIsChat); pSock->SetPeer(pRemoteSock); pRemoteSock->SetPeer(pSock); pRemoteSock->SetRemote(true); pSock->SetRemote(false); CZNC::Get().GetManager().Connect( m_sConnectIP, m_uRemotePort, "DCC::" + CString((m_bIsChat) ? "Chat" : "XFER") + "::Remote::" + m_sRemoteNick, 60, false, m_sLocalIP, pRemoteSock); pSock->SetSockName(GetSockName()); return pSock; } void CDCCBounce::PutServ(const CString& sLine) { DEBUG(GetSockName() << " -> [" << sLine << "]"); Write(sLine + "\r\n"); } void CDCCBounce::PutPeer(const CString& sLine) { if (m_pPeer) { m_pPeer->PutServ(sLine); } else { PutServ("*** Not connected yet ***"); } } unsigned short CDCCBounce::DCCRequest(const CString& sNick, unsigned long uLongIP, unsigned short uPort, const CString& sFileName, bool bIsChat, CBounceDCCMod* pMod, const CString& sRemoteIP) { CDCCBounce* pDCCBounce = new CDCCBounce(pMod, uLongIP, uPort, sFileName, sNick, sRemoteIP, bIsChat); unsigned short uListenPort = CZNC::Get().GetManager().ListenRand( "DCC::" + CString((bIsChat) ? "Chat" : "Xfer") + "::Local::" + sNick, pMod->GetLocalDCCIP(), false, SOMAXCONN, pDCCBounce, 120); return uListenPort; } template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("bouncedcc"); } USERMODULEDEFS(CBounceDCCMod, t_s("Bounces DCC transfers through ZNC instead of sending them " "directly to the user. ")) znc-1.9.1/modules/buffextras.cpp0000644000175000017500000001052214641222733017071 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include using std::vector; class CBuffExtras : public CModule { public: MODCONSTRUCTOR(CBuffExtras) {} ~CBuffExtras() override {} void AddBuffer(CChan& Channel, const CString& sMessage, const timeval* tv = nullptr, const MCString& mssTags = MCString::EmptyMap) { // If they have AutoClearChanBuffer enabled, only add messages if no // client is connected if (Channel.AutoClearChanBuffer() && GetNetwork()->IsUserOnline()) return; Channel.AddBuffer(":" + GetModNick() + "!" + GetModName() + "@znc.in PRIVMSG " + _NAMEDFMT(Channel.GetName()) + " :{text}", sMessage, tv, mssTags); } void OnRawMode2(const CNick* pOpNick, CChan& Channel, const CString& sModes, const CString& sArgs) override { const CString sNickMask = pOpNick ? pOpNick->GetNickMask() : t_s("Server"); AddBuffer(Channel, t_f("{1} set mode: {2} {3}")(sNickMask, sModes, sArgs)); } void OnKickMessage(CKickMessage& Message) override { const CNick& OpNick = Message.GetNick(); const CString sKickedNick = Message.GetKickedNick(); CChan& Channel = *Message.GetChan(); const CString sMessage = Message.GetReason(); AddBuffer(Channel, t_f("{1} kicked {2} with reason: {3}")( OpNick.GetNickMask(), sKickedNick, sMessage), &Message.GetTime(), Message.GetTags()); } void OnQuitMessage(CQuitMessage& Message, const vector& vChans) override { const CNick& Nick = Message.GetNick(); const CString sMessage = Message.GetReason(); const CString sMsg = t_f("{1} quit: {2}")(Nick.GetNickMask(), sMessage); for (CChan* pChan : vChans) { AddBuffer(*pChan, sMsg, &Message.GetTime(), Message.GetTags()); } } void OnJoinMessage(CJoinMessage& Message) override { const CNick& Nick = Message.GetNick(); CChan& Channel = *Message.GetChan(); AddBuffer(Channel, t_f("{1} joined")(Nick.GetNickMask(), " joined"), &Message.GetTime(), Message.GetTags()); } void OnPartMessage(CPartMessage& Message) override { const CNick& Nick = Message.GetNick(); CChan& Channel = *Message.GetChan(); const CString sMessage = Message.GetReason(); AddBuffer(Channel, t_f("{1} parted: {2}")(Nick.GetNickMask(), sMessage), &Message.GetTime(), Message.GetTags()); } void OnNickMessage(CNickMessage& Message, const vector& vChans) override { const CNick& OldNick = Message.GetNick(); const CString sNewNick = Message.GetNewNick(); const CString sMsg = t_f("{1} is now known as {2}")(OldNick.GetNickMask(), sNewNick); for (CChan* pChan : vChans) { AddBuffer(*pChan, sMsg, &Message.GetTime(), Message.GetTags()); } } EModRet OnTopicMessage(CTopicMessage& Message) override { const CNick& Nick = Message.GetNick(); CChan& Channel = *Message.GetChan(); const CString sTopic = Message.GetTopic(); AddBuffer(Channel, t_f("{1} changed the topic to: {2}")( Nick.GetNickMask(), sTopic), &Message.GetTime(), Message.GetTags()); return CONTINUE; } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("buffextras"); Info.AddType(CModInfo::NetworkModule); } USERMODULEDEFS(CBuffExtras, t_s("Adds joins, parts etc. to the playback buffer")) znc-1.9.1/modules/cert.cpp0000644000175000017500000000633514641222733015664 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define REQUIRESSL #include #include #include #include class CCertMod : public CModule { public: void Delete(const CString& line) { if (CFile::Delete(PemFile())) { PutModule(t_s("Pem file deleted")); } else { PutModule(t_s( "The pem file doesn't exist or there was a error deleting the " "pem file.")); } } void Info(const CString& line) { if (HasPemFile()) { PutModule(t_f("You have a certificate in {1}")(PemFile())); } else { PutModule(t_s( "You do not have a certificate. Please use the web interface " "to add a certificate")); if (GetUser()->IsAdmin()) { PutModule(t_f("Alternatively you can either place one at {1}")( PemFile())); } } } MODCONSTRUCTOR(CCertMod) { AddHelpCommand(); AddCommand("delete", "", t_d("Delete the current certificate"), [=](const CString& sLine) { Delete(sLine); }); AddCommand("info", "", t_d("Show the current certificate"), [=](const CString& sLine) { Info(sLine); }); } ~CCertMod() override {} CString PemFile() const { return GetSavePath() + "/user.pem"; } bool HasPemFile() const { return (CFile::Exists(PemFile())); } EModRet OnIRCConnecting(CIRCSock* pIRCSock) override { if (HasPemFile()) { pIRCSock->SetPemLocation(PemFile()); } return CONTINUE; } CString GetWebMenuTitle() override { return t_s("Certificate"); } bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) override { if (sPageName == "index") { Tmpl["Cert"] = CString(HasPemFile()); return true; } else if (sPageName == "update") { CFile fPemFile(PemFile()); if (fPemFile.Open(O_WRONLY | O_TRUNC | O_CREAT)) { fPemFile.Write(WebSock.GetParam("cert", true, "")); fPemFile.Close(); } WebSock.Redirect(GetWebPath()); return true; } else if (sPageName == "delete") { CFile::Delete(PemFile()); WebSock.Redirect(GetWebPath()); return true; } return false; } }; template <> void TModInfo(CModInfo& Info) { Info.AddType(CModInfo::UserModule); Info.SetWikiPage("cert"); } NETWORKMODULEDEFS(CCertMod, t_s("Use a ssl certificate to connect to a server")) znc-1.9.1/modules/certauth.cpp0000644000175000017500000002140514641222733016541 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define REQUIRESSL #include #include using std::map; using std::vector; using std::set; using std::pair; class CSSLClientCertMod : public CModule { public: MODCONSTRUCTOR(CSSLClientCertMod) { AddHelpCommand(); AddCommand("Add", t_d("[pubkey]"), t_d("Add a public key. If key is not provided " "will use the current key"), [=](const CString& sLine) { HandleAddCommand(sLine); }); AddCommand("Del", t_d("id"), t_d("Delete a key by its number in List"), [=](const CString& sLine) { HandleDelCommand(sLine); }); AddCommand("List", "", t_d("List your public keys"), [=](const CString& sLine) { HandleListCommand(sLine); }); AddCommand("Show", "", t_d("Print your current key"), [=](const CString& sLine) { HandleShowCommand(sLine); }); } ~CSSLClientCertMod() override {} bool OnBoot() override { const vector& vListeners = CZNC::Get().GetListeners(); // We need the SSL_VERIFY_PEER flag on all listeners, or else // the client doesn't send a ssl cert for (CListener* pListener : vListeners) pListener->GetRealListener()->SetRequireClientCertFlags( SSL_VERIFY_PEER); for (MCString::const_iterator it = BeginNV(); it != EndNV(); ++it) { VCString vsKeys; if (CZNC::Get().FindUser(it->first) == nullptr) { DEBUG("Unknown user in saved data [" + it->first + "]"); continue; } it->second.Split(" ", vsKeys, false); for (const CString& sKey : vsKeys) { m_PubKeys[it->first].insert(sKey.AsLower()); } } return true; } void OnPostRehash() override { OnBoot(); } bool OnLoad(const CString& sArgs, CString& sMessage) override { OnBoot(); return true; } bool Save() { ClearNV(false); for (const auto& it : m_PubKeys) { CString sVal; for (const CString& sKey : it.second) { sVal += sKey + " "; } if (!sVal.empty()) SetNV(it.first, sVal, false); } return SaveRegistry(); } bool AddKey(CUser* pUser, const CString& sKey) { const pair pair = m_PubKeys[pUser->GetUsername()].insert(sKey.AsLower()); if (pair.second) { Save(); } return pair.second; } EModRet OnLoginAttempt(std::shared_ptr Auth) override { const CString sUser = Auth->GetUsername(); Csock* pSock = Auth->GetSocket(); CUser* pUser = CZNC::Get().FindUser(sUser); if (pSock == nullptr || pUser == nullptr) return CONTINUE; const CString sPubKey = GetKey(pSock); DEBUG("User: " << sUser << " Key: " << sPubKey); if (sPubKey.empty()) { DEBUG("Peer got no public key, ignoring"); return CONTINUE; } MSCString::const_iterator it = m_PubKeys.find(sUser); if (it == m_PubKeys.end()) { DEBUG("No saved pubkeys for this client"); return CONTINUE; } SCString::const_iterator it2 = it->second.find(sPubKey); if (it2 == it->second.end()) { DEBUG("Invalid pubkey"); return CONTINUE; } // This client uses a valid pubkey for this user, let them in DEBUG("Accepted pubkey auth"); Auth->AcceptLogin(*pUser); return HALT; } void HandleShowCommand(const CString& sLine) { const CString sPubKey = GetKey(GetClient()); if (sPubKey.empty()) { PutModule(t_s("You are not connected with any valid public key")); } else { PutModule(t_f("Your current public key is: {1}")(sPubKey)); } } void HandleAddCommand(const CString& sLine) { CString sPubKey = sLine.Token(1); if (sPubKey.empty()) { sPubKey = GetKey(GetClient()); } if (sPubKey.empty()) { PutModule( t_s("You did not supply a public key or connect with one.")); } else { if (AddKey(GetUser(), sPubKey)) { PutModule(t_f("Key '{1}' added.")(sPubKey)); } else { PutModule(t_f("The key '{1}' is already added.")(sPubKey)); } } } void HandleListCommand(const CString& sLine) { CTable Table; Table.AddColumn(t_s("Id", "list")); Table.AddColumn(t_s("Key", "list")); Table.SetStyle(CTable::ListStyle); MSCString::const_iterator it = m_PubKeys.find(GetUser()->GetUsername()); if (it == m_PubKeys.end()) { PutModule(t_s("No keys set for your user")); return; } unsigned int id = 1; for (const CString& sKey : it->second) { Table.AddRow(); Table.SetCell(t_s("Id", "list"), CString(id++)); Table.SetCell(t_s("Key", "list"), sKey); } if (PutModule(Table) == 0) { // This double check is necessary, because the // set could be empty. PutModule(t_s("No keys set for your user")); } } void HandleDelCommand(const CString& sLine) { unsigned int id = sLine.Token(1, true).ToUInt(); MSCString::iterator it = m_PubKeys.find(GetUser()->GetUsername()); if (it == m_PubKeys.end()) { PutModule(t_s("No keys set for your user")); return; } if (id == 0 || id > it->second.size()) { PutModule(t_s("Invalid #, check \"list\"")); return; } SCString::const_iterator it2 = it->second.begin(); while (id > 1) { ++it2; id--; } it->second.erase(it2); if (it->second.size() == 0) m_PubKeys.erase(it); PutModule(t_s("Removed")); Save(); } CString GetKey(Csock* pSock) { CString sRes; long int res = pSock->GetPeerFingerprint(sRes); DEBUG("GetKey() returned status " << res << " with key " << sRes); // This is 'inspired' by charybdis' libratbox switch (res) { case X509_V_OK: case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: return sRes.AsLower(); default: return ""; } } CString GetWebMenuTitle() override { return "certauth"; } bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) override { CUser* pUser = WebSock.GetSession()->GetUser(); if (sPageName == "index") { MSCString::const_iterator it = m_PubKeys.find(pUser->GetUsername()); if (it != m_PubKeys.end()) { for (const CString& sKey : it->second) { CTemplate& row = Tmpl.AddRow("KeyLoop"); row["Key"] = sKey; } } return true; } else if (sPageName == "add") { AddKey(pUser, WebSock.GetParam("key")); WebSock.Redirect(GetWebPath()); return true; } else if (sPageName == "delete") { MSCString::iterator it = m_PubKeys.find(pUser->GetUsername()); if (it != m_PubKeys.end()) { if (it->second.erase(WebSock.GetParam("key", false))) { if (it->second.size() == 0) { m_PubKeys.erase(it); } Save(); } } WebSock.Redirect(GetWebPath()); return true; } return false; } private: // Maps user names to a list of allowed pubkeys typedef map> MSCString; MSCString m_PubKeys; }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("certauth"); } GLOBALMODULEDEFS( CSSLClientCertMod, t_s("Allows users to authenticate via SSL client certificates.")) znc-1.9.1/modules/chansaver.cpp0000644000175000017500000000536314641222733016701 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include class CChanSaverMod : public CModule { public: MODCONSTRUCTOR(CChanSaverMod) {} ~CChanSaverMod() override {} bool OnLoad(const CString& sArgsi, CString& sMessage) override { switch (GetType()) { case CModInfo::GlobalModule: LoadUsers(); break; case CModInfo::UserModule: LoadUser(GetUser()); break; case CModInfo::NetworkModule: LoadNetwork(GetNetwork()); break; } return true; } void LoadUsers() { const std::map& vUsers = CZNC::Get().GetUserMap(); for (const auto& user : vUsers) { LoadUser(user.second); } } void LoadUser(CUser* pUser) { const std::vector& vNetworks = pUser->GetNetworks(); for (const CIRCNetwork* pNetwork : vNetworks) { LoadNetwork(pNetwork); } } void LoadNetwork(const CIRCNetwork* pNetwork) { const std::vector& vChans = pNetwork->GetChans(); for (CChan* pChan : vChans) { // If that channel isn't yet in the config, // we'll have to add it... if (!pChan->InConfig()) { pChan->SetInConfig(true); } } } void OnJoin(const CNick& Nick, CChan& Channel) override { if (!Channel.InConfig() && GetNetwork()->GetIRCNick().NickEquals(Nick.GetNick())) { Channel.SetInConfig(true); } } void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) override { if (Channel.InConfig() && GetNetwork()->GetIRCNick().NickEquals(Nick.GetNick())) { Channel.SetInConfig(false); } } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("chansaver"); Info.AddType(CModInfo::NetworkModule); Info.AddType(CModInfo::GlobalModule); } USERMODULEDEFS(CChanSaverMod, t_s("Keeps config up-to-date when user joins/parts.")) znc-1.9.1/modules/clearbufferonmsg.cpp0000644000175000017500000001111414641222733020242 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include using std::vector; enum { RULE_MSG, RULE_CTCP, RULE_ACTION, RULE_NOTICE, RULE_PART, RULE_TOPIC, RULE_QUIT, RULE_MAX, }; class CClearBufferOnMsgMod : public CModule { public: MODCONSTRUCTOR(CClearBufferOnMsgMod) { SetAllRules(true); // false for backward compatibility m_bRules[RULE_QUIT] = false; } void ClearAllBuffers() { CIRCNetwork* pNetwork = GetNetwork(); if (pNetwork) { const vector& vChans = pNetwork->GetChans(); for (CChan* pChan : vChans) { // Skip detached channels, they weren't read yet if (pChan->IsDetached()) continue; pChan->ClearBuffer(); // We deny AutoClearChanBuffer on all channels since this module // doesn't make any sense with it pChan->SetAutoClearChanBuffer(false); } vector VQueries = pNetwork->GetQueries(); for (CQuery* pQuery : VQueries) { pNetwork->DelQuery(pQuery->GetName()); } // We deny AutoClearQueryBuffer since this module // doesn't make any sense with it GetUser()->SetAutoClearQueryBuffer(false); } } EModRet OnUserMsg(CString& sTarget, CString& sMessage) override { if (m_bRules[RULE_MSG]) ClearAllBuffers(); return CONTINUE; } EModRet OnUserCTCP(CString& sTarget, CString& sMessage) override { if (m_bRules[RULE_CTCP]) ClearAllBuffers(); return CONTINUE; } EModRet OnUserAction(CString& sTarget, CString& sMessage) override { if (m_bRules[RULE_ACTION]) ClearAllBuffers(); return CONTINUE; } EModRet OnUserNotice(CString& sTarget, CString& sMessage) override { if (m_bRules[RULE_NOTICE]) ClearAllBuffers(); return CONTINUE; } EModRet OnUserPart(CString& sChannel, CString& sMessage) override { if (m_bRules[RULE_PART]) ClearAllBuffers(); return CONTINUE; } EModRet OnUserTopic(CString& sChannel, CString& sTopic) override { if (m_bRules[RULE_TOPIC]) ClearAllBuffers(); return CONTINUE; } EModRet OnUserQuit(CString& sMessage) override { if (m_bRules[RULE_QUIT]) ClearAllBuffers(); return CONTINUE; } void SetAllRules(bool bVal) { for (int i = 0; i < RULE_MAX; i++) m_bRules[i] = bVal; } void SetRule(const CString& sOpt, bool bVal) { static const struct { CString sName; int Index; } Names[RULE_MAX] = { {"msg", RULE_MSG}, {"ctcp", RULE_CTCP}, {"action", RULE_ACTION}, {"notice", RULE_NOTICE}, {"part", RULE_PART}, {"topic", RULE_TOPIC}, {"quit", RULE_QUIT}, }; if (sOpt.Equals("all")) { SetAllRules(bVal); } else { for (int i = 0; i < RULE_MAX; i++) { if (sOpt.Equals(Names[i].sName)) m_bRules[Names[i].Index] = bVal; } } } bool OnLoad(const CString& sArgs, CString& sMessage) override { VCString vsOpts; sArgs.Split(" ", vsOpts, false); for (CString& sOpt : vsOpts) { if (sOpt.StartsWith("!")) SetRule(sOpt.substr(1), false); else if (!sOpt.empty()) SetRule(sOpt, true); } return true; } private: bool m_bRules[RULE_MAX]; }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("clearbufferonmsg"); Info.SetHasArgs(true); Info.SetArgsHelpText("[ [!] ]"); } USERMODULEDEFS(CClearBufferOnMsgMod, t_s("Clears all channel and query buffers " "whenever the user does something")) znc-1.9.1/modules/clientnotify.cpp0000644000175000017500000001737514641222733017444 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include using std::set; class CClientNotifyMod : public CModule { protected: CString m_sMethod; bool m_bNewOnly{}; bool m_bOnDisconnect{}; bool m_bNotifyOnNewIP{}; bool m_bNotifyOnNewClientID{}; set m_sClientsSeenIP; set m_sClientsSeenID; void SaveSettings() { SetNV("method", m_sMethod); SetNV("newonly", m_bNewOnly ? "1" : "0"); SetNV("notifyonnewip", m_bNotifyOnNewIP ? "1" : "0"); SetNV("notifyonnewclientid", m_bNotifyOnNewClientID ? "1" : "0"); SetNV("ondisconnect", m_bOnDisconnect ? "1" : "0"); } void SendNotification(const CString& sMessage) { if (m_sMethod == "message") { GetUser()->PutStatus(sMessage, nullptr, GetClient()); } else if (m_sMethod == "notice") { GetUser()->PutStatusNotice(sMessage, nullptr, GetClient()); } } public: MODCONSTRUCTOR(CClientNotifyMod) { AddHelpCommand(); AddCommand("Method", t_d(""), t_d("Sets the notify method"), [=](const CString& sLine) { OnMethodCommand(sLine); }); AddCommand("NewOnly", t_d(""), t_d("Turns notifications for unseen connections on or off"), [=](const CString& sLine) { OnNewOnlyCommand(sLine); }); AddCommand("NotifyOnNewIP", t_d(""), t_d("Specifies whether you want to be notified about new " "connections with new IPs"), [=](const CString& sLine) { OnNotifyOnNewIP(sLine); }); AddCommand("NotifyOnNewID", t_d(""), t_d("Specifies whether you want to be notified about new " "connections with new client IDs"), [=](const CString& sLine) { OnNotifyOnNewID(sLine); }); AddCommand( "OnDisconnect", t_d(""), t_d("Turns notifications for clients disconnecting on or off"), [=](const CString& sLine) { OnDisconnectCommand(sLine); }); AddCommand("Show", "", t_d("Shows the current settings"), [=](const CString& sLine) { OnShowCommand(sLine); }); } bool OnLoad(const CString& sArgs, CString& sMessage) override { m_sMethod = GetNV("method"); if (m_sMethod != "notice" && m_sMethod != "message" && m_sMethod != "off") { m_sMethod = "message"; } // default = off for these: m_bNotifyOnNewIP = (GetNV("notifyonnewip") == "1"); m_bNotifyOnNewClientID = (GetNV("notifyonnewclientid") == "1"); m_bNewOnly = (GetNV("newonly") == "1"); m_bOnDisconnect = (GetNV("ondisconnect") == "1"); return true; } void OnClientLogin() override { CString sRemoteIP = GetClient()->GetRemoteIP(); CString sRemoteClientID = GetClient()->GetIdentifier(); CString sClientNameMessage{sRemoteIP}; if (m_bNotifyOnNewClientID && sRemoteClientID != "") { sClientNameMessage += " / " + sRemoteClientID; } auto sendLoginNotification = [&]() { SendNotification( t_p("", "Another client ({1}) authenticated as your user. " "Use the 'ListClients' command to see all {2} " "clients.", GetUser()->GetAllClients().size())( sClientNameMessage, GetUser()->GetAllClients().size())); }; if (m_bNewOnly) { // see if we actually got a new client // TODO: replace setName.find(...) == setName.end() with // !setName.contains() once ZNC uses C++20 if ((m_bNotifyOnNewIP && (m_sClientsSeenIP.find(sRemoteIP) == m_sClientsSeenIP.end())) || (m_bNotifyOnNewClientID && (m_sClientsSeenID.find(sRemoteClientID) == m_sClientsSeenID.end()))) { sendLoginNotification(); } } else { sendLoginNotification(); } // the set<> will automatically disregard duplicates: m_sClientsSeenIP.insert(sRemoteIP); m_sClientsSeenID.insert(sRemoteClientID); } void OnClientDisconnect() override { if (m_bOnDisconnect) { SendNotification(t_p("", "A client disconnected from your user. Use " "the 'ListClients' command to see the {1} " "remaining clients.", GetUser()->GetAllClients().size())( GetUser()->GetAllClients().size())); } } void OnMethodCommand(const CString& sCommand) { const CString sArg = sCommand.Token(1, true).AsLower(); if (sArg != "notice" && sArg != "message" && sArg != "off") { PutModule(t_s("Usage: Method ")); return; } m_sMethod = sArg; SaveSettings(); PutModule(t_s("Saved.")); } void OnNewOnlyCommand(const CString& sCommand) { const CString sArg = sCommand.Token(1, true).AsLower(); if (sArg.empty()) { PutModule(t_s("Usage: NewOnly ")); return; } m_bNewOnly = sArg.ToBool(); SaveSettings(); PutModule(t_s("Saved.")); } void OnNotifyOnNewIP(const CString& sCommand) { const CString sArg = sCommand.Token(1, true).AsLower(); if (sArg.empty()) { PutModule(t_s("Usage: NotifyOnNewIP ")); return; } m_bNotifyOnNewIP = sArg.ToBool(); SaveSettings(); PutModule(t_s("Saved.")); } void OnNotifyOnNewID(const CString& sCommand) { const CString sArg = sCommand.Token(1, true).AsLower(); if (sArg.empty()) { PutModule(t_s("Usage: NotifyOnNewID ")); return; } m_bNotifyOnNewClientID = sArg.ToBool(); SaveSettings(); PutModule(t_s("Saved.")); } void OnDisconnectCommand(const CString& sCommand) { const CString sArg = sCommand.Token(1, true).AsLower(); if (sArg.empty()) { PutModule(t_s("Usage: OnDisconnect ")); return; } m_bOnDisconnect = sArg.ToBool(); SaveSettings(); PutModule(t_s("Saved.")); } void OnShowCommand(const CString& sLine) { PutModule( t_f("Current settings: Method: {1}, for unseen only: {2}, notify" "for unseen IPs: {3}, notify for unseen IDs: {4}, notify on" "disconnecting clients: {5}")( m_sMethod, m_bNewOnly, m_bNotifyOnNewIP, m_bNotifyOnNewClientID, m_bOnDisconnect)); } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("clientnotify"); } USERMODULEDEFS(CClientNotifyMod, t_s("Notifies you when another IRC client logs into or out of " "your account. Configurable.")) znc-1.9.1/modules/controlpanel.cpp0000644000175000017500000020336014641222733017424 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * Copyright (C) 2008 by Stefan Rado * based on admin.cpp by Sebastian Ramacher * based on admin.cpp in crox branch * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include using std::map; using std::vector; template struct array_size_helper { char __place_holder[N]; }; template static array_size_helper array_size(T (&)[N]) { return array_size_helper(); } #define ARRAY_SIZE(array) sizeof(array_size((array))) class CAdminMod : public CModule { using CModule::PutModule; struct Setting { const char* name; CString type; }; void PrintVarsHelp(const CString& sFilter, const Setting vars[], unsigned int uSize, const CString& sDescription) { CTable VarTable; VarTable.AddColumn(t_s("Type", "helptable")); VarTable.AddColumn(t_s("Variables", "helptable")); VarTable.SetStyle(CTable::ListStyle); std::map mvsTypedVariables; for (unsigned int i = 0; i != uSize; ++i) { CString sVar = CString(vars[i].name).AsLower(); if (sFilter.empty() || sVar.StartsWith(sFilter) || sVar.WildCmp(sFilter)) { mvsTypedVariables[vars[i].type].emplace_back(vars[i].name); } } for (const auto& i : mvsTypedVariables) { VarTable.AddRow(); VarTable.SetCell(t_s("Type", "helptable"), i.first); VarTable.SetCell( t_s("Variables", "helptable"), CString(", ").Join(i.second.cbegin(), i.second.cend())); } if (!VarTable.empty()) { PutModule(sDescription); PutModule(VarTable); } } void PrintHelp(const CString& sLine) { HandleHelpCommand(sLine); const CString str = t_s("String"); const CString boolean = t_s("Boolean (true/false)"); const CString integer = t_s("Integer"); const CString number = t_s("Number"); const CString sCmdFilter = sLine.Token(1, false); const CString sVarFilter = sLine.Token(2, true).AsLower(); if (sCmdFilter.empty() || sCmdFilter.StartsWith("Set") || sCmdFilter.StartsWith("Get")) { Setting vars[] = { {"Nick", str}, {"Altnick", str}, {"Ident", str}, {"RealName", str}, {"BindHost", str}, {"MultiClients", boolean}, {"DenyLoadMod", boolean}, {"DenySetBindHost", boolean}, {"DenySetIdent", boolean}, {"DenySetNetwork", boolean}, {"DenySetRealName", boolean}, {"DenySetQuitMsg", boolean}, {"DenySetCTCPReplies", boolean}, {"DefaultChanModes", str}, {"QuitMsg", str}, {"ChanBufferSize", integer}, {"QueryBufferSize", integer}, {"AutoClearChanBuffer", boolean}, {"AutoClearQueryBuffer", boolean}, {"Password", str}, {"JoinTries", integer}, {"MaxJoins", integer}, {"MaxNetworks", integer}, {"MaxQueryBuffers", integer}, {"Timezone", str}, {"Admin", boolean}, {"AppendTimestamp", boolean}, {"PrependTimestamp", boolean}, {"AuthOnlyViaModule", boolean}, {"TimestampFormat", str}, {"DCCBindHost", str}, {"StatusPrefix", str}, {"NoTrafficTimeout", integer}, #ifdef HAVE_I18N {"Language", str}, #endif #ifdef HAVE_ICU {"ClientEncoding", str}, #endif }; PutModule(""); PrintVarsHelp(sVarFilter, vars, ARRAY_SIZE(vars), t_s("The following variables are available when " "using the Set/Get commands:")); } if (sCmdFilter.empty() || sCmdFilter.StartsWith("SetNetwork") || sCmdFilter.StartsWith("GetNetwork")) { Setting nvars[] = { {"Nick", str}, {"Altnick", str}, {"Ident", str}, {"RealName", str}, {"BindHost", str}, {"FloodRate", number}, {"FloodBurst", integer}, {"JoinDelay", integer}, #ifdef HAVE_ICU {"Encoding", str}, #endif {"QuitMsg", str}, {"TrustAllCerts", boolean}, {"TrustPKI", boolean}, }; PutModule(""); PrintVarsHelp(sVarFilter, nvars, ARRAY_SIZE(nvars), t_s("The following variables are available when " "using the SetNetwork/GetNetwork commands:")); } if (sCmdFilter.empty() || sCmdFilter.StartsWith("SetChan") || sCmdFilter.StartsWith("GetChan")) { Setting cvars[] = {{"DefModes", str}, {"Key", str}, {"BufferSize", integer}, {"InConfig", boolean}, {"AutoClearChanBuffer", boolean}, {"Detached", boolean}}; PutModule(""); PrintVarsHelp(sVarFilter, cvars, ARRAY_SIZE(cvars), t_s("The following variables are available when " "using the SetChan/GetChan commands:")); } if (sCmdFilter.empty()) { PutModule(""); PutModule( t_s("You can use $user as the user name and $network as the " "network name for modifying your own user and network.")); } } CUser* FindUser(const CString& sUsername) { if (sUsername.Equals("$me") || sUsername.Equals("$user")) return GetUser(); CUser* pUser = CZNC::Get().FindUser(sUsername); if (!pUser) { PutModule(t_f("Error: User [{1}] does not exist!")(sUsername)); return nullptr; } if (pUser != GetUser() && !GetUser()->IsAdmin()) { PutModule(t_s( "Error: You need to have admin rights to modify other users!")); return nullptr; } return pUser; } CIRCNetwork* FindNetwork(CUser* pUser, const CString& sNetwork) { if (sNetwork.Equals("$net") || sNetwork.Equals("$network")) { if (pUser != GetUser()) { PutModule(t_s( "Error: You cannot use $network to modify other users!")); return nullptr; } return CModule::GetNetwork(); } CIRCNetwork* pNetwork = pUser->FindNetwork(sNetwork); if (!pNetwork) { PutModule( t_f("Error: User {1} does not have a network named [{2}].")( pUser->GetUsername(), sNetwork)); } return pNetwork; } void Get(const CString& sLine) { const CString sVar = sLine.Token(1).AsLower(); CString sUsername = sLine.Token(2, true); CUser* pUser; if (sVar.empty()) { PutModule(t_s("Usage: Get [username]")); return; } if (sUsername.empty()) { pUser = GetUser(); } else { pUser = FindUser(sUsername); } if (!pUser) return; if (sVar == "nick") PutModule("Nick = " + pUser->GetNick()); else if (sVar == "altnick") PutModule("AltNick = " + pUser->GetAltNick()); else if (sVar == "ident") PutModule("Ident = " + pUser->GetIdent()); else if (sVar == "realname") PutModule("RealName = " + pUser->GetRealName()); else if (sVar == "bindhost") PutModule("BindHost = " + pUser->GetBindHost()); else if (sVar == "multiclients") PutModule("MultiClients = " + CString(pUser->MultiClients())); else if (sVar == "denyloadmod") PutModule("DenyLoadMod = " + CString(pUser->DenyLoadMod())); else if (sVar == "denysetbindhost") PutModule("DenySetBindHost = " + CString(pUser->DenySetBindHost())); else if (sVar == "denysetident") PutModule("DenySetIdent = " + CString(pUser->DenySetIdent())); else if (sVar == "denysetnetwork") PutModule("DenySetNetwork = " + CString(pUser->DenySetNetwork())); else if (sVar == "denysetrealname") PutModule("DenySetRealName = " + CString(pUser->DenySetRealName())); else if (sVar == "denysetquitmsg") PutModule("DenySetQuitMsg = " + CString(pUser->DenySetQuitMsg())); else if (sVar == "denysetctcpreplies") PutModule("DenySetCTCPReplies = " + CString(pUser->DenySetCTCPReplies())); else if (sVar == "defaultchanmodes") PutModule("DefaultChanModes = " + pUser->GetDefaultChanModes()); else if (sVar == "quitmsg") PutModule("QuitMsg = " + pUser->GetQuitMsg()); else if (sVar == "buffercount") PutModule("BufferCount = " + CString(pUser->GetBufferCount())); else if (sVar == "chanbuffersize") PutModule("ChanBufferSize = " + CString(pUser->GetChanBufferSize())); else if (sVar == "querybuffersize") PutModule("QueryBufferSize = " + CString(pUser->GetQueryBufferSize())); else if (sVar == "keepbuffer") // XXX compatibility crap, added in 0.207 PutModule("KeepBuffer = " + CString(!pUser->AutoClearChanBuffer())); else if (sVar == "autoclearchanbuffer") PutModule("AutoClearChanBuffer = " + CString(pUser->AutoClearChanBuffer())); else if (sVar == "autoclearquerybuffer") PutModule("AutoClearQueryBuffer = " + CString(pUser->AutoClearQueryBuffer())); else if (sVar == "maxjoins") PutModule("MaxJoins = " + CString(pUser->MaxJoins())); else if (sVar == "notraffictimeout") PutModule("NoTrafficTimeout = " + CString(pUser->GetNoTrafficTimeout())); else if (sVar == "maxnetworks") PutModule("MaxNetworks = " + CString(pUser->MaxNetworks())); else if (sVar == "maxquerybuffers") PutModule("MaxQueryBuffers = " + CString(pUser->MaxQueryBuffers())); else if (sVar == "jointries") PutModule("JoinTries = " + CString(pUser->JoinTries())); else if (sVar == "timezone") PutModule("Timezone = " + pUser->GetTimezone()); else if (sVar == "appendtimestamp") PutModule("AppendTimestamp = " + CString(pUser->GetTimestampAppend())); else if (sVar == "prependtimestamp") PutModule("PrependTimestamp = " + CString(pUser->GetTimestampPrepend())); else if (sVar == "authonlyviamodule") PutModule("AuthOnlyViaModule = " + CString(pUser->AuthOnlyViaModule())); else if (sVar == "timestampformat") PutModule("TimestampFormat = " + pUser->GetTimestampFormat()); else if (sVar == "dccbindhost") PutModule("DCCBindHost = " + CString(pUser->GetDCCBindHost())); else if (sVar == "admin") PutModule("Admin = " + CString(pUser->IsAdmin())); else if (sVar == "statusprefix") PutModule("StatusPrefix = " + pUser->GetStatusPrefix()); #ifdef HAVE_I18N else if (sVar == "language") PutModule("Language = " + (pUser->GetLanguage().empty() ? "en" : pUser->GetLanguage())); #endif #ifdef HAVE_ICU else if (sVar == "clientencoding") PutModule("ClientEncoding = " + pUser->GetClientEncoding()); #endif else PutModule(t_s("Error: Unknown variable")); } void Set(const CString& sLine) { const CString sVar = sLine.Token(1).AsLower(); CString sUsername = sLine.Token(2); CString sValue = sLine.Token(3, true); if (sValue.empty()) { PutModule(t_s("Usage: Set ")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; if (sVar == "nick") { pUser->SetNick(sValue); PutModule("Nick = " + sValue); } else if (sVar == "altnick") { pUser->SetAltNick(sValue); PutModule("AltNick = " + sValue); } else if (sVar == "ident") { if (!pUser->DenySetIdent() || GetUser()->IsAdmin()) { pUser->SetIdent(sValue); PutModule("Ident = " + sValue); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "realname") { if (!pUser->DenySetRealName() || GetUser()->IsAdmin()) { pUser->SetRealName(sValue); PutModule("RealName = " + sValue); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "bindhost") { if (!pUser->DenySetBindHost() || GetUser()->IsAdmin()) { if (sValue.Equals(pUser->GetBindHost())) { PutModule(t_s("This bind host is already set!")); return; } pUser->SetBindHost(sValue); PutModule("BindHost = " + sValue); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "multiclients") { bool b = sValue.ToBool(); pUser->SetMultiClients(b); PutModule("MultiClients = " + CString(b)); } else if (sVar == "denyloadmod") { if (GetUser()->IsAdmin()) { bool b = sValue.ToBool(); pUser->SetDenyLoadMod(b); PutModule("DenyLoadMod = " + CString(b)); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "denysetbindhost") { if (GetUser()->IsAdmin()) { bool b = sValue.ToBool(); pUser->SetDenySetBindHost(b); PutModule("DenySetBindHost = " + CString(b)); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "denysetident") { if (GetUser()->IsAdmin()) { bool b = sValue.ToBool(); pUser->SetDenySetIdent(b); PutModule("DenySetIdent = " + CString(b)); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "denysetnetwork") { if (GetUser()->IsAdmin()) { bool b = sValue.ToBool(); pUser->SetDenySetNetwork(b); PutModule("DenySetNetwork = " + CString(b)); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "denysetrealname") { if (GetUser()->IsAdmin()) { bool b = sValue.ToBool(); pUser->SetDenySetRealName(b); PutModule("DenySetRealName = " + CString(b)); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "denysetquitmsg") { if (GetUser()->IsAdmin()) { bool b = sValue.ToBool(); pUser->SetDenySetQuitMsg(b); PutModule("DenySetQuitMsg = " + CString(b)); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "denysetctcpreplies") { if (GetUser()->IsAdmin()) { bool b = sValue.ToBool(); pUser->SetDenySetCTCPReplies(b); PutModule("DenySetCTCPReplies = " + CString(b)); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "defaultchanmodes") { pUser->SetDefaultChanModes(sValue); PutModule("DefaultChanModes = " + sValue); } else if (sVar == "quitmsg") { if (!pUser->DenySetQuitMsg() || GetUser()->IsAdmin()) { pUser->SetQuitMsg(sValue); PutModule("QuitMsg = " + sValue); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "chanbuffersize" || sVar == "buffercount") { unsigned int i = sValue.ToUInt(); // Admins don't have to honour the buffer limit if (pUser->SetChanBufferSize(i, GetUser()->IsAdmin())) { PutModule("ChanBufferSize = " + sValue); } else { PutModule(t_f("Setting failed, limit for buffer size is {1}")( CString(CZNC::Get().GetMaxBufferSize()))); } } else if (sVar == "querybuffersize") { unsigned int i = sValue.ToUInt(); // Admins don't have to honour the buffer limit if (pUser->SetQueryBufferSize(i, GetUser()->IsAdmin())) { PutModule("QueryBufferSize = " + sValue); } else { PutModule(t_f("Setting failed, limit for buffer size is {1}")( CString(CZNC::Get().GetMaxBufferSize()))); } } else if (sVar == "keepbuffer") { // XXX compatibility crap, added in 0.207 bool b = !sValue.ToBool(); pUser->SetAutoClearChanBuffer(b); PutModule("AutoClearChanBuffer = " + CString(b)); } else if (sVar == "autoclearchanbuffer") { bool b = sValue.ToBool(); pUser->SetAutoClearChanBuffer(b); PutModule("AutoClearChanBuffer = " + CString(b)); } else if (sVar == "autoclearquerybuffer") { bool b = sValue.ToBool(); pUser->SetAutoClearQueryBuffer(b); PutModule("AutoClearQueryBuffer = " + CString(b)); } else if (sVar == "password") { const CString sSalt = CUtils::GetSalt(); const CString sHash = CUser::SaltedHash(sValue, sSalt); pUser->SetPass(sHash, CUser::HASH_DEFAULT, sSalt); PutModule(t_s("Password has been changed!")); } else if (sVar == "maxjoins") { unsigned int i = sValue.ToUInt(); pUser->SetMaxJoins(i); PutModule("MaxJoins = " + CString(pUser->MaxJoins())); } else if (sVar == "notraffictimeout") { unsigned int i = sValue.ToUInt(); if (i < 30) { PutModule(t_s("Timeout can't be less than 30 seconds!")); } else { pUser->SetNoTrafficTimeout(i); PutModule("NoTrafficTimeout = " + CString(pUser->GetNoTrafficTimeout())); } } else if (sVar == "maxnetworks") { if (GetUser()->IsAdmin()) { unsigned int i = sValue.ToUInt(); pUser->SetMaxNetworks(i); PutModule("MaxNetworks = " + sValue); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "maxquerybuffers") { unsigned int i = sValue.ToUInt(); pUser->SetMaxQueryBuffers(i); PutModule("MaxQueryBuffers = " + sValue); } else if (sVar == "jointries") { unsigned int i = sValue.ToUInt(); pUser->SetJoinTries(i); PutModule("JoinTries = " + CString(pUser->JoinTries())); } else if (sVar == "timezone") { pUser->SetTimezone(sValue); PutModule("Timezone = " + pUser->GetTimezone()); } else if (sVar == "admin") { if (GetUser()->IsAdmin() && pUser != GetUser()) { bool b = sValue.ToBool(); pUser->SetAdmin(b); PutModule("Admin = " + CString(pUser->IsAdmin())); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "prependtimestamp") { bool b = sValue.ToBool(); pUser->SetTimestampPrepend(b); PutModule("PrependTimestamp = " + CString(b)); } else if (sVar == "appendtimestamp") { bool b = sValue.ToBool(); pUser->SetTimestampAppend(b); PutModule("AppendTimestamp = " + CString(b)); } else if (sVar == "authonlyviamodule") { if (GetUser()->IsAdmin()) { bool b = sValue.ToBool(); pUser->SetAuthOnlyViaModule(b); PutModule("AuthOnlyViaModule = " + CString(b)); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "timestampformat") { pUser->SetTimestampFormat(sValue); PutModule("TimestampFormat = " + sValue); } else if (sVar == "dccbindhost") { if (!pUser->DenySetBindHost() || GetUser()->IsAdmin()) { pUser->SetDCCBindHost(sValue); PutModule("DCCBindHost = " + sValue); } else { PutModule(t_s("Access denied!")); } } else if (sVar == "statusprefix") { if (sVar.find_first_of(" \t\n") == CString::npos) { pUser->SetStatusPrefix(sValue); PutModule("StatusPrefix = " + sValue); } else { PutModule(t_s("That would be a bad idea!")); } } #ifdef HAVE_I18N else if (sVar == "language") { auto mTranslations = CTranslationInfo::GetTranslations(); // TODO: maybe stop special-casing English if (sValue == "en") { pUser->SetLanguage(""); PutModule("Language is set to English"); } else if (mTranslations.count(sValue)) { pUser->SetLanguage(sValue); PutModule("Language = " + sValue); } else { VCString vsCodes = {"en"}; for (const auto it : mTranslations) { vsCodes.push_back(it.first); } PutModule(t_f("Supported languages: {1}")( CString(", ").Join(vsCodes.begin(), vsCodes.end()))); } } #endif #ifdef HAVE_ICU else if (sVar == "clientencoding") { pUser->SetClientEncoding(sValue); PutModule("ClientEncoding = " + pUser->GetClientEncoding()); } #endif else PutModule(t_s("Error: Unknown variable")); } void GetNetwork(const CString& sLine) { const CString sVar = sLine.Token(1).AsLower(); const CString sUsername = sLine.Token(2); const CString sNetwork = sLine.Token(3); CIRCNetwork* pNetwork = nullptr; CUser* pUser; if (sVar.empty()) { PutModule(t_s("Usage: GetNetwork [username] [network]")); return; } if (sUsername.empty()) { pUser = GetUser(); } else { pUser = FindUser(sUsername); } if (!pUser) { return; } if (sNetwork.empty()) { if (pUser == GetUser()) { pNetwork = CModule::GetNetwork(); } else { PutModule( t_s("Error: A network must be specified to get another " "users settings.")); return; } if (!pNetwork) { PutModule(t_s("You are not currently attached to a network.")); return; } } else { pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { PutModule(t_s("Error: Invalid network.")); return; } } if (sVar.Equals("nick")) { PutModule("Nick = " + pNetwork->GetNick()); } else if (sVar.Equals("altnick")) { PutModule("AltNick = " + pNetwork->GetAltNick()); } else if (sVar.Equals("ident")) { PutModule("Ident = " + pNetwork->GetIdent()); } else if (sVar.Equals("realname")) { PutModule("RealName = " + pNetwork->GetRealName()); } else if (sVar.Equals("bindhost")) { PutModule("BindHost = " + pNetwork->GetBindHost()); } else if (sVar.Equals("floodrate")) { PutModule("FloodRate = " + CString(pNetwork->GetFloodRate())); } else if (sVar.Equals("floodburst")) { PutModule("FloodBurst = " + CString(pNetwork->GetFloodBurst())); } else if (sVar.Equals("joindelay")) { PutModule("JoinDelay = " + CString(pNetwork->GetJoinDelay())); #ifdef HAVE_ICU } else if (sVar.Equals("encoding")) { PutModule("Encoding = " + pNetwork->GetEncoding()); #endif } else if (sVar.Equals("quitmsg")) { PutModule("QuitMsg = " + pNetwork->GetQuitMsg()); } else if (sVar.Equals("trustallcerts")) { PutModule("TrustAllCerts = " + CString(pNetwork->GetTrustAllCerts())); } else if (sVar.Equals("trustpki")) { PutModule("TrustPKI = " + CString(pNetwork->GetTrustPKI())); } else { PutModule(t_s("Error: Unknown variable")); } } void SetNetwork(const CString& sLine) { const CString sVar = sLine.Token(1).AsLower(); const CString sUsername = sLine.Token(2); const CString sNetwork = sLine.Token(3); const CString sValue = sLine.Token(4, true); if (sValue.empty()) { PutModule(t_s( "Usage: SetNetwork ")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) { return; } CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { return; } if (sVar.Equals("nick")) { pNetwork->SetNick(sValue); PutModule("Nick = " + pNetwork->GetNick()); } else if (sVar.Equals("altnick")) { pNetwork->SetAltNick(sValue); PutModule("AltNick = " + pNetwork->GetAltNick()); } else if (sVar.Equals("ident")) { if (!pUser->DenySetIdent() || GetUser()->IsAdmin()) { pNetwork->SetIdent(sValue); PutModule("Ident = " + pNetwork->GetIdent()); } else { PutModule(t_s("Access denied!")); } } else if (sVar.Equals("realname")) { if (!pUser->DenySetRealName() || GetUser()->IsAdmin()) { pNetwork->SetRealName(sValue); PutModule("RealName = " + pNetwork->GetRealName()); } else { PutModule(t_s("Access denied!")); } } else if (sVar.Equals("bindhost")) { if (!pUser->DenySetBindHost() || GetUser()->IsAdmin()) { if (sValue.Equals(pNetwork->GetBindHost())) { PutModule(t_s("This bind host is already set!")); return; } pNetwork->SetBindHost(sValue); PutModule("BindHost = " + sValue); } else { PutModule(t_s("Access denied!")); } } else if (sVar.Equals("floodrate")) { pNetwork->SetFloodRate(sValue.ToDouble()); PutModule("FloodRate = " + CString(pNetwork->GetFloodRate())); } else if (sVar.Equals("floodburst")) { pNetwork->SetFloodBurst(sValue.ToUShort()); PutModule("FloodBurst = " + CString(pNetwork->GetFloodBurst())); } else if (sVar.Equals("joindelay")) { pNetwork->SetJoinDelay(sValue.ToUShort()); PutModule("JoinDelay = " + CString(pNetwork->GetJoinDelay())); #ifdef HAVE_ICU } else if (sVar.Equals("encoding")) { pNetwork->SetEncoding(sValue); PutModule("Encoding = " + pNetwork->GetEncoding()); #endif } else if (sVar.Equals("quitmsg")) { if (!pUser->DenySetQuitMsg() || GetUser()->IsAdmin()) { pNetwork->SetQuitMsg(sValue); PutModule("QuitMsg = " + pNetwork->GetQuitMsg()); } else { PutModule(t_s("Access denied!")); } } else if (sVar.Equals("trustallcerts")) { bool b = sValue.ToBool(); pNetwork->SetTrustAllCerts(b); PutModule("TrustAllCerts = " + CString(b)); } else if (sVar.Equals("trustpki")) { bool b = sValue.ToBool(); pNetwork->SetTrustPKI(b); PutModule("TrustPKI = " + CString(b)); } else { PutModule(t_s("Error: Unknown variable")); } } void AddChan(const CString& sLine) { const CString sUsername = sLine.Token(1); const CString sNetwork = sLine.Token(2); const CString sChan = sLine.Token(3); if (sChan.empty()) { PutModule(t_s("Usage: AddChan ")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { return; } if (pNetwork->FindChan(sChan)) { PutModule(t_f("Error: User {1} already has a channel named {2}.")( sUsername, sChan)); return; } CChan* pChan = new CChan(sChan, pNetwork, true); if (pNetwork->AddChan(pChan)) PutModule(t_f("Channel {1} for user {2} added to network {3}.")( pChan->GetName(), sUsername, pNetwork->GetName())); else PutModule(t_f( "Could not add channel {1} for user {2} to network {3}, does " "it already exist?")(sChan, sUsername, pNetwork->GetName())); } void DelChan(const CString& sLine) { const CString sUsername = sLine.Token(1); const CString sNetwork = sLine.Token(2); const CString sChan = sLine.Token(3); if (sChan.empty()) { PutModule(t_s("Usage: DelChan ")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { return; } std::vector vChans = pNetwork->FindChans(sChan); if (vChans.empty()) { PutModule( t_f("Error: User {1} does not have any channel matching [{2}] " "in network {3}")(sUsername, sChan, pNetwork->GetName())); return; } VCString vsNames; for (const CChan* pChan : vChans) { const CString& sName = pChan->GetName(); vsNames.push_back(sName); pNetwork->PutIRC("PART " + sName); pNetwork->DelChan(sName); } PutModule(t_p("Channel {1} is deleted from network {2} of user {3}", "Channels {1} are deleted from network {2} of user {3}", vsNames.size())( CString(", ").Join(vsNames.begin(), vsNames.end()), pNetwork->GetName(), sUsername)); } void GetChan(const CString& sLine) { const CString sVar = sLine.Token(1).AsLower(); CString sUsername = sLine.Token(2); CString sNetwork = sLine.Token(3); CString sChan = sLine.Token(4, true); if (sChan.empty()) { PutModule( t_s("Usage: GetChan ")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { return; } std::vector vChans = pNetwork->FindChans(sChan); if (vChans.empty()) { PutModule(t_f("Error: No channels matching [{1}] found.")(sChan)); return; } for (CChan* pChan : vChans) { if (sVar == "defmodes") { PutModule(pChan->GetName() + ": DefModes = " + pChan->GetDefaultModes()); } else if (sVar == "buffersize" || sVar == "buffer") { CString sValue(pChan->GetBufferCount()); if (!pChan->HasBufferCountSet()) { sValue += " (default)"; } PutModule(pChan->GetName() + ": BufferSize = " + sValue); } else if (sVar == "inconfig") { PutModule(pChan->GetName() + ": InConfig = " + CString(pChan->InConfig())); } else if (sVar == "keepbuffer") { // XXX compatibility crap, added in 0.207 PutModule(pChan->GetName() + ": KeepBuffer = " + CString(!pChan->AutoClearChanBuffer())); } else if (sVar == "autoclearchanbuffer") { CString sValue(pChan->AutoClearChanBuffer()); if (!pChan->HasAutoClearChanBufferSet()) { sValue += " (default)"; } PutModule(pChan->GetName() + ": AutoClearChanBuffer = " + sValue); } else if (sVar == "detached") { PutModule(pChan->GetName() + ": Detached = " + CString(pChan->IsDetached())); } else if (sVar == "key") { PutModule(pChan->GetName() + ": Key = " + pChan->GetKey()); } else { PutModule(t_s("Error: Unknown variable")); return; } } } void SetChan(const CString& sLine) { const CString sVar = sLine.Token(1).AsLower(); CString sUsername = sLine.Token(2); CString sNetwork = sLine.Token(3); CString sChan = sLine.Token(4); CString sValue = sLine.Token(5, true); if (sValue.empty()) { PutModule( t_s("Usage: SetChan " "")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { return; } std::vector vChans = pNetwork->FindChans(sChan); if (vChans.empty()) { PutModule(t_f("Error: No channels matching [{1}] found.")(sChan)); return; } for (CChan* pChan : vChans) { if (sVar == "defmodes") { pChan->SetDefaultModes(sValue); PutModule(pChan->GetName() + ": DefModes = " + sValue); } else if (sVar == "buffersize" || sVar == "buffer") { unsigned int i = sValue.ToUInt(); if (sValue.Equals("-")) { pChan->ResetBufferCount(); PutModule(pChan->GetName() + ": BufferSize = " + CString(pChan->GetBufferCount())); } else if (pChan->SetBufferCount(i, GetUser()->IsAdmin())) { // Admins don't have to honour the buffer limit PutModule(pChan->GetName() + ": BufferSize = " + sValue); } else { PutModule( t_f("Setting failed, limit for buffer size is {1}")( CString(CZNC::Get().GetMaxBufferSize()))); return; } } else if (sVar == "inconfig") { bool b = sValue.ToBool(); pChan->SetInConfig(b); PutModule(pChan->GetName() + ": InConfig = " + CString(b)); } else if (sVar == "keepbuffer") { // XXX compatibility crap, added in 0.207 bool b = !sValue.ToBool(); pChan->SetAutoClearChanBuffer(b); PutModule(pChan->GetName() + ": AutoClearChanBuffer = " + CString(b)); } else if (sVar == "autoclearchanbuffer") { if (sValue.Equals("-")) { pChan->ResetAutoClearChanBuffer(); } else { bool b = sValue.ToBool(); pChan->SetAutoClearChanBuffer(b); } PutModule(pChan->GetName() + ": AutoClearChanBuffer = " + CString(pChan->AutoClearChanBuffer())); } else if (sVar == "detached") { bool b = sValue.ToBool(); if (pChan->IsDetached() != b) { if (b) pChan->DetachUser(); else pChan->AttachUser(); } PutModule(pChan->GetName() + ": Detached = " + CString(b)); } else if (sVar == "key") { pChan->SetKey(sValue); PutModule(pChan->GetName() + ": Key = " + sValue); } else { PutModule(t_s("Error: Unknown variable")); return; } } } void ListUsers(const CString&) { if (!GetUser()->IsAdmin()) return; const map& msUsers = CZNC::Get().GetUserMap(); CTable Table; Table.AddColumn(t_s("Username", "listusers")); Table.AddColumn(t_s("Realname", "listusers")); Table.AddColumn(t_s("IsAdmin", "listusers")); Table.AddColumn(t_s("Nick", "listusers")); Table.AddColumn(t_s("AltNick", "listusers")); Table.AddColumn(t_s("Ident", "listusers")); Table.AddColumn(t_s("BindHost", "listusers")); for (const auto& it : msUsers) { Table.AddRow(); Table.SetCell(t_s("Username", "listusers"), it.first); Table.SetCell(t_s("Realname", "listusers"), it.second->GetRealName()); if (!it.second->IsAdmin()) Table.SetCell(t_s("IsAdmin", "listusers"), t_s("No")); else Table.SetCell(t_s("IsAdmin", "listusers"), t_s("Yes")); Table.SetCell(t_s("Nick", "listusers"), it.second->GetNick()); Table.SetCell(t_s("AltNick", "listusers"), it.second->GetAltNick()); Table.SetCell(t_s("Ident", "listusers"), it.second->GetIdent()); Table.SetCell(t_s("BindHost", "listusers"), it.second->GetBindHost()); } PutModule(Table); } void AddUser(const CString& sLine) { if (!GetUser()->IsAdmin()) { PutModule( t_s("Error: You need to have admin rights to add new users!")); return; } const CString sUsername = sLine.Token(1), sPassword = sLine.Token(2); if (sPassword.empty()) { PutModule(t_s("Usage: AddUser ")); return; } if (CZNC::Get().FindUser(sUsername)) { PutModule(t_f("Error: User {1} already exists!")(sUsername)); return; } CUser* pNewUser = new CUser(sUsername); CString sSalt = CUtils::GetSalt(); pNewUser->SetPass(CUser::SaltedHash(sPassword, sSalt), CUser::HASH_DEFAULT, sSalt); CString sErr; if (!CZNC::Get().AddUser(pNewUser, sErr)) { delete pNewUser; PutModule(t_f("Error: User not added: {1}")(sErr)); return; } PutModule(t_f("User {1} added!")(sUsername)); return; } void DelUser(const CString& sLine) { if (!GetUser()->IsAdmin()) { PutModule( t_s("Error: You need to have admin rights to delete users!")); return; } const CString sUsername = sLine.Token(1, true); if (sUsername.empty()) { PutModule(t_s("Usage: DelUser ")); return; } CUser* pUser = CZNC::Get().FindUser(sUsername); if (!pUser) { PutModule(t_f("Error: User [{1}] does not exist!")(sUsername)); return; } if (pUser == GetUser()) { PutModule(t_s("Error: You can't delete yourself!")); return; } if (!CZNC::Get().DeleteUser(pUser->GetUsername())) { // This can't happen, because we got the user from FindUser() PutModule(t_s("Error: Internal error!")); return; } PutModule(t_f("User {1} deleted!")(sUsername)); return; } void CloneUser(const CString& sLine) { if (!GetUser()->IsAdmin()) { PutModule( t_s("Error: You need to have admin rights to add new users!")); return; } const CString sOldUsername = sLine.Token(1), sNewUsername = sLine.Token(2, true); if (sOldUsername.empty() || sNewUsername.empty()) { PutModule(t_s("Usage: CloneUser ")); return; } CUser* pOldUser = CZNC::Get().FindUser(sOldUsername); if (!pOldUser) { PutModule(t_f("Error: User [{1}] does not exist!")(sOldUsername)); return; } CUser* pNewUser = new CUser(sNewUsername); CString sError; if (!pNewUser->Clone(*pOldUser, sError)) { delete pNewUser; PutModule(t_f("Error: Cloning failed: {1}")(sError)); return; } if (!CZNC::Get().AddUser(pNewUser, sError)) { delete pNewUser; PutModule(t_f("Error: User not added: {1}")(sError)); return; } PutModule(t_f("User {1} added!")(sNewUsername)); return; } void AddNetwork(const CString& sLine) { CString sUser = sLine.Token(1); CString sNetwork = sLine.Token(2); CUser* pUser = GetUser(); if (sNetwork.empty()) { sNetwork = sUser; } else { pUser = FindUser(sUser); if (!pUser) { return; } } if (sNetwork.empty()) { PutModule(t_s("Usage: AddNetwork [user] network")); return; } if (!GetUser()->IsAdmin() && pUser->DenySetNetwork()) { PutModule(t_s("Access denied!")); return; } if (!GetUser()->IsAdmin() && !pUser->HasSpaceForNewNetwork()) { PutStatus( t_s("Network number limit reached. Ask an admin to increase " "the limit for you, or delete unneeded networks using /znc " "DelNetwork ")); return; } if (pUser->FindNetwork(sNetwork)) { PutModule( t_f("Error: User {1} already has a network with the name {2}")( pUser->GetUsername(), sNetwork)); return; } CString sNetworkAddError; if (pUser->AddNetwork(sNetwork, sNetworkAddError)) { PutModule(t_f("Network {1} added to user {2}.")( sNetwork, pUser->GetUsername())); } else { PutModule(t_f( "Error: Network [{1}] could not be added for user {2}: {3}")( sNetwork, pUser->GetUsername(), sNetworkAddError)); } } void DelNetwork(const CString& sLine) { CString sUser = sLine.Token(1); CString sNetwork = sLine.Token(2); CUser* pUser = GetUser(); if (sNetwork.empty()) { sNetwork = sUser; } else { pUser = FindUser(sUser); if (!pUser) { return; } } if (sNetwork.empty()) { PutModule(t_s("Usage: DelNetwork [user] network")); return; } if (!GetUser()->IsAdmin() && pUser->DenySetNetwork()) { PutModule(t_s("Access denied!")); return; } CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { return; } if (pNetwork == CModule::GetNetwork()) { PutModule(t_f( "The currently active network can be deleted via {1}status")( GetUser()->GetStatusPrefix())); return; } if (pUser->DeleteNetwork(sNetwork)) { PutModule(t_f("Network {1} deleted for user {2}.")( sNetwork, pUser->GetUsername())); } else { PutModule( t_f("Error: Network {1} could not be deleted for user {2}.")( sNetwork, pUser->GetUsername())); } } void ListNetworks(const CString& sLine) { CString sUser = sLine.Token(1); CUser* pUser = GetUser(); if (!sUser.empty()) { pUser = FindUser(sUser); if (!pUser) { return; } } const vector& vNetworks = pUser->GetNetworks(); CTable Table; Table.AddColumn(t_s("Network", "listnetworks")); Table.AddColumn(t_s("OnIRC", "listnetworks")); Table.AddColumn(t_s("IRC Server", "listnetworks")); Table.AddColumn(t_s("IRC User", "listnetworks")); Table.AddColumn(t_s("Channels", "listnetworks")); for (const CIRCNetwork* pNetwork : vNetworks) { Table.AddRow(); Table.SetCell(t_s("Network", "listnetworks"), pNetwork->GetName()); if (pNetwork->IsIRCConnected()) { Table.SetCell(t_s("OnIRC", "listnetworks"), t_s("Yes")); Table.SetCell(t_s("IRC Server", "listnetworks"), pNetwork->GetIRCServer()); Table.SetCell(t_s("IRC User", "listnetworks"), pNetwork->GetIRCNick().GetNickMask()); Table.SetCell(t_s("Channels", "listnetworks"), CString(pNetwork->GetChans().size())); } else { Table.SetCell(t_s("OnIRC", "listnetworks"), t_s("No")); } } if (PutModule(Table) == 0) { PutModule(t_s("No networks")); } } void AddServer(const CString& sLine) { CString sUsername = sLine.Token(1); CString sNetwork = sLine.Token(2); CString sServer = sLine.Token(3, true); if (sServer.empty()) { PutModule( t_s("Usage: AddServer [[+]port] " "[password]")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; if (!GetUser()->IsAdmin() && pUser->DenySetNetwork()) { PutModule(t_s("Access denied!")); return; } CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { return; } if (pNetwork->AddServer(sServer)) PutModule(t_f("Added IRC Server {1} to network {2} for user {3}.")( sServer, pNetwork->GetName(), pUser->GetUsername())); else PutModule(t_f( "Error: Could not add IRC server {1} to network {2} for user " "{3}.")(sServer, pNetwork->GetName(), pUser->GetUsername())); } void DelServer(const CString& sLine) { CString sUsername = sLine.Token(1); CString sNetwork = sLine.Token(2); CString sServer = sLine.Token(3, true); unsigned short uPort = sLine.Token(4).ToUShort(); CString sPass = sLine.Token(5); if (sServer.empty()) { PutModule( t_s("Usage: DelServer [[+]port] " "[password]")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; if (!GetUser()->IsAdmin() && pUser->DenySetNetwork()) { PutModule(t_s("Access denied!")); return; } CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { return; } if (pNetwork->DelServer(sServer, uPort, sPass)) PutModule( t_f("Deleted IRC Server {1} from network {2} for user {3}.")( sServer, pNetwork->GetName(), pUser->GetUsername())); else PutModule( t_f("Error: Could not delete IRC server {1} from network {2} " "for user {3}.")(sServer, pNetwork->GetName(), pUser->GetUsername())); } void ReconnectUser(const CString& sLine) { CString sUsername = sLine.Token(1); CString sNetwork = sLine.Token(2); if (sNetwork.empty()) { PutModule(t_s("Usage: Reconnect ")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) { return; } CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { return; } CIRCSock* pIRCSock = pNetwork->GetIRCSock(); // cancel connection attempt: if (pIRCSock && !pIRCSock->IsConnected()) { pIRCSock->Close(); } // or close existing connection: else if (pIRCSock) { pIRCSock->Quit(); } // then reconnect pNetwork->SetIRCConnectEnabled(true); PutModule(t_f("Queued network {1} of user {2} for a reconnect.")( pNetwork->GetName(), pUser->GetUsername())); } void DisconnectUser(const CString& sLine) { CString sUsername = sLine.Token(1); CString sNetwork = sLine.Token(2); if (sNetwork.empty()) { PutModule(t_s("Usage: Disconnect ")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) { return; } CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { return; } pNetwork->SetIRCConnectEnabled(false); PutModule(t_f("Closed IRC connection for network {1} of user {2}.")( pNetwork->GetName(), pUser->GetUsername())); } void ListCTCP(const CString& sLine) { CString sUsername = sLine.Token(1, true); if (sUsername.empty()) { sUsername = GetUser()->GetUsername(); } CUser* pUser = FindUser(sUsername); if (!pUser) return; const MCString& msCTCPReplies = pUser->GetCTCPReplies(); CTable Table; Table.AddColumn(t_s("Request", "listctcp")); Table.AddColumn(t_s("Reply", "listctcp")); Table.SetStyle(CTable::ListStyle); for (const auto& it : msCTCPReplies) { Table.AddRow(); Table.SetCell(t_s("Request", "listctcp"), it.first); Table.SetCell(t_s("Reply", "listctcp"), it.second); } if (Table.empty()) { PutModule(t_f("No CTCP replies for user {1} are configured")( pUser->GetUsername())); } else { PutModule(t_f("CTCP replies for user {1}:")(pUser->GetUsername())); PutModule(Table); } } void AddCTCP(const CString& sLine) { CString sUsername = sLine.Token(1); CString sCTCPRequest = sLine.Token(2); CString sCTCPReply = sLine.Token(3, true); if (sCTCPRequest.empty()) { sCTCPRequest = sUsername; sCTCPReply = sLine.Token(2, true); sUsername = GetUser()->GetUsername(); } if (sCTCPRequest.empty()) { PutModule(t_s("Usage: AddCTCP [user] [request] [reply]")); PutModule( t_s("This will cause ZNC to reply to the CTCP instead of " "forwarding it to clients.")); PutModule(t_s( "An empty reply will cause the CTCP request to be blocked.")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; if (!GetUser()->IsAdmin() && pUser->DenySetCTCPReplies()) { PutModule(t_s("Access denied!")); return; } pUser->AddCTCPReply(sCTCPRequest, sCTCPReply); if (sCTCPReply.empty()) { PutModule(t_f("CTCP requests {1} to user {2} will now be blocked.")( sCTCPRequest.AsUpper(), pUser->GetUsername())); } else { PutModule( t_f("CTCP requests {1} to user {2} will now get reply: {3}")( sCTCPRequest.AsUpper(), pUser->GetUsername(), sCTCPReply)); } } void DelCTCP(const CString& sLine) { CString sUsername = sLine.Token(1); CString sCTCPRequest = sLine.Token(2, true); if (sCTCPRequest.empty()) { sCTCPRequest = sUsername; sUsername = GetUser()->GetUsername(); } CUser* pUser = FindUser(sUsername); if (!pUser) return; if (!GetUser()->IsAdmin() && pUser->DenySetCTCPReplies()) { PutModule(t_s("Access denied!")); return; } if (sCTCPRequest.empty()) { PutModule(t_s("Usage: DelCTCP [user] [request]")); return; } if (pUser->DelCTCPReply(sCTCPRequest)) { PutModule(t_f( "CTCP requests {1} to user {2} will now be sent to IRC clients")( sCTCPRequest.AsUpper(), pUser->GetUsername())); } else { PutModule( t_f("CTCP requests {1} to user {2} will be sent to IRC clients " "(nothing has changed)")(sCTCPRequest.AsUpper(), pUser->GetUsername())); } } void LoadModuleFor(CModules& Modules, const CString& sModName, const CString& sArgs, CModInfo::EModuleType eType, CUser* pUser, CIRCNetwork* pNetwork) { if (pUser->DenyLoadMod() && !GetUser()->IsAdmin()) { PutModule(t_s("Loading modules has been disabled.")); return; } CString sModRet; CModule* pMod = Modules.FindModule(sModName); if (!pMod) { if (!Modules.LoadModule(sModName, sArgs, eType, pUser, pNetwork, sModRet)) { PutModule(t_f("Error: Unable to load module {1}: {2}")( sModName, sModRet)); } else { PutModule(t_f("Loaded module {1}")(sModName)); } } else if (pMod->GetArgs() != sArgs) { if (!Modules.ReloadModule(sModName, sArgs, pUser, pNetwork, sModRet)) { PutModule(t_f("Error: Unable to reload module {1}: {2}")( sModName, sModRet)); } else { PutModule(t_f("Reloaded module {1}")(sModName)); } } else { PutModule( t_f("Error: Unable to load module {1} because it is already " "loaded")(sModName)); } } void LoadModuleForUser(const CString& sLine) { CString sUsername = sLine.Token(1); CString sModName = sLine.Token(2); CString sArgs = sLine.Token(3, true); if (sModName.empty()) { PutModule(t_s("Usage: LoadModule [args]")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; LoadModuleFor(pUser->GetModules(), sModName, sArgs, CModInfo::UserModule, pUser, nullptr); } void LoadModuleForNetwork(const CString& sLine) { CString sUsername = sLine.Token(1); CString sNetwork = sLine.Token(2); CString sModName = sLine.Token(3); CString sArgs = sLine.Token(4, true); if (sModName.empty()) { PutModule( t_s("Usage: LoadNetModule " "[args]")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { return; } LoadModuleFor(pNetwork->GetModules(), sModName, sArgs, CModInfo::NetworkModule, pUser, pNetwork); } void UnLoadModuleFor(CModules& Modules, const CString& sModName, CUser* pUser) { if (pUser->DenyLoadMod() && !GetUser()->IsAdmin()) { PutModule(t_s("Loading modules has been disabled.")); return; } if (Modules.FindModule(sModName) == this) { PutModule(t_f("Please use /znc unloadmod {1}")(sModName)); return; } CString sModRet; if (!Modules.UnloadModule(sModName, sModRet)) { PutModule(t_f("Error: Unable to unload module {1}: {2}")(sModName, sModRet)); } else { PutModule(t_f("Unloaded module {1}")(sModName)); } } void UnLoadModuleForUser(const CString& sLine) { CString sUsername = sLine.Token(1); CString sModName = sLine.Token(2); if (sModName.empty()) { PutModule(t_s("Usage: UnloadModule ")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; UnLoadModuleFor(pUser->GetModules(), sModName, pUser); } void UnLoadModuleForNetwork(const CString& sLine) { CString sUsername = sLine.Token(1); CString sNetwork = sLine.Token(2); CString sModName = sLine.Token(3); if (sModName.empty()) { PutModule(t_s( "Usage: UnloadNetModule ")); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) { return; } UnLoadModuleFor(pNetwork->GetModules(), sModName, pUser); } void ListModulesFor(CModules& Modules) { CTable Table; Table.AddColumn(t_s("Name", "listmodules")); Table.AddColumn(t_s("Arguments", "listmodules")); Table.SetStyle(CTable::ListStyle); for (const CModule* pMod : Modules) { Table.AddRow(); Table.SetCell(t_s("Name", "listmodules"), pMod->GetModName()); Table.SetCell(t_s("Arguments", "listmodules"), pMod->GetArgs()); } PutModule(Table); } void ListModulesForUser(const CString& sLine) { CString sUsername = sLine.Token(1); if (sUsername.empty()) { PutModule("Usage: ListMods "); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; if (pUser->GetModules().empty()) { PutModule( t_f("User {1} has no modules loaded.")(pUser->GetUsername())); return; } PutModule(t_f("Modules loaded for user {1}:")(pUser->GetUsername())); ListModulesFor(pUser->GetModules()); } void ListModulesForNetwork(const CString& sLine) { CString sUsername = sLine.Token(1); CString sNetwork = sLine.Token(2); if (sNetwork.empty()) { PutModule("Usage: ListNetMods "); return; } CUser* pUser = FindUser(sUsername); if (!pUser) return; CIRCNetwork* pNetwork = FindNetwork(pUser, sNetwork); if (!pNetwork) return; if (pNetwork->GetModules().empty()) { PutModule(t_f("Network {1} of user {2} has no modules loaded.")( pNetwork->GetName(), pUser->GetUsername())); return; } PutModule(t_f("Modules loaded for network {1} of user {2}:")( pNetwork->GetName(), pUser->GetUsername())); ListModulesFor(pNetwork->GetModules()); } public: MODCONSTRUCTOR(CAdminMod) { AddCommand("Help", t_d("[command] [variable]"), t_d("Prints help for matching commands and variables"), [=](const CString& sLine) { PrintHelp(sLine); }); AddCommand( "Get", t_d(" [username]"), t_d("Prints the variable's value for the given or current user"), [=](const CString& sLine) { Get(sLine); }); AddCommand("Set", t_d(" "), t_d("Sets the variable's value for the given user"), [=](const CString& sLine) { Set(sLine); }); AddCommand("GetNetwork", t_d(" [username] [network]"), t_d("Prints the variable's value for the given network"), [=](const CString& sLine) { GetNetwork(sLine); }); AddCommand("SetNetwork", t_d(" "), t_d("Sets the variable's value for the given network"), [=](const CString& sLine) { SetNetwork(sLine); }); AddCommand("GetChan", t_d(" [username] "), t_d("Prints the variable's value for the given channel"), [=](const CString& sLine) { GetChan(sLine); }); AddCommand("SetChan", t_d(" "), t_d("Sets the variable's value for the given channel"), [=](const CString& sLine) { SetChan(sLine); }); AddCommand("AddChan", t_d(" "), t_d("Adds a new channel"), [=](const CString& sLine) { AddChan(sLine); }); AddCommand("DelChan", t_d(" "), t_d("Deletes a channel"), [=](const CString& sLine) { DelChan(sLine); }); AddCommand("ListUsers", "", t_d("Lists users"), [=](const CString& sLine) { ListUsers(sLine); }); AddCommand("AddUser", t_d(" "), t_d("Adds a new user"), [=](const CString& sLine) { AddUser(sLine); }); AddCommand("DelUser", t_d(""), t_d("Deletes a user"), [=](const CString& sLine) { DelUser(sLine); }); AddCommand("CloneUser", t_d(" "), t_d("Clones a user"), [=](const CString& sLine) { CloneUser(sLine); }); AddCommand("AddServer", t_d(" "), t_d("Adds a new IRC server for the given or current user"), [=](const CString& sLine) { AddServer(sLine); }); AddCommand("DelServer", t_d(" "), t_d("Deletes an IRC server from the given or current user"), [=](const CString& sLine) { DelServer(sLine); }); AddCommand("Reconnect", t_d(" "), t_d("Cycles the user's IRC server connection"), [=](const CString& sLine) { ReconnectUser(sLine); }); AddCommand("Disconnect", t_d(" "), t_d("Disconnects the user from their IRC server"), [=](const CString& sLine) { DisconnectUser(sLine); }); AddCommand("LoadModule", t_d(" [args]"), t_d("Loads a Module for a user"), [=](const CString& sLine) { LoadModuleForUser(sLine); }); AddCommand("UnLoadModule", t_d(" "), t_d("Removes a Module of a user"), [=](const CString& sLine) { UnLoadModuleForUser(sLine); }); AddCommand("ListMods", t_d(""), t_d("Get the list of modules for a user"), [=](const CString& sLine) { ListModulesForUser(sLine); }); AddCommand("LoadNetModule", t_d(" [args]"), t_d("Loads a Module for a network"), [=](const CString& sLine) { LoadModuleForNetwork(sLine); }); AddCommand( "UnLoadNetModule", t_d(" "), t_d("Removes a Module of a network"), [=](const CString& sLine) { UnLoadModuleForNetwork(sLine); }); AddCommand("ListNetMods", t_d(" "), t_d("Get the list of modules for a network"), [=](const CString& sLine) { ListModulesForNetwork(sLine); }); AddCommand("ListCTCPs", t_d(""), t_d("List the configured CTCP replies"), [=](const CString& sLine) { ListCTCP(sLine); }); AddCommand("AddCTCP", t_d(" [reply]"), t_d("Configure a new CTCP reply"), [=](const CString& sLine) { AddCTCP(sLine); }); AddCommand("DelCTCP", t_d(" "), t_d("Remove a CTCP reply"), [=](const CString& sLine) { DelCTCP(sLine); }); // Network commands AddCommand("AddNetwork", t_d("[username] "), t_d("Add a network for a user"), [=](const CString& sLine) { AddNetwork(sLine); }); AddCommand("DelNetwork", t_d("[username] "), t_d("Delete a network for a user"), [=](const CString& sLine) { DelNetwork(sLine); }); AddCommand("ListNetworks", t_d("[username]"), t_d("List all networks for a user"), [=](const CString& sLine) { ListNetworks(sLine); }); } ~CAdminMod() override {} }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("controlpanel"); } USERMODULEDEFS(CAdminMod, t_s("Dynamic configuration through IRC. Allows editing only " "yourself if you're not ZNC admin.")) znc-1.9.1/modules/corecaps.cpp0000644000175000017500000000540514641222733016523 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include class CCoreCaps : public CModule { // Note: for historical reasons CClient and CIRCSock have such fields, but // really they should not. // TODO: move these fields and their handling from core to this module. class AwayNotify : public CCapability { void OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) override { pNetwork->GetIRCSock()->m_bAwayNotify = bState; } void OnClientChangedSupport(CClient* pClient, bool bState) override { pClient->m_bAwayNotify = bState; } }; class AccountNotify : public CCapability { void OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) override { pNetwork->GetIRCSock()->m_bAccountNotify = bState; } void OnClientChangedSupport(CClient* pClient, bool bState) override { pClient->m_bAccountNotify = bState; } }; class AccountTag : public CCapability { void OnClientChangedSupport(CClient* pClient, bool bState) override { pClient->SetTagSupport("account", bState); } }; class ExtendedJoin : public CCapability { void OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) override { pNetwork->GetIRCSock()->m_bExtendedJoin = bState; } void OnClientChangedSupport(CClient* pClient, bool bState) override { pClient->m_bExtendedJoin = bState; } }; public: MODCONSTRUCTOR(CCoreCaps) { AddServerDependentCapability("away-notify", std::make_unique()); AddServerDependentCapability("account-notify", std::make_unique()); AddServerDependentCapability("account-tag", std::make_unique()); AddServerDependentCapability("extended-join", std::make_unique()); } }; GLOBALMODULEDEFS( CCoreCaps, t_s("Adds support for several IRC capabilities, extracted from ZNC core.")) znc-1.9.1/modules/crypt.cpp0000644000175000017500000004260214641222733016065 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //! @author prozac@rottenboy.com // // The encryption here was designed to be compatible with mircryption's CBC // mode. // // Latest tested against: // MircryptionSuite - Mircryption ver 1.19.01 (dll v1.15.01 , mirc v7.32) CBC // loaded and ready. // // TODO: // // 1) Encrypt key storage file // 2) Some way of notifying the user that the current channel is in "encryption // mode" verses plain text // 3) Temporarily disable a target (nick/chan) // // NOTE: This module is currently NOT intended to secure you from your shell // admin. // The keys are currently stored in plain text, so anyone with access to // your account (or root) can obtain them. // It is strongly suggested that you enable SSL between ZNC and your // client otherwise the encryption stops at ZNC and gets sent to your // client in plain text. // #include #include #include #include #include #include #define REQUIRESSL 1 // To be removed in future versions #define NICK_PREFIX_OLD_KEY "[nick-prefix]" #define NICK_PREFIX_KEY "@nick-prefix@" class CCryptMod : public CModule { private: /* * As used in other implementations like KVIrc, fish10, Quassel, FiSH-irssi, * ... all the way back to the original located at * http://mircryption.sourceforge.net/Extras/McpsFishDH.zip */ static constexpr const char* m_sPrime1080 = "FBE1022E23D213E8ACFA9AE8B9DFADA3EA6B7AC7A7B7E95AB5EB2DF858921FEADE95E6" "AC7BE7DE6ADBAB8A783E7AF7A7FA6A2B7BEB1E72EAE2B72F9FA2BFB2A2EFBEFAC868BA" "DB3E828FA8BADFADA3E4CC1BE7E8AFE85E9698A783EB68FA07A77AB6AD7BEB618ACF9C" "A2897EB28A6189EFA07AB99A8A7FA9AE299EFA7BA66DEAFEFBEFBF0B7D8B"; /* Generate our keys once and reuse, just like ssh keys */ std::unique_ptr m_pDH; CString m_sPrivKey; CString m_sPubKey; #if OPENSSL_VERSION_NUMBER < 0X10100000L || \ (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x02070000fL) static int DH_set0_pqg(DH* dh, BIGNUM* p, BIGNUM* q, BIGNUM* g) { /* If the fields p and g in dh are nullptr, the corresponding input * parameters MUST be non-nullptr. q may remain nullptr. */ if (dh == nullptr || (dh->p == nullptr && p == nullptr) || (dh->g == nullptr && g == nullptr)) return 0; if (p != nullptr) { BN_free(dh->p); dh->p = p; } if (g != nullptr) { BN_free(dh->g); dh->g = g; } if (q != nullptr) { BN_free(dh->q); dh->q = q; dh->length = BN_num_bits(q); } return 1; } static void DH_get0_key(const DH* dh, const BIGNUM** pub_key, const BIGNUM** priv_key) { if (dh != nullptr) { if (pub_key != nullptr) *pub_key = dh->pub_key; if (priv_key != nullptr) *priv_key = dh->priv_key; } } #endif bool DH1080_gen() { /* Generate our keys on first call */ if (m_sPrivKey.empty() || m_sPubKey.empty()) { int len; const BIGNUM* bPrivKey = nullptr; const BIGNUM* bPubKey = nullptr; BIGNUM* bPrime = nullptr; BIGNUM* bGen = nullptr; if (!BN_hex2bn(&bPrime, m_sPrime1080) || !BN_dec2bn(&bGen, "2") || !DH_set0_pqg(m_pDH.get(), bPrime, nullptr, bGen) || !DH_generate_key(m_pDH.get())) { /* one of them failed */ if (bPrime != nullptr) BN_clear_free(bPrime); if (bGen != nullptr) BN_clear_free(bGen); return false; } /* Get our keys */ DH_get0_key(m_pDH.get(), &bPubKey, &bPrivKey); /* Get our private key */ len = BN_num_bytes(bPrivKey); m_sPrivKey.resize(len); BN_bn2bin(bPrivKey, (unsigned char*)m_sPrivKey.data()); m_sPrivKey.Base64Encode(); /* Get our public key */ len = BN_num_bytes(bPubKey); m_sPubKey.resize(len); BN_bn2bin(bPubKey, (unsigned char*)m_sPubKey.data()); m_sPubKey.Base64Encode(); } return true; } bool DH1080_comp(CString& sOtherPubKey, CString& sSecretKey) { long len; unsigned char* key = nullptr; BIGNUM* bOtherPubKey = nullptr; /* Prepare other public key */ len = sOtherPubKey.Base64Decode(); bOtherPubKey = BN_bin2bn((unsigned char*)sOtherPubKey.data(), len, nullptr); /* Generate secret key */ key = (unsigned char*)calloc(DH_size(m_pDH.get()), 1); if ((len = DH_compute_key(key, bOtherPubKey, m_pDH.get())) == -1) { sSecretKey = ""; if (bOtherPubKey != nullptr) BN_clear_free(bOtherPubKey); if (key != nullptr) free(key); return false; } /* Get our secret key */ sSecretKey.resize(SHA256_DIGEST_SIZE); sha256(key, len, (unsigned char*)sSecretKey.data()); sSecretKey.Base64Encode(); sSecretKey.TrimRight("="); if (bOtherPubKey != nullptr) BN_clear_free(bOtherPubKey); if (key != nullptr) free(key); return true; } CString NickPrefix() { MCString::iterator it = FindNV(NICK_PREFIX_KEY); /* * Check for different Prefixes to not confuse modules with nicknames * Also check for overlap for rare cases like: * SP = "*"; NP = "*s"; "tatus" sends an encrypted message appearing at * "*status" */ CString sStatusPrefix = GetUser()->GetStatusPrefix(); if (it != EndNV()) { size_t sp = sStatusPrefix.size(); size_t np = it->second.size(); int min = std::min(sp, np); if (min == 0 || sStatusPrefix.CaseCmp(it->second, min) != 0) return it->second; } return sStatusPrefix.StartsWith("*") ? "." : "*"; } public: /* MODCONSTRUCTOR(CLASS) is of form "CLASS(...) : CModule(...)" */ MODCONSTRUCTOR(CCryptMod), m_pDH(DH_new(), DH_free) { AddHelpCommand(); AddCommand("DelKey", t_d("<#chan|Nick>"), t_d("Remove a key for nick or channel"), [=](const CString& sLine) { OnDelKeyCommand(sLine); }); AddCommand("SetKey", t_d("<#chan|Nick> "), t_d("Set a key for nick or channel"), [=](const CString& sLine) { OnSetKeyCommand(sLine); }); AddCommand("ListKeys", "", t_d("List all keys"), [=](const CString& sLine) { OnListKeysCommand(sLine); }); AddCommand("KeyX", t_d(""), t_d("Start a DH1080 key exchange with nick"), [=](const CString& sLine) { OnKeyXCommand(sLine); }); AddCommand( "GetNickPrefix", "", t_d("Get the nick prefix"), [=](const CString& sLine) { OnGetNickPrefixCommand(sLine); }); AddCommand( "SetNickPrefix", t_d("[Prefix]"), t_d("Set the nick prefix, with no argument it's disabled."), [=](const CString& sLine) { OnSetNickPrefixCommand(sLine); }); } bool OnLoad(const CString& sArgsi, CString& sMessage) override { MCString::iterator it = FindNV(NICK_PREFIX_KEY); if (it == EndNV()) { /* Don't have the new prefix key yet */ it = FindNV(NICK_PREFIX_OLD_KEY); if (it != EndNV()) { SetNV(NICK_PREFIX_KEY, it->second); DelNV(NICK_PREFIX_OLD_KEY); } } return true; } EModRet OnUserTextMessage(CTextMessage& Message) override { FilterOutgoing(Message); return CONTINUE; } EModRet OnUserNoticeMessage(CNoticeMessage& Message) override { FilterOutgoing(Message); return CONTINUE; } EModRet OnUserActionMessage(CActionMessage& Message) override { FilterOutgoing(Message); return CONTINUE; } EModRet OnUserTopicMessage(CTopicMessage& Message) override { FilterOutgoing(Message); return CONTINUE; } EModRet OnPrivMsg(CNick& Nick, CString& sMessage) override { FilterIncoming(Nick.GetNick(), Nick, sMessage); return CONTINUE; } EModRet OnPrivNotice(CNick& Nick, CString& sMessage) override { CString sCommand = sMessage.Token(0); CString sOtherPubKey = sMessage.Token(1); if ((sCommand.Equals("DH1080_INIT") || sCommand.Equals("DH1080_INIT_CBC")) && !sOtherPubKey.empty()) { CString sSecretKey; CString sTail = sMessage.Token(2); /* For fish10 */ /* remove trailing A */ if (sOtherPubKey.TrimSuffix("A") && DH1080_gen() && DH1080_comp(sOtherPubKey, sSecretKey)) { PutModule( t_f("Received DH1080 public key from {1}, sending mine...")( Nick.GetNick())); PutIRC("NOTICE " + Nick.GetNick() + " :DH1080_FINISH " + m_sPubKey + "A" + (sTail.empty() ? "" : (" " + sTail))); SetNV(Nick.GetNick().AsLower(), sSecretKey); PutModule(t_f("Key for {1} successfully set.")(Nick.GetNick())); return HALT; } PutModule(t_f("Error in {1} with {2}: {3}")( sCommand, Nick.GetNick(), (sSecretKey.empty() ? t_s("no secret key computed") : sSecretKey))); return CONTINUE; } else if (sCommand.Equals("DH1080_FINISH") && !sOtherPubKey.empty()) { /* * In theory we could get a DH1080_FINISH without us having sent a * DH1080_INIT first, but then to have any use for the other user, * they'd already have our pub key */ CString sSecretKey; /* remove trailing A */ if (sOtherPubKey.TrimSuffix("A") && DH1080_gen() && DH1080_comp(sOtherPubKey, sSecretKey)) { SetNV(Nick.GetNick().AsLower(), sSecretKey); PutModule(t_f("Key for {1} successfully set.")(Nick.GetNick())); return HALT; } PutModule(t_f("Error in {1} with {2}: {3}")( sCommand, Nick.GetNick(), (sSecretKey.empty() ? t_s("no secret key computed") : sSecretKey))); return CONTINUE; } FilterIncoming(Nick.GetNick(), Nick, sMessage); return CONTINUE; } EModRet OnPrivAction(CNick& Nick, CString& sMessage) override { FilterIncoming(Nick.GetNick(), Nick, sMessage); return CONTINUE; } EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) override { FilterIncoming(Channel.GetName(), Nick, sMessage); return CONTINUE; } EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) override { FilterIncoming(Channel.GetName(), Nick, sMessage); return CONTINUE; } EModRet OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) override { FilterIncoming(Channel.GetName(), Nick, sMessage); return CONTINUE; } EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sMessage) override { FilterIncoming(Channel.GetName(), Nick, sMessage); return CONTINUE; } EModRet OnNumericMessage(CNumericMessage& Message) override { if (Message.GetCode() != 332) { return CONTINUE; } CChan* pChan = GetNetwork()->FindChan(Message.GetParam(1)); if (pChan) { CNick* Nick = pChan->FindNick(Message.GetParam(0)); CString sTopic = Message.GetParam(2); FilterIncoming(pChan->GetName(), *Nick, sTopic); Message.SetParam(2, sTopic); } return CONTINUE; } template void FilterOutgoing(T& Msg) { CString sTarget = Msg.GetTarget(); sTarget.TrimPrefix(NickPrefix()); Msg.SetTarget(sTarget); CString sMessage = Msg.GetText(); if (sMessage.TrimPrefix("``")) { return; } MCString::iterator it = FindNV(sTarget.AsLower()); if (it != EndNV()) { sMessage = MakeIvec() + sMessage; sMessage.Encrypt(it->second); sMessage.Base64Encode(); Msg.SetText("+OK *" + sMessage); } } void FilterIncoming(const CString& sTarget, CNick& Nick, CString& sMessage) { if (sMessage.TrimPrefix("+OK *")) { MCString::iterator it = FindNV(sTarget.AsLower()); if (it != EndNV()) { sMessage.Base64Decode(); sMessage.Decrypt(it->second); sMessage.LeftChomp(8); sMessage = sMessage.c_str(); Nick.SetNick(NickPrefix() + Nick.GetNick()); } } } void OnDelKeyCommand(const CString& sCommand) { CString sTarget = sCommand.Token(1); if (!sTarget.empty()) { if (DelNV(sTarget.AsLower())) { PutModule(t_f("Target [{1}] deleted")(sTarget)); } else { PutModule(t_f("Target [{1}] not found")(sTarget)); } } else { PutModule(t_s("Usage DelKey <#chan|Nick>")); } } void OnSetKeyCommand(const CString& sCommand) { CString sTarget = sCommand.Token(1); CString sKey = sCommand.Token(2, true); // Strip "cbc:" from beginning of string incase someone pastes directly // from mircryption sKey.TrimPrefix("cbc:"); if (!sKey.empty()) { SetNV(sTarget.AsLower(), sKey); PutModule( t_f("Set encryption key for [{1}] to [{2}]")(sTarget, sKey)); } else { PutModule(t_s("Usage: SetKey <#chan|Nick> ")); } } void OnKeyXCommand(const CString& sCommand) { CString sTarget = sCommand.Token(1); if (!sTarget.empty()) { if (DH1080_gen()) { PutIRC("NOTICE " + sTarget + " :DH1080_INIT " + m_sPubKey + "A"); PutModule(t_f("Sent my DH1080 public key to {1}, waiting for reply ...")(sTarget)); } else { PutModule(t_s("Error generating our keys, nothing sent.")); } } else { PutModule(t_s("Usage: KeyX ")); } } void OnGetNickPrefixCommand(const CString& sCommand) { CString sPrefix = NickPrefix(); if (sPrefix.empty()) { PutModule(t_s("Nick Prefix disabled.")); } else { PutModule(t_f("Nick Prefix: {1}")(sPrefix)); } } void OnSetNickPrefixCommand(const CString& sCommand) { CString sPrefix = sCommand.Token(1); if (sPrefix.StartsWith(":")) { PutModule( t_s("You cannot use :, even followed by other symbols, as Nick " "Prefix.")); } else { CString sStatusPrefix = GetUser()->GetStatusPrefix(); size_t sp = sStatusPrefix.size(); size_t np = sPrefix.size(); int min = std::min(sp, np); if (min > 0 && sStatusPrefix.CaseCmp(sPrefix, min) == 0) PutModule( t_f("Overlap with Status Prefix ({1}), this Nick Prefix " "will not be used!")(sStatusPrefix)); else { SetNV(NICK_PREFIX_KEY, sPrefix); if (sPrefix.empty()) PutModule(t_s("Disabling Nick Prefix.")); else PutModule(t_f("Setting Nick Prefix to {1}")(sPrefix)); } } } void OnListKeysCommand(const CString& sCommand) { CTable Table; Table.AddColumn(t_s("Target", "listkeys")); Table.AddColumn(t_s("Key", "listkeys")); Table.SetStyle(CTable::ListStyle); for (MCString::iterator it = BeginNV(); it != EndNV(); ++it) { if (!it->first.Equals(NICK_PREFIX_KEY)) { Table.AddRow(); Table.SetCell(t_s("Target", "listkeys"), it->first); Table.SetCell(t_s("Key", "listkeys"), it->second); } } if (Table.empty()) PutModule(t_s("You have no encryption keys set.")); else PutModule(Table); } CString MakeIvec() { CString sRet; time_t t; time(&t); int r = rand(); sRet.append((char*)&t, 4); sRet.append((char*)&r, 4); return sRet; } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("crypt"); } NETWORKMODULEDEFS(CCryptMod, t_s("Encryption for channel/private messages")) znc-1.9.1/modules/ctcpflood.cpp0000644000175000017500000001157014641222733016701 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include class CCtcpFloodMod : public CModule { public: MODCONSTRUCTOR(CCtcpFloodMod) { AddHelpCommand(); AddCommand("Secs", t_d(""), t_d("Set seconds limit"), [=](const CString& sLine) { OnSecsCommand(sLine); }); AddCommand("Lines", t_d(""), t_d("Set lines limit"), [=](const CString& sLine) { OnLinesCommand(sLine); }); AddCommand("Show", "", t_d("Show the current limits"), [=](const CString& sLine) { OnShowCommand(sLine); }); } ~CCtcpFloodMod() override {} void Save() { // We save the settings twice because the module arguments can // be more easily edited via webadmin, while the SetNV() stuff // survives e.g. /msg *status reloadmod ctcpflood. SetNV("secs", CString(m_iThresholdSecs)); SetNV("msgs", CString(m_iThresholdMsgs)); SetArgs(CString(m_iThresholdMsgs) + " " + CString(m_iThresholdSecs)); } bool OnLoad(const CString& sArgs, CString& sMessage) override { m_iThresholdMsgs = sArgs.Token(0).ToUInt(); m_iThresholdSecs = sArgs.Token(1).ToUInt(); if (m_iThresholdMsgs == 0 || m_iThresholdSecs == 0) { m_iThresholdMsgs = GetNV("msgs").ToUInt(); m_iThresholdSecs = GetNV("secs").ToUInt(); } if (m_iThresholdSecs == 0) m_iThresholdSecs = 2; if (m_iThresholdMsgs == 0) m_iThresholdMsgs = 4; Save(); return true; } EModRet Message(const CNick& Nick, const CString& sMessage) { // We never block /me, because it doesn't cause a reply if (sMessage.Token(0).Equals("ACTION")) return CONTINUE; if (m_tLastCTCP + m_iThresholdSecs < time(nullptr)) { m_tLastCTCP = time(nullptr); m_iNumCTCP = 0; } m_iNumCTCP++; if (m_iNumCTCP < m_iThresholdMsgs) return CONTINUE; else if (m_iNumCTCP == m_iThresholdMsgs) PutModule(t_f("Limit reached by {1}, blocking all CTCP")( Nick.GetHostMask())); // Reset the timeout so that we continue blocking messages m_tLastCTCP = time(nullptr); return HALT; } EModRet OnPrivCTCP(CNick& Nick, CString& sMessage) override { return Message(Nick, sMessage); } EModRet OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage) override { return Message(Nick, sMessage); } void OnSecsCommand(const CString& sCommand) { const CString& sArg = sCommand.Token(1, true); if (sArg.empty()) { PutModule(t_s("Usage: Secs ")); return; } m_iThresholdSecs = sArg.ToUInt(); if (m_iThresholdSecs == 0) m_iThresholdSecs = 1; OnShowCommand(""); Save(); } void OnLinesCommand(const CString& sCommand) { const CString& sArg = sCommand.Token(1, true); if (sArg.empty()) { PutModule(t_s("Usage: Lines ")); return; } m_iThresholdMsgs = sArg.ToUInt(); if (m_iThresholdMsgs == 0) m_iThresholdMsgs = 2; OnShowCommand(""); Save(); } void OnShowCommand(const CString& sCommand) { CString sMsgs = t_p("1 CTCP message", "{1} CTCP messages", m_iThresholdMsgs)(m_iThresholdMsgs); CString sSecs = t_p("every second", "every {1} seconds", m_iThresholdSecs)(m_iThresholdSecs); PutModule(t_f("Current limit is {1} {2}")(sMsgs, sSecs)); } private: time_t m_tLastCTCP = 0; unsigned int m_iNumCTCP = 0; time_t m_iThresholdSecs{}; unsigned int m_iThresholdMsgs{}; }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("ctcpflood"); Info.SetHasArgs(true); Info.SetArgsHelpText(Info.t_s( "This user module takes none to two arguments. The first argument is " "the number of lines after which the flood-protection is triggered. " "The second argument is the time (sec) to in which the number of lines " "is reached. The default setting is 4 CTCPs in 2 seconds")); } USERMODULEDEFS(CCtcpFloodMod, t_s("Don't forward CTCP floods to clients")) znc-1.9.1/modules/cyrusauth.cpp0000644000175000017500000002002514641222733016746 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * Copyright (C) 2008 Heiko Hund * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @class CSASLAuthMod * @author Heiko Hund * @brief SASL authentication module for znc. */ #include #include #include class CSASLAuthMod : public CModule { public: MODCONSTRUCTOR(CSASLAuthMod) { m_Cache.SetTTL(60000 /*ms*/); m_cbs[0].id = SASL_CB_GETOPT; m_cbs[0].proc = reinterpret_cast(CSASLAuthMod::getopt); m_cbs[0].context = this; m_cbs[1].id = SASL_CB_LIST_END; m_cbs[1].proc = nullptr; m_cbs[1].context = nullptr; AddHelpCommand(); AddCommand("Show", "", t_d("Shows current settings"), [=](const CString& sLine) { ShowCommand(sLine); }); AddCommand("CreateUsers", t_d("yes|clone |no"), t_d("Create ZNC users upon first successful login, " "optionally from a template"), [=](const CString& sLine) { CreateUsersCommand(sLine); }); } ~CSASLAuthMod() override { sasl_done(); } void OnModCommand(const CString& sCommand) override { if (GetUser()->IsAdmin()) { HandleCommand(sCommand); } else { PutModule(t_s("Access denied")); } } bool OnLoad(const CString& sArgs, CString& sMessage) override { VCString vsArgs; VCString::const_iterator it; sArgs.Split(" ", vsArgs, false); for (it = vsArgs.begin(); it != vsArgs.end(); ++it) { if (it->Equals("saslauthd") || it->Equals("auxprop")) { m_sMethod += *it + " "; } else { CUtils::PrintError( t_f("Ignoring invalid SASL pwcheck method: {1}")(*it)); sMessage = t_s("Ignored invalid SASL pwcheck method"); } } m_sMethod.TrimRight(); if (m_sMethod.empty()) { sMessage = t_s("Need a pwcheck method as argument (saslauthd, auxprop)"); return false; } if (sasl_server_init(nullptr, nullptr) != SASL_OK) { sMessage = t_s("SASL Could Not Be Initialized - Halting Startup"); return false; } return true; } EModRet OnLoginAttempt(std::shared_ptr Auth) override { const CString& sUsername = Auth->GetUsername(); const CString& sPassword = Auth->GetPassword(); CUser* pUser(CZNC::Get().FindUser(sUsername)); sasl_conn_t* sasl_conn(nullptr); bool bSuccess = false; if (!pUser && !CreateUser()) { return CONTINUE; } const CString sCacheKey(CString(sUsername + ":" + sPassword).MD5()); if (m_Cache.HasItem(sCacheKey)) { bSuccess = true; DEBUG("saslauth: Found [" + sUsername + "] in cache"); } else if (sasl_server_new("znc", nullptr, nullptr, nullptr, nullptr, m_cbs, 0, &sasl_conn) == SASL_OK && sasl_checkpass(sasl_conn, sUsername.c_str(), sUsername.size(), sPassword.c_str(), sPassword.size()) == SASL_OK) { m_Cache.AddItem(sCacheKey); DEBUG("saslauth: Successful SASL authentication [" + sUsername + "]"); bSuccess = true; } sasl_dispose(&sasl_conn); if (bSuccess) { if (!pUser) { CString sErr; pUser = new CUser(sUsername); if (ShouldCloneUser()) { CUser* pBaseUser = CZNC::Get().FindUser(CloneUser()); if (!pBaseUser) { DEBUG("saslauth: Clone User [" << CloneUser() << "] User not found"); delete pUser; pUser = nullptr; } if (pUser && !pUser->Clone(*pBaseUser, sErr)) { DEBUG("saslauth: Clone User [" << CloneUser() << "] failed: " << sErr); delete pUser; pUser = nullptr; } } if (pUser) { // "::" is an invalid MD5 hash, so user won't be able to // login by usual method pUser->SetPass("::", CUser::HASH_MD5, "::"); } if (pUser && !CZNC::Get().AddUser(pUser, sErr)) { DEBUG("saslauth: Add user [" << sUsername << "] failed: " << sErr); delete pUser; pUser = nullptr; } } if (pUser) { Auth->AcceptLogin(*pUser); return HALT; } } return CONTINUE; } const CString& GetMethod() const { return m_sMethod; } void ShowCommand(const CString& sLine) { if (!CreateUser()) { PutModule(t_s("We will not create users on their first login")); } else if (ShouldCloneUser()) { PutModule( t_f("We will create users on their first login, using user " "[{1}] as a template")(CloneUser())); } else { PutModule(t_s("We will create users on their first login")); } } void CreateUsersCommand(const CString& sLine) { CString sCreate = sLine.Token(1); if (sCreate == "no") { DelNV("CloneUser"); SetNV("CreateUser", CString(false)); PutModule(t_s("We will not create users on their first login")); } else if (sCreate == "yes") { DelNV("CloneUser"); SetNV("CreateUser", CString(true)); PutModule(t_s("We will create users on their first login")); } else if (sCreate == "clone" && !sLine.Token(2).empty()) { SetNV("CloneUser", sLine.Token(2)); SetNV("CreateUser", CString(true)); PutModule( t_f("We will create users on their first login, using user " "[{1}] as a template")(sLine.Token(2))); } else { PutModule( t_s("Usage: CreateUsers yes, CreateUsers no, or CreateUsers " "clone ")); } } bool CreateUser() const { return GetNV("CreateUser").ToBool(); } CString CloneUser() const { return GetNV("CloneUser"); } bool ShouldCloneUser() { return !GetNV("CloneUser").empty(); } protected: TCacheMap m_Cache; sasl_callback_t m_cbs[2]; CString m_sMethod; static int getopt(void* context, const char* plugin_name, const char* option, const char** result, unsigned* len) { if (CString(option).Equals("pwcheck_method")) { *result = ((CSASLAuthMod*)context)->GetMethod().c_str(); return SASL_OK; } return SASL_CONTINUE; } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("cyrusauth"); Info.SetHasArgs(true); Info.SetArgsHelpText(Info.t_s( "This global module takes up to two arguments - the methods of " "authentication - auxprop and saslauthd")); } GLOBALMODULEDEFS( CSASLAuthMod, t_s("Allow users to authenticate via SASL password verification method")) znc-1.9.1/modules/data/0000755000175000017500000000000014641222733015125 5ustar somebodysomebodyznc-1.9.1/modules/data/blockuser/0000755000175000017500000000000014641222733017116 5ustar somebodysomebodyznc-1.9.1/modules/data/blockuser/tmpl/0000755000175000017500000000000014641222733020072 5ustar somebodysomebodyznc-1.9.1/modules/data/blockuser/tmpl/blockuser_WebadminUser.tmpl0000644000175000017500000000115314641222733025426 0ustar somebodysomebody
checked="checked" disabled="disabled" />
znc-1.9.1/modules/data/cert/0000755000175000017500000000000014641222733016062 5ustar somebodysomebodyznc-1.9.1/modules/data/cert/tmpl/0000755000175000017500000000000014641222733017036 5ustar somebodysomebodyznc-1.9.1/modules/data/cert/tmpl/index.tmpl0000644000175000017500000000217114641222733021044 0ustar somebodysomebody

" />
znc-1.9.1/modules/data/certauth/0000755000175000017500000000000014641222733016744 5ustar somebodysomebodyznc-1.9.1/modules/data/certauth/tmpl/0000755000175000017500000000000014641222733017720 5ustar somebodysomebodyznc-1.9.1/modules/data/certauth/tmpl/index.tmpl0000644000175000017500000000203314641222733021723 0ustar somebodysomebody

" />

[]
znc-1.9.1/modules/data/lastseen/0000755000175000017500000000000014641222733016743 5ustar somebodysomebodyznc-1.9.1/modules/data/lastseen/tmpl/0000755000175000017500000000000014641222733017717 5ustar somebodysomebodyznc-1.9.1/modules/data/lastseen/tmpl/index.tmpl0000644000175000017500000000167714641222733021737 0ustar somebodysomebody
[] []
znc-1.9.1/modules/data/lastseen/tmpl/lastseen_WebadminUser.tmpl0000644000175000017500000000041014641222733025073 0ustar somebodysomebody
znc-1.9.1/modules/data/listsockets/0000755000175000017500000000000014641222733017474 5ustar somebodysomebodyznc-1.9.1/modules/data/listsockets/tmpl/0000755000175000017500000000000014641222733020450 5ustar somebodysomebodyznc-1.9.1/modules/data/listsockets/tmpl/index.tmpl0000644000175000017500000000137014641222733022456 0ustar somebodysomebody
znc-1.9.1/modules/data/notes/0000755000175000017500000000000014641222733016255 5ustar somebodysomebodyznc-1.9.1/modules/data/notes/files/0000755000175000017500000000000014641222733017357 5ustar somebodysomebodyznc-1.9.1/modules/data/notes/files/trash.gif0000644000175000017500000000014214641222733021164 0ustar somebodysomebodyGIF89afff!,@9"Civy GfXm%D2LL#" ;znc-1.9.1/modules/data/notes/tmpl/0000755000175000017500000000000014641222733017231 5ustar somebodysomebodyznc-1.9.1/modules/data/notes/tmpl/index.tmpl0000644000175000017500000000256514641222733021246 0ustar somebodysomebody

" />

<? FORMAT " />
znc-1.9.1/modules/data/perform/0000755000175000017500000000000014641222733016577 5ustar somebodysomebodyznc-1.9.1/modules/data/perform/tmpl/0000755000175000017500000000000014641222733017553 5ustar somebodysomebodyznc-1.9.1/modules/data/perform/tmpl/index.tmpl0000644000175000017500000000142714641222733021564 0ustar somebodysomebody


" />
znc-1.9.1/modules/data/q/0000755000175000017500000000000014641222733015365 5ustar somebodysomebodyznc-1.9.1/modules/data/q/tmpl/0000755000175000017500000000000014641222733016341 5ustar somebodysomebodyznc-1.9.1/modules/data/q/tmpl/index.tmpl0000644000175000017500000000275614641222733020360 0ustar somebodysomebody

Q

" />
" autocomplete="off" />

checked="checked" disabled="disabled" />
" />
znc-1.9.1/modules/data/samplewebapi/0000755000175000017500000000000014641222733017576 5ustar somebodysomebodyznc-1.9.1/modules/data/samplewebapi/tmpl/0000755000175000017500000000000014641222733020552 5ustar somebodysomebodyznc-1.9.1/modules/data/samplewebapi/tmpl/index.tmpl0000644000175000017500000000116314641222733022560 0ustar somebodysomebody

Sample Web API

Text:

Sample text that will be returned plain on submit/API request.
znc-1.9.1/modules/data/sasl/0000755000175000017500000000000014641222733016067 5ustar somebodysomebodyznc-1.9.1/modules/data/sasl/tmpl/0000755000175000017500000000000014641222733017043 5ustar somebodysomebodyznc-1.9.1/modules/data/sasl/tmpl/index.tmpl0000644000175000017500000000476614641222733021065 0ustar somebodysomebody

" />
" autocomplete="off" />

"> checked="checked" />

" />
znc-1.9.1/modules/data/send_raw/0000755000175000017500000000000014641222733016727 5ustar somebodysomebodyznc-1.9.1/modules/data/send_raw/files/0000755000175000017500000000000014641222733020031 5ustar somebodysomebodyznc-1.9.1/modules/data/send_raw/files/select.js0000644000175000017500000000060214641222733021644 0ustar somebodysomebodyfunction updateUser() { var select = document.getElementById('selectnetwork'); var opt = select.options[select.selectedIndex]; document.getElementById('user').value = opt.parentNode.getAttribute('label'); } function init() { updateUser(); document.getElementById('networklabel').firstChild.nodeValue = 'Network:'; document.getElementById('userblock').removeAttribute('style'); } znc-1.9.1/modules/data/send_raw/tmpl/0000755000175000017500000000000014641222733017703 5ustar somebodysomebodyznc-1.9.1/modules/data/send_raw/tmpl/index.tmpl0000644000175000017500000000341614641222733021714 0ustar somebodysomebody

"/>
znc-1.9.1/modules/data/stickychan/0000755000175000017500000000000014641222733017265 5ustar somebodysomebodyznc-1.9.1/modules/data/stickychan/tmpl/0000755000175000017500000000000014641222733020241 5ustar somebodysomebodyznc-1.9.1/modules/data/stickychan/tmpl/index.tmpl0000644000175000017500000000136414641222733022252 0ustar somebodysomebody
checked="checked" />
" />
znc-1.9.1/modules/data/stickychan/tmpl/stickychan_WebadminChan.tmpl0000644000175000017500000000103114641222733025672 0ustar somebodysomebody
checked="checked" />
znc-1.9.1/modules/data/webadmin/0000755000175000017500000000000014641222733016713 5ustar somebodysomebodyznc-1.9.1/modules/data/webadmin/files/0000755000175000017500000000000014641222733020015 5ustar somebodysomebodyznc-1.9.1/modules/data/webadmin/files/webadmin.css0000644000175000017500000000056514641222733022323 0ustar somebodysomebody.encoding-placeholder-big { text-decoration:underline; font-style:italic; } .encoding-settings { width: 500px; } table .sorted::after { content:" ▾"; } table .reverse-sorted::after { content:" ▴"; } .ctcpreplies_row_request { width: 100px; } .ctcpreplies_row_response { width: 400px; } table.networks[data-network-edit="0"] .network_delete { display: none; }znc-1.9.1/modules/data/webadmin/files/webadmin.js0000644000175000017500000002011514641222733022140 0ustar somebodysomebodyfunction floodprotection_change() { var protection = document.getElementById('floodprotection_checkbox'); var rate = document.getElementById('floodrate'); var burst = document.getElementById('floodburst'); if (protection.checked) { rate.removeAttribute('disabled'); burst.removeAttribute('disabled'); } else { rate.disabled = 'disabled'; burst.disabled = 'disabled'; } } function make_sortable_table(table) { if (table.rows.length >= 1) { // Ensure that the table at least contains a row for the headings var headings = table.rows[0].getElementsByTagName("th"); for (var i = 0; i < headings.length; i++) { // This function acts to scope the i variable, so we can pass it off // as the column_index, otherwise column_index would just be the max // value of i, every single time. (function (i) { var heading = headings[i]; if (!heading.classList.contains("ignore-sort")) { heading.addEventListener("click", function () { // Bind a click event to the heading sort_table(this, i, table, headings); }); } })(i); } } } function sort_table(clicked_column, column_index, table, headings) { for (var i = 0; i < headings.length; i++) { if (headings[i] != clicked_column) { headings[i].classList.remove("sorted"); headings[i].classList.remove("reverse-sorted"); } } var reverse = false; clicked_column.classList.toggle("reverse"); if (clicked_column.classList.contains("sorted")) { reverse = true; clicked_column.classList.remove("sorted"); clicked_column.classList.add("reverse-sorted"); } else { clicked_column.classList.remove("reverse-sorted"); clicked_column.classList.add("sorted"); } // This array will contain tuples in the form [(value, row)] where value // is extracted from the column to be sorted by var rows_and_sortable_value = []; for (var i = 1, row; row = table.rows[i]; i++) { for (var j = 0, col; col = row.cells[j]; j++) { // If we're at the column index we want to sort by if (j === column_index) { var cell = row.getElementsByTagName("td")[j]; var value = cell.innerHTML; rows_and_sortable_value.push([value, row]); } } } rows_and_sortable_value.sort(function (a, b) { // If both values are integers, sort by that else as strings if (isInt(a[0]) && isInt(b[0])) { return a[0] - b[0]; } else { return b[0].localeCompare(a[0]); } }); if (reverse) { rows_and_sortable_value.reverse(); } var parent = table.rows[1].parentNode; for (var i = 0; i < rows_and_sortable_value.length; i++) { // Remove the existing entry for the row from the table parent.removeChild(rows_and_sortable_value[i][1]); // Insert at the first position, before the first child parent.insertBefore(rows_and_sortable_value[i][1], parent.firstChild); } } function isInt(value) { return !isNaN(value) && (function (x) { return (x | 0) === x; })(parseFloat(value)) } function make_sortable() { var tables = document.querySelectorAll("table.sortable"); for (var i = 0; i < tables.length; i++) { make_sortable_table(tables[i]); } } function serverlist_init($) { function serialize() { var text = ""; $("#servers_tbody > tr").each(function() { var host = $(".servers_row_host", $(this)).val(); var port = $(".servers_row_port", $(this)).val(); var ssl = $(".servers_row_ssl", $(this)).is(":checked"); var pass = $(".servers_row_pass", $(this)).val(); if (host.length == 0) return; text += host; text += " "; if (ssl) text += "+"; text += port; text += " "; text += pass; text += "\n"; }); $("#servers_text").val(text); } function add_row(host, port, ssl, pass) { var row = $(""); function delete_row() { row.remove(); serialize(); } if (NetworkEdit) { row.append( $("").append($("").attr({"type":"text"}) .addClass("servers_row_host").val(host)), $("").append($("").attr({"type":"number"}) .addClass("servers_row_port").val(port)), $("").append($("").attr({"type":"checkbox"}) .addClass("servers_row_ssl").prop("checked", ssl)), $("").append($("").attr({"type":"text"}) .addClass("servers_row_pass").val(pass)), $("").append($("").attr({"type":"button"}) .val("X").click(delete_row)) ); } else { row.append( $("").append($("").attr({"type":"text","disabled":true}) .addClass("servers_row_host").val(host)), $("").append($("").attr({"type":"number","disabled":true}) .addClass("servers_row_port").val(port)), $("").append($("").attr({"type":"checkbox","disabled":true}) .addClass("servers_row_ssl").prop("checked", ssl)), $("").append($("").attr({"type":"text","disabled":true}) .addClass("servers_row_pass").val(pass)) ); } $("input", row).change(serialize); $("#servers_tbody").append(row); } (function() { var servers_text = $("#servers_text").val(); // Parse it $.each(servers_text.split("\n"), function(i, line) { if (line.length == 0) return; line = line.split(" "); var host = line[0]; var port = line[1] || "6667"; var pass = line[2] || ""; var ssl; if (port.match(/^\+/)) { ssl = true; port = port.substr(1); } else { ssl = false; } add_row(host, port, ssl, pass); }); $("#servers_add").click(function() { add_row("", 6697, true, ""); // Not serializing, because empty host doesn't emit anything anyway }); $("#servers_plain").hide(); $("#servers_js").show(); })(); } function channellist_init($) { function update_rows() { $("#channels > tr").each(function(i) { $(this).toggleClass("evenrow", i % 2 === 1).toggleClass("oddrow", i % 2 === 0); $(this).find(".channel_index").val(i + 1); }); } $("#channels").sortable({ axis: "y", update: update_rows }); $(".channel_index").change(function() { var src = $(this).closest("tr").detach(); var rows = $("#channels > tr"); var dst = rows[this.value - 1]; if (dst) src.insertBefore(dst); else src.insertAfter(rows.last()); update_rows(); }); } function ctcpreplies_init($) { function serialize() { var text = ""; $("#ctcpreplies_tbody > tr").each(function() { var request = $(".ctcpreplies_row_request", $(this)).val(); var response = $(".ctcpreplies_row_response", $(this)).val(); if (request.length == 0) return; text += request; text += " "; text += response; text += "\n"; }); $("#ctcpreplies_text").val(text); } function add_row(request, response) { var row = $(""); function delete_row() { row.remove(); serialize(); } if (CTCPEdit) { row.append( $("").append($("").val(request) .addClass("ctcpreplies_row_request") .attr({"type":"text","list":"ctcpreplies_list"})), $("").append($("").val(response) .addClass("ctcpreplies_row_response") .attr({"type":"text","placeholder":$("#ctcpreplies_js").data("placeholder")})), $("").append($("").val("X") .attr({"type":"button"}).click(delete_row)) ); } else { row.append( $("").append($("").val(request) .addClass("ctcpreplies_row_request") .attr({"type":"text","list":"ctcpreplies_list","disabled":true})), $("").append($("").val(response) .addClass("ctcpreplies_row_response") .attr({"type":"text","placeholder":$("#ctcpreplies_js").data("placeholder"),"disabled":true})), ); } $("input", row).change(serialize); $("#ctcpreplies_tbody").append(row); } (function() { var replies_text = $("#ctcpreplies_text").val(); $.each(replies_text.split("\n"), function(i, line) { if (line.length == 0) return; var space = line.indexOf(" "); var request; var response; if (space == -1) { request = line; response = ""; } else { request = line.substr(0, space); response = line.substr(space + 1); } add_row(request, response); }); $("#ctcpreplies_add").click(function() { add_row("", ""); }); $("#ctcpreplies_plain").hide(); $("#ctcpreplies_js").show(); })(); } znc-1.9.1/modules/data/webadmin/tmpl/0000755000175000017500000000000014641222733017667 5ustar somebodysomebodyznc-1.9.1/modules/data/webadmin/tmpl/add_edit_chan.tmpl0000644000175000017500000000617014641222733023317 0ustar somebodysomebody

"/>
"/>
"/>
"/>

checked="checked" /> checked="checked" disabled="disabled" />

"/> "/> "/> "/>
znc-1.9.1/modules/data/webadmin/tmpl/add_edit_network.tmpl0000644000175000017500000003367414641222733024110 0ustar somebodysomebody
/: / /: /

{1} or username field as {2}" "ClientConnectHint_Password ESC=" "ClientConnectHint_Username ESC=" ?>

" disabled />
"/>
"/>
" disabled />
" disabled />
" disabled />
checked="checked" />
checked="checked" />
checked="checked" />


" onchange="floodprotection_change();" checked="checked" />
" value="" value="2.00" disabled="disabled" />
" value="" value="9" disabled="disabled" />
" value="" />
">


[]
[] [] checked="checked" />

checked="checked" disabled="disabled" /> disabled="disabled" title="" /> checked="checked" disabled="disabled"/> checked="checked" disabled="disabled"/>

"/> "/> "/> "/>
znc-1.9.1/modules/data/webadmin/tmpl/add_edit_user.tmpl0000644000175000017500000005012514641222733023363 0ustar somebodysomebody

"/>
" autocomplete="off" />
"/>
" checked="checked" disabled="disabled" />

Otherwise, one entry per line, wildcards * and ? are available." ?>

"/>
"/>
" disabled />
"/>
" disabled />
" disabled />

[]  
[] []


checked="checked" disabled="disabled" /> disabled="disabled" title="" /> checked="checked" disabled="disabled"/> checked="checked" disabled="disabled"/>

"/>
"/>

"/>
"/>

checked="checked" disabled="disabled" />

"/>

Europe/Berlin, or GMT-6" ?>
">
"/>
"/>
"/>
" disabled="disabled" />

TIME Buy a watch!" ?>
1 ?>
ZNC is compiled without i18n support

"/> "/> "/> "/> "/> "/> "/> "/> "/>
znc-1.9.1/modules/data/webadmin/tmpl/del_network.tmpl0000644000175000017500000000165514641222733023111 0ustar somebodysomebody

"/>
"/>
znc-1.9.1/modules/data/webadmin/tmpl/del_user.tmpl0000644000175000017500000000152614641222733022373 0ustar somebodysomebody

"/>
"/>
znc-1.9.1/modules/data/webadmin/tmpl/encoding_settings.tmpl0000644000175000017500000000633314641222733024300 0ustar somebodysomebody
ICU
    
checked="checked" disabled="disabled" />
checked="checked" disabled="disabled" />
checked="checked" disabled="disabled" />
checked="checked" disabled="disabled" />
disabled="disabled" />
UTF-8, or ISO-8859-15" ?>
znc-1.9.1/modules/data/webadmin/tmpl/index.tmpl0000644000175000017500000000040214641222733021670 0ustar somebodysomebody


znc-1.9.1/modules/data/webadmin/tmpl/listusers.tmpl0000644000175000017500000000313714641222733022626 0ustar somebodysomebody
There are no users defined. Click here if you would like to add one.
[]
[] [] []
znc-1.9.1/modules/data/webadmin/tmpl/settings.tmpl0000644000175000017500000002366314641222733022437 0ustar somebodysomebody

checked="checked"/>
checked="checked"/>
checked="checked"/>
checked="checked"/>
checked="checked"/>
"/>
" value=""/>
"/>

"/>
"/>
"/>
"/>
"/>
checked="checked" />
checked="checked" />
checked="checked" />

checked="checked" disabled="disabled" /> disabled="disabled" title="" /> checked="checked" disabled="disabled"/> checked="checked" disabled="disabled"/>
"/>
znc-1.9.1/modules/data/webadmin/tmpl/traffic.tmpl0000644000175000017500000000641314641222733022207 0ustar somebodysomebody

ZNC

znc-1.9.1/modules/dcc.cpp0000644000175000017500000005113414641222733015455 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include using std::set; class CDCCMod; class CDCCSock : public CSocket { public: CDCCSock(CDCCMod* pMod, const CString& sRemoteNick, const CString& sLocalFile, unsigned long uFileSize = 0, CFile* pFile = nullptr); CDCCSock(CDCCMod* pMod, const CString& sRemoteNick, const CString& sRemoteIP, unsigned short uRemotePort, const CString& sLocalFile, unsigned long uFileSize); ~CDCCSock() override; void ReadData(const char* data, size_t len) override; void ConnectionRefused() override; void SockError(int iErrno, const CString& sDescription) override; void Timeout() override; void Connected() override; void Disconnected() override; void SendPacket(); Csock* GetSockObj(const CString& sHost, unsigned short uPort) override; CFile* OpenFile(bool bWrite = true); bool Seek(unsigned long int uPos); // Setters void SetRemoteIP(const CString& s) { m_sRemoteIP = s; } void SetRemoteNick(const CString& s) { m_sRemoteNick = s; } void SetFileName(const CString& s) { m_sFileName = s; } void SetFileOffset(unsigned long u) { m_uBytesSoFar = u; } // !Setters // Getters unsigned short GetUserPort() const { return m_uRemotePort; } const CString& GetRemoteNick() const { return m_sRemoteNick; } const CString& GetFileName() const { return m_sFileName; } const CString& GetLocalFile() const { return m_sLocalFile; } CFile* GetFile() { return m_pFile; } double GetProgress() const { return ((m_uFileSize) && (m_uBytesSoFar)) ? (double)(((double)m_uBytesSoFar / (double)m_uFileSize) * 100.0) : 0; } bool IsSend() const { return m_bSend; } // const CString& GetRemoteIP() const { return m_sRemoteIP; } // !Getters private: protected: CString m_sRemoteNick; CString m_sRemoteIP; CString m_sFileName; CString m_sLocalFile; CString m_sSendBuf; unsigned short m_uRemotePort; unsigned long long m_uFileSize; unsigned long long m_uBytesSoFar; bool m_bSend; bool m_bNoDelFile; CFile* m_pFile; CDCCMod* m_pModule; }; class CDCCMod : public CModule { public: MODCONSTRUCTOR(CDCCMod) { AddHelpCommand(); AddCommand("Send", t_d(" "), t_d("Send a file from ZNC to someone"), [=](const CString& sLine) { SendCommand(sLine); }); AddCommand("Get", t_d(""), t_d("Send a file from ZNC to your client"), [=](const CString& sLine) { GetCommand(sLine); }); AddCommand("ListTransfers", "", t_d("List current transfers"), [=](const CString& sLine) { ListTransfersCommand(sLine); }); } ~CDCCMod() override {} #ifndef MOD_DCC_ALLOW_EVERYONE bool OnLoad(const CString& sArgs, CString& sMessage) override { if (!GetUser()->IsAdmin()) { sMessage = t_s("You must be admin to use the DCC module"); return false; } return true; } #endif bool SendFile(const CString& sRemoteNick, const CString& sFileName) { CString sFullPath = CDir::ChangeDir(GetSavePath(), sFileName, CZNC::Get().GetHomePath()); CDCCSock* pSock = new CDCCSock(this, sRemoteNick, sFullPath); CFile* pFile = pSock->OpenFile(false); if (!pFile) { delete pSock; return false; } CString sLocalDCCIP = GetUser()->GetLocalDCCIP(); unsigned short uPort = CZNC::Get().GetManager().ListenRand( "DCC::LISTEN::" + sRemoteNick, sLocalDCCIP, false, SOMAXCONN, pSock, 120); if (GetUser()->GetNick().Equals(sRemoteNick)) { PutUser(":*dcc!znc@znc.in PRIVMSG " + sRemoteNick + " :\001DCC SEND " + pFile->GetShortName() + " " + CString(CUtils::GetLongIP(sLocalDCCIP)) + " " + CString(uPort) + " " + CString(pFile->GetSize()) + "\001"); } else { PutIRC("PRIVMSG " + sRemoteNick + " :\001DCC SEND " + pFile->GetShortName() + " " + CString(CUtils::GetLongIP(sLocalDCCIP)) + " " + CString(uPort) + " " + CString(pFile->GetSize()) + "\001"); } PutModule(t_f("Attempting to send [{1}] to [{2}].")( pFile->GetShortName(), sRemoteNick)); return true; } bool GetFile(const CString& sRemoteNick, const CString& sRemoteIP, unsigned short uRemotePort, const CString& sFileName, unsigned long uFileSize) { if (CFile::Exists(sFileName)) { PutModule(t_f("Receiving [{1}] from [{2}]: File already exists.")( sFileName, sRemoteNick)); return false; } CDCCSock* pSock = new CDCCSock(this, sRemoteNick, sRemoteIP, uRemotePort, sFileName, uFileSize); if (!pSock->OpenFile()) { delete pSock; return false; } CZNC::Get().GetManager().Connect(sRemoteIP, uRemotePort, "DCC::GET::" + sRemoteNick, 60, false, GetUser()->GetLocalDCCIP(), pSock); PutModule( t_f("Attempting to connect to [{1} {2}] in order to download [{3}] " "from [{4}].")(sRemoteIP, uRemotePort, sFileName, sRemoteNick)); return true; } void SendCommand(const CString& sLine) { CString sToNick = sLine.Token(1); CString sFile = sLine.Token(2); CString sAllowedPath = GetSavePath(); CString sAbsolutePath; if ((sToNick.empty()) || (sFile.empty())) { PutModule(t_s("Usage: Send ")); return; } sAbsolutePath = CDir::CheckPathPrefix(sAllowedPath, sFile); if (sAbsolutePath.empty()) { PutStatus(t_s("Illegal path.")); return; } SendFile(sToNick, sFile); } void GetCommand(const CString& sLine) { CString sFile = sLine.Token(1); CString sAllowedPath = GetSavePath(); CString sAbsolutePath; if (sFile.empty()) { PutModule(t_s("Usage: Get ")); return; } sAbsolutePath = CDir::CheckPathPrefix(sAllowedPath, sFile); if (sAbsolutePath.empty()) { PutModule(t_s("Illegal path.")); return; } SendFile(GetUser()->GetNick(), sFile); } void ListTransfersCommand(const CString& sLine) { CTable Table; Table.AddColumn(t_s("Type", "list")); Table.AddColumn(t_s("State", "list")); Table.AddColumn(t_s("Speed", "list")); Table.AddColumn(t_s("Nick", "list")); Table.AddColumn(t_s("IP", "list")); Table.AddColumn(t_s("File", "list")); set::const_iterator it; for (it = BeginSockets(); it != EndSockets(); ++it) { CDCCSock* pSock = (CDCCSock*)*it; Table.AddRow(); Table.SetCell(t_s("Nick", "list"), pSock->GetRemoteNick()); Table.SetCell(t_s("IP", "list"), pSock->GetRemoteIP()); Table.SetCell(t_s("File", "list"), pSock->GetFileName()); if (pSock->IsSend()) { Table.SetCell(t_s("Type", "list"), t_s("Sending", "list-type")); } else { Table.SetCell(t_s("Type", "list"), t_s("Getting", "list-type")); } if (pSock->GetType() == Csock::LISTENER) { Table.SetCell(t_s("State", "list"), t_s("Waiting", "list-state")); } else { Table.SetCell(t_s("State", "list"), CString::ToPercent(pSock->GetProgress())); Table.SetCell(t_s("Speed", "list"), t_f("{1} KiB/s")(static_cast( pSock->GetAvgRead() / 1024.0))); } } if (PutModule(Table) == 0) { PutModule(t_s("You have no active DCC transfers.")); } } void OnModCTCP(const CString& sMessage) override { if (sMessage.StartsWith("DCC RESUME ")) { CString sFile = sMessage.Token(2); unsigned short uResumePort = sMessage.Token(3).ToUShort(); unsigned long uResumeSize = sMessage.Token(4).ToULong(); set::const_iterator it; for (it = BeginSockets(); it != EndSockets(); ++it) { CDCCSock* pSock = (CDCCSock*)*it; if (pSock->GetLocalPort() == uResumePort) { if (pSock->Seek(uResumeSize)) { PutModule( t_f("Attempting to resume send from position {1} " "of file [{2}] for [{3}]")( uResumeSize, pSock->GetFileName(), pSock->GetRemoteNick())); PutUser(":*dcc!znc@znc.in PRIVMSG " + GetUser()->GetNick() + " :\001DCC ACCEPT " + sFile + " " + CString(uResumePort) + " " + CString(uResumeSize) + "\001"); } else { PutModule(t_f( "Couldn't resume file [{1}] for [{2}]: not sending " "anything.")(sFile, GetUser()->GetNick())); } } } } else if (sMessage.StartsWith("DCC SEND ")) { CString sLocalFile = CDir::CheckPathPrefix(GetSavePath(), sMessage.Token(2)); if (sLocalFile.empty()) { PutModule(t_f("Bad DCC file: {1}")(sMessage.Token(2))); } unsigned long uLongIP = sMessage.Token(3).ToULong(); unsigned short uPort = sMessage.Token(4).ToUShort(); unsigned long uFileSize = sMessage.Token(5).ToULong(); GetFile(GetClient()->GetNick(), CUtils::GetIP(uLongIP), uPort, sLocalFile, uFileSize); } } }; CDCCSock::CDCCSock(CDCCMod* pMod, const CString& sRemoteNick, const CString& sLocalFile, unsigned long uFileSize, CFile* pFile) : CSocket(pMod) { m_sRemoteNick = sRemoteNick; m_uFileSize = uFileSize; m_uRemotePort = 0; m_uBytesSoFar = 0; m_pModule = pMod; m_pFile = pFile; m_sLocalFile = sLocalFile; m_bSend = true; m_bNoDelFile = false; SetMaxBufferThreshold(0); } CDCCSock::CDCCSock(CDCCMod* pMod, const CString& sRemoteNick, const CString& sRemoteIP, unsigned short uRemotePort, const CString& sLocalFile, unsigned long uFileSize) : CSocket(pMod) { m_sRemoteNick = sRemoteNick; m_sRemoteIP = sRemoteIP; m_uRemotePort = uRemotePort; m_uFileSize = uFileSize; m_uBytesSoFar = 0; m_pModule = pMod; m_pFile = nullptr; m_sLocalFile = sLocalFile; m_bSend = false; m_bNoDelFile = false; SetMaxBufferThreshold(0); } CDCCSock::~CDCCSock() { if ((m_pFile) && (!m_bNoDelFile)) { m_pFile->Close(); delete m_pFile; } } void CDCCSock::ReadData(const char* data, size_t len) { if (!m_pFile) { DEBUG("File not open! closing get."); if (m_bSend) { m_pModule->PutModule(t_f("Sending [{1}] to [{2}]: File not open!")( m_sFileName, m_sRemoteNick)); } else { m_pModule->PutModule( t_f("Receiving [{1}] from [{2}]: File not open!")( m_sFileName, m_sRemoteNick)); } Close(); return; } // DCC specs says the receiving end sends the number of bytes it // received so far as a 4 byte integer in network byte order, so we need // uint32_t to do the job portably. This also means that the maximum // file that we can transfer is 4 GiB big (see OpenFile()). if (m_bSend) { m_sSendBuf.append(data, len); while (m_sSendBuf.size() >= 4) { uint32_t iRemoteSoFar; memcpy(&iRemoteSoFar, m_sSendBuf.data(), sizeof(iRemoteSoFar)); iRemoteSoFar = ntohl(iRemoteSoFar); if ((iRemoteSoFar + 65536) >= m_uBytesSoFar) { SendPacket(); } m_sSendBuf.erase(0, 4); } } else { m_pFile->Write(data, len); m_uBytesSoFar += len; uint32_t uSoFar = htonl((uint32_t)m_uBytesSoFar); Write((char*)&uSoFar, sizeof(uSoFar)); if (m_uBytesSoFar >= m_uFileSize) { Close(); } } } void CDCCSock::ConnectionRefused() { DEBUG(GetSockName() << " == ConnectionRefused()"); if (m_bSend) { m_pModule->PutModule(t_f("Sending [{1}] to [{2}]: Connection refused.")( m_sFileName, m_sRemoteNick)); } else { m_pModule->PutModule( t_f("Receiving [{1}] from [{2}]: Connection refused.")( m_sFileName, m_sRemoteNick)); } } void CDCCSock::Timeout() { DEBUG(GetSockName() << " == Timeout()"); if (m_bSend) { m_pModule->PutModule(t_f("Sending [{1}] to [{2}]: Timeout.")( m_sFileName, m_sRemoteNick)); } else { m_pModule->PutModule( t_f("Receiving [{1}] from [{2}]: Timeout.")( m_sFileName, m_sRemoteNick)); } } void CDCCSock::SockError(int iErrno, const CString& sDescription) { DEBUG(GetSockName() << " == SockError(" << iErrno << ", " << sDescription << ")"); if (m_bSend) { m_pModule->PutModule( t_f("Sending [{1}] to [{2}]: Socket error {3}: {4}")( m_sFileName, m_sRemoteNick, iErrno, sDescription)); } else { m_pModule->PutModule( t_f("Receiving [{1}] from [{2}]: Socket error {3}: {4}")( m_sFileName, m_sRemoteNick, iErrno, sDescription)); } } void CDCCSock::Connected() { DEBUG(GetSockName() << " == Connected(" << GetRemoteIP() << ")"); if (m_bSend) { m_pModule->PutModule(t_f("Sending [{1}] to [{2}]: Transfer started.")( m_sFileName, m_sRemoteNick)); } else { m_pModule->PutModule( t_f("Receiving [{1}] from [{2}]: Transfer started.")( m_sFileName, m_sRemoteNick)); } if (m_bSend) { SendPacket(); } SetTimeout(120); } void CDCCSock::Disconnected() { const CString sStart = ((m_bSend) ? "DCC -> [" : "DCC <- [") + m_sRemoteNick + "][" + m_sFileName + "] - "; DEBUG(GetSockName() << " == Disconnected()"); if (m_uBytesSoFar > m_uFileSize) { if (m_bSend) { m_pModule->PutModule(t_f("Sending [{1}] to [{2}]: Too much data!")( m_sFileName, m_sRemoteNick)); } else { m_pModule->PutModule( t_f("Receiving [{1}] from [{2}]: Too much data!")( m_sFileName, m_sRemoteNick)); } } else if (m_uBytesSoFar == m_uFileSize) { if (m_bSend) { m_pModule->PutModule( t_f("Sending [{1}] to [{2}] completed at {3} KiB/s")( m_sFileName, m_sRemoteNick, static_cast(GetAvgWrite() / 1024.0))); } else { m_pModule->PutModule( t_f("Receiving [{1}] from [{2}] completed at {3} KiB/s")( m_sFileName, m_sRemoteNick, static_cast(GetAvgRead() / 1024.0))); } } else { m_pModule->PutModule(sStart + "Incomplete!"); } } void CDCCSock::SendPacket() { if (!m_pFile) { if (m_bSend) { m_pModule->PutModule( t_f("Sending [{1}] to [{2}]: File closed prematurely.")( m_sFileName, m_sRemoteNick)); } else { m_pModule->PutModule( t_f("Receiving [{1}] from [{2}]: File closed prematurely.")( m_sFileName, m_sRemoteNick)); } Close(); return; } if (GetInternalWriteBuffer().size() > 1024 * 1024) { // There is still enough data to be written, don't add more // stuff to that buffer. DEBUG("SendPacket(): Skipping send, buffer still full enough [" << GetInternalWriteBuffer().size() << "][" << m_sRemoteNick << "][" << m_sFileName << "]"); return; } char szBuf[4096]; ssize_t iLen = m_pFile->Read(szBuf, 4096); if (iLen < 0) { if (m_bSend) { m_pModule->PutModule( t_f("Sending [{1}] to [{2}]: Error reading from file.")( m_sFileName, m_sRemoteNick)); } else { m_pModule->PutModule( t_f("Receiving [{1}] from [{2}]: Error reading from file.")( m_sFileName, m_sRemoteNick)); } Close(); return; } if (iLen > 0) { Write(szBuf, iLen); m_uBytesSoFar += iLen; } } Csock* CDCCSock::GetSockObj(const CString& sHost, unsigned short uPort) { Close(); CDCCSock* pSock = new CDCCSock(m_pModule, m_sRemoteNick, m_sLocalFile, m_uFileSize, m_pFile); pSock->SetSockName("DCC::SEND::" + m_sRemoteNick); pSock->SetTimeout(120); pSock->SetFileName(m_sFileName); pSock->SetFileOffset(m_uBytesSoFar); m_bNoDelFile = true; return pSock; } CFile* CDCCSock::OpenFile(bool bWrite) { if ((m_pFile) || (m_sLocalFile.empty())) { if (m_bSend) { m_pModule->PutModule( t_f("Sending [{1}] to [{2}]: Unable to open file.")( m_sFileName, m_sRemoteNick)); } else { m_pModule->PutModule( t_f("Receiving [{1}] from [{2}]: Unable to open file.")( m_sFileName, m_sRemoteNick)); } return nullptr; } m_pFile = new CFile(m_sLocalFile); if (bWrite) { if (m_pFile->Exists()) { delete m_pFile; m_pFile = nullptr; m_pModule->PutModule( t_f("Receiving [{1}] from [{2}]: File already exists.")( m_sFileName, m_sRemoteNick)); return nullptr; } if (!m_pFile->Open(O_WRONLY | O_TRUNC | O_CREAT)) { delete m_pFile; m_pFile = nullptr; m_pModule->PutModule( t_f("Receiving [{1}] from [{2}]: Could not open file.")( m_sFileName, m_sRemoteNick)); return nullptr; } } else { if (!m_pFile->IsReg()) { delete m_pFile; m_pFile = nullptr; m_pModule->PutModule( t_f("Sending [{1}] to [{2}]: Not a file.")( m_sFileName, m_sRemoteNick)); return nullptr; } if (!m_pFile->Open()) { delete m_pFile; m_pFile = nullptr; m_pModule->PutModule( t_f("Sending [{1}] to [{2}]: Could not open file.")( m_sFileName, m_sRemoteNick)); return nullptr; } // The DCC specs only allow file transfers with files smaller // than 4GiB (see ReadData()). unsigned long long uFileSize = m_pFile->GetSize(); if (uFileSize > (unsigned long long)0xffffffffULL) { delete m_pFile; m_pFile = nullptr; m_pModule->PutModule( t_f("Sending [{1}] to [{2}]: File too large (>4 GiB).")( m_sFileName, m_sRemoteNick)); return nullptr; } m_uFileSize = uFileSize; } m_sFileName = m_pFile->GetShortName(); return m_pFile; } bool CDCCSock::Seek(unsigned long int uPos) { if (m_pFile) { if (m_pFile->Seek(uPos)) { m_uBytesSoFar = uPos; return true; } } return false; } template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("dcc"); } USERMODULEDEFS(CDCCMod, t_s("This module allows you to transfer files to and from ZNC")) znc-1.9.1/modules/disconkick.cpp0000644000175000017500000000304214641222733017040 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include class CKickClientOnIRCDisconnect : public CModule { public: MODCONSTRUCTOR(CKickClientOnIRCDisconnect) {} void OnIRCDisconnected() override { CString sPrefix = GetUser()->GetStatusPrefix(); for (CChan* pChan : GetNetwork()->GetChans()) { if (pChan->IsOn()) { PutUser(":" + sPrefix + "disconkick!znc@znc.in KICK " + pChan->GetName() + " " + GetNetwork()->GetIRCNick().GetNick() + " :" + t_s("You have been disconnected from the IRC server")); } } } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("disconkick"); } USERMODULEDEFS( CKickClientOnIRCDisconnect, t_s("Kicks the client from all channels when the connection to the " "IRC server is lost")) znc-1.9.1/modules/fail2ban.cpp0000644000175000017500000001772514641222733016412 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include class CFailToBanMod : public CModule { public: MODCONSTRUCTOR(CFailToBanMod) { AddHelpCommand(); AddCommand( "Timeout", t_d("[minutes]"), t_d("The number of minutes IPs are blocked after a failed login."), [=](const CString& sLine) { OnTimeoutCommand(sLine); }); AddCommand("Attempts", t_d("[count]"), t_d("The number of allowed failed login attempts."), [=](const CString& sLine) { OnAttemptsCommand(sLine); }); AddCommand("Ban", t_d(""), t_d("Ban the specified hosts."), [=](const CString& sLine) { OnBanCommand(sLine); }); AddCommand("Unban", t_d(""), t_d("Unban the specified hosts."), [=](const CString& sLine) { OnUnbanCommand(sLine); }); AddCommand("List", "", t_d("List banned hosts."), [=](const CString& sLine) { OnListCommand(sLine); }); } ~CFailToBanMod() override {} bool OnLoad(const CString& sArgs, CString& sMessage) override { CString sTimeout = sArgs.Token(0); CString sAttempts = sArgs.Token(1); unsigned int timeout = sTimeout.ToUInt(); if (sAttempts.empty()) m_uiAllowedFailed = 2; else m_uiAllowedFailed = sAttempts.ToUInt(); if (sArgs.empty()) { timeout = 1; } else if (timeout == 0 || m_uiAllowedFailed == 0 || !sArgs.Token(2, true).empty()) { sMessage = t_s("Invalid argument, must be the number of minutes IPs are " "blocked after a failed login and can be followed by " "number of allowed failed login attempts"); return false; } // SetTTL() wants milliseconds m_Cache.SetTTL(timeout * 60 * 1000); return true; } void OnPostRehash() override { m_Cache.Clear(); } void Add(const CString& sHost, unsigned int count) { m_Cache.AddItem(sHost, count, m_Cache.GetTTL()); } bool Remove(const CString& sHost) { return m_Cache.RemItem(sHost); } void OnTimeoutCommand(const CString& sCommand) { if (!GetUser()->IsAdmin()) { PutModule(t_s("Access denied")); return; } CString sArg = sCommand.Token(1); if (!sArg.empty()) { unsigned int uTimeout = sArg.ToUInt(); if (uTimeout == 0) { PutModule(t_s("Usage: Timeout [minutes]")); } else { m_Cache.SetTTL(uTimeout * 60 * 1000); SetArgs(CString(m_Cache.GetTTL() / 60 / 1000) + " " + CString(m_uiAllowedFailed)); PutModule(t_f("Timeout: {1} min")(uTimeout)); } } else { PutModule(t_f("Timeout: {1} min")(m_Cache.GetTTL() / 60 / 1000)); } } void OnAttemptsCommand(const CString& sCommand) { if (!GetUser()->IsAdmin()) { PutModule(t_s("Access denied")); return; } CString sArg = sCommand.Token(1); if (!sArg.empty()) { unsigned int uiAttempts = sArg.ToUInt(); if (uiAttempts == 0) { PutModule(t_s("Usage: Attempts [count]")); } else { m_uiAllowedFailed = uiAttempts; SetArgs(CString(m_Cache.GetTTL() / 60 / 1000) + " " + CString(m_uiAllowedFailed)); PutModule(t_f("Attempts: {1}")(uiAttempts)); } } else { PutModule(t_f("Attempts: {1}")(m_uiAllowedFailed)); } } void OnBanCommand(const CString& sCommand) { if (!GetUser()->IsAdmin()) { PutModule(t_s("Access denied")); return; } CString sHosts = sCommand.Token(1, true); if (sHosts.empty()) { PutStatus(t_s("Usage: Ban ")); return; } VCString vsHosts; sHosts.Replace(",", " "); sHosts.Split(" ", vsHosts, false, "", "", true, true); for (const CString& sHost : vsHosts) { Add(sHost, 0); PutModule(t_f("Banned: {1}")(sHost)); } } void OnUnbanCommand(const CString& sCommand) { if (!GetUser()->IsAdmin()) { PutModule(t_s("Access denied")); return; } CString sHosts = sCommand.Token(1, true); if (sHosts.empty()) { PutStatus(t_s("Usage: Unban ")); return; } VCString vsHosts; sHosts.Replace(",", " "); sHosts.Split(" ", vsHosts, false, "", "", true, true); for (const CString& sHost : vsHosts) { if (Remove(sHost)) { PutModule(t_f("Unbanned: {1}")(sHost)); } else { PutModule(t_f("Ignored: {1}")(sHost)); } } } void OnListCommand(const CString& sCommand) { if (!GetUser()->IsAdmin()) { PutModule(t_s("Access denied")); return; } CTable Table; Table.AddColumn(t_s("Host", "list")); Table.AddColumn(t_s("Attempts", "list")); Table.SetStyle(CTable::ListStyle); for (const auto& it : m_Cache.GetItems()) { Table.AddRow(); Table.SetCell(t_s("Host", "list"), it.first); Table.SetCell(t_s("Attempts", "list"), CString(it.second)); } if (Table.empty()) { PutModule(t_s("No bans", "list")); } else { PutModule(Table); } } void OnClientConnect(CZNCSock* pClient, const CString& sHost, unsigned short uPort) override { unsigned int* pCount = m_Cache.GetItem(sHost); if (sHost.empty() || pCount == nullptr || *pCount < m_uiAllowedFailed) { return; } // refresh their ban Add(sHost, *pCount); pClient->Write( "ERROR :Closing link [Please try again later - reconnecting too " "fast]\r\n"); pClient->Close(Csock::CLT_AFTERWRITE); } void OnFailedLogin(const CString& sUsername, const CString& sRemoteIP) override { unsigned int* pCount = m_Cache.GetItem(sRemoteIP); if (pCount) Add(sRemoteIP, *pCount + 1); else Add(sRemoteIP, 1); } EModRet OnLoginAttempt(std::shared_ptr Auth) override { // e.g. webadmin ends up here const CString& sRemoteIP = Auth->GetRemoteIP(); if (sRemoteIP.empty()) return CONTINUE; unsigned int* pCount = m_Cache.GetItem(sRemoteIP); if (pCount && *pCount >= m_uiAllowedFailed) { // OnFailedLogin() will refresh their ban Auth->RefuseLogin("Please try again later - reconnecting too fast"); return HALT; } return CONTINUE; } private: TCacheMap m_Cache; unsigned int m_uiAllowedFailed{}; }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("fail2ban"); Info.SetHasArgs(true); Info.SetArgsHelpText( Info.t_s("You might enter the time in minutes for the IP banning and " "the number of failed logins before any action is taken.")); } GLOBALMODULEDEFS(CFailToBanMod, t_s("Block IPs for some time after a failed login.")) znc-1.9.1/modules/flooddetach.cpp0000644000175000017500000002030314641222733017172 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include using std::map; class CFloodDetachMod : public CModule { public: MODCONSTRUCTOR(CFloodDetachMod) { m_iThresholdSecs = 0; m_iThresholdMsgs = 0; AddHelpCommand(); AddCommand("Show", "", t_d("Show current limits"), [=](const CString& sLine) { ShowCommand(sLine); }); AddCommand("Secs", t_d("[]"), t_d("Show or set number of seconds in the time interval"), [=](const CString& sLine) { SecsCommand(sLine); }); AddCommand("Lines", t_d("[]"), t_d("Show or set number of lines in the time interval"), [=](const CString& sLine) { LinesCommand(sLine); }); AddCommand("Silent", "[yes|no]", t_d("Show or set whether to notify you about detaching and " "attaching back"), [=](const CString& sLine) { SilentCommand(sLine); }); } ~CFloodDetachMod() override {} void Save() { // We save the settings twice because the module arguments can // be more easily edited via webadmin, while the SetNV() stuff // survives e.g. /msg *status reloadmod ctcpflood. SetNV("secs", CString(m_iThresholdSecs)); SetNV("msgs", CString(m_iThresholdMsgs)); SetArgs(CString(m_iThresholdMsgs) + " " + CString(m_iThresholdSecs)); } bool OnLoad(const CString& sArgs, CString& sMessage) override { m_iThresholdMsgs = sArgs.Token(0).ToUInt(); m_iThresholdSecs = sArgs.Token(1).ToUInt(); if (m_iThresholdMsgs == 0 || m_iThresholdSecs == 0) { m_iThresholdMsgs = GetNV("msgs").ToUInt(); m_iThresholdSecs = GetNV("secs").ToUInt(); } if (m_iThresholdSecs == 0) m_iThresholdSecs = 2; if (m_iThresholdMsgs == 0) m_iThresholdMsgs = 5; Save(); return true; } void OnIRCDisconnected() override { m_chans.clear(); } void Cleanup() { Limits::iterator it; time_t now = time(nullptr); for (it = m_chans.begin(); it != m_chans.end(); ++it) { // The timeout for this channel did not expire yet? if (it->second.first + (time_t)m_iThresholdSecs >= now) continue; CChan* pChan = GetNetwork()->FindChan(it->first); if (it->second.second >= m_iThresholdMsgs && pChan && pChan->IsDetached()) { // The channel is detached and it is over the // messages limit. Since we only track those // limits for non-detached channels or for // channels which we detached, this means that // we detached because of a flood. if (!GetNV("silent").ToBool()) { PutModule(t_f("Flood in {1} is over, reattaching...")( pChan->GetName())); } // No buffer playback, makes sense, doesn't it? pChan->ClearBuffer(); pChan->AttachUser(); } Limits::iterator it2 = it++; m_chans.erase(it2); // Without this Bad Things (tm) could happen if (it == m_chans.end()) break; } } void Message(CChan& Channel) { Limits::iterator it; time_t now = time(nullptr); // First: Clean up old entries and reattach where necessary Cleanup(); it = m_chans.find(Channel.GetName()); if (it == m_chans.end()) { // We don't track detached channels if (Channel.IsDetached()) return; // This is the first message for this channel, start a // new timeout. std::pair tmp(now, 1); m_chans[Channel.GetName()] = tmp; return; } // No need to check it->second.first (expiry time), since // Cleanup() would have removed it if it was expired. if (it->second.second >= m_iThresholdMsgs) { // The channel already hit the limit and we detached the // user, but it is still being flooded, reset the timeout it->second.first = now; it->second.second++; return; } it->second.second++; if (it->second.second < m_iThresholdMsgs) return; // The channel hit the limit, reset the timeout so that we keep // it detached for longer. it->second.first = now; Channel.DetachUser(); if (!GetNV("silent").ToBool()) { PutModule(t_f("Channel {1} was flooded, you've been detached")( Channel.GetName())); } } EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) override { Message(Channel); return CONTINUE; } // This also catches OnChanAction() EModRet OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage) override { Message(Channel); return CONTINUE; } EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) override { Message(Channel); return CONTINUE; } EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) override { Message(Channel); return CONTINUE; } void OnNick(const CNick& Nick, const CString& sNewNick, const std::vector& vChans) override { for (CChan* pChan : vChans) { Message(*pChan); } } void ShowCommand(const CString& sLine) { CString sLines = t_p("1 line", "{1} lines", m_iThresholdMsgs)(m_iThresholdMsgs); CString sSeconds = t_p("every second", "every {1} seconds", m_iThresholdSecs)(m_iThresholdSecs); PutModule(t_f("Current limit is {1} {2}")(sLines, sSeconds)); } void SecsCommand(const CString& sLine) { const CString sArg = sLine.Token(1, true); if (sArg.empty()) { PutModule(t_f("Seconds limit is {1}")(m_iThresholdSecs)); } else { m_iThresholdSecs = sArg.ToUInt(); if (m_iThresholdSecs == 0) m_iThresholdSecs = 1; PutModule(t_f("Set seconds limit to {1}")(m_iThresholdSecs)); Save(); } } void LinesCommand(const CString& sLine) { const CString sArg = sLine.Token(1, true); if (sArg.empty()) { PutModule(t_f("Lines limit is {1}")(m_iThresholdMsgs)); } else { m_iThresholdMsgs = sArg.ToUInt(); if (m_iThresholdMsgs == 0) m_iThresholdMsgs = 2; PutModule(t_f("Set lines limit to {1}")(m_iThresholdMsgs)); Save(); } } void SilentCommand(const CString& sLine) { const CString sArg = sLine.Token(1, true); if (!sArg.empty()) { SetNV("silent", CString(sArg.ToBool())); } if (GetNV("silent").ToBool()) { PutModule(t_s("Module messages are disabled")); } else { PutModule(t_s("Module messages are enabled")); } } private: typedef map> Limits; Limits m_chans; unsigned int m_iThresholdSecs; unsigned int m_iThresholdMsgs; }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("flooddetach"); Info.SetHasArgs(true); Info.SetArgsHelpText( Info.t_s("This user module takes up to two arguments. Arguments are " "numbers of messages and seconds.")); } USERMODULEDEFS(CFloodDetachMod, t_s("Detach channels when flooded")) znc-1.9.1/modules/identfile.cpp0000644000175000017500000001560014641222733016665 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include class CIdentFileModule : public CModule { CString m_sOrigISpoof; CFile* m_pISpoofLockFile; CIRCSock* m_pIRCSock; public: MODCONSTRUCTOR(CIdentFileModule) { AddHelpCommand(); AddCommand("GetFile", "", t_d("Show file name"), [=](const CString& sLine) { GetFile(sLine); }); AddCommand("SetFile", t_d(""), t_d("Set file name"), [=](const CString& sLine) { SetFile(sLine); }); AddCommand("GetFormat", "", t_d("Show file format"), [=](const CString& sLine) { GetFormat(sLine); }); AddCommand("SetFormat", t_d(""), t_d("Set file format"), [=](const CString& sLine) { SetFormat(sLine); }); AddCommand("Show", "", t_d("Show current state"), [=](const CString& sLine) { Show(sLine); }); m_pISpoofLockFile = nullptr; m_pIRCSock = nullptr; } ~CIdentFileModule() override { ReleaseISpoof(); } void GetFile(const CString& sLine) { PutModule(t_f("File is set to: {1}")(GetNV("File"))); } void SetFile(const CString& sLine) { SetNV("File", sLine.Token(1, true)); PutModule(t_f("File has been set to: {1}")(GetNV("File"))); } void SetFormat(const CString& sLine) { SetNV("Format", sLine.Token(1, true)); PutModule(t_f("Format has been set to: {1}")(GetNV("Format"))); PutModule(t_f("Format would be expanded to: {1}")( ExpandString(GetNV("Format")))); } void GetFormat(const CString& sLine) { PutModule(t_f("Format is set to: {1}")(GetNV("Format"))); PutModule(t_f("Format would be expanded to: {1}")( ExpandString(GetNV("Format")))); } void Show(const CString& sLine) { PutModule("m_pISpoofLockFile = " + CString((long long)m_pISpoofLockFile)); PutModule("m_pIRCSock = " + CString((long long)m_pIRCSock)); if (m_pIRCSock) { PutModule("user/network - " + m_pIRCSock->GetNetwork()->GetUser()->GetUsername() + "/" + m_pIRCSock->GetNetwork()->GetName()); } else { PutModule(t_s("identfile is free")); } } void OnModCommand(const CString& sCommand) override { if (GetUser()->IsAdmin()) { HandleCommand(sCommand); } else { PutModule(t_s("Access denied")); } } void SetIRCSock(CIRCSock* pIRCSock) { if (m_pIRCSock) { CZNC::Get().ResumeConnectQueue(); } m_pIRCSock = pIRCSock; if (m_pIRCSock) { CZNC::Get().PauseConnectQueue(); } } bool WriteISpoof() { if (m_pISpoofLockFile != nullptr) { return false; } m_pISpoofLockFile = new CFile; if (!m_pISpoofLockFile->TryExLock(GetNV("File"), O_RDWR | O_CREAT)) { delete m_pISpoofLockFile; m_pISpoofLockFile = nullptr; return false; } char buf[1024]; memset((char*)buf, 0, 1024); m_pISpoofLockFile->Read(buf, 1024); m_sOrigISpoof = buf; if (!m_pISpoofLockFile->Seek(0) || !m_pISpoofLockFile->Truncate()) { delete m_pISpoofLockFile; m_pISpoofLockFile = nullptr; return false; } CString sData = ExpandString(GetNV("Format")); // If the format doesn't contain anything expandable, we'll // assume this is an "old"-style format string. if (sData == GetNV("Format")) { sData.Replace("%", GetUser()->GetIdent()); } DEBUG("Writing [" + sData + "] to ident spoof file [" + m_pISpoofLockFile->GetLongName() + "] for user/network [" + GetUser()->GetUsername() + "/" + GetNetwork()->GetName() + "]"); m_pISpoofLockFile->Write(sData + "\n"); return true; } void ReleaseISpoof() { DEBUG("Releasing ident spoof for user/network [" + (m_pIRCSock ? m_pIRCSock->GetNetwork()->GetUser()->GetUsername() + "/" + m_pIRCSock->GetNetwork()->GetName() : "") + "]"); SetIRCSock(nullptr); if (m_pISpoofLockFile != nullptr) { if (m_pISpoofLockFile->Seek(0) && m_pISpoofLockFile->Truncate()) { m_pISpoofLockFile->Write(m_sOrigISpoof); } delete m_pISpoofLockFile; m_pISpoofLockFile = nullptr; } } bool OnLoad(const CString& sArgs, CString& sMessage) override { m_pISpoofLockFile = nullptr; m_pIRCSock = nullptr; if (GetNV("Format").empty()) { SetNV("Format", "global { reply \"%ident%\" }"); } if (GetNV("File").empty()) { SetNV("File", "~/.oidentd.conf"); } return true; } EModRet OnIRCConnecting(CIRCSock* pIRCSock) override { if (m_pISpoofLockFile != nullptr) { DEBUG("Aborting connection, ident spoof lock file exists"); PutModule( t_s("Aborting connection, another user or network is currently " "connecting and using the ident spoof file")); return HALTCORE; } if (!WriteISpoof()) { DEBUG("identfile [" + GetNV("File") + "] could not be written"); PutModule( t_f("[{1}] could not be written, retrying...")(GetNV("File"))); return HALTCORE; } SetIRCSock(pIRCSock); return CONTINUE; } void OnIRCConnected() override { if (m_pIRCSock == GetNetwork()->GetIRCSock()) { ReleaseISpoof(); } } void OnIRCConnectionError(CIRCSock* pIRCSock) override { if (m_pIRCSock == pIRCSock) { ReleaseISpoof(); } } void OnIRCDisconnected() override { if (m_pIRCSock == GetNetwork()->GetIRCSock()) { ReleaseISpoof(); } } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("identfile"); } GLOBALMODULEDEFS( CIdentFileModule, t_s("Write the ident of a user to a file when they are trying to connect.")) znc-1.9.1/modules/imapauth.cpp0000644000175000017500000001131514641222733016531 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include using std::map; class CIMAPAuthMod; class CIMAPSock : public CSocket { public: CIMAPSock(CIMAPAuthMod* pModule, std::shared_ptr Auth) : CSocket((CModule*)pModule), m_spAuth(Auth) { m_pIMAPMod = pModule; m_bSentReply = false; m_bSentLogin = false; EnableReadLine(); } ~CIMAPSock() override { if (!m_bSentReply) { m_spAuth->RefuseLogin( "IMAP server is down, please try again later"); } } void ReadLine(const CString& sLine) override; private: protected: CIMAPAuthMod* m_pIMAPMod; bool m_bSentLogin; bool m_bSentReply; std::shared_ptr m_spAuth; }; class CIMAPAuthMod : public CModule { public: MODCONSTRUCTOR(CIMAPAuthMod) { m_Cache.SetTTL(60000); m_sServer = "localhost"; m_uPort = 143; m_bSSL = false; } ~CIMAPAuthMod() override {} bool OnBoot() override { return true; } bool OnLoad(const CString& sArgs, CString& sMessage) override { if (sArgs.Trim_n().empty()) { return true; // use defaults } m_sServer = sArgs.Token(0); CString sPort = sArgs.Token(1); m_sUserFormat = sArgs.Token(2); if (sPort.Left(1) == "+") { m_bSSL = true; sPort.LeftChomp(); } unsigned short uPort = sPort.ToUShort(); if (uPort) { m_uPort = uPort; } return true; } EModRet OnLoginAttempt(std::shared_ptr Auth) override { CUser* pUser = CZNC::Get().FindUser(Auth->GetUsername()); if (!pUser) { // @todo Will want to do some sort of && !m_bAllowCreate in the // future Auth->RefuseLogin("Invalid User - Halting IMAP Lookup"); return HALT; } if (pUser && m_Cache.HasItem(CString(Auth->GetUsername() + ":" + Auth->GetPassword()).MD5())) { DEBUG("+++ Found in cache"); Auth->AcceptLogin(*pUser); return HALT; } CIMAPSock* pSock = new CIMAPSock(this, Auth); pSock->Connect(m_sServer, m_uPort, m_bSSL, 20); return HALT; } void OnModCommand(const CString& sLine) override {} void CacheLogin(const CString& sLogin) { m_Cache.AddItem(sLogin); } // Getters const CString& GetUserFormat() const { return m_sUserFormat; } // !Getters private: // Settings CString m_sServer; unsigned short m_uPort; bool m_bSSL; CString m_sUserFormat; // !Settings TCacheMap m_Cache; }; void CIMAPSock::ReadLine(const CString& sLine) { if (!m_bSentLogin) { CString sUsername = m_spAuth->GetUsername(); m_bSentLogin = true; const CString& sFormat = m_pIMAPMod->GetUserFormat(); if (!sFormat.empty()) { if (sFormat.find('%') != CString::npos) { sUsername = sFormat.Replace_n("%", sUsername); } else { sUsername += sFormat; } } Write("AUTH LOGIN " + sUsername + " " + m_spAuth->GetPassword() + "\r\n"); } else if (sLine.Left(5) == "AUTH ") { CUser* pUser = CZNC::Get().FindUser(m_spAuth->GetUsername()); if (pUser && sLine.StartsWith("AUTH OK")) { m_spAuth->AcceptLogin(*pUser); // Use MD5 so passes don't sit in memory in plain text m_pIMAPMod->CacheLogin(CString(m_spAuth->GetUsername() + ":" + m_spAuth->GetPassword()).MD5()); DEBUG("+++ Successful IMAP lookup"); } else { m_spAuth->RefuseLogin("Invalid Password"); DEBUG("--- FAILED IMAP lookup"); } m_bSentReply = true; Close(); } } template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("imapauth"); Info.SetHasArgs(true); Info.SetArgsHelpText(Info.t_s("[ server [+]port [ UserFormatString ] ]")); } GLOBALMODULEDEFS(CIMAPAuthMod, t_s("Allow users to authenticate via IMAP.")) znc-1.9.1/modules/keepnick.cpp0000644000175000017500000001607014641222733016515 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include using std::vector; class CKeepNickMod; class CKeepNickTimer : public CTimer { public: CKeepNickTimer(CKeepNickMod* pMod); ~CKeepNickTimer() override {} void RunJob() override; private: CKeepNickMod* m_pMod; }; class CKeepNickMod : public CModule { public: MODCONSTRUCTOR(CKeepNickMod) { AddHelpCommand(); AddCommand("Enable", "", t_d("Try to get your primary nick"), [=](const CString& sLine) { OnEnableCommand(sLine); }); AddCommand("Disable", "", t_d("No longer trying to get your primary nick"), [=](const CString& sLine) { OnDisableCommand(sLine); }); AddCommand("State", "", t_d("Show the current state"), [=](const CString& sLine) { OnStateCommand(sLine); }); } ~CKeepNickMod() override {} bool OnLoad(const CString& sArgs, CString& sMessage) override { m_pTimer = nullptr; // Check if we need to start the timer if (GetNetwork()->IsIRCConnected()) OnIRCConnected(); return true; } void KeepNick() { if (!m_pTimer) // No timer means we are turned off return; CIRCSock* pIRCSock = GetNetwork()->GetIRCSock(); if (!pIRCSock) return; // Do we already have the nick we want? if (pIRCSock->GetNick().Equals(GetNick())) return; PutIRC("NICK " + GetNick()); } CString GetNick() { CString sConfNick = GetNetwork()->GetNick(); CIRCSock* pIRCSock = GetNetwork()->GetIRCSock(); if (pIRCSock) sConfNick = sConfNick.Left(pIRCSock->GetMaxNickLen()); return sConfNick; } void OnNick(const CNick& Nick, const CString& sNewNick, const vector& vChans) override { if (sNewNick == GetNetwork()->GetIRCSock()->GetNick()) { // We are changing our own nick if (Nick.NickEquals(GetNick())) { // We are changing our nick away from the conf setting. // Let's assume the user wants this and disable // this module (to avoid fighting nickserv). Disable(); } else if (sNewNick.Equals(GetNick())) { // We are changing our nick to the conf setting, // so we don't need that timer anymore. Disable(); } return; } // If the nick we want is free now, be fast and get the nick if (Nick.NickEquals(GetNick())) { KeepNick(); } } void OnQuit(const CNick& Nick, const CString& sMessage, const vector& vChans) override { // If someone with the nick we want quits, be fast and get the nick if (Nick.NickEquals(GetNick())) { KeepNick(); } } void OnIRCDisconnected() override { // No way we can do something if we aren't connected to IRC. Disable(); } void OnIRCConnected() override { if (!GetNetwork()->GetIRCSock()->GetNick().Equals(GetNick())) { // We don't have the nick we want, try to get it Enable(); } } void Enable() { if (m_pTimer) return; m_pTimer = new CKeepNickTimer(this); AddTimer(m_pTimer); } void Disable() { if (!m_pTimer) return; m_pTimer->Stop(); RemTimer(m_pTimer); m_pTimer = nullptr; } EModRet OnUserRawMessage(CMessage& Message) override { // We don't care if we are not connected to IRC if (!GetNetwork()->IsIRCConnected()) return CONTINUE; // We are trying to get the config nick and this is a /nick? if (!m_pTimer || Message.GetType() != CMessage::Type::Nick) return CONTINUE; // Is the nick change for the nick we are trying to get? const CString sNick = Message.As().GetNewNick(); if (!sNick.Equals(GetNick())) return CONTINUE; // Indeed trying to change to this nick, generate a 433 for it. // This way we can *always* block incoming 433s from the server. PutUser(":" + GetNetwork()->GetIRCServer() + " 433 " + GetNetwork()->GetIRCNick().GetNick() + " " + sNick + " :" + t_s("ZNC is already trying to get this nickname")); return CONTINUE; } EModRet OnNumericMessage(CNumericMessage& msg) override { if (m_pTimer) { // Are we trying to get our primary nick and we caused this error? // :irc.server.net 433 mynick badnick :Nickname is already in use. if (msg.GetCode() == 433 && msg.GetParam(1).Equals(GetNick())) { return HALT; } // clang-format off // :leguin.freenode.net 435 mynick badnick #chan :Cannot change nickname while banned on channel // clang-format on if (msg.GetCode() == 435) { PutModule(t_f("Unable to obtain nick {1}: {2}, {3}")( msg.GetParam(1), msg.GetParam(3), msg.GetParam(2))); Disable(); } // clang-format off // :irc1.unrealircd.org 447 mynick :Can not change nickname while on #chan (+N) // clang-format on if (msg.GetCode() == 447) { PutModule(t_f("Unable to obtain nick {1}")(msg.GetParam(1))); Disable(); } } return CONTINUE; } void OnEnableCommand(const CString& sCommand) { Enable(); PutModule(t_s("Trying to get your primary nick")); } void OnDisableCommand(const CString& sCommand) { Disable(); PutModule(t_s("No longer trying to get your primary nick")); } void OnStateCommand(const CString& sCommand) { if (m_pTimer) PutModule(t_s("Currently trying to get your primary nick")); else PutModule(t_s("Currently disabled, try 'enable'")); } private: // If this is nullptr, we are turned off for some reason CKeepNickTimer* m_pTimer = nullptr; }; CKeepNickTimer::CKeepNickTimer(CKeepNickMod* pMod) : CTimer(pMod, 30, 0, "KeepNickTimer", "Tries to acquire this user's primary nick") { m_pMod = pMod; } void CKeepNickTimer::RunJob() { m_pMod->KeepNick(); } template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("keepnick"); } NETWORKMODULEDEFS(CKeepNickMod, t_s("Keeps trying for your primary nick")) znc-1.9.1/modules/kickrejoin.cpp0000644000175000017500000001015614641222733017053 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * This was originally written by cycomate. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Autorejoin module * rejoin channel (after a delay) when kicked * Usage: LoadModule = rejoin [delay] * */ #include #include class CRejoinJob : public CTimer { public: CRejoinJob(CModule* pModule, unsigned int uInterval, unsigned int uCycles, const CString& sLabel, const CString& sDescription) : CTimer(pModule, uInterval, uCycles, sLabel, sDescription) {} ~CRejoinJob() override {} protected: void RunJob() override { CIRCNetwork* pNetwork = GetModule()->GetNetwork(); CChan* pChan = pNetwork->FindChan(GetName().Token(1, true)); if (pChan) { pChan->Enable(); GetModule()->PutIRC("JOIN " + pChan->GetName() + " " + pChan->GetKey()); } } }; class CRejoinMod : public CModule { private: unsigned int delay = 10; public: MODCONSTRUCTOR(CRejoinMod) { AddHelpCommand(); AddCommand("SetDelay", t_d(""), t_d("Set the rejoin delay"), [=](const CString& sLine) { OnSetDelayCommand(sLine); }); AddCommand("ShowDelay", "", t_d("Show the rejoin delay"), [=](const CString& sLine) { OnShowDelayCommand(sLine); }); } ~CRejoinMod() override {} bool OnLoad(const CString& sArgs, CString& sErrorMsg) override { if (sArgs.empty()) { CString sDelay = GetNV("delay"); if (sDelay.empty()) delay = 10; else delay = sDelay.ToUInt(); } else { int i = sArgs.ToInt(); if ((i == 0 && sArgs == "0") || i > 0) delay = i; else { sErrorMsg = t_s("Illegal argument, must be a positive number or 0"); return false; } } return true; } void OnSetDelayCommand(const CString& sCommand) { int i; i = sCommand.Token(1).ToInt(); if (i < 0) { PutModule(t_s("Negative delays don't make any sense!")); return; } delay = i; SetNV("delay", CString(delay)); if (delay) PutModule(t_p("Rejoin delay set to 1 second", "Rejoin delay set to {1} seconds", delay)(delay)); else PutModule(t_s("Rejoin delay disabled")); } void OnShowDelayCommand(const CString& sCommand) { if (delay) PutModule(t_p("Rejoin delay is set to 1 second", "Rejoin delay is set to {1} seconds", delay)(delay)); else PutModule(t_s("Rejoin delay is disabled")); } void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& pChan, const CString& sMessage) override { if (GetNetwork()->GetCurNick().Equals(sKickedNick)) { if (!delay) { PutIRC("JOIN " + pChan.GetName() + " " + pChan.GetKey()); pChan.Enable(); return; } AddTimer(new CRejoinJob(this, delay, 1, "Rejoin " + pChan.GetName(), "Rejoin channel after a delay")); } } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("kickrejoin"); Info.SetHasArgs(true); Info.SetArgsHelpText(Info.t_s( "You might enter the number of seconds to wait before rejoining.")); } NETWORKMODULEDEFS(CRejoinMod, t_s("Autorejoins on kick")) znc-1.9.1/modules/lastseen.cpp0000644000175000017500000001123714641222733016542 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include using std::map; using std::pair; using std::multimap; class CLastSeenMod : public CModule { private: time_t GetTime(const CUser* pUser) { return GetNV(pUser->GetUsername()).ToULong(); } void SetTime(const CUser* pUser) { SetNV(pUser->GetUsername(), CString(time(nullptr))); } const CString FormatLastSeen(const CUser* pUser, const CString& sDefault = "") { time_t last = GetTime(pUser); if (last < 1) { return sDefault; } else { char buf[1024]; strftime(buf, sizeof(buf) - 1, "%c", localtime(&last)); return buf; } } typedef multimap MTimeMulti; typedef map MUsers; // Shows all users as well as the time they were last seen online void ShowCommand(const CString& sLine) { if (!GetUser()->IsAdmin()) { PutModule(t_s("Access denied")); return; } const MUsers& mUsers = CZNC::Get().GetUserMap(); MUsers::const_iterator it; CTable Table; Table.AddColumn(t_s("User", "show")); Table.AddColumn(t_s("Last Seen", "show")); Table.SetStyle(CTable::ListStyle); for (it = mUsers.begin(); it != mUsers.end(); ++it) { Table.AddRow(); Table.SetCell(t_s("User", "show"), it->first); Table.SetCell(t_s("Last Seen", "show"), FormatLastSeen(it->second, t_s("never"))); } PutModule(Table); } public: MODCONSTRUCTOR(CLastSeenMod) { AddHelpCommand(); AddCommand("Show", "", t_d("Shows list of users and when they last logged in"), [=](const CString& sLine) { ShowCommand(sLine); }); } ~CLastSeenMod() override {} // Event stuff: void OnClientLogin() override { SetTime(GetUser()); } void OnClientDisconnect() override { SetTime(GetUser()); } EModRet OnDeleteUser(CUser& User) override { DelNV(User.GetUsername()); return CONTINUE; } // Web stuff: bool WebRequiresAdmin() override { return true; } CString GetWebMenuTitle() override { return t_s("Last Seen"); } bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) override { if (sPageName == "index") { CModules& GModules = CZNC::Get().GetModules(); Tmpl["WebAdminLoaded"] = CString(GModules.FindModule("webadmin") != nullptr); MTimeMulti mmSorted; const MUsers& mUsers = CZNC::Get().GetUserMap(); for (MUsers::const_iterator uit = mUsers.begin(); uit != mUsers.end(); ++uit) { mmSorted.insert( pair(GetTime(uit->second), uit->second)); } for (MTimeMulti::const_iterator it = mmSorted.begin(); it != mmSorted.end(); ++it) { CUser* pUser = it->second; CTemplate& Row = Tmpl.AddRow("UserLoop"); Row["Username"] = pUser->GetUsername(); Row["IsSelf"] = CString(pUser == WebSock.GetSession()->GetUser()); Row["LastSeen"] = FormatLastSeen(pUser, t_s("never")); } return true; } return false; } bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) override { if (sPageName == "webadmin/user" && WebSock.GetSession()->IsAdmin()) { CUser* pUser = CZNC::Get().FindUser(Tmpl["Username"]); if (pUser) { Tmpl["LastSeen"] = FormatLastSeen(pUser); } return true; } return false; } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("lastseen"); } GLOBALMODULEDEFS(CLastSeenMod, t_s("Collects data about when a user last logged in.")) znc-1.9.1/modules/listsockets.cpp0000644000175000017500000002036714641222733017277 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include class CSocketSorter { public: CSocketSorter(const Csock* p) { m_pSock = p; } bool operator<(const CSocketSorter& other) const { // The 'biggest' item is displayed first. // return false: this is first // return true: other is first // Listeners go to the top if (m_pSock->GetType() != other.m_pSock->GetType()) { if (m_pSock->GetType() == Csock::LISTENER) return false; if (other.m_pSock->GetType() == Csock::LISTENER) return true; } const CString& sMyName = m_pSock->GetSockName(); const CString& sMyName2 = sMyName.Token(1, true, "::"); bool bMyEmpty = sMyName2.empty(); const CString& sHisName = other.GetSock()->GetSockName(); const CString& sHisName2 = sHisName.Token(1, true, "::"); bool bHisEmpty = sHisName2.empty(); // Then sort by first token after "::" if (bMyEmpty && !bHisEmpty) return false; if (bHisEmpty && !bMyEmpty) return true; if (!bMyEmpty && !bHisEmpty) { int c = sMyName2.StrCmp(sHisName2); if (c < 0) return false; if (c > 0) return true; } // and finally sort by the whole socket name return sMyName.StrCmp(sHisName) > 0; } const Csock* GetSock() const { return m_pSock; } private: const Csock* m_pSock; }; class CListSockets : public CModule { public: MODCONSTRUCTOR(CListSockets) { AddHelpCommand(); AddCommand("List", t_d("[-n]"), t_d("Shows the list of active sockets. " "Pass -n to show IP addresses"), [=](const CString& sLine) { OnListCommand(sLine); }); } bool OnLoad(const CString& sArgs, CString& sMessage) override { #ifndef MOD_LISTSOCKETS_ALLOW_EVERYONE if (!GetUser()->IsAdmin()) { sMessage = t_s("You must be admin to use this module"); return false; } #endif return true; } std::priority_queue GetSockets() { CSockManager& m = CZNC::Get().GetManager(); std::priority_queue ret; for (const Csock* pSock : m) { // These sockets went through SwapSockByAddr. That means // another socket took over the connection from this // socket. So ignore this to avoid listing the // connection twice. if (pSock->GetCloseType() == Csock::CLT_DEREFERENCE) continue; ret.push(pSock); } return ret; } bool WebRequiresAdmin() override { return true; } CString GetWebMenuTitle() override { return t_s("List sockets"); } bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) override { if (sPageName == "index") { if (CZNC::Get().GetManager().empty()) { return false; } std::priority_queue socks = GetSockets(); while (!socks.empty()) { const Csock* pSocket = socks.top().GetSock(); socks.pop(); CTemplate& Row = Tmpl.AddRow("SocketsLoop"); Row["Name"] = pSocket->GetSockName(); Row["Created"] = GetCreatedTime(pSocket); Row["State"] = GetSocketState(pSocket); Row["SSL"] = pSocket->GetSSL() ? t_s("Yes", "ssl") : t_s("No", "ssl"); Row["Local"] = GetLocalHost(pSocket, true); Row["Remote"] = GetRemoteHost(pSocket, true); Row["In"] = CString::ToByteStr(pSocket->GetBytesRead()); Row["Out"] = CString::ToByteStr(pSocket->GetBytesWritten()); } return true; } return false; } void OnListCommand(const CString& sLine) { CString sArg = sLine.Token(1, true); bool bShowHosts = true; if (sArg.Equals("-n")) { bShowHosts = false; } ShowSocks(bShowHosts); } CString GetSocketState(const Csock* pSocket) { switch (pSocket->GetType()) { case Csock::LISTENER: return t_s("Listener"); case Csock::INBOUND: return t_s("Inbound"); case Csock::OUTBOUND: if (pSocket->IsConnected()) return t_s("Outbound"); else return t_s("Connecting"); } return t_s("UNKNOWN"); } CString GetCreatedTime(const Csock* pSocket) { unsigned long long iStartTime = pSocket->GetStartTime(); timeval tv; tv.tv_sec = iStartTime / 1000; tv.tv_usec = iStartTime % 1000 * 1000; return CUtils::FormatTime(tv, "%Y-%m-%d %H:%M:%S.%f", GetUser()->GetTimezone()); } CString GetLocalHost(const Csock* pSocket, bool bShowHosts) { CString sBindHost; if (bShowHosts) { sBindHost = pSocket->GetBindHost(); } if (sBindHost.empty()) { sBindHost = pSocket->GetLocalIP(); } return sBindHost + " " + CString(pSocket->GetLocalPort()); } CString GetRemoteHost(const Csock* pSocket, bool bShowHosts) { CString sHost; u_short uPort; if (!bShowHosts) { sHost = pSocket->GetRemoteIP(); } // While connecting, there might be no ip available if (sHost.empty()) { sHost = pSocket->GetHostName(); } // While connecting, GetRemotePort() would return 0 if (pSocket->GetType() == Csock::OUTBOUND) { uPort = pSocket->GetPort(); } else { uPort = pSocket->GetRemotePort(); } if (uPort != 0) { return sHost + " " + CString(uPort); } return sHost; } void ShowSocks(bool bShowHosts) { if (CZNC::Get().GetManager().empty()) { PutStatus(t_s("You have no open sockets.")); return; } std::priority_queue socks = GetSockets(); CTable Table; Table.AddColumn(t_s("Name")); Table.AddColumn(t_s("Created")); Table.AddColumn(t_s("State")); #ifdef HAVE_LIBSSL Table.AddColumn(t_s("SSL")); #endif Table.AddColumn(t_s("Local")); Table.AddColumn(t_s("Remote")); Table.AddColumn(t_s("In")); Table.AddColumn(t_s("Out")); while (!socks.empty()) { const Csock* pSocket = socks.top().GetSock(); socks.pop(); Table.AddRow(); Table.SetCell(t_s("Name"), pSocket->GetSockName()); Table.SetCell(t_s("Created"), GetCreatedTime(pSocket)); Table.SetCell(t_s("State"), GetSocketState(pSocket)); #ifdef HAVE_LIBSSL Table.SetCell(t_s("SSL"), pSocket->GetSSL() ? t_s("Yes", "ssl") : t_s("No", "ssl")); #endif Table.SetCell(t_s("Local"), GetLocalHost(pSocket, bShowHosts)); Table.SetCell(t_s("Remote"), GetRemoteHost(pSocket, bShowHosts)); Table.SetCell(t_s("In"), CString::ToByteStr(pSocket->GetBytesRead())); Table.SetCell(t_s("Out"), CString::ToByteStr(pSocket->GetBytesWritten())); } PutModule(Table); return; } ~CListSockets() override {} }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("listsockets"); } USERMODULEDEFS(CListSockets, t_s("Lists active sockets")) znc-1.9.1/modules/log.cpp0000644000175000017500000004530514641222733015510 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * Copyright (C) 2006-2007, CNU *(http://cnu.dieplz.net/znc) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include using std::vector; class CLogRule { public: CLogRule(const CString& sRule, bool bEnabled = true) : m_sRule(sRule), m_bEnabled(bEnabled) {} const CString& GetRule() const { return m_sRule; } bool IsEnabled() const { return m_bEnabled; } void SetEnabled(bool bEnabled) { m_bEnabled = bEnabled; } bool Compare(const CString& sTarget) const { return sTarget.WildCmp(m_sRule, CString::CaseInsensitive); } bool operator==(const CLogRule& sOther) const { return m_sRule == sOther.GetRule(); } CString ToString() const { return (m_bEnabled ? "" : "!") + m_sRule; } private: CString m_sRule; bool m_bEnabled; }; class CLogMod : public CModule { public: MODCONSTRUCTOR(CLogMod) { m_bSanitize = false; AddHelpCommand(); AddCommand( "SetRules", t_d(""), t_d("Set logging rules, use !#chan or !query to negate and * "), [=](const CString& sLine) { SetRulesCmd(sLine); }); AddCommand("ClearRules", "", t_d("Clear all logging rules"), [=](const CString& sLine) { ClearRulesCmd(sLine); }); AddCommand("ListRules", "", t_d("List all logging rules"), [=](const CString& sLine) { ListRulesCmd(sLine); }); AddCommand( "Set", t_d(" true|false"), t_d("Set one of the following options: joins, quits, nickchanges"), [=](const CString& sLine) { SetCmd(sLine); }); AddCommand("ShowSettings", "", t_d("Show current settings set by Set command"), [=](const CString& sLine) { ShowSettingsCmd(sLine); }); } void SetRulesCmd(const CString& sLine); void ClearRulesCmd(const CString& sLine); void ListRulesCmd(const CString& sLine = ""); void SetCmd(const CString& sLine); void ShowSettingsCmd(const CString& sLine); void SetRules(const VCString& vsRules); VCString SplitRules(const CString& sRules) const; CString JoinRules(const CString& sSeparator) const; bool TestRules(const CString& sTarget) const; void PutLog(const CString& sLine, const CString& sWindow = "status"); void PutLog(const CString& sLine, const CChan& Channel); void PutLog(const CString& sLine, const CNick& Nick); CString GetServer(); bool OnLoad(const CString& sArgs, CString& sMessage) override; void OnIRCConnected() override; void OnIRCDisconnected() override; EModRet OnBroadcast(CString& sMessage) override; void OnRawMode2(const CNick* pOpNick, CChan& Channel, const CString& sModes, const CString& sArgs) override; void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) override; void OnQuit(const CNick& Nick, const CString& sMessage, const vector& vChans) override; void OnJoinMessage(CJoinMessage& Message) override; void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) override; void OnNick(const CNick& OldNick, const CString& sNewNick, const vector& vChans) override; EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) override; EModRet OnSendToIRCMessage(CMessage& Message) override; /* notices */ EModRet OnUserNotice(CString& sTarget, CString& sMessage) override; EModRet OnPrivNotice(CNick& Nick, CString& sMessage) override; EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) override; /* actions */ EModRet OnUserAction(CString& sTarget, CString& sMessage) override; EModRet OnPrivAction(CNick& Nick, CString& sMessage) override; EModRet OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) override; /* msgs */ EModRet OnUserMsg(CString& sTarget, CString& sMessage) override; EModRet OnPrivMsg(CNick& Nick, CString& sMessage) override; EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) override; private: bool NeedJoins() const; bool NeedQuits() const; bool NeedNickChanges() const; CString m_sLogPath; CString m_sTimestamp; bool m_bSanitize; vector m_vRules; }; void CLogMod::SetRulesCmd(const CString& sLine) { VCString vsRules = SplitRules(sLine.Token(1, true)); if (vsRules.empty()) { PutModule(t_s("Usage: SetRules ")); PutModule(t_s("Wildcards are allowed")); } else { SetRules(vsRules); SetNV("rules", JoinRules(",")); ListRulesCmd(); } } void CLogMod::ClearRulesCmd(const CString& sLine) { size_t uCount = m_vRules.size(); if (uCount == 0) { PutModule(t_s("No logging rules. Everything is logged.")); } else { CString sRules = JoinRules(" "); SetRules(VCString()); DelNV("rules"); PutModule(t_p("1 rule removed: {2}", "{1} rules removed: {2}", uCount)( uCount, sRules)); } } void CLogMod::ListRulesCmd(const CString& sLine) { CTable Table; Table.AddColumn(t_s("Rule", "listrules")); Table.AddColumn(t_s("Logging enabled", "listrules")); Table.SetStyle(CTable::ListStyle); for (const CLogRule& Rule : m_vRules) { Table.AddRow(); Table.SetCell(t_s("Rule", "listrules"), Rule.GetRule()); Table.SetCell(t_s("Logging enabled", "listrules"), CString(Rule.IsEnabled())); } if (Table.empty()) { PutModule(t_s("No logging rules. Everything is logged.")); } else { PutModule(Table); } } void CLogMod::SetCmd(const CString& sLine) { const CString sVar = sLine.Token(1).AsLower(); const CString sValue = sLine.Token(2); if (sValue.empty()) { PutModule( t_s("Usage: Set true|false, where is one of: joins, " "quits, nickchanges")); return; } bool b = sLine.Token(2).ToBool(); const std::unordered_map> mssResponses = { {"joins", {t_s("Will log joins"), t_s("Will not log joins")}}, {"quits", {t_s("Will log quits"), t_s("Will not log quits")}}, {"nickchanges", {t_s("Will log nick changes"), t_s("Will not log nick changes")}}}; auto it = mssResponses.find(sVar); if (it == mssResponses.end()) { PutModule(t_s( "Unknown variable. Known variables: joins, quits, nickchanges")); return; } SetNV(sVar, CString(b)); PutModule(b ? it->second.first : it->second.second); } void CLogMod::ShowSettingsCmd(const CString& sLine) { PutModule(NeedJoins() ? t_s("Logging joins") : t_s("Not logging joins")); PutModule(NeedQuits() ? t_s("Logging quits") : t_s("Not logging quits")); PutModule(NeedNickChanges() ? t_s("Logging nick changes") : t_s("Not logging nick changes")); } bool CLogMod::NeedJoins() const { return !HasNV("joins") || GetNV("joins").ToBool(); } bool CLogMod::NeedQuits() const { return !HasNV("quits") || GetNV("quits").ToBool(); } bool CLogMod::NeedNickChanges() const { return !HasNV("nickchanges") || GetNV("nickchanges").ToBool(); } void CLogMod::SetRules(const VCString& vsRules) { m_vRules.clear(); for (CString sRule : vsRules) { bool bEnabled = !sRule.TrimPrefix("!"); m_vRules.push_back(CLogRule(sRule, bEnabled)); } } VCString CLogMod::SplitRules(const CString& sRules) const { CString sCopy = sRules; sCopy.Replace(",", " "); VCString vsRules; sCopy.Split(" ", vsRules, false, "", "", true, true); return vsRules; } CString CLogMod::JoinRules(const CString& sSeparator) const { VCString vsRules; for (const CLogRule& Rule : m_vRules) { vsRules.push_back(Rule.ToString()); } return sSeparator.Join(vsRules.begin(), vsRules.end()); } bool CLogMod::TestRules(const CString& sTarget) const { for (const CLogRule& Rule : m_vRules) { if (Rule.Compare(sTarget)) { return Rule.IsEnabled(); } } return true; } void CLogMod::PutLog(const CString& sLine, const CString& sWindow /*= "Status"*/) { if (!TestRules(sWindow)) { return; } CString sPath; timeval curtime; gettimeofday(&curtime, nullptr); // Generate file name sPath = CUtils::FormatTime(curtime, m_sLogPath, GetUser()->GetTimezone()); if (sPath.empty()) { DEBUG("Could not format log path [" << sPath << "]"); return; } // TODO: Properly handle IRC case mapping // $WINDOW has to be handled last, since it can contain % sPath.Replace("$USER", CString((GetUser() ? GetUser()->GetUsername() : "UNKNOWN"))); sPath.Replace("$NETWORK", CString((GetNetwork() ? GetNetwork()->GetName() : "znc"))); sPath.Replace("$WINDOW", CString(sWindow.Replace_n("/", "-") .Replace_n("\\", "-")).AsLower()); // Check if it's allowed to write in this specific path sPath = CDir::CheckPathPrefix(GetSavePath(), sPath); if (sPath.empty()) { DEBUG("Invalid log path [" << m_sLogPath << "]."); return; } CFile LogFile(sPath); CString sLogDir = LogFile.GetDir(); struct stat ModDirInfo; CFile::GetInfo(GetSavePath(), ModDirInfo); if (!CFile::Exists(sLogDir)) CDir::MakeDir(sLogDir, ModDirInfo.st_mode); if (LogFile.Open(O_WRONLY | O_APPEND | O_CREAT)) { LogFile.Write(CUtils::FormatTime(curtime, m_sTimestamp, GetUser()->GetTimezone()) + " " + (m_bSanitize ? sLine.StripControls_n() : sLine) + "\n"); } else DEBUG("Could not open log file [" << sPath << "]: " << strerror(errno)); } void CLogMod::PutLog(const CString& sLine, const CChan& Channel) { PutLog(sLine, Channel.GetName()); } void CLogMod::PutLog(const CString& sLine, const CNick& Nick) { PutLog(sLine, Nick.GetNick()); } CString CLogMod::GetServer() { CServer* pServer = GetNetwork()->GetCurrentServer(); CString sSSL; if (!pServer) return "(no server)"; if (pServer->IsSSL()) sSSL = "+"; return pServer->GetName() + " " + sSSL + CString(pServer->GetPort()); } bool CLogMod::OnLoad(const CString& sArgs, CString& sMessage) { VCString vsArgs; sArgs.QuoteSplit(vsArgs); bool bReadingTimestamp = false; bool bHaveLogPath = false; for (CString& sArg : vsArgs) { if (bReadingTimestamp) { m_sTimestamp = sArg; bReadingTimestamp = false; } else if (sArg.Equals("-sanitize")) { m_bSanitize = true; } else if (sArg.Equals("-timestamp")) { bReadingTimestamp = true; } else { // Only one arg may be LogPath if (bHaveLogPath) { sMessage = t_f("Invalid args [{1}]. Only one log path allowed. Check " "that there are no spaces in the path.")(sArgs); return false; } m_sLogPath = sArg; bHaveLogPath = true; } } if (m_sTimestamp.empty()) { m_sTimestamp = "[%H:%M:%S]"; } // Add default filename to path if it's a folder if (GetType() == CModInfo::UserModule) { if (m_sLogPath.Right(1) == "/" || m_sLogPath.find("$WINDOW") == CString::npos || m_sLogPath.find("$NETWORK") == CString::npos) { if (!m_sLogPath.empty()) { m_sLogPath += "/"; } m_sLogPath += "$NETWORK/$WINDOW/%Y-%m-%d.log"; } } else if (GetType() == CModInfo::NetworkModule) { if (m_sLogPath.Right(1) == "/" || m_sLogPath.find("$WINDOW") == CString::npos) { if (!m_sLogPath.empty()) { m_sLogPath += "/"; } m_sLogPath += "$WINDOW/%Y-%m-%d.log"; } } else { if (m_sLogPath.Right(1) == "/" || m_sLogPath.find("$USER") == CString::npos || m_sLogPath.find("$WINDOW") == CString::npos || m_sLogPath.find("$NETWORK") == CString::npos) { if (!m_sLogPath.empty()) { m_sLogPath += "/"; } m_sLogPath += "$USER/$NETWORK/$WINDOW/%Y-%m-%d.log"; } } CString sRules = GetNV("rules"); VCString vsRules = SplitRules(sRules); SetRules(vsRules); // Check if it's allowed to write in this path in general m_sLogPath = CDir::CheckPathPrefix(GetSavePath(), m_sLogPath); if (m_sLogPath.empty()) { sMessage = t_f("Invalid log path [{1}]")(m_sLogPath); return false; } else { sMessage = t_f("Logging to [{1}]. Using timestamp format '{2}'")( m_sLogPath, m_sTimestamp); return true; } } // TODO consider writing translated strings to log. Currently user language // affects only UI. void CLogMod::OnIRCConnected() { PutLog("Connected to IRC (" + GetServer() + ")"); } void CLogMod::OnIRCDisconnected() { PutLog("Disconnected from IRC (" + GetServer() + ")"); } CModule::EModRet CLogMod::OnBroadcast(CString& sMessage) { PutLog("Broadcast: " + sMessage); return CONTINUE; } void CLogMod::OnRawMode2(const CNick* pOpNick, CChan& Channel, const CString& sModes, const CString& sArgs) { const CString sNick = pOpNick ? pOpNick->GetNick() : "Server"; PutLog("*** " + sNick + " sets mode: " + sModes + " " + sArgs, Channel); } void CLogMod::OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) { PutLog("*** " + sKickedNick + " was kicked by " + OpNick.GetNick() + " (" + sMessage + ")", Channel); } void CLogMod::OnQuit(const CNick& Nick, const CString& sMessage, const vector& vChans) { if (NeedQuits()) { for (CChan* pChan : vChans) PutLog("*** Quits: " + Nick.GetNick() + " (" + Nick.GetIdent() + "@" + Nick.GetHost() + ") (" + sMessage + ")", *pChan); } } CModule::EModRet CLogMod::OnSendToIRCMessage(CMessage& Message) { if (Message.GetType() != CMessage::Type::Quit) { return CONTINUE; } CIRCNetwork* pNetwork = Message.GetNetwork(); OnQuit(pNetwork->GetIRCNick(), Message.As().GetReason(), pNetwork->GetChans()); return CONTINUE; } void CLogMod::OnJoinMessage(CJoinMessage& Message) { if (!NeedJoins()) return; const CNick& Nick = Message.GetNick(); CChan& Channel = *Message.GetChan(); // TODO: Move account logic to a separate Message method. CString sAccount = Message.GetTag("account"); const char* s = " "; if (sAccount.empty()) sAccount = Message.GetParam(1); if (sAccount.empty() || sAccount == "*") { sAccount = ""; s = ""; } PutLog("*** Joins: " + Nick.GetNick() + " (" + Nick.GetIdent() + "@" + Nick.GetHost() + ")" + s + sAccount, Channel); } void CLogMod::OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) { PutLog("*** Parts: " + Nick.GetNick() + " (" + Nick.GetIdent() + "@" + Nick.GetHost() + ") (" + sMessage + ")", Channel); } void CLogMod::OnNick(const CNick& OldNick, const CString& sNewNick, const vector& vChans) { if (NeedNickChanges()) { for (CChan* pChan : vChans) PutLog("*** " + OldNick.GetNick() + " is now known as " + sNewNick, *pChan); } } CModule::EModRet CLogMod::OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) { PutLog("*** " + Nick.GetNick() + " changes topic to '" + sTopic + "'", Channel); return CONTINUE; } /* notices */ CModule::EModRet CLogMod::OnUserNotice(CString& sTarget, CString& sMessage) { CIRCNetwork* pNetwork = GetNetwork(); if (pNetwork) { PutLog("-" + pNetwork->GetCurNick() + "- " + sMessage, sTarget); } return CONTINUE; } CModule::EModRet CLogMod::OnPrivNotice(CNick& Nick, CString& sMessage) { PutLog("-" + Nick.GetNick() + "- " + sMessage, Nick); return CONTINUE; } CModule::EModRet CLogMod::OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) { PutLog("-" + Nick.GetNick() + "- " + sMessage, Channel); return CONTINUE; } /* actions */ CModule::EModRet CLogMod::OnUserAction(CString& sTarget, CString& sMessage) { CIRCNetwork* pNetwork = GetNetwork(); if (pNetwork) { PutLog("* " + pNetwork->GetCurNick() + " " + sMessage, sTarget); } return CONTINUE; } CModule::EModRet CLogMod::OnPrivAction(CNick& Nick, CString& sMessage) { PutLog("* " + Nick.GetNick() + " " + sMessage, Nick); return CONTINUE; } CModule::EModRet CLogMod::OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) { PutLog("* " + Nick.GetNick() + " " + sMessage, Channel); return CONTINUE; } /* msgs */ CModule::EModRet CLogMod::OnUserMsg(CString& sTarget, CString& sMessage) { CIRCNetwork* pNetwork = GetNetwork(); if (pNetwork) { PutLog("<" + pNetwork->GetCurNick() + "> " + sMessage, sTarget); } return CONTINUE; } CModule::EModRet CLogMod::OnPrivMsg(CNick& Nick, CString& sMessage) { PutLog("<" + Nick.GetNick() + "> " + sMessage, Nick); return CONTINUE; } CModule::EModRet CLogMod::OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) { PutLog("<" + Nick.GetNick() + "> " + sMessage, Channel); return CONTINUE; } template <> void TModInfo(CModInfo& Info) { Info.AddType(CModInfo::NetworkModule); Info.AddType(CModInfo::GlobalModule); Info.SetHasArgs(true); Info.SetArgsHelpText( Info.t_s("[-sanitize] Optional path where to store logs.")); Info.SetWikiPage("log"); } USERMODULEDEFS(CLogMod, t_s("Writes IRC logs.")) znc-1.9.1/modules/missingmotd.cpp0000644000175000017500000000213514641222733017256 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include class CMissingMotd : public CModule { public: MODCONSTRUCTOR(CMissingMotd) {} void OnClientLogin() override { PutUser(":irc.znc.in 422 " + GetClient()->GetNick() + " :MOTD File is missing"); } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("missingmotd"); Info.SetHasArgs(false); } USERMODULEDEFS(CMissingMotd, t_s("Sends 422 to clients when they login")) znc-1.9.1/modules/modperl.cpp0000644000175000017500000003126114641222733016365 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include "modperl/module.h" #include "modperl/swigperlrun.h" #include #include #include #if defined(__APPLE__) && defined(__MACH__) #include // for _NSGetEnviron #endif #include "modperl/pstring.h" using std::set; using std::vector; // Allows perl to load .so files when needed by .pm // For example, it needs to load ZNC.so extern "C" { void boot_DynaLoader(pTHX_ CV* cv); static void xs_init(pTHX) { newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, __FILE__); } } class CModPerl : public CModule { PerlInterpreter* m_pPerl; public: MODCONSTRUCTOR(CModPerl) { m_pPerl = nullptr; } #define PSTART \ dSP; \ I32 ax; \ int ret = 0; \ ENTER; \ SAVETMPS; \ PUSHMARK(SP) #define PCALL(name) \ PUTBACK; \ ret = call_pv(name, G_EVAL | G_ARRAY); \ SPAGAIN; \ SP -= ret; \ ax = (SP - PL_stack_base) + 1 #define PEND \ ax += 0; \ PUTBACK; \ FREETMPS; \ LEAVE #define PUSH_STR(s) XPUSHs(PString(s).GetSV()) #define PUSH_PTR(type, p) \ XPUSHs(SWIG_NewInstanceObj(const_cast(p), SWIG_TypeQuery(#type), \ SWIG_SHADOW)) bool OnLoad(const CString& sArgsi, CString& sMessage) override { CString sModPath, sTmp; if (!CModules::FindModPath("modperl/startup.pl", sModPath, sTmp)) { sMessage = "startup.pl not found."; return false; } sTmp = CDir::ChangeDir(sModPath, ".."); int argc = 6; char* pArgv[] = {"", "-T", "-w", "-I", const_cast(sTmp.c_str()), const_cast(sModPath.c_str()), nullptr}; char** argv = pArgv; char*** const pEnviron = #if defined(__APPLE__) && defined(__MACH__) _NSGetEnviron(); #else &environ; #endif PERL_SYS_INIT3(&argc, &argv, pEnviron); m_pPerl = perl_alloc(); perl_construct(m_pPerl); if (perl_parse(m_pPerl, xs_init, argc, argv, *pEnviron)) { sMessage = "Can't initialize perl. "; if (SvTRUE(ERRSV)) { sMessage += PString(ERRSV); } perl_free(m_pPerl); PERL_SYS_TERM(); m_pPerl = nullptr; DEBUG(__PRETTY_FUNCTION__ << " can't init perl"); return false; } PL_exit_flags |= PERL_EXIT_DESTRUCT_END; return true; } EModRet OnModuleLoading(const CString& sModName, const CString& sArgs, CModInfo::EModuleType eType, bool& bSuccess, CString& sRetMsg) override { EModRet result = HALT; PSTART; PUSH_STR(sModName); PUSH_STR(sArgs); mXPUSHi(eType); PUSH_PTR(CUser*, GetUser()); PUSH_PTR(CIRCNetwork*, GetNetwork()); PCALL("ZNC::Core::LoadModule"); if (SvTRUE(ERRSV)) { sRetMsg = PString(ERRSV); bSuccess = false; result = HALT; DEBUG("Perl ZNC::Core::LoadModule died: " << sRetMsg); } else if (ret < 1 || 2 < ret) { sRetMsg = "Error: Perl ZNC::Core::LoadModule returned " + CString(ret) + " values."; bSuccess = false; result = HALT; } else { ELoadPerlMod eLPM = static_cast(SvUV(ST(0))); if (Perl_NotFound == eLPM) { result = CONTINUE; // Not a Perl module } else { sRetMsg = PString(ST(1)); result = HALT; bSuccess = eLPM == Perl_Loaded; } } PEND; return result; } EModRet OnModuleUnloading(CModule* pModule, bool& bSuccess, CString& sRetMsg) override { CPerlModule* pMod = AsPerlModule(pModule); if (pMod) { EModRet result = HALT; CString sModName = pMod->GetModName(); PSTART; XPUSHs(pMod->GetPerlObj()); PCALL("ZNC::Core::UnloadModule"); if (SvTRUE(ERRSV)) { bSuccess = false; sRetMsg = PString(ERRSV); } else if (ret < 1 || 2 < ret) { sRetMsg = "Error: Perl ZNC::Core::UnloadModule returned " + CString(ret) + " values."; bSuccess = false; result = HALT; } else { int bUnloaded = SvUV(ST(0)); if (bUnloaded) { bSuccess = true; sRetMsg = "Module [" + sModName + "] unloaded"; result = HALT; } else { result = CONTINUE; // module wasn't loaded by modperl. // Perhaps a module-provider written in // perl did that. } } PEND; DEBUG(__PRETTY_FUNCTION__ << " " << sRetMsg); return result; } return CONTINUE; } EModRet OnGetModInfo(CModInfo& ModInfo, const CString& sModule, bool& bSuccess, CString& sRetMsg) override { PSTART; PUSH_STR(sModule); PUSH_PTR(CModInfo*, &ModInfo); PCALL("ZNC::Core::GetModInfo"); EModRet result = CONTINUE; if (SvTRUE(ERRSV)) { bSuccess = false; sRetMsg = PString(ERRSV); DEBUG("Perl ZNC::Core::GetModInfo died: " << sRetMsg); } else if (0 < ret) { switch (static_cast(SvUV(ST(0)))) { case Perl_NotFound: result = CONTINUE; break; case Perl_Loaded: result = HALT; if (1 == ret) { bSuccess = true; } else { bSuccess = false; sRetMsg = "Something weird happened"; } break; case Perl_LoadError: result = HALT; bSuccess = false; if (2 == ret) { sRetMsg = PString(ST(1)); } else { sRetMsg = "Something weird happened"; } } } else { result = HALT; bSuccess = false; sRetMsg = "Something weird happened"; } PEND; return result; } void OnGetAvailableMods(set& ssMods, CModInfo::EModuleType eType) override { unsigned int a = 0; CDir Dir; CModules::ModDirList dirs = CModules::GetModDirs(); while (!dirs.empty()) { Dir.FillByWildcard(dirs.front().first, "*.pm"); dirs.pop(); for (a = 0; a < Dir.size(); a++) { CFile& File = *Dir[a]; CString sName = File.GetShortName(); CString sPath = File.GetLongName(); CModInfo ModInfo; sName.RightChomp(3); PSTART; PUSH_STR(sPath); PUSH_STR(sName); PUSH_PTR(CModInfo*, &ModInfo); PCALL("ZNC::Core::ModInfoByPath"); if (SvTRUE(ERRSV)) { DEBUG(__PRETTY_FUNCTION__ << ": " << sPath << ": " << PString(ERRSV)); } else if (ModInfo.SupportsType(eType)) { ssMods.insert(ModInfo); } PEND; } } } ~CModPerl() override { if (m_pPerl) { PSTART; PCALL("ZNC::Core::UnloadAll"); PEND; perl_destruct(m_pPerl); perl_free(m_pPerl); PERL_SYS_TERM(); } } }; #include "modperl/perlfunctions.cpp" VWebSubPages& CPerlModule::GetSubPages() { VWebSubPages* result = _GetSubPages(); if (!result) { return CModule::GetSubPages(); } return *result; } void CPerlTimer::RunJob() { CPerlModule* pMod = AsPerlModule(GetModule()); if (pMod) { PSTART; XPUSHs(GetPerlObj()); PCALL("ZNC::Core::CallTimer"); PEND; } } CPerlTimer::~CPerlTimer() { CPerlModule* pMod = AsPerlModule(GetModule()); if (pMod) { PSTART; XPUSHs(sv_2mortal(m_perlObj)); PCALL("ZNC::Core::RemoveTimer"); PEND; } } #define SOCKSTART \ PSTART; \ XPUSHs(GetPerlObj()) #define SOCKCBCHECK(OnSuccess) \ PCALL("ZNC::Core::CallSocket"); \ if (SvTRUE(ERRSV)) { \ Close(); \ DEBUG("Perl socket hook died with: " + PString(ERRSV)); \ } else { \ OnSuccess; \ } \ PEND #define CBSOCK(Func) \ void CPerlSocket::Func() { \ CPerlModule* pMod = AsPerlModule(GetModule()); \ if (pMod) { \ SOCKSTART; \ PUSH_STR("On" #Func); \ SOCKCBCHECK(); \ } \ } CBSOCK(Connected); CBSOCK(Disconnected); CBSOCK(Timeout); CBSOCK(ConnectionRefused); void CPerlSocket::ReadData(const char* data, size_t len) { CPerlModule* pMod = AsPerlModule(GetModule()); if (pMod) { SOCKSTART; PUSH_STR("OnReadData"); XPUSHs(sv_2mortal(newSVpvn(data, len))); mXPUSHi(len); SOCKCBCHECK(); } } void CPerlSocket::ReadLine(const CString& sLine) { CPerlModule* pMod = AsPerlModule(GetModule()); if (pMod) { SOCKSTART; PUSH_STR("OnReadLine"); PUSH_STR(sLine); SOCKCBCHECK(); } } Csock* CPerlSocket::GetSockObj(const CString& sHost, unsigned short uPort) { CPerlModule* pMod = AsPerlModule(GetModule()); Csock* result = nullptr; if (pMod) { SOCKSTART; PUSH_STR("_Accepted"); PUSH_STR(sHost); mXPUSHi(uPort); SOCKCBCHECK(result = SvToPtr("CPerlSocket*")(ST(0));); } return result; } CPerlSocket::~CPerlSocket() { CPerlModule* pMod = AsPerlModule(GetModule()); if (pMod) { PSTART; XPUSHs(sv_2mortal(m_perlObj)); PCALL("ZNC::Core::RemoveSocket"); PEND; } } CPerlCapability::~CPerlCapability() { SvREFCNT_dec(m_serverCb); SvREFCNT_dec(m_clientCb); } void CPerlCapability::OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) { PSTART; PUSH_PTR(CIRCNetwork*, pNetwork); mXPUSHi(bState); PUTBACK; ret = call_sv(m_serverCb, G_EVAL | G_ARRAY); SPAGAIN; SP -= ret; ax = (SP - PL_stack_base) + 1; if (SvTRUE(ERRSV)) { DEBUG("Perl hook OnServerChangedSupport died with: " + PString(ERRSV)); } PEND; } void CPerlCapability::OnClientChangedSupport(CClient* pClient, bool bState) { PSTART; PUSH_PTR(CClient*, pClient); mXPUSHi(bState); PUTBACK; ret = call_sv(m_clientCb, G_EVAL | G_ARRAY); SPAGAIN; SP -= ret; ax = (SP - PL_stack_base) + 1; if (SvTRUE(ERRSV)) { DEBUG("Perl hook OnServerChangedSupport died with: " + PString(ERRSV)); } PEND; } template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("modperl"); } GLOBALMODULEDEFS(CModPerl, t_s("Loads perl scripts as ZNC modules")) znc-1.9.1/modules/modperl/0000755000175000017500000000000014641222741015655 5ustar somebodysomebodyznc-1.9.1/modules/modperl/CMakeLists.txt0000644000175000017500000001003214641222733020412 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # TODO: consider switching to swig_add_library() after bumping CMake # requirements to 3.8, when that command started using IMPLICIT_DEPENDS set(modinclude_modperl PUBLIC ${PERL_INCLUDE_DIRS} "${CMAKE_CURRENT_BINARY_DIR}/.." PARENT_SCOPE) set(modcompile_modperl PUBLIC "${PERL_CFLAGS}" PARENT_SCOPE) set(modlink_modperl PUBLIC ${PERL_LIBRARIES} PARENT_SCOPE) set(modprop_modperl LINK_FLAGS "${PERL_LDFLAGS}" PARENT_SCOPE) set(moddef_modperl PUBLIC "SWIG_TYPE_TABLE=znc" PARENT_SCOPE) set(moddepend_modperl modperl_functions modperl_swigruntime PARENT_SCOPE) if(APPLE) set(CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS "${CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS} -undefined dynamic_lookup") endif() if(SWIG_FOUND) add_custom_command( OUTPUT perlfunctions.cpp COMMAND "${PERL_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl" "${CMAKE_CURRENT_SOURCE_DIR}/functions.in" "perlfunctions.cpp" VERBATIM DEPENDS codegen.pl functions.in) add_custom_command( OUTPUT swigperlrun.h COMMAND "${SWIG_EXECUTABLE}" -perl -c++ -shadow -external-runtime "swigperlrun.h" VERBATIM) add_custom_command( OUTPUT modperl_biglib.cpp ZNC.pm COMMAND "${SWIG_EXECUTABLE}" -perl -c++ -shadow "-I${PROJECT_BINARY_DIR}/include" "-I${PROJECT_SOURCE_DIR}/include" "-I${CMAKE_CURRENT_SOURCE_DIR}/.." "-I${CMAKE_CURRENT_SOURCE_DIR}/include" -outdir "${CMAKE_CURRENT_BINARY_DIR}" -o "${CMAKE_CURRENT_BINARY_DIR}/modperl_biglib.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/modperl.i" DEPENDS "modperl.i" copy_csocket_h IMPLICIT_DEPENDS CXX "${CMAKE_CURRENT_SOURCE_DIR}/modperl.i" VERBATIM) else() add_custom_command( OUTPUT swigperlrun.h ZNC.pm modperl_biglib.cpp perlfunctions.cpp COMMAND "${CMAKE_COMMAND}" -E tar xz "${CMAKE_CURRENT_SOURCE_DIR}/generated.tar.gz" VERBATIM) endif() add_custom_target(modperl_functions DEPENDS "perlfunctions.cpp") add_custom_target(modperl_swigruntime DEPENDS "swigperlrun.h") add_custom_target(modperl_swig DEPENDS "modperl_biglib.cpp" "ZNC.pm") execute_process(COMMAND "${PERL_EXECUTABLE}" -MConfig "-eprint $Config::Config{dlext}" OUTPUT_VARIABLE perl_ext) znc_add_library(modperl_lib MODULE modperl_biglib.cpp) add_dependencies(modperl_lib modperl_swig) target_include_directories(modperl_lib PRIVATE "${PROJECT_BINARY_DIR}/include" "${PROJECT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/.." "${CMAKE_CURRENT_SOURCE_DIR}/.." ${PERL_INCLUDE_DIRS}) target_link_libraries(modperl_lib ZNC) set_target_properties(modperl_lib PROPERTIES PREFIX "" SUFFIX ".${perl_ext}" OUTPUT_NAME "ZNC" NO_SONAME true LINK_FLAGS "${PERL_LDFLAGS}") target_compile_options(modperl_lib PRIVATE "${PERL_CFLAGS}") target_compile_definitions(modperl_lib PRIVATE "SWIG_TYPE_TABLE=znc") if(CYGWIN) target_link_libraries(modperl_lib module_modperl) endif() install(TARGETS modperl_lib LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}/znc/modperl") install(FILES "startup.pl" "${CMAKE_CURRENT_BINARY_DIR}/ZNC.pm" DESTINATION "${CMAKE_INSTALL_LIBDIR}/znc/modperl") function(add_perl_module mod modpath) install(FILES "${modpath}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/znc") endfunction() # This target is used to generate tarball which doesn't depend on SWIG. add_custom_target(modperl_dist COMMAND "${CMAKE_COMMAND}" -E tar cz "${CMAKE_CURRENT_SOURCE_DIR}/generated.tar.gz" "swigperlrun.h" "ZNC.pm" "modperl_biglib.cpp" "perlfunctions.cpp" DEPENDS swigperlrun.h ZNC.pm modperl_biglib.cpp perlfunctions.cpp VERBATIM) add_dependencies(modperl_dist copy_csocket_h) znc-1.9.1/modules/modperl/CString.i0000644000175000017500000003262514641222733017411 0ustar somebodysomebody/* SWIG-generated sources are used here. This file is generated using: echo '%include ' > foo.i swig -perl -c++ -shadow -E foo.i > string.i Remove unrelated stuff from top of file which is included by default s/std::string/CString/g s/std_string/CString/g */ %fragment(""); %feature("naturalvar") CString; class CString; /*@SWIG:/swig/3.0.8/typemaps/CStrings.swg,70,%typemaps_CString@*/ /*@SWIG:/swig/3.0.8/typemaps/CStrings.swg,4,%CString_asptr@*/ %fragment("SWIG_" "AsPtr" "_" {CString},"header",fragment="SWIG_AsCharPtrAndSize") { SWIGINTERN int SWIG_AsPtr_CString SWIG_PERL_DECL_ARGS_2(SV * obj, CString **val) { char* buf = 0 ; size_t size = 0; int alloc = SWIG_OLDOBJ; if (SWIG_IsOK((SWIG_AsCharPtrAndSize(obj, &buf, &size, &alloc)))) { if (buf) { if (val) *val = new CString(buf, size - 1); if (alloc == SWIG_NEWOBJ) delete[] buf; return SWIG_NEWOBJ; } else { if (val) *val = 0; return SWIG_OLDOBJ; } } else { static int init = 0; static swig_type_info* descriptor = 0; if (!init) { descriptor = SWIG_TypeQuery("CString" " *"); init = 1; } if (descriptor) { CString *vptr; int res = SWIG_ConvertPtr(obj, (void**)&vptr, descriptor, 0); if (SWIG_IsOK(res) && val) *val = vptr; return res; } } return SWIG_ERROR; } } /*@SWIG@*/ /*@SWIG:/swig/3.0.8/typemaps/CStrings.swg,48,%CString_asval@*/ %fragment("SWIG_" "AsVal" "_" {CString},"header", fragment="SWIG_" "AsPtr" "_" {CString}) { SWIGINTERN int SWIG_AsVal_CString SWIG_PERL_DECL_ARGS_2(SV * obj, CString *val) { CString* v = (CString *) 0; int res = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2(obj, &v); if (!SWIG_IsOK(res)) return res; if (v) { if (val) *val = *v; if (SWIG_IsNewObj(res)) { delete v; res = SWIG_DelNewMask(res); } return res; } return SWIG_ERROR; } } /*@SWIG@*/ /*@SWIG:/swig/3.0.8/typemaps/CStrings.swg,38,%CString_from@*/ %fragment("SWIG_" "From" "_" {CString},"header",fragment="SWIG_FromCharPtrAndSize") { SWIGINTERNINLINE SV * SWIG_From_CString SWIG_PERL_DECL_ARGS_1(const CString& s) { return SWIG_FromCharPtrAndSize(s.data(), s.size()); } } /*@SWIG@*/ /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,201,%typemaps_asptrfromn@*/ /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,190,%typemaps_asptrfrom@*/ /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,160,%typemaps_asptr@*/ %fragment("SWIG_" "AsVal" "_" {CString},"header",fragment="SWIG_" "AsPtr" "_" {CString}) { SWIGINTERNINLINE int SWIG_AsVal_CString SWIG_PERL_CALL_ARGS_2(SV * obj, CString *val) { CString *v = (CString *)0; int res = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2(obj, &v); if (!SWIG_IsOK(res)) return res; if (v) { if (val) *val = *v; if (SWIG_IsNewObj(res)) { delete v; res = SWIG_DelNewMask(res); } return res; } return SWIG_ERROR; } } /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,28,%ptr_in_typemap@*/ %typemap(in,fragment="SWIG_" "AsPtr" "_" {CString}) CString { CString *ptr = (CString *)0; int res = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2($input, &ptr); if (!SWIG_IsOK(res) || !ptr) { SWIG_exception_fail(SWIG_ArgError((ptr ? res : SWIG_TypeError)), "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } $1 = *ptr; if (SWIG_IsNewObj(res)) delete ptr; } %typemap(freearg) CString ""; %typemap(in,fragment="SWIG_" "AsPtr" "_" {CString}) const CString & (int res = SWIG_OLDOBJ) { CString *ptr = (CString *)0; res = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2($input, &ptr); if (!SWIG_IsOK(res)) { SWIG_exception_fail(SWIG_ArgError(res), "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } if (!ptr) { SWIG_exception_fail(SWIG_ValueError, "invalid null reference " "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } $1 = ptr; } %typemap(freearg,noblock=1) const CString & { if (SWIG_IsNewObj(res$argnum)) delete $1; } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,53,%ptr_varin_typemap@*/ %typemap(varin,fragment="SWIG_" "AsPtr" "_" {CString}) CString { CString *ptr = (CString *)0; int res = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2($input, &ptr); if (!SWIG_IsOK(res) || !ptr) { SWIG_exception_fail(SWIG_ArgError((ptr ? res : SWIG_TypeError)), "in variable '""$name""' of type '""$type""'"); } $1 = *ptr; if (SWIG_IsNewObj(res)) delete ptr; } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,68,%ptr_directorout_typemap@*/ %typemap(directorargout,noblock=1,fragment="SWIG_" "AsPtr" "_" {CString}) CString *DIRECTOROUT ($*ltype temp, int swig_ores) { CString *swig_optr = 0; swig_ores = $result ? SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2($result, &swig_optr) : 0; if (!SWIG_IsOK(swig_ores) || !swig_optr) { Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError((swig_optr ? swig_ores : SWIG_TypeError))), "in output value of type '""$type""'"); } temp = *swig_optr; $1 = &temp; if (SWIG_IsNewObj(swig_ores)) delete swig_optr; } %typemap(directorout,noblock=1,fragment="SWIG_" "AsPtr" "_" {CString}) CString { CString *swig_optr = 0; int swig_ores = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2($input, &swig_optr); if (!SWIG_IsOK(swig_ores) || !swig_optr) { Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError((swig_optr ? swig_ores : SWIG_TypeError))), "in output value of type '""$type""'"); } $result = *swig_optr; if (SWIG_IsNewObj(swig_ores)) delete swig_optr; } %typemap(directorout,noblock=1,fragment="SWIG_" "AsPtr" "_" {CString},warning= "473:Returning a pointer or reference in a director method is not recommended." ) CString* { CString *swig_optr = 0; int swig_ores = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2($input, &swig_optr); if (!SWIG_IsOK(swig_ores)) { Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError(swig_ores)), "in output value of type '""$type""'"); } $result = swig_optr; if (SWIG_IsNewObj(swig_ores)) { swig_acquire_ownership(swig_optr); } } %typemap(directorfree,noblock=1) CString* { if (director) { director->swig_release_ownership(SWIG_as_voidptr($input)); } } %typemap(directorout,noblock=1,fragment="SWIG_" "AsPtr" "_" {CString},warning= "473:Returning a pointer or reference in a director method is not recommended." ) CString& { CString *swig_optr = 0; int swig_ores = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2($input, &swig_optr); if (!SWIG_IsOK(swig_ores)) { Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError(swig_ores)), "in output value of type '""$type""'"); } else { if (!swig_optr) { Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ValueError), "invalid null reference " "in output value of type '""$type""'"); } } $result = swig_optr; if (SWIG_IsNewObj(swig_ores)) { swig_acquire_ownership(swig_optr); } } %typemap(directorfree,noblock=1) CString& { if (director) { director->swig_release_ownership(SWIG_as_voidptr($input)); } } %typemap(directorout,fragment="SWIG_" "AsPtr" "_" {CString}) CString &DIRECTOROUT = CString /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,143,%ptr_typecheck_typemap@*/ %typemap(typecheck,noblock=1,precedence=135,fragment="SWIG_" "AsPtr" "_" {CString}) CString * { int res = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2($input, (CString**)(0)); $1 = SWIG_CheckState(res); } %typemap(typecheck,noblock=1,precedence=135,fragment="SWIG_" "AsPtr" "_" {CString}) CString, const CString& { int res = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2($input, (CString**)(0)); $1 = SWIG_CheckState(res); } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,254,%ptr_input_typemap@*/ /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,117,%_ptr_input_typemap@*/ %typemap(in,noblock=1,fragment="SWIG_" "AsPtr" "_" {CString}) CString *INPUT(int res = 0) { res = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2($input, &$1); if (!SWIG_IsOK(res)) { SWIG_exception_fail(SWIG_ArgError(res), "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } res = SWIG_AddTmpMask(res); } %typemap(in,noblock=1,fragment="SWIG_" "AsPtr" "_" {CString}) CString &INPUT(int res = 0) { res = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2($input, &$1); if (!SWIG_IsOK(res)) { SWIG_exception_fail(SWIG_ArgError(res), "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } if (!$1) { SWIG_exception_fail(SWIG_ValueError, "invalid null reference " "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } res = SWIG_AddTmpMask(res); } %typemap(freearg,noblock=1,match="in") CString *INPUT, CString &INPUT { if (SWIG_IsNewObj(res$argnum)) delete $1; } %typemap(typecheck,noblock=1,precedence=135,fragment="SWIG_" "AsPtr" "_" {CString}) CString *INPUT, CString &INPUT { int res = SWIG_AsPtr_CString SWIG_PERL_CALL_ARGS_2($input, (CString**)0); $1 = SWIG_CheckState(res); } /*@SWIG@*/ /*@SWIG@*/; /*@SWIG@*/ /*@SWIG:/swig/3.0.8/typemaps/valtypes.swg,183,%typemaps_from@*/ /*@SWIG:/swig/3.0.8/typemaps/valtypes.swg,55,%value_out_typemap@*/ %typemap(out,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString, const CString { $result = SWIG_From_CString SWIG_PERL_CALL_ARGS_1(static_cast< CString >($1)); argvi++ ; } %typemap(out,noblock=1,fragment="SWIG_" "From" "_" {CString}) const CString& { $result = SWIG_From_CString SWIG_PERL_CALL_ARGS_1(static_cast< CString >(*$1)); argvi++ ; } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/valtypes.swg,79,%value_varout_typemap@*/ %typemap(varout,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString, const CString& { sv_setsv($result,SWIG_From_CString SWIG_PERL_CALL_ARGS_1(static_cast< CString >($1))) ; } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/valtypes.swg,87,%value_constcode_typemap@*/ %typemap(constcode,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString { /*@SWIG:/swig/3.0.8/perl5/perltypemaps.swg,65,%set_constant@*/ do { SV *sv = get_sv((char*) SWIG_prefix "$symname", TRUE | 0x2 | GV_ADDMULTI); sv_setsv(sv, SWIG_From_CString SWIG_PERL_CALL_ARGS_1(static_cast< CString >($value))); SvREADONLY_on(sv); } while(0) /*@SWIG@*/; } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/valtypes.swg,98,%value_directorin_typemap@*/ %typemap(directorin,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString *DIRECTORIN { $input = SWIG_From_CString SWIG_PERL_CALL_ARGS_1(static_cast< CString >(*$1)); } %typemap(directorin,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString, const CString& { $input = SWIG_From_CString SWIG_PERL_CALL_ARGS_1(static_cast< CString >($1)); } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/valtypes.swg,153,%value_throws_typemap@*/ %typemap(throws,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString { sv_setsv(get_sv("@", GV_ADD), SWIG_From_CString SWIG_PERL_CALL_ARGS_1(static_cast< CString >($1))); SWIG_fail ; } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,258,%value_output_typemap@*/ /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,175,%_value_output_typemap@*/ %typemap(in,numinputs=0,noblock=1) CString *OUTPUT ($*1_ltype temp, int res = SWIG_TMPOBJ), CString &OUTPUT ($*1_ltype temp, int res = SWIG_TMPOBJ) { $1 = &temp; } %typemap(argout,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString *OUTPUT, CString &OUTPUT { if (SWIG_IsTmpObj(res$argnum)) { if (argvi >= items) EXTEND(sp,1); $result = SWIG_From_CString SWIG_PERL_CALL_ARGS_1((*$1)); argvi++ ; } else { int new_flags = SWIG_IsNewObj(res$argnum) ? (SWIG_POINTER_OWN | $shadow) : $shadow; if (argvi >= items) EXTEND(sp,1); $result = SWIG_NewPointerObj((void*)($1), $1_descriptor, new_flags); argvi++ ; } } /*@SWIG@*/ /*@SWIG@*/; /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,258,%value_output_typemap@*/ /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,175,%_value_output_typemap@*/ %typemap(in,numinputs=0,noblock=1) CString *OUTPUT ($*1_ltype temp, int res = SWIG_TMPOBJ), CString &OUTPUT ($*1_ltype temp, int res = SWIG_TMPOBJ) { $1 = &temp; } %typemap(argout,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString *OUTPUT, CString &OUTPUT { if (SWIG_IsTmpObj(res$argnum)) { if (argvi >= items) EXTEND(sp,1); $result = SWIG_From_CString SWIG_PERL_CALL_ARGS_1((*$1)); argvi++ ; } else { int new_flags = SWIG_IsNewObj(res$argnum) ? (SWIG_POINTER_OWN | $shadow) : $shadow; if (argvi >= items) EXTEND(sp,1); $result = SWIG_NewPointerObj((void*)($1), $1_descriptor, new_flags); argvi++ ; } } /*@SWIG@*/ /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,240,%_ptr_inout_typemap@*/ /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,230,%_value_inout_typemap@*/ %typemap(in) CString *INOUT = CString *INPUT; %typemap(in) CString &INOUT = CString &INPUT; %typemap(typecheck) CString *INOUT = CString *INPUT; %typemap(typecheck) CString &INOUT = CString &INPUT; %typemap(argout) CString *INOUT = CString *OUTPUT; %typemap(argout) CString &INOUT = CString &OUTPUT; /*@SWIG@*/ %typemap(typecheck) CString *INOUT = CString *INPUT; %typemap(typecheck) CString &INOUT = CString &INPUT; %typemap(freearg) CString *INOUT = CString *INPUT; %typemap(freearg) CString &INOUT = CString &INPUT; /*@SWIG@*/; /*@SWIG@*/ ; /*@SWIG@*/; /*@SWIG@*/; znc-1.9.1/modules/modperl/codegen.pl0000755000175000017500000001055614641222733017631 0ustar somebodysomebody#!/usr/bin/env perl # # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # use strict; use warnings; use IO::File; use feature 'switch', 'say'; open my $in, $ARGV[0] or die; open my $out, ">", $ARGV[1] or die; print $out <<'EOF'; /* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*************************************************************************** * This file is generated automatically using codegen.pl from functions.in * * Don't change it manually. * ***************************************************************************/ namespace { template struct SvToPtr { CString m_sType; SvToPtr(const CString& sType) { m_sType = sType; } T* operator()(SV* sv) { T* result; int res = SWIG_ConvertPtr(sv, (void**)&result, SWIG_TypeQuery(m_sType.c_str()), 0); if (SWIG_IsOK(res)) { return result; } return nullptr; } }; CModule::EModRet SvToEModRet(SV* sv) { return static_cast(SvUV(sv)); } } #define PSTART_IDF(Func) PSTART; XPUSHs(GetPerlObj()); PUSH_STR(#Func) #define PCALLMOD(Error, Success) PCALL("ZNC::Core::CallModFunc"); if (SvTRUE(ERRSV)) { DEBUG("Perl hook died with: " + PString(ERRSV)); Error; } else if (SvIV(ST(0))) { Success; } else { Error; } PEND EOF while (<$in>) { my ($type, $name, $args, $default) = /(\S+)\s+(\w+)\((.*)\)(?:=(\w+))?/ or next; $type =~ s/(EModRet)/CModule::$1/; $type =~ s/^\s*(.*?)\s*$/$1/; my @arg = map { my ($t, $v) = /^\s*(.*\W)\s*(\w+)\s*$/; $t =~ s/^\s*(.*?)\s*$/$1/; my ($tt, $tm) = $t =~ /^(.*?)\s*?(\*|&)?$/; {type=>$t, var=>$v, base=>$tt, mod=>$tm//''} } split /,/, $args; unless (defined $default) { $default = "CModule::$name(" . (join ', ', map { $_->{var} } @arg) . ")"; } say $out "$type CPerlModule::$name($args) {"; say $out "\t$type result{};" if $type ne 'void'; say $out "\tPSTART_IDF($name);"; for my $a (@arg) { given ($a->{type}) { when (/(vector\s*<\s*(.*)\*\s*>)/) { my ($vec, $sub) = ($1, $2); my $dot = '.'; $dot = '->' if $a->{mod} eq '*'; say $out "\tfor (${vec}::const_iterator i = $a->{var}${dot}begin(); i != $a->{var}${dot}end(); ++i) {"; #atm sub is always "...*" so... say $out "\t\tPUSH_PTR($sub*, *i);"; say $out "\t}"; } when (/CString/) { say $out "\tPUSH_STR($a->{var});" } when (/\*$/) { my $t=$a->{type}; $t=~s/^const//; say $out "\tPUSH_PTR($t, $a->{var});" } when (/&$/) { my $b=$a->{base}; $b=~s/^const//; say $out "\tPUSH_PTR($b*, &$a->{var});" } when (/unsigned/){ say $out "\tmXPUSHu($a->{var});" } default { say $out "\tmXPUSHi($a->{var});" } } } say $out "\tPCALLMOD("; print $out "\t\t"; print $out "result = " if $type ne 'void'; say $out "$default;,"; my $x = 1; say $out "\t\tresult = ".sv($type)."(ST(1));" if $type ne 'void'; for my $a (@arg) { $x++; say $out "\t\t$a->{var} = PString(ST($x));" if $a->{base} eq 'CString' && $a->{mod} eq '&'; } say $out "\t);"; say $out "\treturn result;" if $type ne 'void'; say $out "}\n"; } sub sv { my $type = shift; given ($type) { when (/^(.*)\*$/) { return "SvToPtr<$1>(\"$type\")" } when ('CString') { return 'PString' } when ('CModule::EModRet') { return 'SvToEModRet' } when (/unsigned/) { return 'SvUV' } default { return 'SvIV' } } } znc-1.9.1/modules/modperl/functions.in0000644000175000017500000001277014641222733020225 0ustar somebodysomebodybool OnBoot() bool WebRequiresLogin() bool WebRequiresAdmin() CString GetWebMenuTitle() bool OnWebPreRequest(CWebSock& WebSock, const CString& sPageName) bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) bool ValidateWebRequestCSRFCheck(CWebSock& WebSock, const CString& sPageName) VWebSubPages* _GetSubPages()=nullptr void OnPreRehash() void OnPostRehash() void OnIRCDisconnected() void OnIRCConnected() EModRet OnIRCConnecting(CIRCSock *pIRCSock) void OnIRCConnectionError(CIRCSock *pIRCSock) EModRet OnIRCRegistration(CString& sPass, CString& sNick, CString& sIdent, CString& sRealName) EModRet OnBroadcast(CString& sMessage) void OnChanPermission2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, unsigned char uMode, bool bAdded, bool bNoChange) void OnOp2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) void OnDeop2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) void OnVoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) void OnDevoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) void OnMode2(const CNick* pOpNick, CChan& Channel, char uMode, const CString& sArg, bool bAdded, bool bNoChange) void OnRawMode2(const CNick* pOpNick, CChan& Channel, const CString& sModes, const CString& sArgs) EModRet OnRaw(CString& sLine) EModRet OnStatusCommand(CString& sCommand) void OnModCommand(const CString& sCommand) void OnModNotice(const CString& sMessage) void OnModCTCP(const CString& sMessage) void OnQuit(const CNick& Nick, const CString& sMessage, const vector& vChans) void OnNick(const CNick& Nick, const CString& sNewNick, const vector& vChans) void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) EModRet OnJoining(CChan& Channel) void OnJoin(const CNick& Nick, CChan& Channel) void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) EModRet OnInvite(const CNick& Nick, const CString& sChan) EModRet OnChanBufferStarting(CChan& Chan, CClient& Client) EModRet OnChanBufferEnding(CChan& Chan, CClient& Client) EModRet OnChanBufferPlayLine(CChan& Chan, CClient& Client, CString& sLine) EModRet OnPrivBufferPlayLine(CClient& Client, CString& sLine) void OnClientLogin() void OnClientDisconnect() EModRet OnUserRaw(CString& sLine) EModRet OnUserCTCPReply(CString& sTarget, CString& sMessage) EModRet OnUserCTCP(CString& sTarget, CString& sMessage) EModRet OnUserAction(CString& sTarget, CString& sMessage) EModRet OnUserMsg(CString& sTarget, CString& sMessage) EModRet OnUserNotice(CString& sTarget, CString& sMessage) EModRet OnUserJoin(CString& sChannel, CString& sKey) EModRet OnUserPart(CString& sChannel, CString& sMessage) EModRet OnUserTopic(CString& sChannel, CString& sTopic) EModRet OnUserTopicRequest(CString& sChannel) EModRet OnUserQuit(CString& sMessage) EModRet OnCTCPReply(CNick& Nick, CString& sMessage) EModRet OnPrivCTCP(CNick& Nick, CString& sMessage) EModRet OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage) EModRet OnPrivAction(CNick& Nick, CString& sMessage) EModRet OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) EModRet OnPrivMsg(CNick& Nick, CString& sMessage) EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) EModRet OnPrivNotice(CNick& Nick, CString& sMessage) EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) bool OnServerCapAvailable(const CString& sCap) bool OnServerCap302Available(const CString& sCap, const CString& sValue) void OnServerCapResult(const CString& sCap, bool bSuccess) void OnClientAttached() void OnClientDetached() EModRet OnTimerAutoJoin(CChan& Channel) bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet) EModRet OnDeleteNetwork(CIRCNetwork& Network) EModRet OnSendToClient(CString& sLine, CClient& Client) EModRet OnSendToIRC(CString& sLine) EModRet OnRawMessage(CMessage& Message) EModRet OnNumericMessage(CNumericMessage& Message) void OnQuitMessage(CQuitMessage& Message, const std::vector& vChans) void OnNickMessage(CNickMessage& Message, const std::vector& vChans) void OnKickMessage(CKickMessage& Message) void OnJoinMessage(CJoinMessage& Message) void OnPartMessage(CPartMessage& Message) EModRet OnChanBufferPlayMessage(CMessage& Message) EModRet OnPrivBufferPlayMessage(CMessage& Message) EModRet OnUserRawMessage(CMessage& Message) EModRet OnUserCTCPReplyMessage(CCTCPMessage& Message) EModRet OnUserCTCPMessage(CCTCPMessage& Message) EModRet OnUserActionMessage(CActionMessage& Message) EModRet OnUserTextMessage(CTextMessage& Message) EModRet OnUserNoticeMessage(CNoticeMessage& Message) EModRet OnUserJoinMessage(CJoinMessage& Message) EModRet OnUserPartMessage(CPartMessage& Message) EModRet OnUserTopicMessage(CTopicMessage& Message) EModRet OnUserQuitMessage(CQuitMessage& Message) EModRet OnCTCPReplyMessage(CCTCPMessage& Message) EModRet OnPrivCTCPMessage(CCTCPMessage& Message) EModRet OnChanCTCPMessage(CCTCPMessage& Message) EModRet OnPrivActionMessage(CActionMessage& Message) EModRet OnChanActionMessage(CActionMessage& Message) EModRet OnPrivTextMessage(CTextMessage& Message) EModRet OnChanTextMessage(CTextMessage& Message) EModRet OnPrivNoticeMessage(CNoticeMessage& Message) EModRet OnChanNoticeMessage(CNoticeMessage& Message) EModRet OnTopicMessage(CTopicMessage& Message) EModRet OnSendToClientMessage(CMessage& Message) EModRet OnSendToIRCMessage(CMessage& Message) znc-1.9.1/modules/modperl/include/0000755000175000017500000000000014641222733017301 5ustar somebodysomebodyznc-1.9.1/modules/modperl/include/perlfragments.swg0000644000175000017500000000065114641222733022676 0ustar somebodysomebody// Make perl strings to be UTF-8, they are already UTF-8 in ZNC core %fragment("SWIG_FromCharPtrAndSize","header") { SWIGINTERNINLINE SV * SWIG_FromCharPtrAndSize(const char* carray, size_t size) { SV *obj = sv_newmortal(); if (carray) { sv_setpvn(obj, carray, size); } else { sv_setsv(obj, &PL_sv_undef); } SvUTF8_on(obj); return obj; } } znc-1.9.1/modules/modperl/modperl.i0000644000175000017500000001651114641222733017476 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ %module ZNC %{ #ifdef Copy # undef Copy #endif #ifdef Pause # undef Pause #endif #ifdef seed # undef seed #endif #include #include "znc/Utils.h" #include "znc/Threads.h" #include "znc/Config.h" #include "znc/Socket.h" #include "znc/Modules.h" #include "znc/Nick.h" #include "znc/Chan.h" #include "znc/User.h" #include "znc/IRCNetwork.h" #include "znc/Client.h" #include "znc/IRCSock.h" #include "znc/Listener.h" #include "znc/HTTPSock.h" #include "znc/Template.h" #include "znc/WebModules.h" #include "znc/znc.h" #include "znc/Server.h" #include "znc/ZNCString.h" #include "znc/FileUtils.h" #include "znc/ZNCDebug.h" #include "znc/ExecSock.h" #include "znc/Buffer.h" #include "modperl/module.h" #define stat struct stat %} %apply long { off_t }; %apply long { uint16_t }; %apply long { uint32_t }; %apply long { uint64_t }; %begin %{ #include "znc/zncconfig.h" %} %include %include %include %include namespace std { template class set { public: set(); set(const set&); }; } %include "modperl/CString.i" %typemap(out) VCString { EXTEND(sp, $1.size()); for (int i = 0; i < $1.size(); ++i) { SV* x = newSV(0); SwigSvFromString(x, $1[i]); $result = sv_2mortal(x); argvi++; } } %typemap(out) const VCString& { EXTEND(sp, $1->size()); for (int i = 0; i < $1->size(); ++i) { SV* x = newSV(0); SwigSvFromString(x, (*$1)[i]); $result = sv_2mortal(x); argvi++; } } %template(VIRCNetworks) std::vector; %template(VChannels) std::vector; %template(VCString) std::vector; typedef std::vector VCString; /*%template(MNicks) std::map;*/ /*%template(SModInfo) std::set; %template(SCString) std::set; typedef std::set SCString;*/ %template(PerlMCString) std::map; class MCString : public std::map {}; /*%template(PerlModulesVector) std::vector;*/ %template(VListeners) std::vector; %template(BufLines) std::deque; %template(VVString) std::vector; #define REGISTER_ZNC_MESSAGE(M) \ %template(As_ ## M) CMessage::As; %typemap(out) std::map { HV* myhv = newHV(); for (std::map::const_iterator i = $1.begin(); i != $1.end(); ++i) { SV* val = SWIG_NewInstanceObj(const_cast(&i->second), SWIG_TypeQuery("CNick*"), SWIG_SHADOW); SvREFCNT_inc(val);// it was created mortal hv_store(myhv, i->first.c_str(), i->first.length(), val, 0); } $result = newRV_noinc((SV*)myhv); sv_2mortal($result); argvi++; } #define u_short unsigned short #define u_int unsigned int #include "znc/zncconfig.h" #include "znc/ZNCString.h" %include "znc/defines.h" %include "znc/Translation.h" %include "znc/Utils.h" %include "znc/Threads.h" %include "znc/Config.h" %include "znc/Csocket.h" %template(ZNCSocketManager) TSocketManager; %include "znc/Socket.h" %include "znc/FileUtils.h" %include "znc/Message.h" %include "znc/Modules.h" %include "znc/Nick.h" %include "znc/Chan.h" %include "znc/User.h" %include "znc/IRCNetwork.h" %include "znc/Client.h" %include "znc/IRCSock.h" %include "znc/Listener.h" %include "znc/HTTPSock.h" %include "znc/Template.h" %include "znc/WebModules.h" %include "znc/znc.h" %include "znc/Server.h" %include "znc/ZNCDebug.h" %include "znc/ExecSock.h" %include "znc/Buffer.h" %include "modperl/module.h" %inline %{ class String : public CString { public: String() {} String(const CString& s) : CString(s) {} String(double d, int prec=2): CString(d, prec) {} String(float f, int prec=2) : CString(f, prec) {} String(int i) : CString(i) {} String(unsigned int i) : CString(i) {} String(long int i) : CString(i) {} String(unsigned long int i) : CString(i) {} String(char c) : CString(c) {} String(unsigned char c) : CString(c) {} String(short int i) : CString(i) {} String(unsigned short int i): CString(i) {} String(bool b) : CString(b) {} CString GetPerlStr() { return *this; } }; %} %extend CModule { VCString GetNVKeys() { VCString result; for (auto i = $self->BeginNV(); i != $self->EndNV(); ++i) { result.push_back(i->first); } return result; } bool ExistsNV(const CString& sName) { return $self->EndNV() != $self->FindNV(sName); } void AddServerDependentCapability(const CString& sName, SV* serverCb, SV* clientCb) { $self->AddServerDependentCapability(sName, std::make_unique(serverCb, clientCb)); } } %extend CModules { void push_back(CModule* p) { $self->push_back(p); } bool removeModule(CModule* p) { for (CModules::iterator i = $self->begin(); $self->end() != i; ++i) { if (*i == p) { $self->erase(i); return true; } } return false; } } %extend CUser { std::vector GetNetworks_() { return $self->GetNetworks(); } } %extend CIRCNetwork { std::vector GetChans_() { return $self->GetChans(); } } %extend CChan { std::map GetNicks_() { return $self->GetNicks(); } } /* Web */ %template(StrPair) std::pair; %template(VPair) std::vector >; typedef std::vector > VPair; %template(VWebSubPages) std::vector; %inline %{ void _VPair_Add2Str(VPair* self, const CString& a, const CString& b) { self->push_back(std::make_pair(a, b)); } %} %extend CTemplate { void set(const CString& key, const CString& value) { (*$self)[key] = value; } } %inline %{ TWebSubPage _CreateWebSubPage(const CString& sName, const CString& sTitle, const VPair& vParams, unsigned int uFlags) { return std::make_shared(sName, sTitle, vParams, uFlags); } %} %perlcode %{ package ZNC; sub CreateWebSubPage { my ($name, %arg) = @_; my $params = $arg{params}//{}; my $vpair = ZNC::VPair->new; while (my ($key, $val) = each %$params) { ZNC::_VPair_Add2Str($vpair, $key, $val); } my $flags = 0; $flags |= $ZNC::CWebSubPage::F_ADMIN if $arg{admin}//0; return _CreateWebSubPage($name, $arg{title}//'', $vpair, $flags); } %} %inline %{ void _CleanupStash(const CString& sModname) { hv_clear(gv_stashpv(sModname.c_str(), 0)); } %} %perlcode %{ package ZNC; *CONTINUE = *ZNC::CModule::CONTINUE; *HALT = *ZNC::CModule::HALT; *HALTMODS = *ZNC::CModule::HALTMODS; *HALTCORE = *ZNC::CModule::HALTCORE; *UNLOAD = *ZNC::CModule::UNLOAD; package ZNC::CIRCNetwork; *GetChans = *GetChans_; package ZNC::CUser; *GetNetworks = *GetNetworks_; package ZNC::CChan; sub _GetNicks_ { my $result = GetNicks_(@_); return %$result; } *GetNicks = *_GetNicks_; %} /* vim: set filetype=cpp: */ znc-1.9.1/modules/modperl/module.h0000644000175000017500000002632314641222733017322 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include class ZNC_EXPORT_LIB_EXPORT CPerlModule : public CModule { SV* m_perlObj; VWebSubPages* _GetSubPages(); public: CPerlModule(CUser* pUser, CIRCNetwork* pNetwork, const CString& sModName, const CString& sDataPath, CModInfo::EModuleType eType, SV* perlObj) : CModule(nullptr, pUser, pNetwork, sModName, sDataPath, eType) { m_perlObj = newSVsv(perlObj); } SV* GetPerlObj() { return sv_2mortal(newSVsv(m_perlObj)); } bool OnBoot() override; bool WebRequiresLogin() override; bool WebRequiresAdmin() override; CString GetWebMenuTitle() override; bool OnWebPreRequest(CWebSock& WebSock, const CString& sPageName) override; bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) override; bool ValidateWebRequestCSRFCheck(CWebSock& WebSock, const CString& sPageName) override; VWebSubPages& GetSubPages() override; void OnPreRehash() override; void OnPostRehash() override; void OnIRCDisconnected() override; void OnIRCConnected() override; EModRet OnIRCConnecting(CIRCSock* pIRCSock) override; void OnIRCConnectionError(CIRCSock* pIRCSock) override; EModRet OnIRCRegistration(CString& sPass, CString& sNick, CString& sIdent, CString& sRealName) override; EModRet OnBroadcast(CString& sMessage) override; void OnChanPermission2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, unsigned char uMode, bool bAdded, bool bNoChange) override; void OnOp2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) override; void OnDeop2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) override; void OnVoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) override; void OnDevoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) override; void OnMode2(const CNick* pOpNick, CChan& Channel, char uMode, const CString& sArg, bool bAdded, bool bNoChange) override; void OnRawMode2(const CNick* pOpNick, CChan& Channel, const CString& sModes, const CString& sArgs) override; EModRet OnRaw(CString& sLine) override; EModRet OnStatusCommand(CString& sCommand) override; void OnModCommand(const CString& sCommand) override; void OnModNotice(const CString& sMessage) override; void OnModCTCP(const CString& sMessage) override; void OnQuit(const CNick& Nick, const CString& sMessage, const std::vector& vChans) override; void OnNick(const CNick& Nick, const CString& sNewNick, const std::vector& vChans) override; void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) override; EModRet OnJoining(CChan& Channel) override; void OnJoin(const CNick& Nick, CChan& Channel) override; void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) override; EModRet OnInvite(const CNick& Nick, const CString& sChan) override; EModRet OnChanBufferStarting(CChan& Chan, CClient& Client) override; EModRet OnChanBufferEnding(CChan& Chan, CClient& Client) override; EModRet OnChanBufferPlayLine(CChan& Chan, CClient& Client, CString& sLine) override; EModRet OnPrivBufferPlayLine(CClient& Client, CString& sLine) override; void OnClientLogin() override; void OnClientDisconnect() override; EModRet OnUserRaw(CString& sLine) override; EModRet OnUserCTCPReply(CString& sTarget, CString& sMessage) override; EModRet OnUserCTCP(CString& sTarget, CString& sMessage) override; EModRet OnUserAction(CString& sTarget, CString& sMessage) override; EModRet OnUserMsg(CString& sTarget, CString& sMessage) override; EModRet OnUserNotice(CString& sTarget, CString& sMessage) override; EModRet OnUserJoin(CString& sChannel, CString& sKey) override; EModRet OnUserPart(CString& sChannel, CString& sMessage) override; EModRet OnUserTopic(CString& sChannel, CString& sTopic) override; EModRet OnUserQuit(CString& sMessage) override; EModRet OnUserTopicRequest(CString& sChannel) override; EModRet OnCTCPReply(CNick& Nick, CString& sMessage) override; EModRet OnPrivCTCP(CNick& Nick, CString& sMessage) override; EModRet OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage) override; EModRet OnPrivAction(CNick& Nick, CString& sMessage) override; EModRet OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) override; EModRet OnPrivMsg(CNick& Nick, CString& sMessage) override; EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) override; EModRet OnPrivNotice(CNick& Nick, CString& sMessage) override; EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) override; EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) override; bool OnServerCapAvailable(const CString& sCap) override; bool OnServerCap302Available(const CString& sCap, const CString& sValue) override; void OnServerCapResult(const CString& sCap, bool bSuccess) override; void OnClientAttached() override; void OnClientDetached() override; EModRet OnTimerAutoJoin(CChan& Channel) override; bool OnEmbeddedWebRequest(CWebSock&, const CString&, CTemplate&) override; EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet) override; EModRet OnDeleteNetwork(CIRCNetwork& Network) override; EModRet OnSendToClient(CString& sLine, CClient& Client) override; EModRet OnSendToIRC(CString& sLine) override; EModRet OnRawMessage(CMessage& Message) override; EModRet OnNumericMessage(CNumericMessage& Message) override; void OnQuitMessage(CQuitMessage& Message, const std::vector& vChans) override; void OnNickMessage(CNickMessage& Message, const std::vector& vChans) override; void OnKickMessage(CKickMessage& Message) override; void OnJoinMessage(CJoinMessage& Message) override; void OnPartMessage(CPartMessage& Message) override; EModRet OnChanBufferPlayMessage(CMessage& Message) override; EModRet OnPrivBufferPlayMessage(CMessage& Message) override; EModRet OnUserRawMessage(CMessage& Message) override; EModRet OnUserCTCPReplyMessage(CCTCPMessage& Message) override; EModRet OnUserCTCPMessage(CCTCPMessage& Message) override; EModRet OnUserActionMessage(CActionMessage& Message) override; EModRet OnUserTextMessage(CTextMessage& Message) override; EModRet OnUserNoticeMessage(CNoticeMessage& Message) override; EModRet OnUserJoinMessage(CJoinMessage& Message) override; EModRet OnUserPartMessage(CPartMessage& Message) override; EModRet OnUserTopicMessage(CTopicMessage& Message) override; EModRet OnUserQuitMessage(CQuitMessage& Message) override; EModRet OnCTCPReplyMessage(CCTCPMessage& Message) override; EModRet OnPrivCTCPMessage(CCTCPMessage& Message) override; EModRet OnChanCTCPMessage(CCTCPMessage& Message) override; EModRet OnPrivActionMessage(CActionMessage& Message) override; EModRet OnChanActionMessage(CActionMessage& Message) override; EModRet OnPrivTextMessage(CTextMessage& Message) override; EModRet OnChanTextMessage(CTextMessage& Message) override; EModRet OnPrivNoticeMessage(CNoticeMessage& Message) override; EModRet OnChanNoticeMessage(CNoticeMessage& Message) override; EModRet OnTopicMessage(CTopicMessage& Message) override; EModRet OnSendToClientMessage(CMessage& Message) override; EModRet OnSendToIRCMessage(CMessage& Message) override; }; static inline CPerlModule* AsPerlModule(CModule* p) { return dynamic_cast(p); } enum ELoadPerlMod { Perl_NotFound, Perl_Loaded, Perl_LoadError, }; class ZNC_EXPORT_LIB_EXPORT CPerlTimer : public CTimer { SV* m_perlObj; public: CPerlTimer(CPerlModule* pModule, unsigned int uInterval, unsigned int uCycles, const CString& sLabel, const CString& sDescription, SV* perlObj) : CTimer(pModule, uInterval, uCycles, sLabel, sDescription), m_perlObj(newSVsv(perlObj)) { pModule->AddTimer(this); } void RunJob() override; SV* GetPerlObj() { return sv_2mortal(newSVsv(m_perlObj)); } ~CPerlTimer(); }; inline CPerlTimer* CreatePerlTimer(CPerlModule* pModule, unsigned int uInterval, unsigned int uCycles, const CString& sLabel, const CString& sDescription, SV* perlObj) { return new CPerlTimer(pModule, uInterval, uCycles, sLabel, sDescription, perlObj); } class ZNC_EXPORT_LIB_EXPORT CPerlSocket : public CSocket { SV* m_perlObj; public: CPerlSocket(CPerlModule* pModule, SV* perlObj) : CSocket(pModule), m_perlObj(newSVsv(perlObj)) {} SV* GetPerlObj() { return sv_2mortal(newSVsv(m_perlObj)); } ~CPerlSocket(); void Connected() override; void Disconnected() override; void Timeout() override; void ConnectionRefused() override; void ReadData(const char* data, size_t len) override; void ReadLine(const CString& sLine) override; Csock* GetSockObj(const CString& sHost, unsigned short uPort) override; }; inline CPerlSocket* CreatePerlSocket(CPerlModule* pModule, SV* perlObj) { return new CPerlSocket(pModule, perlObj); } class ZNC_EXPORT_LIB_EXPORT CPerlCapability : public CCapability { public: CPerlCapability(SV* serverCb, SV* clientCb) : m_serverCb(newSVsv(serverCb)), m_clientCb(newSVsv(clientCb)) {} ~CPerlCapability(); void OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) override; void OnClientChangedSupport(CClient* pClient, bool bState) override; private: SV* m_serverCb; SV* m_clientCb; }; inline bool HaveIPv6() { #ifdef HAVE_IPV6 return true; #endif return false; } inline bool HaveSSL() { #ifdef HAVE_LIBSSL return true; #endif return false; } inline bool HaveCharset() { #ifdef HAVE_ICU return true; #endif return false; } inline int _GetSOMAXCONN() { return SOMAXCONN; } inline int GetVersionMajor() { return VERSION_MAJOR; } inline int GetVersionMinor() { return VERSION_MINOR; } inline double GetVersion() { return VERSION; } inline CString GetVersionExtra() { return ZNC_VERSION_EXTRA; } znc-1.9.1/modules/modperl/pstring.h0000644000175000017500000000454014641222733017520 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once class PString : public CString { public: enum EType { STRING, INT, UINT, NUM, BOOL }; PString() : CString() { m_eType = STRING; } PString(const char* c) : CString(c) { m_eType = STRING; } PString(const CString& s) : CString(s) { m_eType = STRING; } PString(int i) : CString(i) { m_eType = INT; } PString(u_int i) : CString(i) { m_eType = UINT; } PString(long i) : CString(i) { m_eType = INT; } PString(u_long i) : CString(i) { m_eType = UINT; } PString(long long i) : CString(i) { m_eType = INT; } PString(unsigned long long i) : CString(i) { m_eType = UINT; } PString(double i) : CString(i) { m_eType = NUM; } PString(bool b) : CString((b ? "1" : "0")) { m_eType = BOOL; } PString(SV* sv) { STRLEN len = SvCUR(sv); char* c = SvPV(sv, len); char* c2 = new char[len + 1]; memcpy(c2, c, len); c2[len] = 0; *this = c2; delete[] c2; } virtual ~PString() {} EType GetType() const { return m_eType; } void SetType(EType e) { m_eType = e; } SV* GetSV(bool bMakeMortal = true) const { SV* pSV = nullptr; switch (GetType()) { case NUM: pSV = newSVnv(ToDouble()); break; case INT: pSV = newSViv(ToLongLong()); break; case UINT: case BOOL: pSV = newSVuv(ToULongLong()); break; case STRING: default: pSV = newSVpvn(data(), length()); SvUTF8_on(pSV); break; } if (bMakeMortal) { pSV = sv_2mortal(pSV); } return pSV; } private: EType m_eType; }; znc-1.9.1/modules/modperl/startup.pl0000644000175000017500000004700714641222733017725 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # use 5.010; use strict; use warnings; use ZNC; # From http://search.cpan.org/dist/perl5lib/lib/perl5lib.pm use Config; use lib map { /(.*)/ } split /$Config{path_sep}/ => $ENV{PERL5LIB}; BEGIN { if ($ENV{ZNC_MODPERL_COVERAGE}) { # Can't use DEVEL_COVER_OPTIONS because perl thinks it's tainted: # https://github.com/pjcj/Devel--Cover/issues/187 my ($opts) = $ENV{ZNC_MODPERL_COVERAGE_OPTS} =~ /(.+)/; require Devel::Cover; Devel::Cover->import(split ',', $opts); } } use IO::File; use feature 'switch', 'say'; package ZNC::String; use overload '""' => sub { my $self = shift; my @caller = caller; # When called from internal SWIG subroutines, return default stringification (e.g. 'ZNC::String=SCALAR(0x6210002fe090)') instead of the stored string. # Otherwise, ZNC crashes with use-after-free. # SWIG uses it as key for a hash, so without the number in the returned string, different strings which happen to represent the same value, collide. return $self if $caller[0] eq 'ZNC::String'; return $self->GetPerlStr; }; package ZNC::CMessage; sub As { # e.g. $msg->As('CNumericMessage') my ($self, $name) = @_; my $method = "As_$name"; $self->$method; } package ZNC::Core; my %modrefcount; my @allmods; sub UnloadModule { my ($pmod) = @_; my @newallmods = grep {$pmod != $_} @allmods; if ($#allmods == $#newallmods) { return 0 } @allmods = @newallmods; $pmod->OnShutdown; my $cmod = $pmod->{_cmod}; my $modpath = $cmod->GetModPath; my $modname = $cmod->GetModName; given ($cmod->GetType()) { when ($ZNC::CModInfo::NetworkModule) { $cmod->GetNetwork->GetModules->removeModule($cmod); } when ($ZNC::CModInfo::UserModule) { $cmod->GetUser->GetModules->removeModule($cmod); } when ($ZNC::CModInfo::GlobalModule) { ZNC::CZNC::Get()->GetModules->removeModule($cmod); } } delete $pmod->{_cmod}; delete $pmod->{_nv}; unless (--$modrefcount{$modname}) { say "Unloading $modpath from perl"; ZNC::_CleanupStash($modname); delete $INC{$modpath}; } return 1 # here $cmod is deleted by perl (using DESTROY) } sub UnloadAll { while (@allmods) { UnloadModule($allmods[0]); } } sub IsModule { my $path = shift; my $modname = shift; my $f = IO::File->new($path); grep {/package\s+$modname\s*;/} <$f>; } sub LoadModule { my ($modname, $args, $type, $user, $network) = @_; $modname =~ /^\w+$/ or return ($ZNC::Perl_LoadError, "Module names can only contain letters, numbers and underscores, [$modname] is invalid."); my $container; given ($type) { when ($ZNC::CModInfo::NetworkModule) { $container = $network; } when ($ZNC::CModInfo::UserModule) { $container = $user; } when ($ZNC::CModInfo::GlobalModule) { $container = ZNC::CZNC::Get(); } } return ($ZNC::Perl_LoadError, "Uhm? No container for the module? Wtf?") unless $container; $container = $container->GetModules; return ($ZNC::Perl_LoadError, "Module [$modname] already loaded.") if defined $container->FindModule($modname); my $modpath = ZNC::String->new; my $datapath = ZNC::String->new; ZNC::CModules::FindModPath("$modname.pm", $modpath, $datapath) or return ($ZNC::Perl_NotFound); $modpath = $modpath->GetPerlStr; return ($ZNC::Perl_LoadError, "Incorrect perl module [$modpath]") unless IsModule $modpath, $modname; my $pmod; my @types = eval { require $modpath; $pmod = bless {}, $modname; $pmod->module_types(); }; if ($@) { # modrefcount was 0 before this, otherwise it couldn't die in the previous time. # so can safely remove module from %INC delete $INC{$modpath}; die $@; } return ($ZNC::Perl_LoadError, "Module [$modname] doesn't support the specified type.") unless $type ~~ @types; $modrefcount{$modname}++; $datapath = $datapath->GetPerlStr; $datapath =~ s/\.pm$//; my $cmod = ZNC::CPerlModule->new($user, $network, $modname, $datapath, $type, $pmod); my %nv; tie %nv, 'ZNC::ModuleNV', $cmod; $pmod->{_cmod} = $cmod; $pmod->{_nv} = \%nv; $cmod->SetDescription($pmod->description); $cmod->SetArgs($args); $cmod->SetModPath($modpath); push @allmods, $pmod; $container->push_back($cmod); my $x = ''; my $loaded = 0; eval { $loaded = $pmod->OnLoad($args, $x); }; if ($@) { $x .= ' ' if '' ne $x; $x .= $@; } if (!$loaded) { UnloadModule $pmod; if ($x) { return ($ZNC::Perl_LoadError, "Module [$modname] aborted: $x"); } return ($ZNC::Perl_LoadError, "Module [$modname] aborted."); } if ($x) { return ($ZNC::Perl_Loaded, "[$x] [$modpath]"); } return ($ZNC::Perl_Loaded, "[$modpath]") } sub GetModInfo { my ($modname, $modinfo) = @_; $modname =~ /^\w+$/ or return ($ZNC::Perl_LoadError, "Module names can only contain letters, numbers and underscores, [$modname] is invalid."); my $modpath = ZNC::String->new; my $datapath = ZNC::String->new; ZNC::CModules::FindModPath("$modname.pm", $modpath, $datapath) or return ($ZNC::Perl_NotFound, "Unable to find module [$modname]"); $modpath = $modpath->GetPerlStr; return ($ZNC::Perl_LoadError, "Incorrect perl module.") unless IsModule $modpath, $modname; ModInfoByPath($modpath, $modname, $modinfo); return ($ZNC::Perl_Loaded) } sub ModInfoByPath { my ($modpath, $modname, $modinfo) = @_; die "Incorrect perl module." unless IsModule $modpath, $modname; eval { require $modpath; my $translation = ZNC::CTranslationDomainRefHolder->new("znc-$modname"); my $pmod = bless {}, $modname; my @types = $pmod->module_types(); $modinfo->SetDefaultType($types[0]); $modinfo->SetDescription($pmod->description); $modinfo->SetWikiPage($pmod->wiki_page); $modinfo->SetArgsHelpText($pmod->args_help_text); $modinfo->SetHasArgs($pmod->has_args); $modinfo->SetName($modname); $modinfo->SetPath($modpath); $modinfo->AddType($_) for @types; }; if ($@) { # modrefcount was 0 before this, otherwise it couldn't die in the previous time. # so can safely remove module from %INC delete $INC{$modpath}; die $@; } unless ($modrefcount{$modname}) { ZNC::_CleanupStash($modname); delete $INC{$modpath}; } } sub CallModFunc { my $pmod = shift; my $func = shift; my @arg = @_; my $res = $pmod->$func(@arg); (defined $res, $res//0, @arg) } sub CallTimer { my $timer = shift; $timer->RunJob; } sub CallSocket { my $socket = shift; my $func = shift; say "Calling socket $func"; $socket->$func(@_) } sub RemoveTimer { my $timer = shift; $timer->OnShutdown; } sub RemoveSocket { my $socket = shift; $socket->OnShutdown; } package ZNC::ModuleNV; sub TIEHASH { my $name = shift; my $cmod = shift; bless {cmod=>$cmod, last=>-1}, $name } sub FETCH { my $self = shift; my $key = shift; return $self->{cmod}->GetNV($key) if $self->{cmod}->ExistsNV($key); return undef } sub STORE { my $self = shift; my $key = shift; my $value = shift; $self->{cmod}->SetNV($key, $value); } sub DELETE { my $self = shift; my $key = shift; $self->{cmod}->DelNV($key); } sub CLEAR { my $self = shift; $self->{cmod}->ClearNV; } sub EXISTS { my $self = shift; my $key = shift; $self->{cmod}->ExistsNV($key) } sub FIRSTKEY { my $self = shift; my @keys = $self->{cmod}->GetNVKeys; $self->{last} = 0; return $keys[0]; return undef; } sub NEXTKEY { my $self = shift; my $last = shift; my @keys = $self->{cmod}->GetNVKeys; if ($#keys < $self->{last}) { $self->{last} = -1; return undef } # Probably caller called delete on last key? if ($last eq $keys[$self->{last}]) { $self->{last}++ } if ($#keys < $self->{last}) { $self->{last} = -1; return undef } return $keys[$self->{last}] } sub SCALAR { my $self = shift; my @keys = $self->{cmod}->GetNVKeys; return $#keys + 1 } package ZNC::Module; sub description { "< Placeholder for a description >" } sub wiki_page { '' } sub module_types { $ZNC::CModInfo::NetworkModule } sub args_help_text { '' } sub has_args { 0 } # Default implementations for module hooks. They can be overriden in derived modules. sub OnLoad {1} sub OnBoot {} sub OnShutdown {} sub WebRequiresLogin {} sub WebRequiresAdmin {} sub GetWebMenuTitle {} sub OnWebPreRequest {} sub OnWebRequest {} sub GetSubPages {} sub _GetSubPages { my $self = shift; $self->GetSubPages } sub OnPreRehash {} sub OnPostRehash {} sub OnIRCDisconnected {} sub OnIRCConnected {} sub OnIRCConnecting {} sub OnIRCConnectionError {} sub OnIRCRegistration {} sub OnBroadcast {} sub OnChanPermission {} sub OnOp {} sub OnDeop {} sub OnVoice {} sub OnDevoice {} sub OnMode {} sub OnRawMode {} sub OnRaw {} sub OnStatusCommand {} sub OnModCommand {} sub OnModNotice {} sub OnModCTCP {} sub OnQuit {} sub OnNick {} sub OnKick {} sub OnJoining {} sub OnJoin {} sub OnPart {} sub OnInvite {} sub OnChanBufferStarting {} sub OnChanBufferEnding {} sub OnChanBufferPlayLine {} sub OnPrivBufferPlayLine {} sub OnClientLogin {} sub OnClientDisconnect {} sub OnUserRaw {} sub OnUserCTCPReply {} sub OnUserCTCP {} sub OnUserAction {} sub OnUserMsg {} sub OnUserNotice {} sub OnUserJoin {} sub OnUserPart {} sub OnUserTopic {} sub OnUserTopicRequest {} sub OnUserQuit {} sub OnCTCPReply {} sub OnPrivCTCP {} sub OnChanCTCP {} sub OnPrivAction {} sub OnChanAction {} sub OnPrivMsg {} sub OnChanMsg {} sub OnPrivNotice {} sub OnChanNotice {} sub OnTopic {} sub OnServerCapAvailable {} sub OnServerCap302Available { my ($self, $cap, $value) = @_; $self->OnServerCapAvailable($cap) } sub OnClientAttached {} sub OnClientDetached {} sub OnServerCapResult {} sub OnTimerAutoJoin {} sub OnEmbeddedWebRequest {} sub OnAddNetwork {} sub OnDeleteNetwork {} sub OnSendToClient {} sub OnSendToIRC {} # Deprecated non-Message functions should still work, for now. sub OnRawMessage {} sub OnNumericMessage {} sub OnQuitMessage { my ($self, $msg, @chans) = @_; $self->OnQuit($msg->GetNick, $msg->GetReason, @chans) } sub OnNickMessage { my ($self, $msg, @chans) = @_; $self->OnNick($msg->GetNick, $msg->GetNewNick, @chans) } sub OnKickMessage { my ($self, $msg) = @_; $self->OnKick($msg->GetNick, $msg->GetKickedNick, $msg->GetChan, $msg->GetReason) } sub OnJoinMessage { my ($self, $msg) = @_; $self->OnJoin($msg->GetNick, $msg->GetChan) } sub OnPartMessage { my ($self, $msg) = @_; $self->OnPart($msg->GetNick, $msg->GetChan, $msg->GetReason) } sub OnChanBufferPlayMessage { my ($self, $msg) = @_; my $old = $msg->ToString($ZNC::CMessage::ExcludeTags); my $modified = $old; my ($ret) = $self->OnChanBufferPlayLine($msg->GetChan, $msg->GetClient, $modified); $msg->Parse($modified) if $old ne $modified; return $ret; } sub OnPrivBufferPlayMessage { my ($self, $msg) = @_; my $old = $msg->ToString($ZNC::CMessage::ExcludeTags); my $modified = $old; my ($ret) = $self->OnPrivBufferPlayLine($msg->GetClient, $modified); $msg->Parse($modified) if $old ne $modified; return $ret; } sub OnUserRawMessage {} sub OnUserCTCPReplyMessage { my ($self, $msg) = @_; my $target = $msg->GetTarget; my $text = $msg->GetText; my ($ret) = $self->OnUserCTCPReply($target, $text); $msg->SetTarget($target); $msg->SetText($text); return $ret; } sub OnUserCTCPMessage { my ($self, $msg) = @_; my $target = $msg->GetTarget; my $text = $msg->GetText; my ($ret) = $self->OnUserCTCP($target, $text); $msg->SetTarget($target); $msg->SetText($text); return $ret; } sub OnUserActionMessage { my ($self, $msg) = @_; my $target = $msg->GetTarget; my $text = $msg->GetText; my ($ret) = $self->OnUserAction($target, $text); $msg->SetTarget($target); $msg->SetText($text); return $ret; } sub OnUserTextMessage { my ($self, $msg) = @_; my $target = $msg->GetTarget; my $text = $msg->GetText; my ($ret) = $self->OnUserMsg($target, $text); $msg->SetTarget($target); $msg->SetText($text); return $ret; } sub OnUserNoticeMessage { my ($self, $msg) = @_; my $target = $msg->GetTarget; my $text = $msg->GetText; my ($ret) = $self->OnUserNotice($target, $text); $msg->SetTarget($target); $msg->SetText($text); return $ret; } sub OnUserJoinMessage { my ($self, $msg) = @_; my $chan = $msg->GetTarget; my $key = $msg->GetKey; my ($ret) = $self->OnUserJoin($chan, $key); $msg->SetTarget($chan); $msg->SetKey($key); return $ret; } sub OnUserPartMessage { my ($self, $msg) = @_; my $chan = $msg->GetTarget; my $reason = $msg->GetReason; my ($ret) = $self->OnUserPart($chan, $reason); $msg->SetTarget($chan); $msg->SetReason($reason); return $ret; } sub OnUserTopicMessage { my ($self, $msg) = @_; my $chan = $msg->GetTarget; my $topic = $msg->GetTopic; my ($ret) = $self->OnUserTopic($chan, $topic); $msg->SetTarget($chan); $msg->SetTopic($topic); return $ret; } sub OnUserQuitMessage { my ($self, $msg) = @_; my $reason = $msg->GetReason; my ($ret) = $self->OnUserQuit($reason); $msg->SetReason($reason); return $ret; } sub OnCTCPReplyMessage { my ($self, $msg) = @_; my $text = $msg->GetText; my ($ret) = $self->OnCTCPReply($msg->GetNick, $text); $msg->SetText($text); return $ret; } sub OnPrivCTCPMessage { my ($self, $msg) = @_; my $text = $msg->GetText; my ($ret) = $self->OnPrivCTCP($msg->GetNick, $text); $msg->SetText($text); return $ret; } sub OnChanCTCPMessage { my ($self, $msg) = @_; my $text = $msg->GetText; my ($ret) = $self->OnChanCTCP($msg->GetNick, $msg->GetChan, $text); $msg->SetText($text); return $ret; } sub OnPrivActionMessage { my ($self, $msg) = @_; my $text = $msg->GetText; my ($ret) = $self->OnPrivAction($msg->GetNick, $text); $msg->SetText($text); return $ret; } sub OnChanActionMessage { my ($self, $msg) = @_; my $text = $msg->GetText; my ($ret) = $self->OnChanAction($msg->GetNick, $msg->GetChan, $text); $msg->SetText($text); return $ret; } sub OnPrivTextMessage { my ($self, $msg) = @_; my $text = $msg->GetText; my ($ret) = $self->OnPrivMsg($msg->GetNick, $text); $msg->SetText($text); return $ret; } sub OnChanTextMessage { my ($self, $msg) = @_; my $text = $msg->GetText; my ($ret) = $self->OnChanMsg($msg->GetNick, $msg->GetChan, $text); $msg->SetText($text); return $ret; } sub OnPrivNoticeMessage { my ($self, $msg) = @_; my $text = $msg->GetText; my ($ret) = $self->OnPrivNotice($msg->GetNick, $text); $msg->SetText($text); return $ret; } sub OnChanNoticeMessage { my ($self, $msg) = @_; my $text = $msg->GetText; my ($ret) = $self->OnChanNotice($msg->GetNick, $msg->GetChan, $text); $msg->SetText($text); return $ret; } sub OnTopicMessage { my ($self, $msg) = @_; my $topic = $msg->GetTopic; my ($ret) = $self->OnTopic($msg->GetNick, $msg->GetChan, $topic); $msg->SetTopic($topic); return $ret; } sub OnSendToClientMessage {} sub OnSendToIRCMessage {} # In Perl "undefined" is allowed value, so perl modules may continue using OnMode and not OnMode2 sub OnChanPermission2 { my $self = shift; $self->OnChanPermission(@_) } sub OnOp2 { my $self = shift; $self->OnOp(@_) } sub OnDeop2 { my $self = shift; $self->OnDeop(@_) } sub OnVoice2 { my $self = shift; $self->OnVoice(@_) } sub OnDevoice2 { my $self = shift; $self->OnDevoice(@_) } sub OnMode2 { my $self = shift; $self->OnMode(@_) } sub OnRawMode2 { my $self = shift; $self->OnRawMode(@_) } # Functions of CModule will be usable from perl modules. our $AUTOLOAD; sub AUTOLOAD { my $name = $AUTOLOAD; $name =~ s/^.*:://; # Strip fully-qualified portion. my $sub = sub { my $self = shift; $self->{_cmod}->$name(@_) }; no strict 'refs'; *{$AUTOLOAD} = $sub; use strict 'refs'; goto &{$sub}; } sub DESTROY {} sub BeginNV { die "Don't use BeginNV from perl modules, use GetNVKeys or NV instead!"; } sub EndNV { die "Don't use EndNV from perl modules, use GetNVKeys or NV instead!"; } sub FindNV { die "Don't use FindNV from perl modules, use GetNVKeys/ExistsNV or NV instead!"; } sub NV { my $self = shift; $self->{_nv} } sub CreateTimer { my $self = shift; my %a = @_; my $ptimer = {}; my $ctimer = ZNC::CreatePerlTimer( $self->{_cmod}, $a{interval}//10, $a{cycles}//1, $a{label}//"perl-timer", $a{description}//'Just Another Perl Timer', $ptimer); $ptimer->{_ctimer} = $ctimer; if (ref($a{task}) eq 'CODE') { bless $ptimer, 'ZNC::Timer'; $ptimer->{job} = $a{task}; $ptimer->{context} = $a{context}; } else { bless $ptimer, $a{task}; } $ptimer; } sub CreateSocket { my $self = shift; my $class = shift; my $psock = bless {}, $class; my $csock = ZNC::CreatePerlSocket($self->{_cmod}, $psock); $psock->{_csock} = $csock; $psock->Init(@_); $psock; } sub t_s { my $self = shift; my $module = ref $self; my $english = shift; my $context = shift//''; ZNC::CTranslation::Get->Singular("znc-$module", $context, $english); } sub t_f { my $self = shift; my $fmt = $self->t_s(@_); return sub { sprintf $fmt, @_ } } sub t_p { my $self = shift; my $module = ref $self; my $english = shift; my $englishes = shift; my $num = shift; my $context = shift//''; my $fmt = ZNC::CTranslation::Get->Plural("znc-$module", $context, $english, $englishes, $num); return sub { sprintf $fmt, @_ } } # TODO is t_d needed for perl? Maybe after AddCommand is implemented package ZNC::Timer; sub GetModule { my $self = shift; ZNC::AsPerlModule($self->{_ctimer}->GetModule)->GetPerlObj() } sub RunJob { my $self = shift; if (ref($self->{job}) eq 'CODE') { &{$self->{job}}($self->GetModule, context=>$self->{context}, timer=>$self->{_ctimer}); } } sub OnShutdown {} our $AUTOLOAD; sub AUTOLOAD { my $name = $AUTOLOAD; $name =~ s/^.*:://; # Strip fully-qualified portion. my $sub = sub { my $self = shift; $self->{_ctimer}->$name(@_) }; no strict 'refs'; *{$AUTOLOAD} = $sub; use strict 'refs'; goto &{$sub}; } sub DESTROY {} package ZNC::Socket; sub GetModule { my $self = shift; ZNC::AsPerlModule($self->{_csock}->GetModule)->GetPerlObj() } sub Init {} sub OnConnected {} sub OnDisconnected {} sub OnTimeout {} sub OnConnectionRefused {} sub OnReadData {} sub OnReadLine {} sub OnAccepted {} sub OnShutdown {} sub _Accepted { my $self = shift; my $psock = $self->OnAccepted(@_); return $psock->{_csock} if defined $psock; return undef; } our $AUTOLOAD; sub AUTOLOAD { my $name = $AUTOLOAD; $name =~ s/^.*:://; # Strip fully-qualified portion. my $sub = sub { my $self = shift; $self->{_csock}->$name(@_) }; no strict 'refs'; *{$AUTOLOAD} = $sub; use strict 'refs'; goto &{$sub}; } sub DESTROY {} sub Connect { my $self = shift; my $host = shift; my $port = shift; my %arg = @_; $self->GetModule->GetManager->Connect( $host, $port, "perl-socket", $arg{timeout}//60, $arg{ssl}//0, $arg{bindhost}//'', $self->{_csock} ); } sub Listen { my $self = shift; my %arg = @_; my $addrtype = $ZNC::ADDR_ALL; if (defined $arg{addrtype}) { given ($arg{addrtype}) { when (/^ipv4$/i) { $addrtype = $ZNC::ADDR_IPV4ONLY } when (/^ipv6$/i) { $addrtype = $ZNC::ADDR_IPV6ONLY } when (/^all$/i) { } default { die "Specified addrtype [$arg{addrtype}] isn't supported" } } } if (defined $arg{port}) { return $arg{port} if $self->GetModule->GetManager->ListenHost( $arg{port}, "perl-socket", $arg{bindhost}//'', $arg{ssl}//0, $arg{maxconns}//ZNC::_GetSOMAXCONN, $self->{_csock}, $arg{timeout}//0, $addrtype ); return 0; } $self->GetModule->GetManager->ListenRand( "perl-socket", $arg{bindhost}//'', $arg{ssl}//0, $arg{maxconns}//ZNC::_GetSOMAXCONN, $self->{_csock}, $arg{timeout}//0, $addrtype ); } 1 znc-1.9.1/modules/modperl/generated.tar.gz0000644000175000017500000077205614641222741020763 0ustar somebodysomebody%fk{Ǒ__1)%gmڅ@Š%H=ypA0^U՗QHI,뭾WuuuuOyOx6N31,gkJFY>{mZ~#D e^0yuїh( G[I ?O{S߯o$G"h^,Ϯ )WP4I2@냤a;_}lg/^@kE#{)ԃ| Ikkb+EOD,q`ܹe @Qn@sx,uǴ븃+jԆ!tۃؘGXA啥2dUX{|Lb瓼$3,C>$*Qd> q$&@:Et|wlM-z ޠPepZ0aHks-V&( 5A7\A達|>eHgn=8v/X^Yz~jlC+d) Ũ*5NR=̈́ϲ*#}US;jAplptcZ%L eQغ\dhesg/j+*#Eh Jjn@G(A XFp@  v[sN?{z;_xԁ̔G}vkmi/]A`DG^{ zMg+t1'$Ai6lcGHx@?pOE)cNJTuERY>K;ZZrqtKvv>ҀʌEj(S)[JMvsg`YN |%Ql)JR_hX┺^ii a5ZKw佛E>AI,q~:Kgіi-F ᖶlTMϓVYf6.$g2ibl^f`Zup*!~_Tg[_a|zH["/[ޖs'뼡=EF~VI=MԆH]NaİUJK Sa 5[x>E7|2Zz{Rqk*'p%I,o.J,E W?~H\7ɡe9o6ߤ= …m2 .2 `wz;m&QF͋7Z=׆l8㝝d2L:WȢeЙ a4b^<]|g૛aS{Eg`ߋMO6C ' `zh*xN  `I * bu 14c!慃a-+&n')~6.e^Nn;T{B:}Zj1&ܞ=1O7tnS80>S2uУVEOh_Ys2ȆԚ+_ mZLƷ'Q|Az`urO K_oh  vv4Jax{s8ǝόlo>YoڣH+VvO-u76 :o_4l3NaUװaX{:LLvߑF t$]R_6Bv=G|gY5'K vVXʎ}XAhcruPdBvKr+EBfq((e&'0g$ 9aOY/wT" I[T,L2P#CErZr,Y1P|s]+*KS /BubQɾFuޱ˃9]rmNkC3TgC ڕ:תgXǕ? Ka%#9|˥_qAgi>,tFU$ԼhSnGV;z]1 U֩u ndlыtD7a IǥPUdUԲDk^]]iIp|h,z KٳG_qVZ@ICeְsbGIYe?ӴV02Bf2y ;dLTc>+i; #rܪT@Ǖ ))z:T_hxB(0iw/9Iaigw *?NTǷ#Ӵ8dC_j( rֿ ᬯZ6zZ/zZCzCon4`ʭ<:1Y$F@V$3њ45A[9 \~HK |B*@*z}?lou` lGҸt(t.#M V8/ϊbsQ؊7/BJDlj'NvR7dl98CKϊ۵38~mαv6!G2K~p7+r%0w /m\$4hOИ\zRmU¹*m$?*QiNtь|҉h?>kDG:xշfu#TQ ]) iO.ө@.2cfAv>ƈ0$}n5=cNf%m1ڣꯄڼ 6ՒOF"QF{Z|4ӅO%,WZSl\nJ%3fߊ*;܁\a\w\.7z.dّ%L '_90~:fXq>) 9ݗ hJx8ǯЊP1N,+V!iF@gFc ã*. %n_Fꋃb2Hg Y}|/E s| 1ڣ{}YB<Qb~{tgl|ܖFČE[@I&y&ŭ̎5&'27nOށ)bC1n[Qgu8Ş|V)ַZ1PF0)?9&rMT^@2eB:#2)UVs=MFt"S٥d+(t*T;6š[$SU!EXR1RWRT&Lm^Yvh2%/%iLsccΣ^'^(R"0SN*d.zqZ1bGq P)K"7g8oy<Amf)zBW،8^go8H1 bo~y {EE 0م;MX d`Ih]qBa]DLTL[2o#W;j}h2ֳMy6҆_eRrR&|:Ur W`IJ0i4˄̀k1VC%az?nӲQ K"꿓גou:ƸwQ\h@Bs|BSf2#NIq(6D[hW6»:X{sl:M2@7 p67?Ŀ6W_C⿛z} (:FTY2,V7m*qJ٬ByxĴDfSWc_߮o$O3шIc A'zHi/2;ԍ?/nhxTZz)ȶ3S{|SopX[m@pEV O4fr r0(;0BGŹ */Y0,2#1Œ{ה_USB&vR,qDcH9~z,ke.5`|ǣ@ ?^j [GSiVkFsItS9,Ob:>}f1_@L8%%\ 5=M.9 nqabβRJٟd^(ŤR6:0j]gd mՖ-6zxtwHDJr7lZ%4`ϲ8k'xXRXwd [mf0GmcOObZW)L" Nr'Su&p~MIv}1o4sۯ+`bɳ) glI3%[XM-9SVvi&[BM)|lH( J fG]Df/@:ŲAӃrC+A0} 0]FxDeUtV,T!!4ЊzATr@D9vl p@P*x RumTX'gy6e(pX+K)P2X3BS糤WS;j/لÎUM1KʀB*֖@LVߎ<tCT@&MCSl+@KʯV ե6yrgf@S CvL/8,@*ajwrwLL0,*77d&:E-kP@H]?g !SD!/"E0^4鿲/p*Olc]0k{5kڻ`S%澓wnÌ<-+0VoLH?0R$,CX\&NWT n!><8ms9mBOO&ϴܣ˭ջ =fؤpF2{Dad~nz1X  &#dŁKI]ܮiɆ`*,s0t_S v54^KRR&/d9k8hԍfPoN\2CxIn%XQK 5! ?2F+nQ(;CH/YckRf_bKW/TPT*s }(ӍAW/uRGTM9 $0^Jyx&ήrZ6mU)XM8grT@}x {>L+6AWz@ ŭ ʞ ʬ1MM#J J9.!`?RS^N5P)]LXuB6"oE$+|"Ϲr#SX&Uf{_HBasx_0ٙ++"JqCLa5DHBr jRWSݟt(32::i+Ȁ%ù$x7Ims΁quN}-HZ7O:.(_Md=^,h lz|$>N.աA7b4l%JO'?YQ[Ź_`zJi 7tc"xlRf詴7V< Hf ǞxeUxͻ\ bh4e1 cW)jӧݷ6)kҕ.l\a݂&LR,_S88?`/D!#6/td瓢m://b4zv<~ Ko=} 3e?x5u;+H(u/nu έs38i{hDDuϾ{e0ᙕt X1]>!6җ9^>W4Ѳz!{' s_dЫr=|CCF{x%i+m {dA4+u!i㓟/D2?s>^;?!w*EȢls?S2{>Jy\ H,9!g2? aohP=(!Wc/٪H͔^[w{s(5#_ 2?F0``V[$XZq=Y ,/~ nճky  [n5f W!nhC=2tx("u$f Bn B ⩁vq"A2YCrd -hT  >GwJoRbt0 F2vfj`}qQ%0P uMteZUv}YD.3D/%޻2@S &ݺ὆Rƺ_ӏ(n*yˣ?FTLvdMrL R :7سdSĉQ[/(eVMltO`])?4|U-EUk_VUQOCk5rMZP^əʥº1UT=U!d'oҨtQhNۍnQ`^ Z`7¥uB'SX 75 [ֺ*OO>L >v <Q'S;?;G2N֗~n^PKy׭ g\-UXd{_=,?_1N'}cK{n$yrHk];Id52//^)kxwk/o PA UCսn=8}A&-H197ҵSx$Y.&%+KO)x}=[6LW4 q`=bv4 -ܵ0'iQD*TNTrq򚱤vɚ+0K~Jf&%Qoʨ̫Ag:laȅ 4ܢрp-fu3yDeWIxIb2 a뎋GT P Y!WP+𲀬MV1fKeOtU͟ye|fg?.d^\嚪%^ޒbi2'7?Km/9"tEnC^}9$3M*+k8!s?5$;ScOJgIx܌ Xnwx)ZC14]qlso~ [|9ޘw );lrWؖ9׷tt9,XI\&f䷪_wg; uk13+?$T㳾H)5Q odydp6Sى A~.Õ 7tlPQc>t(Pv_;vYza%H;8@6s0U nŸ-|bM 03T*@L,Zb9ۿ`o|!>w-ߞrcrڣwTBcj.*PiJDs8JEۜT'Cн<9W1+$  kO|ynk5<R5n 7 evô6~"%':p_~48|,Bb.󘂇Z:-&L=7@h@I"k+2䳨o:)f ֪#~FYUl+Gq=5>[٫z'B0a<| p]gY=̺vDg4gATq4V55:&nԹYf@IܛKU`J2׵S*I <+௯ZGNuu̴pmP 5Y7HCĢP #8fm\q_ъq,w+b5l>فp m2#PuշnJd`a~z ^-/pea$`Js~׆'+W ⴐq-yNkɋ~1f_vO\MB. Ԉ#*㬼ȧ+l0aSI~ c(&a.%&נP;Iw@$^?;r @5X4jFӚܵ,z6N!kx#JņXl~M-pF֚l.T.UhD0^W~̛ +z~*ox`ŌϚ\P Q6$>i8#ga WT[9 fSLD%JS;Yk#-RWZH\KYP'Oi sζd$WՕkI#ܬ%`;/ v{H(!^10nl'<>ph2$.F\f/R٩';O @.3'~udH}rG"|yAXB*fU:o*NXJ1K)9hS%>(AU=߆y6:PP \ X__׏==@}$hATs_ǢS.3;cjM ( 9/,N1=ӈ+naR]Qw'9K*x6] _fW6Å~.UۛH_nb7`8Ɩ0^l,$z!Jci딇So0 nMr5*a&C3 : Ѩ%}*tFߝilkup㗒${Ś`soP-02p>۴G`sEdw| I#/tSMgdJ/H.5LwnphqĈV U6|'Ś":x*|NCl2a&CY.7œ S|Ґ+ҵ5! x k ::ek?p5SB9*SC@A,`E:$EQ E2^U8W{ &urQ8c8~BO4znWp=$.*UkvO^Ö>ؐHPJcykUX2PWmAgpzK[0.PhšTu 5:J8u݊UĢMs? sO;& mL^湦.nf,~cbA/B=IڤGj4y Q>ˆ|ZZ#EHmu!(^8 ZXçM>7dXf'Bi`u>v|::uZG-aC~d^+?\aF2JY%_XSПSC+#>K{ 6O Iݎ:~ glziX__D_=_O6|css7o_%럶Z3ב @#p' Ťn>1z;Q!):-J)efy@ܗ6I^U9zշmirn_K6i|qKJs`EQ Kk u-d'vIl'lwVDg+|/h_ѭcO _1A1/9??D \JXWNu8u_QGyUVGWy6j+:dxjwWf{e?dPUd?RV}X[ j ]wNotcv&9._X/|ك9*/~<^Nt @HbsdD$$shE `iUIb 2r$B>e,h/"ӫwpIeD$VY"4}>$ZΓiȤnM5N$ ,@F( А4ݖ~J u??=3Xq&vӾ \P $~8꺘+ %6V+1NBgJHX; 0㫝[WB @\T DHН@ǃ)w"DP".^CG*)'jW_(A|t&l?Z.i! @bSA?cZF(b[/M"i|5<}i ,*u;!G+@&=ίL/JߑqQ`YNIa"f W,\sR.>\ǿms&-rd8%دz%nm;3Mj%Z$/geYYbMDWӃkĐؚ'Oe+>{qj~i*QYJrXR1$SU{%r@^j)I\ZyU?Xbyf ɵ0Jz-9+t1"tAb5{#ְ?p^N?<;JK=si?NӎEB.S5uV1kbAD,Nڜ: 5dw' W,Q*o$╄\|슥;~U̘|"ڸN:$7]h/ӛAH5""٬T :< CTX4pzN~ـf餤'C1VZ]oe-u\Vh q>k{g.d֒>r^G.?PFò2 adB|1@,c|7#" &+2pE !{ٵ~h.!c+/#~ ]7ylvmWulgU\D>"0~r>y"6-o2 D5~aFߪ0֚@#u]`ݤt8>w TL '4{R.f&_$6!]nBu~fW7$Wk#:VNnj]Y;` "2T0(ʹ;+ap;lG&CJ̊iM![ h ٯj^DûF@Hv1kcCt@wyw^.sJEj @Ԡ%N +7΃Aa^8D/PEhY 1= -TebHfW٬m0s C7[q`#ɰ XM>ڽCE) QΓ$@Jя֯[qu~7بj b OGkצ c͆w#iPMzBovp{YP}rp~bHZP=J"N/m<(,/T9h%rJ&10<](Qay>b k-k#k,[g>\;z f Oafr#9PڳGqp[WɁ/8> [7Ʀ*xD~K]A:@DA<2aW*& *]Y7 H>PNG Nvl8/Oq>ra2Yu^d2Y!@_T( |_ũQzYZmo-hLMc}p@#Ĝvh5t6VytVG`%aJB~FKduhj-)1\& x9 e̬h]-ZH"=\Iu} -jɴEj<eK ]?rJqt P)v]֖07'I $޺HW{[.YC|^ԡaAT:{Ld2X2֠"TzŨt iu[0 I :lwƒhFW,- ^aqQ*ɀHn(32Yv.Q$gg[%1D#R[*k#P؆$iu0+&)s.8PJr\v\qɗӚf Dx+ٹ$`0>fF;wb8$>HS,`Pa@?cc Hp51` Eqr*>WRєˆm*ʐR4slSY0S Cx8ruhXpd$9;j'#w*jA#(5? H~75" F4Z1]@ĖX޾rn& J}E~5 ǦJ䬆CqUz:ˋЍe֛d4@McR gL-fPq, (4@#:GL]mp|xI4!VZt qiWGg@1Mmꃨ!Ԏ:Oo:[tmGy1ٞTTav=5iْ@a %֓{p{*Y0)dDC꺘k#Am-I$1`gaˀ.Dİ&W|2[$K4[  L)wF[VIR j{j]SՀPBXx\\MC |ԦҀCFAؑJ|U[PNhP{x`7lMUY:;Gоlbc,CDw޶j3m+ ^ixྍ꯿V6`]jz7h=:zoz0vUF}/l-G `b l+a^E_>{KCUOqw2* m 6]8 Ֆ#[+m*ޚȡ̦l9,wD,Zю32xVDm> 2lY2}v߭1tyjg ejն wUwOOet'F!ہ6Gm1`Iob]}!vq8?/eEb/n8`Q⚡" :2uZGdȯ(DpBފ]j>;5 I-V#Mrmtf =lmD},YÿO5~;Η=c2ose3a ‡PA99*NY~vk*ҡaG0GNơW-‚H.:xӫ)FPY alw:**UY&4)>h>k,x˧wW5.BWbݘ7t?Ǿ7cIP{ˉkIp=d;_,|1$PLJ;w]NANCi"lKLɶ/ujuByjA_`N~텲deIAԤk.e}-Erz_} D$"۹/IW$"9TuF\B[PdLsL!Zꛨnk Wm ǽ@a CT C&˝R*]ZHȮۡ h$wٹˡiNMxj#ғ{IH%/.GD̛}&HGdQ};z"M$HkzW IG.iY7µ0{;(ϽsK!]SoO !ȊN@t5uǓRQ5R3! HRz#GC_CR*FY TzS^ I..!њvFHps#Qo|枨õw>(cs$rT@$Iyu6lb`UR-C@ԫΪVUC,9)M)>AjeQN3[SRZ d\n Po/'Yx gM8)ѭ[hI6hV64k4Um}E ¹+T5ћ\F%WGh & 4-Fђp{5*Fs$/Y>%,}a~u#H5{Ge7Q4یD|Ŵa%zIݦ_8z|塺.f`R#,F9Fq}RErWhNxKle|>U"UHz&,qpjڀzPG$&Ϲ.SkJYSͦ53Qzi+Pd>aL_cF7m| e?DP EߛoC$s7eg0  iių8e_<\K.o~7ENx蚀Gok+㟪 \XnWx'OY Y}lX.X;7,]X dD4d..8۟BDeY[ͦ.>Tes6 TÅ3W}X; ]۱ʎͮ߇f:xO*ћ|:?cMȄ. /ICA{8WO_SbIG~'.PjaN\pOWF4#OP.\NNChz,JÕE!t<>M=_Yg+A" at%>q*w5Yq54_Yup?14aI̔4eӗbJ˴D!\6~}|w\cOM' q͢XCHLl?l =\:jp`d 4ݥБqr?J [ Bت7}F]xў2$1Avg˶G F{EF Em92 [g>Ɯǧ>{Gw4P9*[0(? (somJu60>Bljv3' cP:^EJay>ʝI(2nI5`ZSNCk9?ˀPq2Z̈i6e.QȌxaDeUT[y9/eAAp a.mGb=<(|  B-Xּ*b@hur܃/odK&lA ֜w~yR%wӉ="!є&1,4Tk/r h.ANGmafkpf!Lu;ux:G1)t AP9!]V4鍠¢"[BLl$xk!a wH"=؁.zqReI:V6v<\zv<.8 jgݲ,9W;/W\\M܁y̋"YubC\­l7mx'Q4$EnR_o7;~FVKŷTP@|orrٸzԥrY sQZn ?4 J-MTj]`y1 wY60%ns`y=C}b}f,x,Y@X.AArBlF2DۭKTUZ$]VUUhP}b ˻~4nD\:&Pe˃ }^w JW8 #JW:n_LQFԕF;JW6qP;ErKY,w85]NvǏv-F#> I@Q!jgMEsrSc{ˁuk3j @'< `<S;D?@`jG|Ky^2L<ʻyw(?$g5B(]"0\>| õtFae8c://ӜAn]WYTp=?l]sg >butЬHu};DHDp9=ts_@҄+1$6խ_Tz6Zw@ 0@&uݑAE4b;䦺#@k(!R`>*@"@&Cu_CCn@|zJswE6X|DSJ` K^ 7V8a͜D3 :Θe(teƬzԘUSBLgHD^&r?dAJm?FJvEdୀ iMRyL1q21(3}"TyLb8rMmZ0Hl2<'Ò"VSMuS*vֱYmj3K{eTD5d*P~2Pu5XvB"6])%t;QV9 ]%݈wGe~Fweɾj)Ɩ>MX|#: tIl^bĊH8CEK -iBزd@L 3l4Cn 2?桎w=F%Z>0MPݭAHW(")`밻 BQ:ۭcI|4Gq^wbh(7^!?/YM 5"c۟HJx ܦKP]o2e'}&Nlfثgn.vLbex)TІ hCAG8 n,L0F )di e$b Hh Ί:w ==E ?<%fT#)T _߽-BY)#'KϬZNmq:A8l)QFMLE|{O4lS0̿>F"`GG 7.$)ID#)1T g$rQ-fY>p~!T h#`hHҋVpI[rs#ߣ@,'Ju_k"ۭ57 ]mn`;ǁl`IO!Kq~jԭ~)+|QjN92Ɣ;1RVη7r7ugc eX}Vj2#<`&Hm`x[iN p^pb/1Cj |61p`8*[4[&VKHպݢ3VdAtUq(UE ]ZXIj.ft@9}n5GgPsf#kU\{fcp,pd$ߵ,v sN y lSmlaԉE!Pq` 泚A7Ђ2ve2Q ̶MPВ'Q6Y|.Y.xW_r=0'.ڜCD"mm\Fj.aQރ86Kew VwY;x6<[,/k!/$d3^khtCߊqa!J`֠ Y‡CkbT9o |K&&_{o2i\KS3? ffVË )Ën5U%gu qDu̓4UE[]%Up9}mMkٙ44=?zŒ 0s𥄫@r$, !j쇪 LS9 GEwg]pY^tBBoz( Gy P'=OfːK!q=IGbB&-t/k@9S'FI4); Rs1~LKxI{ԥ)-hB=螴A׵dwI0ĵLVLЭvgcjEocוnx7RWkm+d9w_:Qi )h Bvv"݈D"$0=H([%"z;;he'wK-вkZpQ(Ԩx'ɶ\" =0 M*` Qֲ4ѠF~ EiPD/#0MA !ϱuL472։ tnor6ۄ۳}9K+cjut$TM=59P``ۣs9tG6xDgM8-xβ1ΆޟqXi2֌&UY&B D tH.::ـ[;;5XAXp-cw>* AhryGqQ_Zf "uaϻ94պFjt%2"35ChA{lуiWkYp+z &-<ׁ24c1ȶBdX\sӉkÿX苻syrWc( C[>՝}[iɪ(ZkjݾL#El  5Ԧ6^"&jZ#4PUq,#71>D6 `U.Z2|fdSh*_A;chʧBR]ibE -˗$9 Օ@3 f 'Z(屬-<3* V1<,@jSg^PYuaLPeO9μ^8DnX.z麢C;Xu mʘ! kZ]L됊Ln(RXΗ~Gwl-F:.c8d4^6>o5~@_b#vaM"˰;[w4ղ^j*'0Vf I]UGU ̏ SZ ;0f[8wT,uTcI))c78~^rzʉ}g{ a:@l^ d,Q&~vI>(lY>zoCl)ʾ+Gӆa4]Y;ԯկ8tqܷ|lWf6YK1Nl:f9n-|$n[z*/%~Ӄf+(Ca`#(ڢ_e;yqF‘4ҵ#& j&6Ճ+3\# լ!$`]ˋAҋ߷ =x)ddpO밆T߳A )߃iYm5Y0@HQp^j"hς}@ZAӇ0TL_ =-O֚$5o pH3"eV eD,# pR]{.(l9.bwLm#w};y0-2ijpE]``;͈8 x kA,<=<` $vl6 >F.j>$C8.[@f`ᒏNj9]Nnx'cPTaVF6{ݱX_AQM}pW=hmY2=ʫqTB7|w6TPI zNrԵkᰑ*^ ҋtii6ZCW=|[$/]Mx5|qG>{'-c%cmudRzv{G{[Μ8PNWQ Dlw;ќ]:Q}?WVT$EYd`c 2byV[ѤaB퓤pl+c ҽ 1ϑ:Ku|*U!]ԫnXK,oc.³R og/\Qxk)x \+MV1uX<=4`X58N{oׅuok;1hr\~g1dluvZ,(¸5"yTW0 M9(A39GrGqgkHrEWg8df2K!`̞6v>n[꽫mcCȳToJRؾLH5ʠ0z2nCaV1!ZF\(p_E<wAq7Ա+ bS,$ې#r*!gVn<'fY9b]ϲ ncKi<9V)лf$Q(m-HրyEƯU*0X~5к3Z.NRʥ7l@5BvTӁRμNZ)GƍbMG{q',Xed0rfDAF~[[昅|."ifM zo9d)Cmo3]f`LfvCRvԷ]ywYGo@)_wqLSW*X$jP2C _dx\y;* I(`G7/PXtKuNH x:L ΝX+$ߢ?VH[z_йF=Wu#ޗ|G&v﷙r~7c8e:EedtTD+}wl黓+v2Rjuu╼;/y] |$%Iwr%NFC5IJͯRcsK[ [ߝL_E^Y}11MaV7 lxKӌUmH|N^P*)N/2νc/.UR>1@5ƪPO^Zj%4zVu83ŃP(O틗LѮ3qeʅ-:N w_h,7 =JY8cLkU+ Ts@Zu_) NQ2Xih3F^(OvS{m8a 0i誘ZNiQNnN~M|S8Z-jN"* 룁PP;*0bg} iY@wxkv+:^YsWQJݴ^FE,<`)8[G|nQ4P1}f_aCޝ L4Eaz$sa}No_Gۊ,F=-TrX緸vAH%'>{I/ꮹD% vFN*yާ9F_pje|߯$0ڨ;8u-y3^It]PDZR4SL5aPT@JI@| NsPd]ds H+Q$z'g;wߑ~G~;1(ZD|^߳URمL:\!PLn_MS_=4|Cu-/練v7ɖq$ݠ#;hF+UNE6 ̗, pVg|EGņ*_wxjҹLf_h6Y~)뮨=e_~ kNDٰG}ǻcZ6 ^bwZ MvSLG^JOx v7=iZ1bT2Zd7$];΂. GlNLvmM\;aC}g|,0z"ي?"i7#/F!ɬ 8ʅ8Aȱ/: BT̾MdͣY1Z 8k\6xo9@ 4x̓#Ç2ZaQ#(z~!{^,Ų|&S +r~Ի9f ^oC6-0Mm 1_ (]"FE* (-~m I޲7v,jCP kfpS MO U23;jPvR2Vꌊ'mL}2|1L(8H5j\G D*UcfUSEZԘ*CYS PYƍ+cPY@uh`}*m *j @C=)P6jsTLX3u[E!mJ /P ,;[-@U  i;[cfRu[d {hPA _J3yJFxԞ3km`c *ZϹ 8!-ByRK+ԋ:}`Gؤ'-)王C[[Z}氼p]e9c(uxlZ Qff P5dc#=H³n <+u?!:~,w')rA_OJtuWoϫe`9_ipb*Lp8H:*lo mMP}w'(]WWWW+`XI<_D7+k+Um%A7K4ю$>zt /* +GA+ˀq}|~ .#GDQ3q7Da 1K _1M#6V,|Ġ+KOA!YVjյժQz}gon0sOΘH_"pJ{ WHڸIlgQQmhp^sM B+jCzk ~|6Di%WK Ii/ u[KΉ$ S ~Gf 8 vIp:fբiŋ~ ZS|ΆxomԷjiwtwjgFvrpt|,ϞSZfuv|p6|Y0(-E*\/76þ5i(8K)Զ)X%686?|x po8ZIH#.h a@H.?MӧD?D:jAxVkyB4@]PPԢp*FaGdTo'l·]ޗl>*GdMhx5⺧ Х^>J8LCEIw p%6ߡDۅ\prz2 * #{QG-Xŗ>fFmsoV;ݭ~ۭ/1< Xr8eŹ^Q 7ͿMZ}`m{!d5O IYjD% xu=Sޱe(NcʱĠCv.\*-}bR}8glkGM 7پ\eDz7#ic Y #2SيتDd?n49@JhCcʆ )P>ixE@/-g+l(Ni:WwktL!F@ 85EEm9td4ҧg@IRZ(/k)[}h+`qf 63 U׭DL΃d؇QpP9˭6Xza=n኶qs*Xb:9o `t]tm0}{`a] X6i(;7&E! DIZns~sj6F=V 7 Cq\V\jIӹnҊ2'xnݟa/`EXT,Q89u|)kpg~\Qo*A̤"$dTiN5ހxZM:l^/ldW yqq5plA3 mv`KMbƧ_>n[ZIP]]{7+8kb|!N]9jҡq{c{l"A`o`{ǁg76ms&S.Q%? |Q{ ڥ_XPQ$0_; XWG~E% " 0ڸ,p,V ,M+[ 6k0Bw X(2"JiGv:uՊ2䄋+m` hA LUKB^1l+ʌ)oAJ|q+#W7+ KAz%ЇhЁø`"O cO,(" }$eUT EH- +I|ԋx!l-)XCF u֍|"t::2ý-ioG'zOfկvm/1(xZ\^~Z+?g2rF%RMCUjYQLJ g/h\s ,J֩haW:r^\ v<6]pknjn DgJB5x1p^}͡ҪJW 8aRJ]m8-p)1W9%@|V31۶mة,߿MvF^%{1 ɬ=l6ct\/lq, %>g-XLXem\Fk=hGjΉ-2}*kl>؄B_Lr5_b2?|G10w7ipv3p&IzrI*n] |\=r?L03FLD O}X~A\G2xfpu56Avq {ܵX%x#k蠣I$ 3̬1ֺVv,(jeלUWXbV E$S;@ `rȈ좳yX,45]g+МRDm ڳsř<5Y51vS$Ԛ#AB%HŽۗfٍ;( .O74袓 ;Is0[6fIԡ!RޝMF.iCf ΥE yӌTҀ'&Ru5RcRsnziØ +cǒ =mHMj/qn3(fϚ(8VCkأ+)_T'*|z%6CQi^KA7[̏2S3E%ߐO.Zi&2nBXqǫt^vd.;0[&˰o,ex%S'.p=*p=[`}.<ӆ-c\4D!!a?ƥ-ZU78b}7M)˼5hw |sF($gQIZvhPC?l:rCT*i@HR}B_- {Y&n$F8MJ T%8ƅe @dA1Ӧ9o†')1A=~V41.3L*X`ܶzh2YI SjSLֶiPV%m6<*EiD@ TpGiiI 2zvcS6p)ݲİq9l 2LJDC6H*̊(Y#3jdW>~D 30 l31F#"Ϛs#NcwZܫg@h k&]6jExSaf͂Y# {it^cCJZ+/^@5auYK[@ 2$(cu x= 2/S3WGDjKl/nW*MePjYS!H4#^H2\w(?>lsU Θ2=8G+ g&}go`8,=)׉ %YY}6e`|ĠWLF "/t!l7Z *%(yԸ/>-FW_7 vSC7I9 ~UI\rmCxEu]ȀAC$FPW9H n8/1tgr c)~dpL|aˏˏJIӴoxuռ4e.<ư"*AwYw ^ca;qfD]JP{ˠ=(8%895ז|xuxMbu3 5:q4XT[6߂GpbcQ DÞda|ZB!%Ln"ƕ`NipSpMntՉ:I8Awl3KJبjOM.6H^ QlfW6֙4̺:-3x 6Lg">! J,8lPDM_ZjA% |ׇZfaҺ}c5p@k@l,^U0]aҁ-Laa3G p's>]޵ݎx0É~b5f667b iz,]WZzjBYx!6ԫS/F/Y&kJR-Gw-(%lrbVB5飉e b}Z&'56-: +dbBf>~AS#@P4`Jfn7#6ίMj*28GgFk .Ygon U_E'4V>[aMw8ԠzCUD]$=9h߽Dq/B',xv_7@IIsG@ 58$5}0'[M Fq ;\_nD[#ŁIU[dwmz%?MT 4Ehp;Sn,y|*XC'Tvn%t}1-zOp* O$_D_[G(VKBKUaUq_Є_G!/*QkzWһ˰cKp-=lCQS`d~1с(.,_ gҫ LƣYS_bP11O£kva7Ed[PsK3Yo1[;c1鼕Yc\IMA\FЂfUpޏPg P+ U@T*|ie9,yد,L& uN MaDi}! áe}uqd |> &ϋ4Yb^VW˙vyMaDBĞmy;; 1giLΨ\7@Fx)8ͬ= 28i O`:6O|ʆۊ5CR^T j䉸MwF3kD4ê@kwگKr|?Ɵ갓e;n [w!?,`^=V"A .Egjr fWVa^t[ѵj aX0-[ f+nIG`~`ف\' v`N֘eIQ!B٘K/%vXD/:YJEMpҗ/_)iO&oR8vo <v4b" e:FCz^=gqvWq}a78''++UԨvO!*/=Vz6܉w Z#öhdLQLjV3L ,@AF7aj7 ~+G{n]|Ѡ4ЇpBqTy^Wի!E ;qzL#aX8흰m4b {8WkJ4/K5axI+ kU@uՄKku![Nkޚu(w5^QdU4aV`UQYC}!dY`U1ufYBb3/ccϋ k>72>⼆_,l bj|?MuQ}R X\@L&<]n*. n +NnRX{UI 3Zsv"J%5K vUrtp? ɓtp{vP vN[[J4sTq3{p|?O\$#=?8X4WIUh}?{A:XdܲٔWɪX!Ljd A&敋'8sl/p(^56wjj)_Tݽdh&6M_jkh 2#r;YrdiI+LJ>싵Qj/f.Lu{Wbmbㅼ!EzQSaPO?+W~иl.Mws?٫n+f)LC;쳣nW F645Ic?Fh\H'BOhvϹ#5<53QNFd^EK7cKO )ƈ ~ jB5│ApKm)A%R=k @n5 2#ŝY4O9"\Řy붓kBO$氟b2PpEs?dZ4'[PXy~l.΅GعxUcZe{O.D8JPKk ā@Ř}_u㪲:.Ӭ bb"y(.×1ބ%[Ndp["{5F2h1br#!kPqVrOv\u OŠ_͋vO\Ia"lAbm%P; y36+>L&KQ^T/M~ ^ "$';p Ƥtǩ\4;?>H5Ipo@#HƐ^o>oN0KMk4+:P1`kX}Ydb8#&>m<0FVς?hV9L`zo(YϞu+{P`ҦlA4X?l@ZbuHC*"/e Ҁ8ਠ6B#uxoejҢB8 [ZbrIWן:D`gϲ KAOghSȁ$I6(5Nx sޱ[/>hSQ#vLgr#쳽f,GX7A҂g~Du#3jXZ-VjN4ptm_ѧ$4WFŮawlIYҧ݃r`JBILq7f ] >mFz ܴa't~8ww|O m7f}1ַF˰#s K~N~rup8ӓ5Q1䪇pI?nJ@MC~by zc{HÛ ݨVl"CmiՁ=fZ`-a;}x(Q0kj Ic7Z.=t\eFA4b'C@iC<kkCn301ۢʚK!oq"On j1%&6[ГAlCL!>OzAzICFO P bxhw&Y8npCz!ʚfo<< !CI,%x 5},@gWa:# xH %C.8 ۮCSY歇5zg95kI7$_ h%kڞg0jրb ܯ $/j13}?}Gv:\'N9%BoJHP/"תCͺȴz)`lotCXQ$!WTڤOBـSCDCzs<$|U[I'ݲ磸m?yh~ZM{}2~Pcgq~3*x ,egMtxvdga`ГeВyg:-O!=TK!фLݒ,ܤۆ1 f;7ܤHS{6i) sSnS:TCbwn _$OsSp߱UWۗI蠺"$W]uӐA4+&5ݤtA˔⦥ZSV5,VWT]I[7t- q=PHrZL&?a܊3S9'[ I|iUicqҪֹfZk؟X2sEAc^+gZ6E;j9 w>qX{$lZŘk9XI{dǵZrPQW L0/xXA+JzT!mkVdڡWQ j?ϩYPPH !t2/@(1r5g=B/lY0 iusu]t@;??QZhLZ(W`YqgaJ!g zK+1,Gy%|Z{[˕~$:k.ˆx;cJe0!Gufϑ@BND&XPKLz7.~u!Sql*?WicKx(SA;Y߹gmٹ:lٯLhO:0l8fK+I|AgK9;q<]Z!["^dKRxS,knx`xZ=oG3aǧz^ݧQ=9<832_xU3C1u4~j탠uyD ÔBfJF`yJGHJ`߿{ NgAZ6"F/]2\Dc:AxI?\vϝg~RCp,@v܉)@;6 [wc2fPBf W5XHd`A饪2͝B5HխAA୷bگm~惑 m=t 4_oT'Fl5Y˨$FSHv"cK&홌šGaP]8Ka!q)jZ+0ݬIˎ=U cj0CH$;F~7CCGK [Ϩ##K}qܵxSa(bO0u >?y`xlOLYdqc(6p[ i4o-yEɜaRdNUN5l؁ 4 5 9ayk&Xˢ9KD&|iFq[|C\5&l0Օ}8ƤN1\sȿuO1l\h .܆! ?9]Q~Pf^kt/k^pA9 YȾA\)}~^>2)cmT# V#צ1BQec K0`996%Dm1>K;ֲM ʐ\1+k|N*F-:OBEkiCKy<[o2@=`677 hS Y\ GLi5DO Yl !wSƪaƳ^$Y,ZenzfcvbBF0r˘2gHz64'jbW`'lnbÛa0.SXғ|,:ah`Y$8^-YH۝J2I4qQ:DY!pAޝyw>XJ{8Д̹!ҟåJsX|wc811mca(2N| iϵSmᆙʍZpuEKzd'f}~,0N2`gpLd8h$~ؽJKs uzdel?Ơ@!mrNc,'EX'ɕ!_!-INeDʷUЂV1мU"jQrf|Pa`jNAfm S(*0صe;09fY>AԿ6%#w aAfW,QROyјNƊIV:s(h6oW_٢: fp`M26.]*r1hk#Y$_Js D@p)$dbL\= -% \ v]M~5"-QJM0rФT7r9?ʁfH竟(2&fفQ-)DZ;e1*(ӷCuў j:k3 CY az_hRMƟ2:r3fuLDȔqo03t@eL)O(=]u5PN4dzV="w gm#, =UfYXnڪY"ϧr0R$bA<"dO\h%(L#|*Vy&T/d;e…|6`Z ﰶO%c\QZ<#43&`q%Ku{Uf!0GU AV]:h=B+ţ%Q5dט pD-K@_'dY(g.saLzOЮ5oY=oS&ͫ,&͇3W[%m^%hbֳ{1e Û.4za^ϑJpu.>|vZuU5yJ5ʨTF; o`xA7Apǁ}M7.ӟ= x .{r!"}'"rB;af͑ LbbcjKtl,x:>/RƞC./C H:I,rvH!vfA6&si1s>)qbs)Aюϰkx"T9Ǯ]H.`K.LbP,kg10TćȉQ)v4O>uc16X .m*e}W]T Ur|TtrQUw|(ݩ;>`(Ix8GD|#[DdOnia 7 8$piaV*[{ ?d3 yynᏮ", 6 >x0+^v܌,2D1-zH8!?^I慊 WjmdxL=8b+l#a[뒺#Ah-~YѴ̋pto+::}V2S5<}ujg.^5W~ϜC}Aݨx}YnRIwYGrS/B%6-;gMc>/w7Q{ WƐOFl@%g{yLFmFdq1 y<??ܑ*^+k |0 q"Pd@ |A*=x{60^F}pb! Za&H/jl=j6V+m^ʼq0]6wn ~|sOMP[[{#8aJg3QVG֠{#^;4c;G( 7}h]?P{*_ Elf0awzK8<#`ƭH%M|: ^ћE,VrfCS*s'ȅ_':LAExyȰ w؏ɰ ,ۊ)SotIZ.Am 1vfLĸWV΃=]V) 𴦖mpЋfq(DKhthm^t͏O_x&wЄf=YH$(hw0Pʔu-N!AM`ݻ l4 : 8m< Lcgҗ~BcA¤W΃(H< 0~%"[ #8`S!lEU2d/`q-Y A>ËNљ =6Hdil n=% RCgι@0z䆨c.!ISj9ٔV8V f4 ywZx D]J7$nDZ Z+/nmӦ˹ @ MKz6<E"Q#&hsoMZ_2B %R )Kj'n:8-6}{"G%9|O}Pܶz6f?DJ Cja4Ƭ&k_ZxMO"vif 0DTf<MyQƔ5&T} cIȎr'6'11bnI^]`iAj'U0i>|=g^@X'tq_دpFOS?]D| .G%zy֬T3 )QvBB `@i$"rV+e!nE}nS^7r\O2ՇЍ2g{"IsA %zb1"*($T*Wj?3a VR Vs`'Z`z ?83{gJp kWI5##a^]_4閜"*x˾1<"xtr∮~_jPnTVh3;ePݨ8"?:Q8pWLQG9,QGp~~)-{G%t"OOeV|Rju̸Qɘxm(iʁv^U6W7kO{\Y-;B#*b+QXq)S`4E˩ `=w5VT#;ޘGחE ?j9RV~*s+NsI$7'^_ ʁnnfzi\A+HUIm{e7N0nEJwN4LZ`ɞ  Y]ZMMNrքV$\j^Nc%!nH 2!P𢥓10@AGۍXQ7< `87u{yŋ@c?j>((W]pޕ`ǭLVe:2쨿p%-VDaDzDruhfIgJ\9qĵ)vww[Z~(,I.08V.߈Bw-ڤ7=yI<UL^{{f$no Y QgBx W)RF #-#GZKlX,iƓCxzC~xuL![j5,o a2 y4Snϋfգ'˃} J jW2֥P. m,tUM3TQ-^gW3_Nc?Ŝݞ7Mgu4D+yv`?hPSerwrTPI@e-D jUSs8) O]Gbz6Bך ;j1|wO( /3QC?z!ٗF3-0f;ꍫ(12N,>mDyoGE|}ۃwWczŬ&'_/~f8~k &wO3QS@ڹ}pA>4dqPsVsk.t5 &cHn2(,x; oGQe/w yAU+EnQa[Tvb:=S⪸W a߱jzcI|5 /i2H\H`fW&YSH{y-=R%wfh#!lZ*G-*P͝|Sy} `t/eQ… bˏW@ֶ';H;.3"O Ž6P5'>ZfJpB 6E*GF7;qʇ{>f?ff*A XUJP׳8O_r$R?`(o+($`5`>gV8~z= 2^O|$~]HڱaߘR}c^wDdC)Pm;E;DIVb|n;.bcwl7G8 ۍ FƷgb|1ňhx( czB+ZMX!1aCq~`x[s r_b \ 7ir[ y>z# 9>=NcR$?7Cɱ;qRύ12ӭiuy港?EDyE~( i56f GVT s q_tH߽]Α3 LČc'Vq,%AcخpK@ *lY:CLq} q^Pwz^<#ꅢ(k >XG~ L/1/Qw(;=]S.h{[st0}-CK!g]~:SlLcIhGnÇU7:T֫{C0ӭa}3G}|ĵt {vO_%{|n=n9LkdmM)sB(„y@$7ST<Ƚ(@-jʕ`*J+<\4uRF$JC!MrC}-Y_AiԔ~:6m.r\f=X"~܎(*Iȏ9=Fx6 ]Ebr9C~Ң2{kUʷ>F0&=ꝆI{Lr c1blsxamJLjúE@v<,,yX)3A-#cmX^l?-6CI)v7g\S찬{0f또 ;dG? (r£'?q\{n^Io54Gߘq6'9muv7ogLꜭ(5sjbrp嘆}$~3~~!f.8iLc/&':(cnlP yJZ= 7P%%/'T2Oq|o=#_$ɋ9Q敇]qIYwDlL5$ҧK2ϕЯP6$R3 %߷pU@q-0g1 _O{0," <cȮ2ϒ r>M.$ y޲͞SA!CEQ~ 5~SL l_fDV[[9nDbGS]uϯB<8lX/rn,{y2;g)NOSj~譏}a d:5fu7=2)ҷ,O+%.o!=4|pnX.J׷*ubt6v^76cYjxPh7Ev\D3bwNe+G7_~,v㑽sx.Z8Ǹdn]r]=\hqco@r7{Q+u-TqwT5lw ܽ;MFujulm4_wuSYzͣ׉ƒ94ʰ@Olfs |itvqϝCeƗaPvIon=p=-qm‹oaۅu:~ tq׫Y/o sg/m Ż~2;y } b}-f] Ci^hVD^g?Mv|09>Wi;EGiI[=Vk^Oy49 "Suu#kf4n1s>ɆQxyWtKo oއhJ[s54<ߋ>~l,o!=9q>1>o4s:?ʄ5ָރx6.3j&Qf >'bGyh{SE[QsSLYaI=Mu ,By'[nH٠HwAv;N#R<,y C퇡v. 3az~7ݏisԿ9cLGy(119h1Jƭdd. n##sĻ}12E-oMQxwW9u Os\{0]sm[\\W^b`l H u{}~4EZY. ݹAŠ}4q'q^cq,~z^69 i# WL$t_ V)q^څrZ*<6p6|ib۝Y8\x/sc ݸ|w[!wyјŅ,++X܇NR^ߋN ,<1$d}AX>ةuW&7Oy V;iNߚiʇygbgʇ#G*@Q2R5ݠ嘭T\*d.G..B}mz7|;N^^* #Pu|-tfy}u[7悄p9Wk2g$LmrGϯгzU^]I>Q[XNL(*֍JUٛ~ W 15e"̙ÒS)7P)r7U}biZJ*[*_]:'O"U95D3É4֣l ʃws#q3 c  @aW*\ ح3|l.dh7a!j\IP=lHL( $Z$=4^|/up,cٸ-~bÀ $0c|Sn(߂_ s=HW5ZP[2*9c5=K]:8w :uNs9$xiHh3=}N2hx +hxX:|1. [7b JF ځʲ(jZ!,Sjm"DV4zbW[ڰ =||76x+13$W~͌+ d$[,Om7ך?0r SʟϴMt‘!TQ*Cx'1;a0o+\y媔OɜEK-qND}C@X.Vc֢S1R0HͪNh}h2yfɢfZ5Kg9bw͚ h㪤V]s2C@@, 3!31pMnIfDa9ƕCdQ7AoJ`ϯ.+qg E>. Άdjb:t+Z^ds ؀c :aN`;LF>R!0)QZ^0 5Q̟r@F{q|Չ jZj4[l>nAi9bR-.iL~O=ϔ.=z#BF)8+gyѿ0r0sTeG%htm]aRlȾkpGS} mttcH4FLJr/)& ݎ.'#|?/9l9tM?{~-z۪Ϳ0aXw X "{갨&coRqi:jy-C0\M+lx^GS][z`Up95TJڪbyj?]z"z2E:k:zЙn8IyG=<ՠznsz4".gR:GzBʤ|4zxJ}KHf)KM!NHlq2>]5\Yw*OW{}E]\a@b_J. xtDd=2:UKJ:@GͿ7BY4w\ ~=nFH}w )v?1)iVr!2B!˘9#J35Tq{@q ?{nc׿1ï~s9d3#S3G|MCzJEW8. $uvWv.PsxwiF+‡sU+bU辟pUfи mEo杣7j#;Ź΢V\- `%L*531ƯÖ_Mį-8a0|5G+hWH^*6RFHR"NhN27si 1pJz,;ћ667HG}Y6ۮ2_W/#QҦn}hnwXzLWLGieJavmP[ݱ|U7 s*êgX0 ȫog ۔#'+1ZK%?:gGa)ߌ5B5.뇯 |_11EecSK:g/#dGdGDp3w42~ PP䀐+R8ߝ?\HiWY]xU\4Cf1˙mi֌-fwV*aVVqJ~Oƚ[]۪# !ù3w64xyĮ cʤ;|SZd6z9C߰D~H<>x "ɴ8b&) A~PㆤP{UZ ~K_ݠ|.F9鏉Q!D6{ ߮7IKcq=}no:%};C&!;KSpۭ{|o[>k?sFHI"#ֱw=2=Q}:ճkk~^|$|G0Ww79"дW8$f{R8ɹ&N`Bņ'~h%JhkVNJJQΊ2$9K>9ی,bÕuJ٭ɗ7\Օj͖8 ʥ0+JY_^h(eho0}X W3&y11U7&8\Y"mҬJ%R+tõ+:}521l˨@oտi%VmSčRs&d 5TvSjufzOSÐ:²o:iy3~N载':O/Ǚ{A WDDL|1c;44&Ä+0n!w6_wh b*x%aM{4*M?itΘBC[oís QJ8-< s۳q?dw`8S9Yn8wrn]=WBѣgfW;N<£p!-Gã sGo.o i; \=:@Od7`VL2uOR?\yΏE:@4?aZ}k)?P /}ӷ-՗uaYLݯor]𯂗KXGLta+gcl}S<NS#@YVV YŀI-MO(.-d_z ؾ.'""k-IObim:Swq]%>+%o!G9@"6@kPpW p^z@cD5@zJ`?|zg}LSЯг5x65llxC2dp53֯), %ZeGL12Q`Aʸ(N^) Dglک~.g1 $ uFj,~MίR` ګ?oAٛO,!Ժu cǐmZ,[nX8Hպu 1d6} {6uGjA5xǟ9ODϫhWw𴳄0Z+BC,6O;۬cENUiL}FE&j""]R]Db=#V#V1l"n5?-cM2ml2r=7oYbx}|m)^կvIuCFhn\(`͡@ɹݴB`p/OqS Kxg&YAR`o5y~uXѥ_2y~&i4͢>01+p6er8aU!ԴWy LOG5.5}JQNX WuEAi\)"K|.<(U81hd'B+HXt/s$&ه4q~Yd+0ӹo ö/݈p莐廚GuCh|XYMs {g[(8]6XU׹"uί'>_;^nmxNԏ:pIGiTvٴT0qhVm DklMi5%U<8a hG{8F{؀5ň,FH5GX, m> G;+—> +j~(US8B|/K,Ho߭{I^ugAAQXDoKpaC ,e^ZZeҀU}W}-.z#ܣU.2Vc ?J`Pq&5BD#q!G'cTI<Ԍ 'eSwQGY!|3@(Jb w~ZtN$2Y*zNUvLWQFYZx&(\8:>˾O4U! 5g"XϓTq@w;2L4LΎI=+uRVhN(P*gP8-X?O/sfq 'I ް4:`7lvoSQ"D_k޺*sL?,5cRR<\߮1VZ.3V,#r ^#.V 6m#&FM "M8'єtD)i@S;O;$ĶhESҎDӢ#EjMnhZiю&8drED@G~E} c~L0?]+jn:;bFK4ZhD%nj3Vl*N6a)9-h EK(ZB'n Pjw튽NsqQGM<\MS]D@(=Si\%bب)V_[f8~T*9 sO:=p =b  ! \DAY#$ J8 CP2$$!AHyY[$v4j:tvrN4,Z_mJ&8٫cZ)uN'eX`7c6aDh>X~~/b :<ޥ 8@MWGFvr&V6l@mN|) ̈{d~V2VJ4ܧJ"O9gNNzs8>(L&i5[QIFk3&:{,1&_tj0?h\O1BF/t)zځ:%S̊FQ5sP:U!Y^2~ǫ՛w>D7ܜRFߐYJ$2 zowJC4V29(̱i:UWWj) uQI)&ԎMH{CUfy@2וU}Rܞ/cLYKďd';ܢ<MFO'co5U۳tmpS6fum4R:)x9O]vMk5cF|_M7_F>T?w`9Vxw/MC* \s)-ȁz= /^u0WKi)Dt7U˖7X|1^/(d*4dk||4HT^HޕF ciUlw\CYהŎxe Urh:l_زn\Ԇř-|z?ܼl]vP}꜋=I;?aW5w>ޓqO}M*J׶jXZf+0Ia:pՋ!M8_ݪ|սf_V%ݲ::oe ̄7id5m(r؊Рכu qh %Zg+s; < C'uhv$9BJæBFKZ+gK'锐ezjYM\*L2zʼn1nKoMV"UhNÜ^W;[ :JT[ h'9r;cP PLLb8Pןm}|ʇMV=h+EOkSł JwB 1 @BN"spFMH#x2*"~YlhuyslݩQbHgVIOzSW¶X0lR2{V4qE8@bҐ\T\KA.P~ۧw xs*sh7ͱ](< )107c\O='bdE0ެƣ &! ήt<l\xCz}HW܏ɓltu4!k廷)1*8@`#') Q;.yn6ݽQeȿkџ^mܚj{pT* IV _ C"oKnAۋc61mQ204ZI5_|~~ U55WVC:NRi41!\H{(D՝-Y M#GUKcͮhvEkͮDC FA-)r Uau輇f0g_l\oeC]Ʀݛ64=;6zz5Rkj,J'lpLWZZu^_EP JݿmmC@@ B"pA HzD ! Ir@g@s>կ({FQ1,~R1F5BqdhɁ+67oz#@ܱƍ},%6.{?(G1a{TbNODE;",D)ʬx3Bכ8Œ\Ek5ڎL-RC҃j7~Yoa7,qt3o;L^oVR!kqR4XS@ 7 Z=*nW \ )ʵ#h>FtG 6Ƃ'iU[?bV1j\ (b5Qc5QI/OjuO\i !mR<~t#y-8g"z#\=#\ {[톶 %%V1wј (%!$Mt-?vt:H &_BO6xYxY#.߿xY)%sӴia\ d"|Գ b# b \+1$uJjm'mCtJ(Z>O|NѠ3c&}?f= #%3131hM(Ębt/n/˨iO/QJ-?iNGG8Mf%OkBs@ ƫ|J,TWq˽sUS6X,TPWu:-{~/v[!RLNZ~?,_cջO& ؀F4ir&>1M}MnkݮVn:_ѴTkDiyJLj> <.*Pۥ\~ysClT'sPe1:,-}-]HIK2<֦HliR)T EZ-7)vb$뀸N+lgLP؍FsK?S"і3󷚢ao Mϐg-LadNzGD΍je;%XG48%N`!=zpэQ@eGa %K@|a{n^!텽j,_;+oev^ ԗ=1;o^.!wB7uY6ݭxvB@BURq`D0!`$?32sn[ܰ"'Bw4w+nd[;%'@=o#B3Ow%|!!էO[Q3Ze?v&?DͥCgŜ.}h LJ۳VlnzMı|D]® g"M 9+ ȈUs#/1;ٛ'GmbcȣFa HI ǫ՛w>) _O"A*ʅ1zӔ }*ߟp.EM |ˍYߡc/{H?F0>&4:wH=HTFguW6 fBFǷQ{{*ҊyBd_GFuShQbL|(>߬{6l01tZLPdX'xP i0Pp rrO T]R9pOq ~Mکĕ6=(smU[-0]3SƕΨ:Tǘ[@ZR._aJ_1۶%s|?VKϴ,},?K7~t @y/k`펾}.SMnj+ L -"NOVwN@'#((:ݠk\%j ͅsE?䅰Qb81dU"x[\\ b !]Eu0t w|ʔ%o3X~ҀIVl>sCmمNo0)n>!iF0 Iq[17Ӄ-C1(^3]萀,Lj$E_xrL-LøY p'ގT\%hyA@"3TqCх`sc7LA;=zNX=,R\_! 6 E=)LwGE9#6֎seҤAIphj5A, 7>ZGѳ>~d@tTE@urP E0$+0,X?sm#!zM{ͨ hϢ)jwh@gbא.t8YyLNkFu9v& v(,\ܿ^@"~9(r LB/ Rvhp|mËՓV:/VkHnH}OF&bVuёHX>POJA#+j_c IB_d ?YjeL+T7,|s)*`Ī3@,\lRD&v]7s-HE; djg; a3;ZN.C1SaS$-FonZV$քٚyBA;*5L7հ&5)00kaRNBqq1GN7 *E70:'ˣ)BdE vj>jV:Ralr龁?ș'lFeڨm.C ?inkԘaMKFb6brMK ۭj NCzHURxye XD-KXX"30\*ջ@&[ަ 'Ġ/4!cPhCls0VpűH0WH'T/5V/Ջҥzz{H#1IfzDF$yTwzM׻;PO"uˇ 6W6c %/$V + d.p% Cz#cgAF]"1Wvd MeDi^˭|W@nuCR`4 C˛>YݝzݝgP9e^@TqCRUR$.'멃QDe~}GM bsߩPqn }"#]tH溪ANv)VmA%CI/ɓВ'<Dמ"psN>?[g2e?" F$!@B#O_WfTpUF`+ `8id!2d\;2Hd0z^~7 Hu|/'e~Դ`Q㛯TQ{;joZaIhoeLWiWS4L k&ja{6 UzOXwxǺÝKIypT)^GqŎQ{μ % ]KoJ CO/Xq`S~VL ̲jjudy+T\`#ѣ!^f:Xa&b`dmO ۪#u!0 $ vHNKc!7 !Bt &}\ mz#@F?HEg|?V+t<<ױrcsfep ң}ԼdJ'*T:n0VUnޝ뫩Bԃ7AmklT^%V]*^qwDp7&p}5BqAw`wkwWrC/~/xA^J /@7}p ]p.MۣZ1+pQP \ބԷYq3u|[/6r0^JEMNRUA'ք@7ΏԽH?Gf^TE  o(-ceN;]K6e.>y”nL01[milp=DQJa*Nc*_<(n/o^7PQen0.`PtlՀA O;ADeKPcqUz\_WX{c'U8x'<>#RdBT.ܜ(Zpsnni0ԬiPs O /}N Փ[T,|$_(T lԴZ;6.Tq`욋 0OИzWUMD?rRuQK$$T"!a(ٹ).PmmV 6Gkb9hF34 f ``{4:Z\Jz=,.Ϭ+7Z h/E{)K^|uhq`.h(υn1 ܨڽU4v:=ӵͪĽ ";`<l|S@b4Q1.C,JPeוΆ/Tu5ف:Ewx(^y,\C>@n}ú F-܋S|ܙ3 or9Juϩ?Ωrg0I|N⻆BVPugnp4cs)e4`P\1Kwdۅk-rA6_(aIO]g QjyCy{yF!dF[%EN?4>C=:FHlG)B.B9f!|~j_ϏܩT+$6bݫ1:V*n'.]bץq5gJpטﲝ#w-2X3ΰRQ\J4X KVX~H7Cۆzˁ1'D7w<>6SY0Tk. L'mKPz޾zo+S| 0ӕeE-5ВS A#0A-AZc`|Hot ?m*%6{ VOˠ=H YLM\V5/= Vmm xV<,)A:W~w~!(Z;XwKKB>JS!SCwQIDrqusw< FK b}js"{J;ŞN#+t6ȪwvobSȱ R SJB \y1֖F/).gTzWdS!4+giݶ] Rƈ`1Ts9mjriAcI1'9}cnٶ1ĸc< :2F7,CmA g;0P [1b!Cl*bDD`M!l݂ Mt ɞ{]bN[_>(AW>}dWiv}JpVte:V#AkWHhj5koW85 {4`mUB{2i_S"P˄ۼu)8ΟJz ;<=P3z(PD-AbjGkpi-JbRu:c۝W3cLgLo눌Rc f6=U@ʫoϥ>b[:lY330IxɯœnG@&اA{nU^^b`m+$L_ 8n, @G yYm1"pס7\m8U¿(FpfX1,QS谄Y)%}T|,02T+bϋzSj7m^= h_E{YGb 'Ń+:Hc^~S}wBo߃c {FZMg3B%1M3|J)4"1F#rP/v5tjv}41ptȳ4+PP6R|$oqx}˗.$sqp7Q+&]ج8c=UE P?OhHl(El>/»`p͟crwKi@@eD` !2&,FFq sԣ2"d P–O;+PR l4C\$ qw;.u\$=xOuF"gLR.'?vDor FMzЦzʝR6F٩ +ˢ..Fŷɭ.-! uH0]TAJpVR϶Sƫɓ xS Sp,^<6kG8Krl^.K9L+f*W_bM5~qǏٝHewZͅ4C::31S,,ʩ[j~Wv þy5TTo y4UVZbXa.@0h1n8ej/Ho/ry.`O#L:@9tAjC%Dadw?.kBwL$/yXV03,Lʡy"#lZ.JCnu`0 ]EC΢aSw1xS=ۦF0eA Y(\(D~l[Ȫ3z05"YQUDzUZ .m!`Ͽlyn7YM Bx7ހk i-jF* !: $ ܠå`4\"oH.remv w"a ֤&DFۡ2C&_{5vh4<a>`gcL*q9WzH& 'C n(1`"IpIa ՁVWZ쮿86.U5#‰2=okCN?BYe,&mMk@_=O?f.Wr(K B"C-vՔ$# ]DKY4Bf\MK6U\.Ư0wILug Iw?Jo#pJâjfwr nXP Dq 6: `>.NмucMf"<߭?o.3$LljYi $@&M2@8'y@3iizI.2t3_Koެ ~9 J4Ba_&F(]=z,L0DU  'U]6wA Ŧ`28L- y=Y]c41Y+MGԊM/xkEx_qy?S>4<1)݆DF6K_߆ ^>uȤGE NC|^R).(^oc5LUg@aAfw>HFkLCc x0Ϊduvn*e!r]9^%r gWL+},+ ¤jNwn(ezUM_].أˌcA`gu#+BL]jo߃clgP9Nm6Z{t0!fWk[fш|i]+ tơh\ˆfUҜvǶEy~_l ؟]n ^V^ݧ9Ӟ[.!1+p8^liGoԨbdNOsw+pZβ6β~-ל\Ч֌N` MbPM5XNc!Z;<<޻PYĸ3Luمc-1:C\*b+Lvb_#zn؅)F!*X[ߝ ?/j@8i Wk^ZksbO<m^ @ X=~!F]²x;Νxf us }RP\?P=FƦ;7!ͷ^\ XZ3RwW>M&h#P& ppğ'"310) "5#Q(0 G kaAf6Ł<nrmH~.!eQw'0` Nhb@bF'x :x f*^dBĩ(TLwWR[wt@l}*t;; 3ړF?\ !Ha6ܼ;wilT'b] $0;L>K50c'D kmzɸ||H{|!Aqj"d_r[~2/BU 棃'zG撞ED"<vQ+0/pKkX0 k /O FHDQFc݄V8`j]Ho]]oW,/>7Bc;cƐ7DҔͶW/{5!o\Zp(uKu@[ߦ &8)B@Kc;_sKĜ= f W,j f A{Òf5ns2p}%`ZhoP&cYVq_om= qXCO:oJ7KN۹:RGh;#| sܲs[!xBo"V_;`63րg0(l CS QvEk U33 aVfB^c6,֮h Dzek'LzШl&|!CM|KNO&n¦45")1: ^ȭa[#c0 [ب)㩸˙ʻI)eJy$%eaB~mw+sC  %u-,K_w.dC}"/:D(h6'lC3{"ogm3;QSmMة <N>sdVO#9C4ˢs`K~n&4oֿ;}Olo䒍Hsr3{n9.D;[^j0M.pn w&eC .< Ra#Ks+  &aN&È Z tk"{Fy/{2$yLzvKvJDyA8Ѱ>iWFqY\@o vb]t.ȅt5̙ccQ~A(NMO * 7##SW=[@nluMCbhF؎Q%"-ZbU^ug&vwä[P(":^x37ycU Y'4-'Z(*:#&aIZ꙳lu790Q`ez8<@@uɠqmYͱO{?0Ži,̭ s`XD|3IfjҰ3I/$DBqR1!N(G16Q > ˪:l=ŲQfD#vA B:;&|i_jRcݠwAgr>ap88E뀗$)ƈ^ND510ݣ=BoG{HxkZ'ur\l Az3CQVeݯ?F[H&EIe:<1exX"CI#j.>B#&oq&\(SA2whG>|?:9(UtbB:7⑋9C7 jP,WQ@3Cs!YcZ: Q*w AEWA5KT)QDA(Mךxap(1N( P@J2 ߾[1 p6fqg2>d $#5FM8;TV]MꎸJtl</bՃlm`Kꐘ^u@N6FH] Z`nou߲+Vgd:|[kP^T)SS(8#$$fPoGZ9a +OO%zq9n N:.?{c2;v+j/ʶh$D#! H`$tր.|-fTdꕽh*JAEuĖe01K2+o%b7fc#1Ѥ!u"VT;G\ȉ됓sSb Z"$8km|j/U/اjW ءt-QqZwTL3끇`/c~}dCa B1BWַeM`Cz*.tsP_6]uk,5K!YBI|dd\\}Nd󗻻oֿ}H,0UfΔV15'~ub!UH$SmJ%PV)e:CF2D[-{€AHWV\>ׅ jU%xE}k"gFjFUG}}~{ hrI:vZXmǦi0 o< ~Cp\Ysr#itW5.N9Hަ ).yF{&~jBeJ5rm^v}  «YnUpOp"hrPY(8373וO`~ mxƈTD^up7(_ _dq q*2d{^o` 0*0Q)̹jP+9LN -а`jnt(chg#Pet9n$aӋ{W$uhʂd_\A6ݐ$+,MdM WXDdIfrjZO9hw󠼆ymλu3e F"@+T#TIfT(JS+ځQW.wFcFżL9zu4Aǧ&7[{shr(+&79=nr(bڏt2CڝEF@CQpXlnvV/(hA#cд̔Ak& k{Abe ) 5y}{w|Lް ^݆!Cnxfi%#1JDbJBVGx9'P+@B8gZǢ|E4ɾOme]l9O 1Q~D8cŅj\-.~S#Fv7VinD;Ec0'L9L40eM 3e&z˥ ˥ z`[<.Ize9:MsF5ӾA;t9Fht` F Af+܄9si~_[YsL<(N:uymvQ@6zF/B/V}zCK3Ƴ9>jQ7 ȍ(P;@%hz2իeyٴ(`l:UiƐGOG=n;ŵyC1ٴyNE'ODzlԼcLA]m݆; ="b5yϣwp›+lΝ[5 gS 0ȹge_VRlq}GC{^˪z̠{3ȲݣͦQpVi)MhDSSFEgkW=5uI?2~mқRa'x-=cpΦdpިΪ{z@|̀h >,p8qnrCt=e I ~W&c(RP3B:t3, ѓM|Nn.#(>GYßGnF)4?azLΙ7e~t "x*/h~}"b`#Z-\LTՔ $ur$4#? +ЯD __2T߳"GPqW|I p ! Mn£_.&,9䜷dkə+rkN '7oV/iՍ]X>!.KŜO NL<˫?tRuկ6ѷMȈ'yݕ|?/ msbo&ُO6wZ$t8-n_~.LNlŖ?s]C|?.$^Z/cq"EUm=X-zX.L$ŶF#?֏"KCDG? LP?I~?5;??O ~EO,r*OQyL`$ `M9%q~<?~?~?Bs*?*~2LՙV?R2|$E:Ez<~5Ec!CꈡꈡꈡꈡP!_C=`w}sG;Gυ{8pj.hO& xzE/xŋ.^t]Kw%] ?{({Dc=qD#zEØ=ڵeߨ^`TgDk> daGxMhG846i`:z~%i 6ZVF[e &>w'cdiO#v;-ªwS,#rJ_=W\WZdž_ЛZxyη˜4SpfJ.F_<`@۟ R(x_?\~puTՀdW;DC tiKɭGrԻ; E|^\bTJM .dW&[ѐ:<(%c%4&לtM1ZUpt3쿈_8Q[@oKp~DN42(u(yGw7)QIG4לsMkȝ)=Àw|^uhTg4QK"j| hM{ 9i|א;[>uިAQgAKgg!2{~fq\x!h\sfk"wgܙcpק c\wAIg{{&}\ mz#X77<?ΞBT#} i~iL o]x/t>~ksfu@j?@qݿ- 4`|]!/h ~}ތUx @2r{xLI˕A 5b$91zv<5^If,v/x>fWe 0/'Rq .arvEg8amKviw|#i`Zv搻E{AT+$aYc8E:EԜ97'$\n5_YcK a_XjQkYl։ojSt<'qkm_̶ ɦ[ h.QJ+yg1>fOc=9F1zs0La}DkFaMnm[bmc0^Lt6smh3G٭ͬ 4h6Ne @-\aX\ mhEζ lb1cSt61M6#ZڈF6[}UNN- BfqN]R3(bq 席`ylܙ-kvSpa;q[di[Bögm}贏"~kE25h+G9[}y!8-.0ߒīZL­mZ;ϹCNg@ 0M)k~cv|Cz /th?̍*uѸA\tqMU50g.X2rޑb\ Íw4)gmSǑYN)%C˼r⣿=/Q£G/MIz}NHy*NH>FG>}h h̰MڜoIIגtfDnGcxh Gc82[ф7\ϡ1Q޳`i5ztl4ݢ6V[ڢn#*5)Oܘ&=8!ӓ^5/qf9IƉ:qexK߄ɖkdKVɖ#I{/, =א qe`FC`雪8Rw yT'UIqR%X;7}%q"~82N)Dp'R~ @!>EPJbuS) $V77`ڟ&RF#zgZs"YrOn#vl*۞46'3_/#V2 J^ ަY\P6=!Jث* VZq-%cQc`aKIEj=w/ t/Q p.:z HD D ~ؖ3.XjwdW7ErU&/d=3 jtpD`jDbFD8x!{&?)t_nQ /o"k;k_AM{h`o"ܙq44a-4"`G{l]S8ޚwRegէiD4Ϊܙqx5z׽pNiN7kNU>ŝ};=O,wz\q=rijgn;Ն89{㦷~9?)O S_{o! 4 ,C{,y*xO%,Mh FS0`4)5K @+z m,IO?AOr}mOTk>yT'=! &diB &dc 1 LY#6Rtهir? x듰v+^tsHWrY@׻ޙ j' 7ъt>~/wݜwLk»;ۜ@s{nsG宛szwݙsM;祄? K/ Gk}nǻug>5|nx5A_Mlf;5Bx =DM,DOwAZWxY߮;z:USͮ1ٖw!y_ޏΦN=LMBϺQZ-셪!-pȒ\vk/}mr>t -ѯ&Bіŷ|=/ן~?5}VceVl~Ծ T(nt"ru 7$ώJ mn+ho_{i/,o+(WyA}O3&d}+D7cnspaf@z;JѫO+R~&O 1vT[n[$o\{Sb}+$sw/~~.t<{pˋ/__}`;nuPӄ#YM?mTbq)L-+w xX`H"Vy_s[b,qeDEėi _欛/|OLifTsN}圭%j⌱y X 444hHX!a +a24'?JF '"kRގ==?Irt?ijюm@vkAt<;ٝlEv 4j\Y~*PmEEB˫hѨz֪;IleSgO/_^,^q<I`M} ~CKt詌@M[KxEXNP)JVY.Z[{7ᾊ4lZ5JvU!ѳŮHr5it2^q&7cPCw`E_}pkEGh1~nhS{@zE[^} J5aJM{qHnKaA3D/tmfUaܨ*8?KkLcy{ ;R Q_b0Ǹ^Rjv|P ,vPúR:/@G䋗"w,.51IѨcc|"U &JQDM5AV 2e,6<] *!H[TBR Iq A &H'_Oz} pae,] qO#0vediMUɴv[EKvÐ>h>_A6WҔ57ɐy+ dEIޘģ)I~*\֕u4nTC0z3ھȶW+(AEE{ ;%ry5,2ʫjZKp3:>,{RٵSd+j+XMD*&@SόN;]JAm XlNERPvp`\L&7; kʥAbg0PwSWK})T]Uka9sA«uʁYχ2$yĂ__}`(o}[@T2}HaL2*lIįLهӗԺA"{SlNxepfwh=ޛק///^m`t X/ldL3z3юboP?nwX]+v )6gXл@] TOnb*nn6{OfV g(;^Lսb$\6eLT˅5py&Tk:}-I걝ֶ_8PdI #HDC"&(dB(! \Z /cRp'M:vJ&Ş^6A_0rr\mسǒO9^:^dY^R2IsoqJ`rJ0աS1Bw1y c;w_RqhvL8+{=BG*6J;_g*p5T\siT\7S*ε(xl R*;}HK8J`%tzRqPa=8.R>NX!t\D]8ʵ>H^4Sՙ*(;SIcݹx`ᙬjvnC5OXM;iL(4]DqNj %Z("HHz8VH5=#DLAEKfŨBb zO EңĴx3{mQ'}ws/`_N~Y1Q쓂uG=^DD`dבdמ=}P{+${l'ɭ ],'t۷[\OtgMK \Ou|e8K$^x <κ3x`kNUg{;,SS C:[?2NTuvVu~U]t!䗓_WuI#`Ru!UHĥs:s:s:s:s zܳL Qu&:PGUg䫱 +ZurVT,[TP/KXIZ~? ]hS~-3tX %.ɫm k[6ɭQ<}:l*bwΗ[;p'4i"A2t( E5^K{lwY)A/޿,g59 RupM#&奍,]}zq#BL3R~3#BZqD3,gW'9dG'< f\ ,^47l^o6Fl~бef6/7>&@Pqxye9Y8~'qISfs*&)`Υ/LI>:CrWᦤME]TY|ޅX!/Fq:Oh\2 4*P&$QK; h#)['OHSyʱbVEݶVmbET -І΃*T2Z.[=h$8H#{ G]UR^jK}%0$_|>yS룕us0f['2Yq#k*èi^)٫ mWŌ6j<7U\K(d&.'RVMxVmf&b٪|2dÁhuΡy%CDaSGIUz? pȃv&7 \* = ]LM|ٗS&I@zJHโ#=.LjLkQ/Q\*nW[s?=|ݏV?+RVI.97|۪:A2Bf]\/$l3P|? 3(&" ,ԧ2ٷR߫+_?Ri\o}짿=Od%*^{Zg:'K 2g^8lv>РjGѡ2ovs1BssZ9ҳ9;\zjY,=U^-_R$vDeBYq|.ͫc*lQVlw 3OL&)8(@ewY;M4~w*deC.I]™_*5=mML$^^D=^{c#vV>[~1S*4d%W7P 2H`7G< {/V:dzUcs<++"*EUf?!r ɘgI)B !KC!]g?R0SC*RЫ"aRiqzt $|!`}nUU=Rʽ)DKבCZR>@o8SPYgAjЯ"nO뭒ìw=&\tyuyqai~ d}hxyyd[J.ΛN.6"%ocre ]) s tɼ|`flM70@? pc@'oC+(&[YϠe"Tɫp!5H/{sx.O+&|#@/I1X N^7;M6ȹ6%6^"&lWJ(ͪ!)Жwӥ|MSz4H.s]J]*W{Us\ޗ"V*W;_%0Ln'N#r/v3hQn&t\Ѭh,W, 8/Γ.ul]PDkw.шaJt+w:&.( SJ4q&N\ 1G| S_e孵21,ea6X:IsB:GBl <"Q U#T?izyH pț7xv[7Nd;ٷIG4P|%-34%O fML/1Hോb 6uT$ucb]E N߬ESXmfaC(鰉cHJիx9 >Kp3zѹ.Ђ [_/_,ޝY`6hQ2k9m!jwLJ6Q7H .BʘM,I$l1^(xv4cxqʹTʭ;4zݸq%yrTdc[WA;ð ر},WP[\n˩`&K.65-1Mj#(0{~"K8"*!)i|{ra'CI󊛩u|=Sv(_}Q8zvedOb _5/;]>g޵Y!zwt/^>HGɎPu7 hw-ֻ};ֻ}Cp9wBkOU:0i MēNHf1tzW_ZڏTEHUTE(*U:XR?:+b5w˯ XuLH;ƈaDnXaEx}.79>.H lmsrp%H QARw=ˋ ;{h9d\(vƀ JysͳڔLhQ/tq.&zxړU ˎxM'[1p[Ŗ^5zNC~ObU!O҆(@,=m‰n@pBӎ`0 ׎ŊuerڛHhȥ-Wۿq̋1]>>+T3y9Cȡc=g| kG.f7%]MocWhWhwm춱W /Ҧ_ ͱ">+=i#Ja"⥤TR5jw;M=._X-sP,N\L9PFj:A}O1Z%"xXHJI$ &FDLtjT bȖON69le!M) H,yKP^#ăRŔO>z2ǵ/} iKw&*C9 {u0\/_r55yd"r6n[ș0j.Un<~-V]>5|`;VuNx} C%fZYjzE+p䲣4i]yCX(MFx\4l{W;m?hPssY۝}ZͶr[DɲY{ޥLqWiRrFj? a7a,ԡM颒O(Ң~J"]D"r:QR8z {X!I2$9*b[=4nJ:Ҝ<NqtK09=;@δ5>Njc;_oZ̄zE !%)/a _f{$`/•>g52m+͂ x! b4gNX^;{|^bp+-?j$l14nxU-#82j;ŵV-#NN.Of7tHq]F3=؝^=_lUW)HqUӫw̍^aoW{C+zEl^?ӪW؃lUW)HqU;ԫnucﶫ[.En?ԫcjY+GjFܮ^j\jFRvloA7x/5T|T<_;**coW{C+zEl^?ӪW؃<fz7tHqWF3z=؝^]fjl~oz H'gz[;ԫ͝Tj8xhq[^J(;څ=٪z-LU*JCQYUO^^XqԂP4V)FuMV-j:+)mtqƚCAft.Aהf7!УQ8GHL`NZ6W2Kl$X_##bEq/cZ"è2" hHTd`kEE%8Qv=Evw;!J:$fsc0bЩGC4)‹oBD87x0^VA%[ ( "1g;+{`MSgƠ,9+Ҫ&g prRx Y:%! 6W6ŒbsOo kG)dQh-9ҮÌKmYÆF5wY@n0&rt Ŗ V-5V,IQn43.g  - 5X@h<3}SQgx#[u6E%ᨯwF2@"d8#mϠUdٳ- ia CR[ CB o5Dh$>aKF@ @1(NO1Ն'^i/<{}~(y _On3:-C8@͚MSoI7Ӡ\i.A2Ӓ<-4o\ rDg+[]5mZK:TDMЙŔ,(.vR P)  P*M\Ӫ&l+jW%jL h]~'Aj?P/:6xBBnlQ?e ns>t*ANTLCubVÆF^釁n R\0=}\P&z{% $F@Slol&^R&7WjLzb]zK#T_OUڡ52F}U0D0 9J٢~ѝ^u>t*Aaz?VU0#أ-a0&1BdCl|KdxM4U07VdҫҫXbA\~J1K:! /@HUSsS z4ҫzӫ{ҫXHbkKvW_}Bz0ӫ WݦWaӫ|4ؿcrK|JL U0c.k 1 -1 548Y10x3qS1 )F4bow4Xi4]1 Km1 a 4 4^釁ncl)|˘fsG'%.5,r54h[0E{{0^7884' ŏ2́ ]O vmcpԫx#WkF$RĐ/x$|+mzjMlhDw,\ q5$2z3;SȽb/lM&Ⱦe<+a_9f4If:Nbkqamnq&ħ_}YWmXMQǟӘVUC˳UE4 AF76( ÝBx}=_3׷oY)Ҭ|bR?;5yXFWb#sBFx8B&SMLNmwLE^*Q~UD&҇W{b ΑL}3! TTnlnV7ܙm9Jd] s ^&ӏ[qNcJ 4I4.sMM?dX/G߈ϭb{k TC|V5HtR%Uh)T"VD= 7A~Q2w)zXjG6(ʜwXLo,:̋37l+e;b ܻc2դ 37'xlTV3֘sݰ% EW6 jښH j/[#۪FN5V3|Fo6aKqeuL8Y-?nV;Rj1?Xfey%^%#WlU<Ϸ-ן~*'/NNݷ:O|XfsȊ~E:w7+:R H@lG,}LYtQ3c86f7.Kg]'>cR Zɲ\Ri[b3MtS ;VSDmT 8CqYA1~XN5z BA~ܗ#&c.n峞?Y%\,`n_V8.Frg 1ڞ.o|2,J.:c#y#i!Av̌;C`2#"h?j&ۧ{,X(d7|r]~1|`q \ovj{]}4#X`kRJ\J Ã3UED% dH#mWˋq%ăr>> ">c?E/}w'ˋ7= O ҅#Vܚ`Pq?GUqUar9Gѣ'8ivp~#Got3.O1,s'#Q9|ǵCAMÎ1rб`TdC#֕f,nvXRjs*_|1Hkw[`dHB/OąNcg~:I?p&_F 41SSvgn.m|6tkSބ9[U6Xt]8 bRqjVV'\YL]%S'mAD__j#ď"vƮ)Js۰Pq^CCHm>Y<ܐiCߐժE%IQ$˩8 _{PIuV]á9OYcU@σݴS3zV "Y}b0K~zXg:h^9+ !@9xGcߝw?4&#|X}]"֔2%K%ߧ/e[B'6pI FqG<,$z#[qSWD^p$u#*F5-rk !2H,b;Q+p(p=lWرW?,DyȒ\_g^fϋ?p7>k>Pk/wiVգxߊ6b4%%횉C+#ɊK6\nj~]#5YIuNϷv3T &[vƪm%(lƅwF0a{Um1.>qf)J`*p3.A`EKPT'),wJfnM!c6d?U 5@@%j?l)j` 3bpЛs h1\K*d+vh} {h/vu 1Nbb%g= ?7a+bE;eȫI MN=`gN1!7PzͩHm\cω: eUAOJnp&z7 dhgxAXS7㬮oB^Bd}B+IkMWiVC8n]rU#ta^MFvW>'UfY3[Hro>ǯWYqGʬ(P g&*\Ym!فd&'qX qLF^|ӷhvMn'<&3jWz6Yis/瞠fsc_f$W]ߜߜ MU(w(yňwO=yw_ G'g2hv5F_<"NU_[-EUISIOeO,bylnokV3 B~!h S-~(*qx+A"x,@nTaY*D5ӓkPm.R@ʩ f/ueѴp0()39`h N"-KI:XH>f$%(%ͬd3=vgt\V 6o5ųcv_%j?`g4,Vqbct=<{ZT҈LTOSFM5"JFWm&oLagnҺf`.3{48`U)kL# 3%~3myLHXb,K̿>C5?.`]R 9xԽeoo0_DҤ\vcG:#)yL"t#!ǿ}=p۝y_B3TC_؍\V)64=`ݖP?f؂QtȎa!ODŽ-0R`&gQkTݒX^Xԋ ,Ռd#XL0-A I!B0) f%lT3!GPB0w2l[p(i0BO.j3): e+743\0'1v[,z(C +NJZohO%6ʵYoP_]E!ˋقOO(q qQkD:4ֈ41! =㈩)#lcŒ7yN0F*[ ΒmC5a[ܺ%fYS-%$TP#w:Se?5k˿1:1ӾO;ө Y+zځ!Dr6JXjcK?/ c>%"A WldӐH $Q_vZ4>{duC}I*mZԪt__N'S<)%/f?EJ]v@K]DĎhhSی'1 7Vs(T'K:Cв)Wَr[^eβWlM WMcUVE6C.Crʬ %^,1&ʕBJbP=8/VRձs4ߵCy\]ҳcWԱc[_1rN̾kdS` nB+UGJLxTW{Pu43R량_5{mh6m_,w{xrtú1nB.A6Z$ )jFBq~Ԇ+:38Z)#rXLL>z>Iߚ0.ϧ1[9rh:oM[Ӟ[<[ӞIn37=k$*Rbc*3^M&[.pXx0tXF`.f9K\sǒgɸۻwc#673^kθ&t 5]Iܚ4 dgҤupEΡXgY> Rxu NGlJW# ͯԇyFVjraR–6⏑aW=.{uT |c)WK|>nm= {ndA YXFa#JLܬrNH[g(?: xm(Vi(i,xӰ xX7|kcĽF$z#W[߮"x'.zճ[ ??g\T(%7AR qlE(γ·]-RQcW1ʵGpa׾%P+Q.+ev.&4Wdq6RU<`_J {:-X3u1E|{|7i AY sl݂?zEUʷϫܰe0\)B@o#Ha)M ]N˻lpm2BAc@JTH*XȚݗu|M\+C/tfk؊p }Pm_7W_enp [EEu1k LR)%wJL8DO ᕟ8 {89 4OT,x8?|NK+pn~Yg׋|/OzC8 9>Zs +qsFKk\`gMhWyY/2\2轖^\:NUR?Z$2%הgVM>&Rh*opڷz _[gH Kh 7ˆp nfG,+bˊ gYѝd8yf]6( YF)R\ *QzD"'WCx!DrXby3Z/?\d;Z^1r<#B; E9BHgf蜖ebɩxa8P&(|O$70ri2obIdTd:1MP);dyx=\Ey/LO?r>- O ._5HH80nv;ڞ@!5Ub{-o=nѠC3IԇD&ck7 M w\ֹnYec tu##5]+z빝 jC6B)W纗*.X wi3mۮؙPd{np}PÆCQJe ;qQ0^ڢAcԅ'/m.:H@W8qoEE .os:e`ӸJfR[~gSucG8?,!F Dԥt`-ބ "C+ c&)fl1/#M0%ʉ[㍹K/'sѧ4dJ޾8D ih:2HI˰tHur݈B/<䰥:Xw|8H &$h4=Ahbڂ&!;WzJcztIF,~\!XăMз"0hU._"V /B3 nZN.>p# /`+K] 0jRC ;7Oog4 Mˠ<ӿ@T^"hJ % ȴx8͹yq]˂ R0%“KI,P*ER6g e3bWcR%G],Y,QԔXf`5k,*7]قˆghSag;b2~а\B;O%;OskgaC7yȯR:`5&KgM5H <䬂(vJo2 6~%m`fVj.[<. Zsq@Xa@F)P Fm؈?8[rgj_OnvB{LbDk޾]CmɉUreW S$tqtM$kf*; T|]Ef:!QVqX _>|w  ɐl _"ID4d\້D˴{1a~JEpo+o3+W4Db;OsޯT'1$1R")ZVO$)" y)br)Mcђͷd=ˆ6J-& ]a``?nv*#1A ;=ZQ i_PT )Ҡ=1''{l.K@K2w&C0W.#jmZnwϟ?fW=8yqn=&k{( iDxFc!Q=ezv RTV5hKRZίJ*M$"6#XOɃ^X, pY?܍ _L>ቑ {_clW/ϭ#OޣoP}=Xpx0_9awH%s2E z]΃z_~'t~O?nps p o}1_/w8JC^>?gcOK`ۭ{=jlsqzD΂_W_d}Pqr@<,G=-'$O ͔4˥fDp|9q͉y ;K1{,RaQ셝oF-l̦lJ6`V 2x{ .>Pǧ,GR! Bݩ|*=A4. Vl=!T3>qچ'Bzb[O8#P=Lf݄zTO@4=-4-RhZa4@Fs"(М A<.T9|mʕJ1F+Ũ 8 !&$Wu=JJ1VzRT0W>-+-sl{`s+=W Js <. FbAJ,xsl&z+ո+Ր+NR\isnT]0:JɽAozJ߬R+#I uct0M[z/-&^nPiOZτS06oL42%D}RZSW nﲔ7Uݔdԭ)c 'g,,x3l&zиlv%(7Yиd:+M^*]wD{<}ߌqˎkQȦ4މ"~'ˡoV뜕@{1MqL)nFsZ>&԰RK" QMb*C#w-ն]ϓbԶp$ƛ/X۰n-gU[ΰvv~Z,_"j6WRtR-77}W6*p8)aKZ9԰;QZUs96ހs(0{ D g=盈> @V6gcheA{6W=* Kj*O+Szk}*5?ce+iDAz ş*^s `rҸj,~8آ@QRALIb\)iZ$Vs}օ/y-:JYoB&翁OS]quܬh]tכFuM|wD8e_օ?EN6QfD?lDa"N4/%<Lr ^A5ڥCoUr}ܱEw\;>g\Е\,kZD +26Bj:)L e88hk6cWXbZ EQ%C7IRqKx–" / ˍ0fsӐE˂D,4`݁)`-L!`H/*tqEMZ( $`)5<_6HѾOI>XԌ2j /^=k<kZD@Lp xgISеaT6qSur%`(V kD)5;1BѰq55arW]#*v#YxiYp<@×^GZ:%5kyoOK1-p"w-vvK{0 Su|\DXպawϩdgd9z]NVE/cLd55WF/\ T*s*IDGJ'|T/`BkX$ K_ppdQ.;l2( obу/U~d5Wl+m|jKzbcDyuNvp (M 﫝hDZ q5agWp:*\AA!/cS- 9 K6WCf0\ kksх5 N!hXԸHšTiETKё,g,8pK#-qOÒAn̵J81D8=|6O;]b8@C5P k ͊$4`vs0 Yݞ`9L2vVCX5`O5jOyv1|#Apgo_w>~؈B)6(P$.|))dnwŪcosff O<(юut6jHV2-"ܞ"dP0d2nH;fA/vc HNog&eMp݊Z8*mU;R,h 5.t'?p\:@N&0(Ђ,䵛& Jt g" s#m B0&YE'O5b}9\gcL㍂w8ʶбFAXBzP EhW-֎R~gOi H(P:(VA ,Ǚr\a}}Rr}ڽú\f7++DJ~t**|v5pۙӰѝ@\Mkt\ =__foҶ i:%/#F}4_F[3@´5]Zlm64YeM2nH ny@>=l|6 l׹1 _}J.܈ H~14[6'e"'$պrY(Y` ʫ%.%.,q?KXe-1Ңde-|uSnJVO䭖V Fz?*/"^qEdaV|b>n%lTu_o .,]ʦlʦUY*fVBkşdʳN79H,6YG%zN/~+;>aD0)9zDxf^B9-=gHLnhv1=7z8&ێ/ jq2Fvg+ǬH4(֐$[ r[}!_]vvw6#p]_rAC!nmBǹ,uMHH*Cm"%RbA'ie6DD+ Ɏp5"O9ovW?#S4؍&J,O.uɘD__7W_| eLU|B :Ĵ"\Hwx /_.Ev[Уܶ_FWF"%80:itR'Ӹl_ड़ɼkY@yUнbNNakZ9Y2iODC 夲ښ9Ew7gxvΔ.pVII8HG]Zۭf'SS29=<Mh_zW.F"xRFGwXty4bV{Og}%TiFYٌ/(f(Teiƒ* WkTCI Q-4aDklQ[}d7w':x$ W6I:܈'K-?\3H:;}<3]xD+Nr^nP^d.+ǵ^ϺԠ!嶻xicϷ`}qc3#m8cOs>#y l1s͙967EJ}sRԯܾs%솢w쾼oN]0lW6*͹~e(\f6eh]71O%ufҭ3SyV_}}o@:t9:#3 voL~gj> wÕX3;YOp}xקק?9#9A{^a؃Rx3@Bz aO?a`P0g̛CjMzBJ +,c ֹjwT#<_7Kx)t__ ܣXt)O/N_def"NUXT5O~#Іq MakC'8zw7NM\6N/3{R&(L{B#]n+ & SЏR$EaJ.LĴYF }.!_|*~/~^p)r,(;@UU"*ftؔ*Q[ ڬw%iD;ӑ׵m3X,M|s?*ox}A׷ofhT% 3HV[- ] <G:I恒~kIXbN0Q5%k.S54LnK10g $w:Se?M_Q.f>9o\mƟWr3L7.oLco3xmf6n&`Ps5Yjx0}|LÃzi/|OEv8-ޫ}A6ՄF~܅}F?Mw>vCȇbi*ce}##3έ:_{y&;W6WZ;\kKNM5 v!{NznM^ bZPe+O m ez~P(s+70)`i*j%l9%kY{w[H&~gH⚠ڐJ,;:J"M:ht9/gI 4A~8xpqmj&)*^0Q.(é8I%Gz|+8uJ>NZ(-zPF]!"I,AKӒ=7;qjYMZ@B11 ͝xm zn }l/.|u @.v8t^qnZXp!3X^KywUӰW1hYx1wW5/* v0h0ffL, aZT6XcBb3 @DN.4Њ4aev]J94"`Q+.?Omln[Xˮ5Q!̋X{]X>a)uS_Z/Hv` 1Wcf q\z~%#|Ir7RNOΎAӕRsV/Fwߠ=giV.*?~@geidF PL]*@{L$ &\O(ba8BY><'ļ0O9 s^|H3ktI-ay&e_HH+ij+O z$:"HۗyNn_!=)xZtԱm'wD9oKo<)(^J야̉?M M\;G4gXd<,iw٧@ݸuT#K7otA7jD罻~\q~9nr@,-ɔZcnu+Ǧ6丅%^mq낖H&>[ab OyLļʾԍI}e uzJvĠRRzNUr;K7@_) f+|>y:ΙfkӍlmH24A6sc!fmyfo,&PiCzcQMn57m{}K\o,9Vtwq:ҡn݀}(s:Td9$K/TXu_h',Yŕ^*O -ulv!ve>+^Zx5ݎl.8?[S'Q3vφjcK{]۰M2ͫ} 6U6,0jŸ%:z0&0; ÷r;% `ȌבȍwOzt,i1'I 愿/Yb!V V1>H) TMLjm/翽}$-T >/1:dD x|Lo"BHp]Cי@"Ntv}uz͹1n77w:;?FXW]6{=@0a`V<^_ ؎Gw5G}|X-i@ k0/8}3) .vW|nҍ|c>6v49ԍVrHs>QBk@!ABӽ]+Ǝ?@T0yP%5+B xVȢ:az:x~mHzd8X oCÈG{Gx J-iB܆QT-D?Z9$ȺJ?-&5+E q l!ōYƩ⥖ {̃ Ȳ#M\&Yv/gonAWirkS/r%r:'0ڇj`Tmw|Ԣm6B8R隮Ԓꌅ>OѴM8:C;)v* کКh5:C;ah 'C2'ˇvJ|hKnh q=ah '_k윷{%sI~;᛹<7N{K.=9?5.8\a710r9Y]e4JC3I[O߹ln)l-dDHO`3=LDDrM?5(?m"%˾GYޱJoMzs| lyƽ?XNt 9vx(p#NFJyFB}'1lNdp< vĨP)d) {drF"k*yZZcZ=N*+ؖaz 8qyJKy甥鲆FUS,]3o5&0r 3CprpzNE,QKønGG.Ġh4H3ԐˈڏX b%,-n*9hcV,7UA^HVle!LaJɏOQJv 7l MÝ'3-긃r9P4!w i ~$O  C‚Hu^O q xVVd@If8xyY.c&C'0uu*yT;"-. :6Z]1a9!h,Tx`{k$kol{<@AO[ɀdO,2,r E|*h.j/j5kK2ɫMŨt!Jܲ&fX(CODt*QV7fV5}% !7mRAIo,CupxXnaݚmo={L)g.>&yVs ǩ=1F[i5aWPq d--r Kz--fN ;s'z\]%y&D`\}l#&@5.nrˍ.7z=N,O=^'3z3n{Oz߃4%{_?} P)) BS(ha@:.h?(mofvt~tTPcM? Bj@GvpgO=j+*rz}H_x،8^LF8}=5*i_wh^Hod>A 3g8QG"q'{ 'xURO8o~Lo4ǿ ǁo05fsf ;K̰E sq)uV'Y2(\{7mզX _.?[;1;WGg&amſ.ac8F+_{9 }v)P羻5><m~39h:rZ-ÅO" $,tfx2^*%SBڐH'W\T T? a}V@2!H<[,b+f7*GưpBng`w Ta1XmZ\m90 7L6zIC-Fj̥G88%ȗx<` Xb3`iIaBѫyYaR_05yo˯T3]̓E%׭y@Vziq}k"J\Ph꿵< fxp:0a!ֹwLernp7G+]oq7,Pvt\̎Xw -*$}z"$ ǥ}#!;uכ#gocʦJos2cK s)_n?_4y.8׷{d'֪Zx|nA gbwQ`ÉbDτxeN6H9%}^8tMxM j D{0)n#Kk/@.;0'ha*2b}V3|1֨WLLV\&}c Pb2d|=*rx\4>*) vYl0O@p;@aۋiwYLZ^m29@0 TXVr_.ba$,*X!#3L)RQHP=ÍU9XiOhc,;'baek$Rc؞yzGrp=Ô zŬf֨W*:9ŗY Pd͊MN7E 7 X #B\ Vߎ`݅Wѫ10]G0/zY>6U㈵SV}gPa.Noju:lN%5A€:[SڪHe]M%?N+umM%W,lw}@W0̓+%y #`)}P;c8-r6@6fVV+ogPko1j ? +sN;f 5C]S u8 4]+;\%w| p5y ܲ 'S\lyHwhks%q9dFۓ;%9T|^kK8x^Nh/yzA a(SBXRP6f`R 5[6CVV n|D3նIȦ=\d YakGPפRym-vքtVb .X߷+Xu-n 'Xa\Z=,*He!M%?#N+u9M%W,lwQ@0̓+%y #`)}dP;c8-rPaHy0Ykum8 ,0qJϠb@8~AV(vD[kϫphWhw`-.fq pKlje]N{_.blclF1 xV!?Cٶ/UH-C]25lmO[ V-F#Be9ߋau\*scvn8Ԁ"]9}خ^6'ǞFSVDNIP.,amOl Z"Z\Q 2OonK|suFEհ[d?3"W6X-8zDT2F`taŐ k%)<|["Mi'vX1 '(^k%9l&%m]қK94engK xD(|za\BX]C"PeUA,Y[^mB׫e58Dv!B+AC*c$7 8j!2zP9@Χ-ҭ{> S;}&9j<</M EA0P;ݨWF"LA#(/iD.5bn%=ɇ&jm(z#.޾eT[PEJa`̛k~A{7ԟf@4!q7wTz *V¨h z;qA@j4Nq4Ozj7Ú 6Da] e]epx:O^| 27 ;'ܝnB_mz\aX;|o}07MO!'O0{X,, EÚϡh@UFd\T{-*_h$V*Z^fvD*d GjePsru`6!btOn  G$m+10jXi36:rcVw{{'xΨUPr!Gt)=5UEt`no׉uW%U;ެ>={v Jw~ z}S^.Ыɻy,|7Z?vMXL6.f:C4 S`8ƃf$}7iߍ|s|2[][ CxWӊ߬!)y0|<i?sLcјRq dj-Ʊ8n(cQZ?cO[q\D.c*N.:霘J礡tN*et"d{I!:|>Չ臆"AW^Z?Tg'h9f 1qbcPLu;8m>9ziKG5tdWȫZ^P_ Yf>mDoH NR@/%h=Z~vF3֝A5,;AUó1m]XeOftҝ/W7uD de4H)?X\*~>aL#{{mxv { TSlEŗ@)xOz7sD]Cיwyv::=̼up5CxB?oe=GH^g0p3)I8c[z1z,k(a)]9~y5? c0aމ/d_=1Jp<wWT3 az^{lG» dLJzh &|p0M ĘJeU*3]f&VIԱfIr(퓊EYHɖҒ"YӠJXfc]Zg`:~7 {Sˋ6:0X=ZB\!O'BeN):Ֆ(ʝ$5Nlʇxx,s;lL'oau~*8FGƨ漏nP^uJS[}|H,,r_؆uSڽ;>xDdso'0np ^Cgi?2̶'BcpYSHgh9PNX؉Rr=Fr "ExKg}yp;eNh_L[GX2Umc_xRW݉q\"&HĂL߬W w42{Z_ wrkFEgp'Sˍ UpfrJQjA"ѿqtD~Bo`ɞ$+X K ýUy?vmPpP8jnKa Sr h`+oeu\)j!ԽJyەdg8*$Srq Wqms*/fඞXq;E^?kzRm n4c')^Ʈ`&;#iPlnP(L n@`®Ŷ@z%Ƚ⊼.KfKv !uB0Pއu=|  s^"8+%2dkT\#M1HGe[Ԋ0Hզ9>)۵@m|k ȓ~ {Еy\nEo(d6?O޳ފ +C\@'Xv\ |z;A+: Fvf4bmr~[p`xV{*`PWynS˅_ (3n+>YV`cܡwm2X|g އuF7d'zH>WizY5v7~_W]%_x8LgB}z{Ghx5avjw4tJjIz>bJ|\HwbE@de,< n{FxQTZQ nEBh܋H/Я90ulp1۬aYi 5db)> 6×8AP)"YCxD{K`je03Xh&˙=߯7m "魚}Nbѓm_sIui'Q׵:B%D9"tB6*9 ߔxR_,4 >Y-)qzI>:Cs[F(Ww3vWtdYwtUZ*:eS{O+vΙ)8.XzIFRKwN#nFjkp[ [aHQ>xKñSU#Nk` ّ*uW4ׯFse]gx,7)SjRuNh48MmqqKc+h&>[aؽb(OCyLļƵԍI:Ss?,ARM}T‰Ŭj1IDuėj0HmuS3U,,;YySc9*EJV 0T0k4dcT%YǽﯲEt'k U?wmzy-{UU* Xq~{Tn9^%1-㲻Ȧ[YM:on*EWɫ̓Gm.?T$4. ʙI!hЗI tIJȞ:ǚo{mQ /_ZaL& g^PHk,L jw fAm>?$/ݬ:M{\2d-.PS᥅}B<].If0Y['oau~lh&[?_F` x]^v9?@K'"ү|nZs8wtCsGB9^ KI6HwPi=>r/G>}qQ8ȩ򒤽;⇮OqHr4H֯H 8s^h9hc9Hs~K t=M"q{USmwatx8U6?v | @BG`*Y}7'crX~qT2SJK~MCB Tc[9oy^0p<Gn}Zoףevl 3'C 愿/Yb! V1>bw) d($޾/^.?pH7&";tq@>?NgWڜvp'6j 2>0u*LlLI>Bu ~,iGȫ.~wz0s0ۇyqe4ߑ?] ŏt*FכגU 9U !~$uA.cX:|ldzl4%Y)udELfL[T4iN i(aG)67#߫tzm;TM,0Yva, H9'ʶrJ(!Tg뻻sL >B,W,:;O_כ/=bm ʖJk%G=Qzԃ6Z3zf2nYKXJ߲S14i3jJKbᄖK,|H>1 !?DhZ*v!# $?`SKwHu-!:\;[|N6Xz>fnAwm$4H$,|Mha΀Mf)/HDĀdK4AL8bcR49Bg$&UzQC(AX(!(AgPŲ'aTƂ-|%{XJ%x+d hͼl=!|8R V• [glNr5$,D`tA@E bǝr8ނ5&MfT=Q[N$~{H*P,FK})qZ`ˬ!"k_*s8,X x*M 9* +VY SjRaYm$(!Yc'AuO b շc8+¨7UT~׼R@%q4lT8bPU]3zF,H7`"L7߽Ȯ7b絍`DqcoqRǩfa֑/YuF2Ԟ0C-CM\nHPg4˗͆xb8!dz! q# Oxj>ĒF<* `L[;yȡD~Y>u w)w+/},ݾ[ߤٮRm7G{esz}Fk\*,| dy1]le Ic5k&Z#eI"F+O9 d89,9Vk:!ߏXQtzQz+0Ko.~aKwga"F)Kx'Puw/7>I'Qi=>2f6]n9=HKFS\K{B?>}>Z < F#~$%$k@('P M<|*_D >?$/{} dtG;惘t a# bLQcMp4H³^5Tou>V뼣*>4X bW]x'?FX7:yz0b4UG\tbDd~笯1e?`hQ]ϕfP8?2uӺ-nt'PS3[h.}HCĠJ(C3w>.79֫ouF(=GΊgC7IISXRoe/c,zjRb-}f%)U 8>/S{-"|Aj5IRVt\-F*z`N<|Ii|QUVT%RNypG2҄M[1,ϣdҪd, T&!7.ѪD 98;(|i6v'A%1Xa=c-C+2"C5`&c26?E6}_q4O#yɊ$#ɦmI,VxAGw5]*FwUO6XHw e,$)B7|Ik=,io2+&|,}q#olIas;Qi4Ub[FcvэF7vv7 I 6܍)#ƴm 66.Jv J^;iډ֯hY],IusK߭R 1sKTItS7wN>p={ 0;| $]COnhTג9"~@#" 2 XX#*^ rADia;Jy.˛J F#Q7Ė,#٬M<47r@Uepv&fL:a~wQ19Yn8v'ʅ9;6Mypf9vϷWd'֍TO kv.yrcKI6VZZt >{}w!~:)4GP# Gḽ*ُn7G%ۛwoog Fo)id[8<8"wJE᭩R&bT(*R2uQl=(--AYV,)1y.?BN<"¶A0 2N+^}Gث."l!*5ڥaD)B[tml]}x[ȏ-2pꂣՍJ^õum6؞5ۍN _<3\'mDf5+g,Z>[?"1\ k5Пz*]i泙 7wnٙq|56Q-2F΅fq/kn7MuE9+_֛Ex6fUG{o Iة׊C |3B:䂺S!D2檓@RR!Ë"~TE,nF-T 2#킲> .i& n/(~TYoo2kjzG7Ctj-$(wi"İC!涌7fceq+3cƂ$l&hH*ɪ옢SpuRbj݀i tT;Y*O$dPRf*BT+4汃F0E.0VhAKӛLD ;֌N1ٽÕ;-̥_1u68Uyq#n4CGDtuHLJ[H={*щ#H(kؠV!K٦;ٞ9S4:W)Zmv*/sܰyRGMN^s<x0 9vyO ;߱7qo>9A*iCA=q z 4]1q~s~ҷgo/~y xGc?u֣w1q/$Y Tt#P+8Ǣ#pa3Ԥd(%ZhBXlviq~$V^ub6i۷Neo=n։'m:ن.f4ٸ=s acpW: HOd,]i5wv5A{MNN;\qA&xgj8 ֋^ǚ**g6iBKQ0{ ̚Wi9AFRj$I kyyIi]4RI )+ t{nf[g Z,hIҶ`,̜mAnZ0%/G-Pt (zo&(YI0ɦѹ灢c:J6 $tZQd,pyR0bw'b; 8]AYI4u0Y\ a77: 9 wi^AC).As=J"Jr% %4D%J; _W+@7 `Çࠁ_|>5>TE HHMe8)GvBC.k#5F 5unpMzŅ"a\x^Jt"mǀT p `z}\ !>DDgZ*+xue{mH@ [ABAMͮ d$_$ſW. "&l6GS2BF"p3U/5OnUk$^[v]babو.SSZ;MP_6qv:c RN(Bˆn*:ZT5\7fJ ͤ8T;*Z uTSV=7t(UUmm46ƈ T#ưX:a]*kQ?5LQMM +mLGMNT_WzJB ~ c3N} ;xv<5+v0T*Qb)ѩItI(X,%HR,dUMqdx̒FYpf҄>aBO*IsaO9"~fܛz4lޱl9XAN~DF:3lyi*ݞۯg=\O&[n19O unܠڬ2Lfyr>ѥ$thB@e,t鋋߻o&k ~ElJd|ta?j9JX ZQYD(IhBXp)7U h)&j;hD8 i6 JߑJr{~+ FD41TBѐȦ[:j iPҙ9%Ls?# O 8[7Ϟ%:}!me C}|]%`("*j:Sr/33tQ3^_Bos0O]bE&_XWӗ`B O3 N#KKzU ,Ah6wV/LXW n* aWEi]jIB SBԕ&g8eE8nGΗ/=dh ~MbpB2Vu>dƴ:IFMj!$:ӽցsF!<\/[iyhrCǬ.$ExXeWa*BAoӾAP'=4)o񄧲 sj[3n]fpZYw_D S= =5|5۾ED^5סyVXʮqr'em7~.C0uĆ֢,0Cjș VEx* 釩2tqæZuT{jHT|w zsC{`t3J~Ofy74~z )&ق;{B~Z `4%xLn7jv)1O{OS?ܐuvԂw_hkW/9;>61ZnG_ly =ޭ76<l{PV#M\3Wnէgx IA.D~~ZCB'ӱ? 6?-CiUAxWw]Ts+eTtET_mIXN30yÏsoGeLΩ}"C:DiD,q T-d>@ۇ޻`+.~?yUʜL 12=nt"kt@?$,!ҵX;3Ë73ڡkFCq8 (g !=dEٚ9xAwL.00lLK[(77N7Z^E&5>(d~hIk"; D*GT*c>2YK@m.sJfG3D6Lˉ\6c*_`JSN0F(\'e 75rKO2@NYaNCR0&% (|du!0$Bj[ eIf@#)`Uf=6$VɬdCFRϞ1x ]vw;Vnۦytj[)KslٌE z*7/eTR]w&pyO-4c5ttܜޮWpmwryq܅K/a/MF\'DQ⍠XШx|E<.Gxs k61><{mo,67k@_u|Nc[r~$眢ؤb~۷^Oahɻ_%OTƷ 8I8",R"#D ]%*.{؛DA߁aEWנi}l/r*,J| ^4,v/^4kRԪn |Y5]\ۋ&TvWb٢'\u.(QQFTU~W^!K Gq 2LYR.-HJakdv͕ӛtubs{C.Ε{tn>{suF6u(LJXɿH4=mKq{>$Jz:j׭agjK,oҘC] F  ͘I;feBzp9i78s-n#[#Cn+ LqxmF>2ZʄÙcPHk_fQwmts5[>[D7 h ^D1Ҷ0h@h KRƪdTvUI2˷Fʅp8^bd&aktPI&P![87F0gN\Q׹oeJy9'bfp-ɐJ!g-سͳgbޔ읳Jkt֔jDZ:=瘚kVO=eN}HYo/ |6梹0IJzU^P.uw+%6Uakv GֺL8-no/)S]'#E"Uzxvx;߃{V,i(ucjeS_2N|KS^լR T &5r T. ˴\J V'\FrjeZ]AQWQ LYÂYam{/J?w/5.Q;@tFp=o];Nx+7zEs0E R4HMGg)t]qIoū;*A]y5k 0P5+&5$dm߄TctWELب".H9iN;_ȫ5Ooocp*dXB8@}%<)$bR4PU+-Vx#.\Q; INBtвEzk-eM_fZhVJЀFP# VBD[^ً-~X[b} }]o-%禾EY*qa,U%v ]k]t}htx,uq]j.lTUC^R "V$O AXҲƅ:bD`-&[oq gbmLES4Oo>ooe-XRXǺNjU-%VŪXZBh{j~nZtaRMPDbܴ| B4ֵSׂGl-'l'TI1lU_R#VT˧ұJ :n`Et&3љ^%So,>p1V̨ Z4dѐECVmȚs= W{=1VjbUMU5u&VĪP*KZjl˪!dFXұZa#tQg?_mn}pkΆ.#ְwŬsU-7o/L9)c͔#`lVy) U=hnyl'C=PWDVZc dMR9@]JW%39n$qIXK2ǵ*qA_Ղ wy1asF8GN)W G''Z5_(a uW{o !kYTa\*畞S=QjE lo#D5VUŮ /Vznw:c-@>x f;5~> *Z K5B)jrZjJV=Z/6;(╞:8QoNo%bյuC3^/l7Fo6ެ؍jd㺫5J*:SљTtpWUM]0 /Zmٍځ뼗˱V; v])^a5{նji].ҰWiKuo26vcapr_~(e|;oLrM$h/""BX ,7  :Jo׋ٛKW.3Pt5s4s,3˽e^2qe~}ߵ㷳hPʅ`M`ZIJZ(y5o.yCMNy!s;ifLʏ~&zsr?̮]BD c ~ RJ:۸bZ'eE@D"v4Hl &.\Dw'Xѓ&zX5" Ht)`Fm|@(}֌[- q 7c qc}x6|Rxs~j~~1?[ajgL &ioΈ}f.Ͷ}yV.gY6b E Z^: Yө- .%l-ނ[<^lK %PIvۻM]Zuld3~6Oшx,9-pP * `ǘ@DQ#~Q-__t@:1myҰ+nE6n +0ѤMc4NM:~t@`F='U0z4Ѭ5q NJO\*p#ΏӼPpm!!nM.GH轷Ig.1!v4Bl#-Q"]FmXq¾_ >y|ɌJM7*4l>'׃Zr|xS_bJlүJTX~I(T'܀xTmjZAxMW@fҔ%]oRCQaU=[ 5j0|R>P<ܹמt^$R|0,:B# x`$YsDdX\/8{0`hXtgNBt?hb b<۫U+,"λ\CQ=n>Rx2@JQ ZBoy4DZFx>Abc!_۱P^B+=M۴mJE.%\hW"ҧ77?ۦUGfGR_Ӟ׀},J/`Mrw<0"afs8եp1d,bH Y)򴕮tG%b7|Yn*"E)hB F =P/ȠԘ:@6i2!%{mŔ󺣘]i31TT=)0N_]@yH80 ^ TUv+w c!?"aS$TwIv- b J.AMP-A2嵐u!e=`T,DTlj )E\]v_002 c}GHׂ$:;I< Y:>}̴pk"HN-sDYrކ[J,lj9j5ATܘ0MϼI3W)ҎW KpliH J0W(;i(%)C QM%ABq9?6 S}M " ގCӤ̡Lte+3L+c`:3fMM<ޒHq˼drD4i< kwSC\qUZv٠mnN`>N^|1m.}!z\<6ZшQ[sicqѭYJ,N+b/շ,ޭ: DsH9剒-ڋ D+6?L:Rf<FVKϮ.ee! 3Q-[&. 7V`و;;:> IUqx~Lg;`͌|D0D n!VqG<uځA P^#(HϦY'GѳkSP@KL.$V:O $|CG"U M~q>)'0jOmw AV 44eى0fE5v@j|b8ySJF˟s;%,D{N]z焻sbU/xZ ^2el;Gl{nTiQ\'?P#ѳjdi!MZFڌr/+o5pq]Dll1?~eYa:Lptgb=U I?vUqUZV {R:b,-)/2V%vf @7jrn26GPla1uQ̰8vV@^wDB5B}h;(YbX,KGm|A{2,ΖJcro25g&i&0fR3]0amMJH\LZ邘IԎA [1r&MtXJ)VviGSjxH7bNmUNMoI55Χj%&&0kr5m̎`urMMȮ lZ9^ԎAf [1rzMzXʯRXvlW3lmlL BGH7l- uud)"'S8e4`08`؉x@D];@Ae?`e#[⦕'.%[ݬn]@ΕIXJCϲ|zvY$$p]&3t57r A +Q ӵ)>l?_n_&7F遨InqJe@W~Lug!J 1I&I"`kE@>z4;ʾSd. q2!nj .E1SL4O|w(Bs 9B|y/yre=ZG) ruفy|'#E ɝO7O$@"bBI"aIĆƂr#כfF Q$JrPJgK #G("wy1]l0D۰5Hp#AIdH@ 8y~!ǀbz$GO]@5RᴻnN9G f>\ 1Sz2Ҧ[4R7BS0AdqC*f,XJa䫧9{rJ;N5 [7/;+_Ӆ6Z'Z<PS"_gz+7nwݦwPX \#Vk*X A#`/ dqỂ"]KΗ/M#qf5I(^}~L^Z~`lnA14nqE\Z!A!,"#H<?03@EXġǍ5Ĩ1Ί5ƨf;:bF>H1wNjWirGv(g:pss @:3RC V(1Ce^ ]) Uwqd V []3L8gR]fJAQ9ۯ֐\ꕚgPd{t&-t,KY%,=w;Jsv:\SsL8{E<Tg} *`U2~WE V'({R^ooͻV0Q`%1x-WC0'+b >ߵ$5|MχtJ]Ɔ7e<* ;vU[9Wj }|6ޟ gdlmA) zHr$?.|}ñl/"#ޫMIC+k ʃ@>h-UEww_ʓew5zЏc'fO_\Ǐlஔֱkp0zF0@Wi,DHGmltݦ+8{?eLe!A{VWσO7fG޴o?_sϠ?W9Þrk P9 n&x`lO36y"3F\{ؖ>a1PD8DX} 2v9]-RHcL-bxLC{gHXLex{^ сj|COMW -X8+1Fj4ijw\ f.ܹ2f<6Dzd#~q7t5Wb0d5ΠC) mNA)٨,1c 6|Ws>2jvhuqXU[BFơcLLjKi`23$gA+LBGC7eMiAv`[- >װ͗ЧP^%/, ُ??`wNfG9W $|KLZ}sp5H Oz7s8]Cיwyv::=GrK˫o ڒw stƷ.>Ÿ;7+*AF=&E#6`lC ƹ;?F?.~wz&0ڀ9fx[ o/S춟GwA8>>BkB^o؋>T"Ό@lW}!+pVe,)֋739tʜi;h<>D*>|;@>2j! 84k0k~&%@X4anItJ_&LLDtJB[tH-r֏'(o+Ֆx}#0t/^B&4 (H: ̈J([c#JLyC:IrDaALlrԅUqF(6*D*B! Z3 *eϝDuguCTER&Iz?ǜw'9ou,.Y{^_f(\&}dҫd~vuc5U~ab_f^t(nq{ql]c{tb>vnޓޖsjOMIT%oQe% @@(ԤmYuZ>fd/#5-Ä;'Tbo]iw < W/b79|K1ۥOSLc,.؞#e??rsk9ňt8.s nB't%[9! 4B#6CI`mKPgAF"V qᦢTulwqKnǶƢYŋ,h*7eTiE*/ncvjS= _*C}}AK{)6f3YQi!W-#IV `̴Oٴ#dini8D(ͭ&\}@p;}VTC_aўǾD*ŭ_(NT?t3'0%+FqN~9lUJp|\4bʷ`\98hCV\fEo>UrslW(Nǚ u z^QxG*:ұ/;MzOq"r)(-_Z(|IV3 A* ?ýIġ^(bjlp7A q(A70 ;ȽD_y qòG C /uɭO-wѳp-_#+8}PlN @=KhhPP$7I$DAL͑jײRM i﯀j)zr ;O/C-{,{ 9ae4NJ CU`\rJ6#<7&YG玙]1 q3,j ʉ䌲'; yZ=lXFcI9k NjMm-8wbr}Gg͝_к ). UQӀفqA>@y~z`]{GwFR%gaI˧um+`9u5H`VT;Y(bƌ(|IQRt;OtI =Z,8 Bp6xVk ;E;.<Ѫ91" ( Z3]ND:p" 8lć2 4 ] :` {r1"?lᄋG>Ӯ#g1r0L%: .n Ю j#B'0xrzrtӓ3OXOEǓ.0/RSO0S 2Ahw~BӺz=4oy3ءk-p `d##Ȱr9[:?]BhEL\Pp=p?pܭ[nD8|[e zA$T3 "\p-mU3LTJk=ZD˘_![4^Q";{fFhX-QSq|#R QΤB`a2" @__r;|Kg/=iGޝ1jەVY3jMmkflĈ?3%4_fIg&ğYg*lNښ2+Aw*-|gD3\3 ¸_føvp6p l`T}7n@ˮQ gY0؆#Â`+_FobB٠P*Yt#w$bDj'לXm@KgF(\sݢ=8Z+ΩTasRoŗdM T2#/_ﷻ"C/VA3َ$h$ ID}:(Kj0ٓgnjhHUf[/%٢89&R EE.??{;  /ΡXD4ee/pod%N# f!#[R;KT >.%}o'dZuUކ[S V E5AdofTU%Qc Q{rh8"TXՃ ݫgWX@,=[տhn?~+^?ϣ[V7's'ף_?_ $KN[. EoT('yNϤ$8fR,z/Pgb$L勼sɿ8gQrSF÷B7!{/~78P*C^]oK!r YMBr)@uc^y&lӲGz\9a#-p9]1X? ZJSd#U". M:Pŋll,E!쎅o|8q Ĥ ؔn\|4q32KgK,9C%?R/mKeT|HQ)aQsjMZ3,`BrÄj6ճ(mGX <{Pͨh$lᅳ652 k)TY;q]ecڔ[շNLc+\2D(CNݼ[dOeIJZI۪>7 % IK$A]nFLy/??cĥ| ZF,yh3n?#ƍ' L6F8.6d3upî/^.Tݠ{9Z{ZikB>U@b!?h[2dЦUnf)E'+:_:r݊!4a _=^=;X1^d@zړ_ӊ~"sy? ŋI&XR2]VoS =5A5@wA5}ZJ5 r09JJpG r#{Uï|j;Z=UDN{;>mUYnx ]:#tQ&ؖPu1a S:s]L*[@MiEZS=XK0LX(lt:.j&B( iq﯀vT$xeol@z%ɪiqC }{JL1:U[ǒƪ) VG8ڗY}J=ÐC^k3(_zξlwC+2mIRW >&'j$y1Ф^Z8vѽzr% tna pIdCa@0ӛJfO>|.hu#šK;-׽z#_)CLLP2f-ݒ1SōbLyCE[-Qm1+a:¡ho4g$L`7c833zii5`\N ZЂ,4ZUN֮iDa l &7N %$g?) }6 arM [ ]J _FLT@ϣ8a!ar}A'w=ku`!2F<>` :p]}ju@\ӡ͋Mƫ6NgGz=e=;lca {빉n#LestNMZo4!2qϗߒͽ7IU;. `ffx|#*:KŭaA;VĶ [t'S)^=|_E}š1 F\%M,$B(!dg#$Щn+de {lIv{zO]nX矽H= Hj470Jec;cS}gMΌ1Q=*[ӢZAaص؜ZcHԼ髫+TOt, ƻ1?B&zD̦l~!~M'<ˤWrqy!|#hlnգZJN5c hS{AB2{c8R},7+1f#SOsUβAF͇o͠bp,0f;h)G/MBx}<2I Qv7!:wA:1sXIZcMx,]$%xuJ<"w>y HSi|G7&N\~0&o:x-,MyRgPN bp~璁#)ciU\u[l>*O;%D+!!C׍s}VJN ʈwgW)TRbUJLuqͨO*YprKGkyj=+yVj {c!:6oǥmur]Ǘ~m'h#LpwS\25SL)2!Ovb>x/4"y8#TP&2q"LMDF:vIMPG”||p"g}>d3TkFU"!##ԃB@홣/M\f(/G糛"xܷĈ0oP|McpubJTgi|Vnrr> hv0` IpmR8DRͯ^lnSWFfA큧܄,F ˫W5=~'Ȋ0<:%.dȟ qoDzQ#}z/ߟ>XcC]xތ'T%zeQ/ |].I n2\wt^ ~Fjx䃟vi|CJ?R=aE%**j$#" 7%(3qNs* 8ޠzR2թ?ϗd 8>N=_?|RIT}xH68CJ@wՃr34p"LD zYdIÃW͑ lHs$ﱤG{`#w%=qi5)gnq3oE]lc_Te-,L[Qt#2| c0(j>{Տ~rƴ:ߑY$}o7EEȝ 3׃ABsL 9Nrr <{Ak7r4UkW<4#ߪy$ol(j;?87 Ѹ0f/Y_gv~}(Y :}ߪd>yjs @7CvjIE"AVB/B]Dq66lC Ơ:! Qg#vQiI'5'F!ɏm5czd<>xYXۧ"D}Zl΄?s.p]LxИ;+G`b-īS`b}9)@j `{4أ ;2=Ie;!’be;%Y\R*%UJFR\s-ʊw 'I5hCNn_Pl8YjL9 %wZJxUi xco۴Q_Pl:z:z!FĔj! [7o M[]`Cn Xր0 'EzFdJ/&qQV,M҄P$p6\Շ~1_L3848 z=5g\ 6';;IJh&>_.o06ؑ%h&|Ǥ\)'i_~vrՃ S$|& *⠂ *XbZK1/ uq̴0~J}\iIqv}xH6F4Gw3֛DzpwqaD܈h)ەr'U4 hW)D@KGJ2Q6z.S;@LZTq83o7 S5٠ :i+.dNC2 d0$ IM61cA /iϥԳb=#kC7+E[W֣AèQ$R4weFڊ=TPټ{6 B HK`$4$hAzߦ">ǹ͡Bf Jh3 C3Xm6n@Ch30848y>pk3Ħ; DV(t.lMg5CUdt*RxV?hLMpƝC0V;75\1> Ǩ^q "iLe,`TLP7izr|a rג/.V/ 9@4P4<Z>N'|kӆ4 ȯw6- pX+Ԧ\s@J4N1Zͫc&[fhberyݯ0 &M&g(0+F & s~A }^h ~ڴy#J*9d?"x, F'hzwh僖7?HM`A0Ch[u.v3 !eɶup-EhH#p6\fBaMЧerqj)B89Bk+Z' 1&f _0MhȤs'RP9ΉB!*;^$<aPE0aȓ C6'Rf jV`7j.M\6yf 3O[Ca搋'7 3FWXfCaHdqWe3a{-tO2\zZ!;e|+;S9FHq#郄Q@$]ޤU=;n &y~KS-vv ٨p{@Vze)ɿkdHl钌jág^=^6?*:\, 2m=dKIo)Ձ2Gώ?N&@I)=;&V1ܼRa%LIp5 4 -4Y$cB,Bq6Ba8X7[T@ޚ`4mm%}!&G_ ?sI 5BRzr <ˆ^z:"hZ{l MFS: !Bn1,i-N;[V]m^ͣ0+ja[FIi<Y0ʂy mu}*QvO|]K^0Bp6E$mw /s4ik̨gR=gۈUM,&u-ihYlI_-kfֵ\J{ [d[2ԟ]C'[ڑYv4Enm vڀuW s[o]mɚk!l@ _p;`$d `P@;XcYL}+S48`cyxcI@4G\;k=g'n'4)>>ri`\^xH;O_ha'zb;~ "( EĖ)5itwgg?#;5\jI IJ`5Q(Q#,;Q O7>xVVAs'|h ƘFq]gL3ҙLXٰX}b!PNr4)TM<|gD3}u~_JVWaotֳVb0} r0j9Br qiJ:SrE]VK+{ZrH^*[z%sn譚pĕęh0HYD{-?'pcdh ̚ ppuC%g|M >a)C!yYަڥKWG4֢B݃[_xP ҠH^{\C8rW5tǃ \Ժ^ =pt8L] 2J|t;&_2. `f99QV~PaiK$c~<_ߋ+ ]@RH8*s5ĭv|3=4 cd$ZqPT)D3aZ`A# Oo,0ldM+8={V5@ޚ0̐s?3Ҁ|x$͸>M͹ޛtէUDz-5Ji"y3eVʾlw{QS`4 +ޝpy-8"B 26rS"S:"_9HVL GV(7]D`ۺO~Uu}iX ο'?G,5y^DM2g>mc BEu O7t[BH/'k q\:{,WCXҼ@aRiZ ⴯pY:D@;Ow߭Mj B{Q=5Jy()ZJ e4+(kap\$p{,Y00 縯qY\X-9uXK/S-"8Ȣ=OC@E`ptVSPJ9\ċ3!pfEܙ-ΈV+&?F82ۇdU;8Y cnI)^sVZ ^J׊n)$ʜS`8bXvf #'${2]f8hť:92BBvѫ&&trmlta'0߷O9ן>yλ&hoM]Cg)^'_RiGDMGdi7~m9:BmѠ}? ׌G==ə45 N hOU=^xBU P3K>*Kݸ?X}A uon@#NKc Pu~DO^ k]ygBZC ix٣@ C)B)#Qma$F#/ʌL K_ 8O~|ZkO4nDn$ gn/~.!n?q}պr6mw$;RVZ9DzNh֦ˑ %|gD3үſU^q}FwHwp4ip%݋? =\Ey{B.W"d_qɣ:'E\G52MN5ų55}g> pIY:SI HÄS!ʉI,)&Qb`b<1Q"aN~Z %v-6f?1o_-cݦɲp=k$vdxi :3qF BJdA`'!х(xt@vK@蝽6]?ӟ9^R:%a=y@%4(6܌eIW'~m ]}B&q˿y4>6iz4<]lӅ؜?Ժc+ (Ɗ7&W;w+J= ƚCƻC}.u,B:=a!P* ,{q*99 oOOg"hA8ѡNm}OF{ViJ9 ~uRju0Sx8CBʷ.6]w{9ߓ!Me(bo);BĂxK>xbi/~S6S/~C14dpyL<B \IVFLtH9$@\h +<R 6>sMt-PHKI(NQYQu'|.X]}RO߷%/pLԐ{lle]~SK]ڭwGc|M^yM2٨8sc 3R0qY79 6鼳Z!0/"ȋ 4EE6^76vH]T34i9=@ZbNTRAKYj5@fP^P<5 fbIGRz Xք$;MW} ^q^. _ĸ$fS)S*D"0#۔GQ #L@0qsg<8`K _0}C⡥^{myz2RAj?5a'E 9Q̦dvGlET{-=a'Gڄ >aʗXU'q7%y%zW.꫈(G#HZ] B֫GHya-ȋy1') 1sq.%`0ŰĽT|xksgň{t.2V 1Xvaw**ɴ֊v4F ߤɏwn ·¤(!A0ڑ7š&;'.ߞM%+cWM"+/>_ްRXtd-ŋWEee"!r_Bb@VsLvSrVTt*w6@&t.;zx$CXH7Xo)aR9+3f(HSfШ[;~a=~C0(IueQLOf1s'ch϶=kzx)?/_bZpzۊpzs+eVEa0OtAIfΪ٭@0f*ƈ#~` ,"U~О_QROQ0?Qv;VQj9d0d wEWI{$0E&}Y(赒,G$z.(M4*VcJ [, aQ{ZC 3JMBӁ#t}Pz.5!t~ُ\@p"}Ą`ϒ>,.Xs9Iq_L,%tՃBM2RS蹄Ԅ/ӽ7, u3$K8ފdchainfWͷ8Uvf?4@ |DТY YJ|EhYH,!)C \8Nl>u`WD zT\UH[NjˬԵ5`Jg3"Ŀ0պK`2c RB}^ ]ha>rV#r1:: rq08ɝEMf{``za!>;%M?@ y)?H®}x}{mư.ɑH +# AG&-I!͋IB&(bA]&B & Xz@J]m0d1B ,i1 96IYN$A JIrC#b 6rd2 ?Hˣ|#ez.ov}zO]|\n>SPgXءuyOxґjiX4.&<󰐗Κ%i_GD0b>$Ź+VEOn:xO~~_\[ets<+/ ~es%1k1r+0ZË?_;țZb\o~|L=>ό~Ϭ_ + ~!v &g$=WHoY7|nۖ[nz [ g As7xN DJ "8f `Xs s4ib,f0+w"tq @K]LqX6bޠmQ)> ^r!\pa&υ4f*ӄɊ͊ڒ)1?f˻4?~ei1 EWrꗆm='"OV5;+ǶgV $W5f㇫W"%}&?T Pxl;Uv0TjuibRkr@vMU㊵>PXPNO`)l}nHXcRv59fvH'<Ȍ. cK|'Rv ƈ̒ dn{{wwSIJS^z'&FiFOڴF8.ɜShM ,@z%U1kWYǧO ~BYz^?Miy-;VA}Y!D:,WpqD&GW{Wp<\9chKsH3&7}ݧﶟW֝y j:/c{^; \_Ԉ{NxwO锻+>4*$Il$[{+`X6,tުԻrI)f ;Dd^zl`GOl o͑u :],>7jOo5dǫzc>?Z& u~{o /^kB80 j󏋥rI/ =kuN+rTD! Ѕ2=;՜ OseePG]R]jp.0p*Xos^.VSJ&G]oz' !?0-,>|Fo#7fϐu ̞O*BBaK@bi"f–3fRaK &O0.|!~0|. >-t`vNlZڙ ":WXf ;+6^jګ{VE+$t Q)' 3Id&/ 3eNμCCF"/vW.o3{6h%hٯk?+{.=Ma Wjp`׬r1cNFVEE0'9̉`NqmZ1Ңk W;_t&nQL'rPjQ)&ZzkCU7zJvan@o ]'#:X0Lk0FKDs8agNF|8hrppvد=qM:7R1t75ّg3ofzmn& ۙ˥7ZUMW]@2{m)4ek7ҽ0ڶkgW-L^ R-h=Lcqt<%f>3T@r*1ߑM[Sw1yumFNIt_oՏ[A CRd[ S`ܦ.cnHĔj;o[ζ)8HY S,h\ ~Qexׅ.X.|M^̫MJ}V[QA/@a4vޯm7׎X 3!Xr͏7z mP)K\TKwƢ]$aanee_;&?sˣ[n1ӭi]#wM]QtNoGĒ3I<|ލgxn a΁Cyh"mY!zJԒ²Xrt6-wP=x*. < Ky|xִKJ aaQV S" Zl"Ta*3B%͊Yqg>eQ6i{u^PWB{nmZi/q7+mݧ7 {"㡘PƟMۢ6 ݦfʏ [05Dh|!;?RF0CA<,[7muyY+߯4 LQ_x/TMIgWKr 0@{Pnf;TyDIP<y͋gy;Cܞnsܨgy'杘N}FNDʭnC˗ZYDcJ|W>È@:^r" _4;TP}v Rg߳JYUCBcxt`<˛^}qZq`e*C{+#oI ƿrK?~b -d{ɹnȲKABh^<-%NGjFTZ*|"eiF|pg&h'15^Ve 8Cq|u'J셫¦*,bx|UX>+ܒ_)~0|fKO:z/w[yJ҂y;+6b6սL3&eSzUkm+ BK,A+Ku\jי4ԋvJ-΂:Qg ]'b.P{R?WcЊAͣR @p\:B(! u2tuW [ ]/ [SZ!T:aAar'7:ku ^A2E֚&l+_>_|Z}!%_GBD8O;{msyCO=Od1@.BΪ.ԣڎd.踠ゎ32EA\k!B\/WqWqՏs}gmBgWHDh&فgmBO`Q+cZTh.TXJ h1XF ,負Dt6v "f qu#"!"!Z?knmD55ZJaˏd}"֧_fc~(N Kn{gzb/cf tNѬ}6/6 ,YJ7r |)F rc&_b/o~_܃e/y]=/E[P1٬lwpf! sZ BX"㧟?m?i )ezK [B~xL{N~ E~cYჸh:[+]e3Ҕɚ`C¬omY}@eHpU(cin#p[VHVm&R#WU~ҵ\t?kK֒HUuC%ZOA)pa,cE0\ RÅBx3^)j.-Z)*jje_,`>XMw[qfl3 ֡\˲x; c<`6)Ml"&F]VA*(oS2gj I0L5LrL*aAg \[B'qC7qCWSYH .@pBw ߙm,~+++W*W }2e-4@Z$WiU]_^ݽ]\]/ q%&:OFh2LFI8-F#NFtXa\10k꟔YEY\:F RF(Lć2:22@<:Hz0@:)_bC0QO/. 9}`\ Y2pR)#P` ~ ʢ%8Ux|13ϴ=n^>mLd٘}~_\ܥ\orPF %{U۫*9 =fQŠ "O簖q P^zu{_o_^hTV|WZMe2*erNL(Hd*{jO@-k7e*pYjq=yȾ-r xM[h;՟%g1uN&钰șQ C1fYyK|ֶ^&m 7"\Iv|_l+em4!xy=3)[E`M˱Q^I~[5=%K_C$kj,MDؕ z]iD,_ta+.,PuBĒ >1`]c } cD4bwpfl؏H؇A[Aa4h I9Sbd>wqVuKf1PS[\ FM~9t7h)mH&>PlK6ծCV_1ѯw;c)t(RyH CRC! r;I!1蝇xHLx!]^䵇t)8H ]~:8Sp#z/>8GbL7FāF,yi#b<%E=-ys8=3 Ș_l߾S0yȮK8FO޸=:֯d ;ZZʲ ӝzf1{#1vYۿ|Oا6n'4f 枿79πY#UhX|\1`Zk Q9ˑ eVKV-HV?=nGyM4UCsˑ~T1}wU .mq2+uyP:)) \Jޭ6~yd },dKL.?a+Av!. CA:F(at*O”ւLXLMμh!+cT%S@WP]fp m~\Q|.Vg,A`>giLmƻ(En [2B1$ X&nvSAO*$]%̎S7ܶ:+*Š]I4{Hr5޶?^ӛm;Pe)4,ae|'Pg*̡̔NwT> mXҲs%Q.:!)}!b3Qҩnd}ʏ#Pv$J=-awK _c@?E"a`ȃ,hibs*d/C}}-szGg_)E >p$7cԓUg]:hCfs VzMӌgF{& hS=@ģŇ).Η˻d\;s;"7/Qbg7 21d1T_.ABDPbTL  WXI'P*9:SZLVQsI)c=uW1 psv&Z fV6knݙKbz97^9E_'v[-nl.YoS܁vzoi8i,;_3$b}) ~Erb7p$oc>CZWL~XkpQ. j~šAxݪp+bwV'KlcH Jm3V>>lūT\km0j+A)~]7"WMZpzz;=mtѾxE+VfAMK [pЂ z,1=&pi9_Б!rrB>Gɭ mF؆qi im}D N±,˂W yZ[v)!9Or93w]d!U I3Y ' ޻"߂|Knx ^6'}^+g/#-||MJė1'EMQI:e6Z@,$GDf>es*xA~L@(M^}j.C8#WۙЮ*?j ylL3AHgu\PB%H(r"DPJ9]tDiH=5 i ndo7{s&b`:RT7Aq)krC҅LQ1 1t Q2"#7H(oӇi0&8}6f:i_|Ӈ!8}4Z`bvMߍk: ^w\poT-gVGPߦ6ƨq.4 I+Zow0YLq]kbR$(jCYp|A&UOF9Tjwip r{9XzL0xce7d.RB.J T5T?.f?Q&7t+tz (aps. s^(L(D*UUm*N=%C<Ty );TҴ~U E*. (QƤDK 7חWwnW *OB%%T Ӂ!Ըh9Ã(jp/?yQb+f y yNB·T`HBnoի4\h\2k4.(xټ5]K ܍ʗ)!Sz%{1#օK06m< CR a.'a+b$IM/7z!bX>XK!u>+GuKgú}٥VqPU"׬VqOҮ#kr!hx0AP= > rr/A,z=^У0#`˶H_ OW'!A Mk]`[dQ\ "DFPá]\*qNj o^`PdƋ,u GJ<ѥɷ4WI ѥ%&,[5;$͑6O9, atŏmVZ5tjI\,`0_fzOc3"ޤuuq|~lX!8;:lGH>%F:ti 6>W̷37w/:嘚3~*tի[{^+6R4K0i㥲v5v~y97т̤O@#꣔4vFX7I&}P=>SёRZ=1W̚ϣ_V ҌY]{K ͓ia$%VaCJ5M.c NV=T>3 OSL1R ]Xf `rKn~Խ}6?}HjL~$uCw~RφueC{0Rmcsf9&ZţXw;c) ݊Ee$̙88 %'Zt%5łr9.oh^NI:L^w5̺e8d;3f,8 ,S_?c$L=qF艳wʗB2G;Zmv7+$A,,nfb ׫* .CV[{4H)MܙUk9ʽ7b{9}ڣs@r3yP^nF"/dS|ey)rS̀m<钗]yvT;.~-BxtIrQHOM2!:踈K#RE \5NMrJmq^Ȅ/V1II?2,j 8%h/Fhz5~.9`r %{5jpwQmtײpյ"0Z„Q("GV狒5 @jȞqA5$*>ֻ|qF [rǛ~=.v_=&.ǰjN'OlݺӺ nXf2nJdX'K,ATfAd:mVuK̭Fu{_*!h{߹ףƨԜsVeSU֩tj%̴V /Ҩa?` bԁ#v&['{" ?oծ*"X$\S@K2Q\.&0V1AMK ([aOf- iUe~qL!_s̾C_w_\am;rtt( v$W Ues*ۍRF0jDm;j%mg6ƺ-⋦  dRۤaԲbҗ0'`G;2ؑa9y2R0)l46ߥ_?j&G]oڢvD#?-̿?Y֡pR0&9===̊#Uվ_b}[vryo^T<?c//}.wW'LQ W0Akg5 U￐Bꨢ(0З3q:NwiuNEdp/c1)>#6|W׋?޽3oLHޱLD:% "~ҩɭb.]M5֙Cc?8 3+qܰ02}W#L3)묂WNB}| ->#8:cߏN~7|OͯS}zzj1qߧ>g=ub{ rWۣb is_?mwGd.7pX8PlUpdXxwk`tZp-ٶ snM+r3OeAyޭDk/sn! I0u/!~:z׾ ⠜}C(΂)RfisRE#ՒaPՓj*1^Re~uU(_AhUB, mpGF Q*WgbDTa<^+"+PlV͋grIn{#MgԨ.jӕS`L2δ|gD3~Mi?=B;u0A"VusȯBs=;= M@lHS7Xkyk5%/rUl[=MDf{;>/7p:NJy|P)sb_N^_@R@ w;O(Lh1E@Kt3N3LN]Jfko<~{GKXpR簣[;]䯨Φ"MEٚщt%ڐC<ϥ=CHغj{g>s}ܘutS߂ "ɂ&jInZAzrwRyڬ/Řn"oH[ۄ;yssQ&m.^8TWgt36h0fxaQ5x ]c~ᆄ4t^#Ow`͆"&mA7H nɰjlAlkA?n/or[{wŜZZ+[t"2<}u8 V&_(iXK(6lUZ Y˃11vHVurFi%-^owgFѣi$OyST>t-jzYU0zxeo% ʯT#+rahe!Il$[{**"*g}._*пd Zye O XSJn1)݌+\ AzDEV搱ҭ>-4 KoM`5xZ٣l㧆}\F`MN.}x\'Uiqx]>Av\>°0'QdjΒ/^s /5`5#`:1~:g넝'];202Ra3性Ҙ;p'PUT'A1A3A) VyȤɂnqREbpASlUteYvؙy;̲etRG$l:)Ld &L-Gv `. ×Fn)FAh &_O#["!-{z+T$RSo8g5b4cq%()7l  0>͊yHBFC^at!bL@/7v}<<eٗd.-sXB{ܚ>ǭ'm<~\tUo? %ӜSYP$Zs+}u ͉gTvNz~bWJUѠDG aO+DA!B!1c, %L8Chc=3R7b+[(;,P1Q3FIV _Squ`׾v6m՗mlsRړ LbZRZ'e*^mVٗb . #) g·Pq\_9-]CTࢁ p2)0N1]$'ʨ?r1}]^nqpbE Ś,%٧=$zQT.6XE 0u? ]F7iBq _vq~_\,#|As*C%Z1Q` Հ`WU$wY +uOVnF+wDFyW|˚/gw<<{4 Z3#9rAcWVA+y5 f4x*/د :Vn'm<;8p_,[+\* Oh+_9ΔZbdp+\uB>IVM$\=|[9$3wZ1Y_ow_V-ӄxi> zy/˛{?c@4X]d_=A9QĒ~lt5`jb c4NlTC+&lQe^  |y23H BtY݊ެ/W2"?n`s w.>#rlcBo>\lͫf6{a1yo*P{MwQ\(zvMc|X-dW?sa*USczۚ%!%21˧4t5>Mhc1JRy.V6RYM]Ko7St#2߫\R ʺt$I,6C`Qd2q,d ҔSȷ$b3B Ԯ>jU >!2%<3̠>Mx#x8XKe]+# :C&ӧЎ%8C Cg"$}-etg+(t5/K-xc9~g[Ի/.̺n+Q aj,zl'B;5㏗28;z+҄3a[CW_ [7qk5mMۙ` ! 4ULh8Yטkz]d{E[o'FTxI l%Px&DI<_. Nh\3kyƈY^ms9rQV .;KG]C[-FD[.vO>]?VI>I ɡ/l?"U@,%O.[f/.,0晍)B+yhⰙzj%YԘZpGwcs.֫x@/(\t|PU|n|F%9_uFꐙ9P{O⯓ˇz9y]=?.{a*2nQSgZEi -l<&!f@ &DNe gCLj!|b@\dfIX2#X) `bL)3)H22c}2fFj2B'.G$hcRl[i!k[h7ɛC1i59L`(@@ެ7ߕUTjvأ69᠇> z8RQ:p-1i-9&50Z"h t4*C@bi85C ~̓ -p פVI&0J'Y{KI,vQhBxEqHj)0N6?xq$܁k޵;nI>7|%G;k[v֙=$زF({=}h{4[33xo H:j BPPail▹?d1{߃MĬl`l|a-Sl"u&dڃf6IpFW9:=4$%$il{-ufe+] U"ӋWwmα?O4}lt5'j,=0o崼`G7nTaf!/؊1%jAVKmޯ֨/4vt9un Ev4E)ۖd,KE@Np |ِ6&9&r6UV [K^H΄^51`'t!NhP,UX݊{2kCxUTrˏ0^Ql!h"e҉iq3aLY,AuT҃4L8M D\4sL{%Zq%='wH*WREeUZ^+IX~yp=] 1E?h$a1<ȷٗ>31_i[pXz~}fyt~!EʀaTHے/>m7;<5U<>"RG*A`8Y M~%?ϋ$C"XY##26x6QS#KLul#% .Pe =4gPOVFI$KK"βS YGKHV%LFiw"NK2/3X_2t7^췇gi2Eb\{%>lqS< 9N tHN+LƜ cϘ zV/Q%JdApfsYŬ p[cRb`Y'EI40~xN1Ui%PAWPj/SP'~QǚR!EKu`*70V*ừx(]==RqQ>O2u/hǛuXzA>˹j6tl\Y i2P–~K:W<1%8Y%*e# IuMYSz+W_|Kp8! P(oPdx _!Iꠂ[{Ê]]Be. =d}Y)LLq+S`oUxU8nf:ry7x<4\ GNcr`px/šy(z;V wJVcp̲M2W*wO {ѧֵd3ذh݂\X) %*8qA(3X9%*P8q![C{=/V緿\nӓ$J i#eB9Pf$KwT(MJg81*P7Jִoq|2c\:~dַ+ī$dmӟ߼axLh5Y@kd Hw|p[ֽ6V^?65mzJtYt\;Uo"jٖ@ЕRR _jBVwe@\,(,]/7|mKԍ,U V) ~T!6vW04\>D1aoOSH nkv++5iKݕ1I@ =鈊tezqeOb{@B"M? P%v H_6F92S/etH7է) q KT_|H2 ܞ$Hx)2S 봓H!nNO/jEڴs.KNi e_=CW2zK('*$*TzKWϧލR΃QT, wsz2~s!nf}-f1:0XU)Xswu2.*UzaB*-d6Gw(Mp5+-kH.2*]8iQDKI u=RO;N,MGKD*&luC!l٪( %>ۼ Ƃ]­H l*6{BPG`4KʁSU!Ա/'vHumˇSksWS^qO-7|P?zIpQ[R/v=n&wTO}S=Mt:_o:6Vj}lh]{sۏ%8\l:sp Wţ0z @x#SŚ%!@JOS5/0̼aLI_F&Vwuq?u:"_K{^|gj<{Fؤ?JtBեXKKs~.9ZŮgkml3.&պ'=B0dkPFoz9}8͛nlh9Y#b#=jQో?Z[a<&#d[ Rwpvy;?;ë4o[8kdS 61-p﫮$Spdi_hGlAdG?,>m;d1%_*a :Dalu$n*BR{̅c;׼;+1? 䀞i "7'Wʜ;Ͱ#ğPH zRܠ97hI[59P)c],'e{Lqiv+}Z9W 0nez$cZ洿KӫK^BTM,efō~Mb!߀6-A%=O^ww@5hx RA58]ic:驨? y㙢o|rABʇ3Cy,v,t_T|3gJ*oxE"G_ ZBI.IK@cL+n=sid'ېjVLfj8zؠޱ f$3Ys!q @xCNa5a,I9)&3IIC3>OY>F`=#79X 7yߴ'\m )g w y yMxlS9ޜ;ȻgAј:T $j0`ǟ+*Q$bJt$/O<YSt[+DڕDi{YgKXRDdpi?4hB8.)erOK}h ROaPA,S"uH绷uĵ +3 YbЕ 0Ft1( tNHTZǔvT8'0ʬMOڬ| \l9`"ya |W*pwE5{Nx׵F2N '׫i2Hgy`/^+npwb|ڥ=-& "ʿ믗|<uy~j?/?`yk˾HU)QG7.[}a^{9^J6Ϸ%]|nv;aGfϝqÎizHDV™v%)9Az^%Cs!dָFw1'5Z=O<$Gֆư)fowj b;jeSz%l20u䑼j]< f.^ r9TDqRAډ,:TlXD|r [6zV0\|YW*v(F%C$EɐzTA^L_.ySF̩d/yM 9SBk W> R$E1t3lWr1UH9fEzpCC9Q@k^) 7.Hwˏ$ *a"JH)22izї[w)X4,8'-PV~C8TCƘ0z)re󕽈*m_L<"Eq:ˊ$ ےrI~Iz$U$8Ʊ 3+u+)9e$xݔuSFFzTxe\}k4^^9$-\]9VؤrpbQCpHWW #@pw:iXf:Lu.:l >Myi}=4 99; qf^Bho w"ԧFS߱o`R!ոr(g S6x$:jMD*"vpT _'UgɥC 5ZZDLGXa:JDdzV !TfXa6*{S6oVfmUŸUmi(z6QA {5Q,YՔ鸶) =448Ci&q00d_N;#1 qI ' r< X~w0:!?vu aXV=gzp'AGbn,VՊs2!lWG1i+R(2FB}dv 4l$uuwu(-mbJW դ~D_#X2HM$uull^HT:f{4Q(U9]V@!2)#:D&*>S_[E,5Mcus؇7ѵdd~HzZXjE}'HcZ|>j=W>jnan{./A B-FW4s9.u3`p;n4V>Ja[u#luS-ormNou;}Ǵ^br 9qj`Ճ]qpSbgUB Nk;$ug745l}8ľ b0(S@!5¶!gJ3+16f-L 4F 5pucs.fet/qhWQݼ؉gH~g;9k 3NyQR>+F+b8_}bND?puw b,L ~4w9V&Z=m^;zt49jVjaiDN0.\$r" 7nhR黅pG|7DxG"bmv}x4+LeHYT4[V !1Y4bӒ[A7cGuaq~z#ɯ=? !OuR/ZRÒэZc}Ľls =Bhg 6BUov i.5Ryq.U]>mLF]N\P\{7-W{S^[On(oVM~ΐˁ?Q<4d\lp|Ν3ONV\QN;Q>KLqtK5!sg62JMVbcʧtkL̯TɴN{3;UϤڣiM3UAhZHL$v8Y^MVf@K=Q!gT)"iS{0'8m;bJq)qdw~drVOk)<([q-+}b֒'jV,4юas wev&堎N\EסR(绉]*<,$,nVD㭽TSĴ<Vuk;-5yC}ȈB~-nq-Dq9œZl ._P IL'"%22Ne/{ J[/}RP6 ^˷+P+q8d!$'?9='+q~8X9_bOΊW(cQJ Zՠa*.JJ2e.d&Pk ;pģlPل>kX9FtvW yxq} N ݆/G;?GNEMi~:]άyGu!pPZw3t+9`2%L֦hO02LL)DY1QÍ #:Qs*M}\:V-Uk,)eWGƈzح2UzZ,}z=ų?߫}ƽ>]6t#W}Ur*Gr'Gr'?νڏW_ݸOߏ~ո3JwZ4vu踿f_?ZѺֽ>R#ϫ: լ O Jn3?u±Ԧ8ıG:XvEr#Al`a`VaVT3}7cQiZB|)W֛)X-vVyNj}=fJ&:9/y[Z;؃y\Q:LLsq%i L>~IF}tG}tG=].]ּxq=:p۬m߭,Τ5Z5ZTwSHkaC ]A4ofbU}\LQ|l>"q}N Aoԥ0lkݚR Bmwf$=} G@cĖ/Z)O>8Ĉk_ b @h.Z8jM-n0 ZYbОutCH,tR?1A{R%I}zqTU>KԸx 5ޱNIޣi*=%iэZňL{uͅ}+a2OۼMw{E t^M{Κ:SzܴשקM{= sy{*o^m 6o+ St,jOƱ2U4 86c~&GIU Aǘn蠎蠎蠆WwL?TMIc(&Ƕ-㙁@gFP㙁m/["k"#I}'BtiZQ EG$!CF=%P :L>b;-LJq@Er[d|;"֓ƤpaPz 1Jkbܵ ګ*q2­K5&atFat|7YXC_uƘ1s9&'Q&+$˜T1ޏzc=q~ܽO ݇-1i!Ґbaw ?;u 7 j~Ώ3 9?+v+T(|K1 Q 3Z/Ղb>_6ǐ v"ċXbhD)D @{r2=* [ޞ0#8^p)0{>!Oؔ E/E< _1ZO}GVK8쨭vrZ)s޲[l^5pNUMBd6jcᩫMqm4XŢB1gť9flzTdGɦ"\k0)ݭA֋RW}+8!F5ڳ 3ֽ KŴK vu廖:b\aJŃM;Йʲjo78>9؊ԋA(Eپ&_yI!^ܢ0ގ_&i4=a?oUFΩi=fM5E˩fQqOMEH CܲP[D#a2s]y\#:N(EHn/]~W7ߺͪ}Lj~վ ۆዝ2*϶]~+H!ݞ%4͢_ܕՇָoܖz盘K#ãLVG)_/&Z$x?Ut+. f!}P׵9ԙ_?񤎸WRe*R[oы .3+pvS% ΃)v ]u_VBGyTQǚdoF6}:6wj(o"/Ra)a!{w.n~a$|9` \2jc/=GuZ@6ay^:89+n{\p‚~2%Q_ϛ,ר.Fug>OJp^?l?" KS0&CXT$Uikc1rlV$̄kfm"-[<E|leOp*nO޽:׉0P6] BV1Gjq4D2nᗭk-eR+s $/^KH7zZ?3V;%x3pacVe !cdP$ʰvV30LLIc5pCLˀy>9<չL*p&_N_[6@{s&nKoH}EF@:þ뛳A=tM,ik:.gYвɢVN~]bH{h1лwͮn9T1}nFx,&mrɥ Uߟ[þ|\- "KQT!{ I vP1URS+y.;!ߑxTjf`M_Tj("-T9icQ"t́:eBn!s<_1J;(R*؛o1q:2 o.9Jh"eH%&H 2L;DHwAk *!v8onW./yGhRՙpr#'qpQ4}wb%,}Tыf/"y#;5Op&Bmh?e<ӕrg\m-w= ꋶ!&z3){2?sH- b@ɀ[akh+u)eRc`!V{ tLP2 vt 2} w)>mj6}aSV|CZ4x/1 2HL|v Aa-El|Kq+˹O^.[  fd½spYd`||D:;b]EkqqCgks;eItD[LNYAgbTp"z m`_}fueLmYT)bUf 'l5TWjFWiܥֵyqY[0{ViiLRRRW['V/몣5y)UQ#YeEqP.Akpcqg$|#V\Hs5j&Cpv'J_ >"W::{d=u"w>HOOh Ӧ]?pt=zx[E$j*D|T<Q]$/SC*JY$^(6߲ [jH#gHG.eSx$~LYcƻ#'S~ /?H/Ӟ/GuB@݈qڬn7nĺmjh覫MFaapkQ:'4x4hrT쀫-V!N )/:.:v^ 9>IX&ޖW-%Mn2Hp1R$ O2Ku]q!{}i=sk,z2/F~_KO$]W/*z[^n_[8xM↷1>Vtsqm7u\΍$@l oR̀ PKhMA*OrX:I9FڀTkpgy-[25D,1(ZՏt KPF Kև%6y"*DpІ&RnSۏum//z)}>jWb5֐UGTTkP-4+XO.NFJI14 pUjHh* 5 6*LvL#SW6)]?~_6[&yAJ'k'TfpZUog5ϼuRIC ]=,D1\*#üj"ZU!?7SwC^&ᨬx@б]?am{nf=(ñl<&cn"G_ݟ_@o^\%k|-}}`'V"v[]g @LVI3@hyN82|mIzOo+|GMV?_@Vo}(3o'?/kĒWxG}wW&(駻@c*taV? x]Ty}c|_SD%O_Jؿ0? |޸w #u6`Oqp9oJ|ʹS˽<^ݽ~@ODg/&JȽ @^IxA@^?OmMC?h'>^Oy겨ݝ7:%Q#:)u!Al4<j j@G$VAWIsU!Z 긺l"$L{@г?Ϯ;Ϯ6*ᯛ$B&ϱ3QJ 7p-$֒w7Xs|%((K\b-15TN2ѱ(>jDccD>5e-\mP ߧ&K,wcwmC p?sY7 $?̂l!%M+}"7ATS&ZS\GS!N­IT zl[F}#Muv˘S {/n;:Nm@TfJܻBH;"cD.tL""(.>ԏowu=;}(9`+ݢ;''%`dWr;28}lP+}odtu@*4M` !l5{P杏\}j}=7p7o4Mhn ͶBIm"}l/dGt2o!!OdqhQ+{z% p[`g=nth=quTA)m83ÖW7e=hlyS\1_"n|g5_O[_UᬄS?ly!T\1Ѓ6WCeݞjrbs-p]4h.7{)rA` y"7͡n #U9p3eu 2a:+@j~Tt6r)Y(J+ yu_ @Dzȍ"׹oOr6 ܼ0Nd\Jk'W ˳]j@@OYfRJM5oDP`Qz]PU%vׇFњ ) t;6\k+K*%X:$p>4ru]h;OZ[E;r!z%%jD~dc.pdúc$+VZ,$}EC+UzK K hɛP)ZKG bL@8DMQ`(JՊ}}4< |REl 5wޘmaXӐ%b9x^mCwd}s~xk ޼|_~f4Y-Tntdq{Vnw[n>wCzQ%W)Gu*{'}}P^ quU6bǫ>B` > 5D(Qd+k 匂V0~}k:lt6|Fit~3]|C }5P'GiƉ VC&IJVV⠀R8c"9ڋLߴLF KLY⨞%,QF$. EgGSj_@lΔP9ޜwFGmග# vX !tT`Lu^1مx&wWWyQ 7A^-eeGbD-G]ԡOέh ʲ1H)/Fë1,:e&[@OvZd~eWk_d,!oq$mL㐃'qcԏaDxI=r|L'dJ~9byo_6, ̣s+G7Ɲbէd&_צtL#ĥ8]KՀo}u>ί7Ebq8%{rܮ]A q˪IڧVTOy~'t *?J\ȧbtml&TIU\eF Yj c*Q]FJDiȴY Sv,Fش;itNFsث/4}lA_.b-NY"㠀 Ci~+w9h%y\E4]/J&B.&:E\%ؙRB. p!طhY+hb+r+z_C[yiP~/6h6NoVɛoEJE.n?fJR#PkmnPBL!Җ,_<4Hu #J5ƨš(ԓLѦPQ[M di@K3)8WKVtCuU{+RQܮP"~ ^Rmb=-I]^5ճ|7KI򘥢vy־u4^"nN5j$p@ow]S!)bw}~llh. k80إVFjI{PS;4{{{H=SP*) tl&4Vؿ0G?x \ϾywFcTnkQqBURwv|˫_>F/Kc8@/./󥸉Z'"҂oX~jgҚzi 9G/-M/M;nؽ֘^kQ~ d:hq'딸Sz>OLFjInOqLAL?|Jo$MGڞgT2} N2 I2hփFjI`PzSZ=ȊSTz,6[edpdJE2 wԒT + z Xw*m̸PJIi*LE}:-isJv (h@(lt3VE!ii%At (8 -soc~ꙧݐ2٨+fMT eӽ''g :zZXt_*a>r= {1D?B9-⫭%iuCu=`M=*iW%~NI5T+T/s<<̡?4!vC?PMRpE#ӂԼ$LW__g1}<<= ?4-͠f@CC)&6Vn"AkfNceVO7%@yS43t3\*ڙ\C5l<'AHA|55/Ճ8ק9Tttj)5^9_V >@ {?mhB6+enw[c?_Ժ;hMwbq_f?ҴOg=m돰l1>MOo1@)݉#js^/Ofd߽yU΅fxkf-f׀mKN~ZA&{yq]B!>-+Y;t?r(]a|;{wDW}dB}; L*yfeQ jºϜI_hj ԝDSwM]I4'@OUfiY&Fpʩ4a킼!@Q>CCBKp& $j.v:%@oJ1,guD;N~'}Ӝh/w#L4vpI9V-/ab  C'K| >y؋PEm}un. 02V6:qcc' ِ_7%s/".yH2yğ # b:*$Հ C^tΝ` nDhl[aV '3Up3j/A!tOp?XvEOy8 S@uc0`];p:ZWKHu0^'ZHvVμKt46<\|r45pu*Cu@=T}O~|OБTj2v&?U/+:bApGIRR5\DhgqH K"*[BW)8t2Yǐ$#夭]~<*J_RTRa+srY\ aYY~ٖk V꛻N+F_V%K/ y+d%\ (}ry]v/_}ʶU n݉Qbvwsmc엻 Ք*iN/Tu*_'-TBI%^i,ӟ߼ԃlZՐ&cv'G},CIa C [Vv;Wnof cOU09$wN8imN}Z>n٤(j!L]E@J m\@b]JRSؙJjDXڅVBQRn競 ZUڿ>Sv}yȁ(ͮ*>m f~pO-A"6V?nY kT؁ ૕v].+R -L&BLZIIAʏK'Imt 0e nO*|e"!.=L2<Ԁie4$/Woa1:H/`:F,1L?_Oދфc+,FU:"3h[,S wZ"f!RHp;_DUAcsUq%wUjPo|-Xe6d'fkDko]ŦX-ztÄhYAl_obz"W tfEϥ2PY2ڃnc+44 VuX0Z%5@e T&f d("q.F|W;@)QSV+t Drk]^)>1+EĞB`yOt)@X(\gnOHj[oz'm ek$|b8l\6Ի/i 9@ t.5:̥+\|o̽o(5Nu4k|{ʻ }]16dƯvBW_v۬dzCpD_4PmCrn4;qh$uЍX^YO`g>a.\r+&PKB$gN۾38346eۓ46fx08RׂyQHW32*U΂̒:䲤-Dܾ/ep*l!9I' %[qbQyղ CܳD0\n+QPtFܬY 1<1~@P+#ub:b 5A Z}UN i>Z'pv%f-vGiI{={r&L z> " D F7?YAFIɫ?l˧>MXLajڎuj[bh<;?cY0x08G<òjSbΰ==v$f;ކ؍< OJ0.gzMZeڰva2X*Iv 2R? =EQBP NK}gz+wP/$q8S1hRK,.ס;ъ-jgU( $p$/rr6z 稸|1NFfػ:pd+r}arQ<[:YlaŢ%P>aCà= +V~K7fr,n'pCNx~d*cqϾ^ZE]?9_ 6f^c ^GuLK:?.qo`;X_8)$5PD1-p[!X9dC -",2KEzdX8['N m1Xiy4beg!YL?|+|CIC+3Wg\a[>/*Ț0ᓤ9_kq-k4fB^FEEJ`]/xxLz-_!a6z7D[ H?L 2${ &!zU}hއA"އUț=4*+ @'?}qg[LrnL%-'ww][uxԠÙiU=Κ8Uxܤ#UOL>UuMS6Ud㱲&FF챉(Ĉ4 QfrX|[_tɇvϷ?z_O_FSw\|/*$" FVoa9_uKip rI G2GO@pcFo%:P&߄Ǧ7vmWNC24n'M ]AYZL.vϟߖS9Q%eB*mz%`r sAﮇb,+13#6 ^ /|XnA_mRE҅EfkMfu5dAz^I}@Bȏi%䉺pUX:StW ͏WE?/U-l,'dk큿.[wȽ;-._\E Mri!P`|PW_nKK3/o=㼄ifu]v(f<ټUnt'N*+,5'=C`+ eGEgh(8Bdwŧ׽ \[l+گ4d/ 3Fe2Nݷ}<V[f nK1G&{A>}k JKy,EJu["XJT,TRֱcorjW@4ϰ3*gЪ[BPGFLʕu:78~*C)!ei\TzN =u'9@KShE_&Vص .u7fl G¬ARv!5T [xfU[6ÚY4& *J;_~]L> ґCpDlww8 hy@v6]ϋ+!')d#$|@٦7[U/@aXܩd?Cg+YF[sWM1$ِ4Un%]2M;/fw}rvov1v{dUo&&ߝZt\JkiT|Qi[99-pZ7Jk\. kD(0 )Gj܏jH%eELwH0E{\mi~ڨ t]L 치/j]T@tT:Jj .QTfS>ͽf4f^^2T$xxDcò4G6al_mKRbY Y 2G}3QG_OM_dI3#z!Y'S ƮaǫZ\Zq pY60f102ӺKbf:0*ճ"\rHvf$ ̳h;cZ4sNxI6$k5aWਸ਼_ʢE8d)Bfq2 @ӱY@>"'VtvRdQ"SmHڝPL PLL{}2c2uxռȏ+QY2qFUj}֚i4Trik§+Mƒ\,}Sr 2 W5F#aW58X,l_hCR\ȅpžm'G%V3{B&:b,Ěe FհeȣVX'"\bU P`]Uhm(֛c2afJ5seNE/rҲė(:bW#G.o@6 y]wu]bRKNjtB%*qmU3U s-ږ@vnKmʹ|} IUKeU&JkuKfU%gU9-,%Њ)RJ,qny,`pGu T,VL |1l߽ya"O,)fWdz^`0o8B7e!t`ާ XyQ%Tiނyæ|H W8 ["5z9BC 8+!n=uO 8&2^`cm/L1/' 43uq1 ,Еq=V{dmjeOy+5ݬ >ԘfdW5blYm]jܷׅSdxԬyj<q&?(.U~pmUeiCGm^k[W"Ҡ1E9F5^/h:R-An4G|kāgUGVG-$O/2iYSë́tm "ߧ#c]`wgfѩߝykqٙO#U6h]˔ŵF ?StIL4$"[T.˱v[l}w;e|jn=Vjsvx^+dZ7ZfAìB1RH x]lowYcOzy-7fhT&\%IO`lf$e9˷6{\-w!͡"p#`#a6bUr>ER:O>ec`{}~r>oa ~lnnu_pVyʋÛ o~}K# J?6Z9t d=B7ANv0 GmAC2,ΏgWsHOrcz @ѾlocW?gj_Uf & 3U9 +w&p'f(IJԩ ,p <[Y|M=hc"UĽ O_0Ǟe'8<ۆɂt\?]܉veȴ&Ja JEr"q^;0!y|,1Q١Ib#J+B';t,?6B&204U!-$E7l.`cwm[wuXvek^VA`a[SۤU3,v籑oFz3vG d>r.oC@:J_}X1xp}-[ Vbz[&zY,^@/g-9k3,<|.o<SܰےQq%x*,[:m~򢠏PmRMNQ/g>|^ Tec H=lx-g_2.PKF%(@-K ,R(B¢?Oݫ_LbN>n}/~ϟC?\~>5([eZ&m}Uϥ@ g_ LOƬ޶:2 SĒgx@UUˀS%i5 GbfT#tdڄfieY?P$n](A?8 0 <[{ a7ˏՐ'=Etǂm̀ۡowyY] Cx_V*V1Z({?n8U/wg_pԮg?Di!Uwo,^e G =<ZV]^ zǪW a-ġjDԀ$_xm- ѭ]XջvC-W}? U;z˪݋-XQ68nLiKUK3"p X5!fP a9$o2]8Xcݐr-=B@(":hT=XŦx6bjh6; ʹ 27VCWjpϿ@40 Ƭ޶N$4K:;)CGMR]?&nƿɀF\k;T.j#C浄徱nōzL}a~q17C°3dQ3\a;@? U7~˺{MNwjN?Jg[|;;! A!~i;g+ժB F+4ZARm5 ͗sKk0V5k4W5ak<@ amWDVC.g48BQF\H26$4 _Y ??m3gэc9t kbaJKszL跛r~6A7_ WCshxKK0J Nǭ޷2޳ HMa6P)67C_XKUqqnF40nօ:Gdcb6V'oUq1KvsnA xoan}~:C}vͤ,`(UC1xH vh;;Hn 7inml6J_v3:x>@CB s wu9%BS=bO` Y6 ȗzF徱+;@:ӈ忲^86aE0M\~ܬYk_{Sl.(VJ2>E[vJ!_hخg5.循 Ƽqط٭bUW?>v Úw7YەȀ Ә axp Цc5G< a$^P {ȵa-(~Aog`X &{ (\03bE?)/H[c=H\#{X$V=W,W-NgMd(\f}xoo*{{vO5.fVw}}+,2e?"̻.C-KC ;X|r:$mCU rQRf܂ } s5gj4D#t]uld'(=>6SoHܾ7ʍvQl7Tv"*3qJBpc:+k\}̬$Mo0eU. f"AͷëO :m^/?K qsDŽ\/?jIl|F;r6CcR" OηfߙL@v8`Gx=k[{0 FۼM1,, "w||7׋Z_`<ےX[C|\^WV>E_ ywUҫbQ 񿖪#k=Ol'=i3{O|{5cMvT7vxL}{u^}vn4ivh"|T34%pHlXܭu h9liPSliBƘ@s 7Mi EmQ3剻ZQj lkmB^DA {\%j2I]1Jpr3ށFEPn!pz|#$)Ճkf뒧ٯY*P"fC fG$*Ȫ24 .;<>S C _IKA`>mHQm~E;sșMuk5Fq $KBDru 9HtSn D;ra* Z}Abx:/C؉|r#aes *ft_>'9A@YKS"9ڋ`HG GG{vӾ+7[!22p[f}vTPSCġe;C&2'S<U f`WGLodN;%ׁGނYG챀ŏKJ%=+pB.%xa[Lƅ@ p p]p$nL$OR09hޑAwsW7}P ̪āH*>X6L )#=(QTrrfp9'Ls0rjpS}0.Gg75äyf@ٿ4/Tࢬҟ{-J,SsȚ#aaIo W6LaBb]3 E%'oW.+/no}b-ߞؔ;x(n>eEQY;&Gi Ȧ0mp&r| `=-PU w&͙$6DrgZcrV`?L&ȸVW_ םHq$ClY[Vlen͐тғr'5@*=FH Ḿ|N@`-}eEȏZK 8zVY~J8_j^bjHqrZ)h>@aXT[|)^->bKC5}Uك'*1)`*b3̦SiJڭSE UƩ+=zHUSDp-7.u&gLHUhC WP~J2^jTOiK#M!rܛ1MX_Ftv֗ =e/ZTV~EK_ J'G VP\V)T}pYć([l-nB&.qFx Q<;DP4ⓁU-f!>of%<~X0- ?;ƒz$u#9U;3<B]|w5<a18-4L I8->Ʈw> 0o^O*^`sēHV H kZQBVrOh6|slN| A5^3tO-cxJlG#IM׊IRQh}$cnv81=Hia{ez75#}úXצ\TGjUmпjɶ U|7h}QjrĴ9V>VUվ*8Yn~fXLvBU/K/h25(b|J)S(/\[U5)/qKuiwܬ%\w>>%w l\))uqa#>Yr]D7b,7D>dO:> ޽y?T?!&qE^ڔi0֨ANտ2_l~:< :Q[=-WQhP\=H3N[mh*ž<-L׀= }66 hVӾ1 dFEuc MFY8f%ڷf !}lֽ0Sl@]N^H_Be2_Xi'B3S_Ο3{sWƝ0?;aw&BsgpChBrlL>#Vb~wiZlMER'& N)ggG\ц'JQfZ㬗& 2<c.ՆJ7}WۊMF@K3d\ʉ˕ņiJ2Mٌ'Q/Q)Ac 7؋ Аd߱VT1?4_:$9ujLLr06& O9 ? ѧk0vՃ~k&Zt2&ĭ\7t {m9lg+'Bnٔɪ>r"f^Nȉ`=7`2&BlfDlQ,tZfОn #!rHi\:ߣkLЄg0}0Mn~ah;PRʮa.x~Mq|s R=UG⴨z(AzHWJQ8? H؁h4AyʡU23~G{&=/\ ޣh0`8 0 &Wsspd"!(utG]CT'aG=@c#8a#<uO uՁ!-wB)ⷆ4U e>ci>mqVEoVmNN7 H8!DHMíwj?-wGS*c]>Jwt>&+VݭS4i?6s`O=d{IQ 'L&:JyLP1_C6 qtrt$IL,P3@Ա][љGۦM/qp^6ŴmzIR"6Ǚqf9$i7}o>*>ao>Zn9Ԅ9@ӣ _jBi#Lbt:1FqOlXCݠbm 0hRA(п_PB1YsuK=S"oۚa@yf#p|]9*:VvK ]봤.}2iZΚDju q{[ n{Ѕ_Cw|[.!p ^rW 3>Ӌ[GY3}(h𩍈>ɗp>h qdJFؙ(-,\(\u*qBVqJW9WuIF}kpѭF=DI.17V Sv^SOJ52B {y]~[^f"9\`#} WU!S͟r[p8slt]wӞI:e2^)B#iW/(&=,=~K5iLOj:ulj/mDD~O?Pb`hjXѻWPH+,ݫPQ5X ,XKR92s!,֥F [SP?wKb=,njѿDڬ걥 F4{Սo, %UU^VUuJzz&sx٤ҫm7(V]T VuN {jdz{ej:gK +G{fJ)g+-aJ rq1ph@t`i0$qakʎ&hQ֮jɱo+񃾾("G]sɵP8 ),ZUˏmmbgUaUsUe)sk:o r}@hC,\M4-uEp+Td-\]EA  f/` 7=R 0k 3+AfRHkG] KW C ! т"\GbLzsI͠ '!FE Ž%5`? WH S{K _@4|LԗG\R >;`kpZH ,A]r=$ޙfH i! 52bFPˏ,&$2b)eM0pW;U?|uaTg 5zٖ/ 42{VܭY [!5?!оnWi3]XYmdafҞE/U<*QL?f[Dj#=y8;hm]O_\רLJNs, ܶרw]f]6<(0f]4=Ru'jB4od7Mݒp&tDnR;3rLZAȖTud>( U50~W {&tmN4H/,E{b;>"7R B+xob/KS|3s@:>$*_L"BxFNꎘMғ;l9rՓBh&|'_*bͿ~J:0*E5Ŷ(l3-,>1\WUVxnpiYՄPHtWV3jީVsr\3ȣnf^ EO _t)Y$<ҊEuw&X1oc_3Y3U`Ԣ耯h?'PB=?ңsQ6s (|;9@lo¨8? .>Wl'V KƝ >Qo1 C)<4ȵ\xBqmxT6&ޔHl6:U÷Ugavtk"OF-tClkjZ =?@'6@mkٔ}b/=E|3NiA:r߱Ɩ?{+m $Y#;$=k'9LQ4Dz9gqA$$!@J? @]]nT]z] |gڗ~[o vYۦ&|ӵxI|1yW%2f|;0'pg/kDǐ|숃8ɮ|~U>Ϲ 4w8u"‡^hui/t$XWDZ8%/_4BRL0rͩz@8I &T^fұN8n`gp0K>J\K FigvpQË_E?'w|tG~a׏Fh!9N+_Xo_3|G[.%/~dbe龎 URYD?߈lwb!^7O{aO4ǽXqt6<jQ0Kϯmqx054Fiǎ܆+SjϲfzReȚreQP~KHNqD˛j `ݢ2[Pf2wHV!5H.&2IwJGn2c&%!.1xv3l8NsD1G2.i 41,49e jMFi5w::٬utpZ`so3 [Xd&VX~qxntd+**wґ7qI}M)JJEW7Q!U$\W>A&#xT$M3`,!˗ x9 d$/[J|`8%s4V.=xǰK*Oԟٝ™)+N?jOA ;HDV~ gM,p;;$+#C:q8?+an.#Jj f 䆂'E F(>Yx cW!jɗ-JNQr3JĔ'-Du'\oBuƵf]شY ѹfّ GS.z_=qVtڨ c^gjg Wȥ3tu ]du0-29xw7BL~ڢVYb2ISBF"=̳ (s"~QKjosBI$*0 OA KS+*n< u%a ,(JO".9޻?s1KdHɸg5 LyX5G+XAGWt`8zò]ЭXꀜ68hQqQ&fc߹0g|.&Yc ko?b[ 'ch@A [ l{$ٙQYV5{y/_|xuH\J`BBd)ٗ6g\NjΨ|LK(We}o^쇤:i 1HN'iFzS 9l flch SCOx|j ^{ ԩr]28fik0p.lZ(`V*OZ/Ee*1]3O7݃03/Ft:ŋt sU2ӣxcH{U0,DTIã A 0!ѪEp%$Q_H8o9a5Jg< oFfk$OYjT+X'E>K̵)9Rs%cVl(v!OS=C<jy;ِLJĘiz5o2WI}7DCvr?踖qbؕ)TpdɈ )]c1Y`.( XR9&|6*œmla }sHM'wN$?gHVzңĞiY%ŵy "ЄO!ɹ럅y̴3tpt*C'JK JiҒ|[~y̢~/3';֩AL/m:9[12^M")9 1:*ND:%WyA o?-i a ~FRjL $SG^R%6K u 5R6;Xo*a\ЎGO,Sca[gHp[r8r2l[ivdkJ1mGzRn&zE/Np˗8%yr颼LW2nOL?g7 c { )JddDtA/2OR|th6  ktLưgzcJF"Rߢ}v.>{hNͽae5+zVVvv`+Eڰ~5;jt-Yb1BZrw/9P]RnjL&0(huF{l%BZDޢpF< ., .݀;cѢ\E'Y+IVƑu6x$Dv_C6$?M֚2&LV AqF[,z"FX%įE+wC! w掛@@Y|x3w{v>ְga(!zЦDpz:e`v{Y5=|1,90oh5@0dWR'A8.X(y>G cwrG~ϣԈ[V⪓AqЧmt[f[[Lk/5\c% G^}-M]" H_|$E4S=},a~̫<9:ɗ 5<91)f*{\"ɓ;^=g˾: ٗwd(ofv'7i~ط$4 xP"o~GQ33 AJQ%6DぁIi5x=ܙlXɠ攉jK#b%w<僡;9XniO 6]Y$Kb7ݽI7iܓժ iFRk[.̾Rqɹ!}n,V]OW_Z'{inr_j&Zu\R[@c-N:b56d2tLc90ANOeB b,rqGJEق1ϡx3&錍"y(?0.Wsd U9^.R\pVдF^qxH &;Xs{`,aΘ:Uف1Q83bLrXΫd$G7+QD8=I$tG=18ICA\P!W,%Y_%E: +d8Mr=W'Cua?RVGl).I7ZMQ8Иex6cC>r5=:9Q:0ep?C ט QkÓ[KM^qXЍ]D fzjt甑Ƈ-1eʌq@ `V 6|8-l:* ܕ|@N*[P/ h3}5gbn/h0p5h0n0FdhL@ `ds ~^nɇP!I-Ulx09&c]ĩgܮgE4"?Bq߫ LEskeh3=5&( ط#mD>\Dfzj,AP!A!oG <2Z2Z44?^.譂  fyj"eNoWq?nah:"btkrv%=IX&hsAO۪;8Uu^-kWt/8!/}Z%F_N'M'V:k#,.W: `h/tj#SǙz:˥M(WuҋDB8s@t׳O$~RPrػH/Ag;,8f Nsŝt7$/8;Fb& Axq_:U{z!a>@{o>D *wB+4 ܏ 8ñ/8Á5㌆/$V +.biOFLJ$)H<^/k"5+1_ct|r= |å;n+q{;/qt-;n-8+Ap:U]A KÄY̫5x5x&?&sn|ϓ3cFOs4 #3c.y%0"E%ɫd(>}T2UEyLW co~!IOIEppV7ˣ˻e\0椽H(Ioᜆ oRūGΓ@x76[.=`ؿ:Kd/uKFf劧 G=XN^U ;x"@U[8EzB )GR@dBFT2:3V&(o~cYLb8rl硽byu:<g7pFT|i 0W#>HU̾VjV@VM%j-hL)0ALAhl ,8ҠVQ c~DqbJ#`~ɋZ(ʓ@s}/LpurMpU,dutT7#!.LedܚxMt3, /SEu:%+wǩPο-E]wH .c f0ϻ}i¼ ؼi0Cf:srޣ({ prÇUYG g+{15s*Z"& b̹!ŻAh#QgZ N@t.-euH( @=@W hPC:j EuP+qMA哭"jf?Y_u#*t]m*Wlvk2w;\C~َYf;pj9x3VBV7[8S'"RD.hsaC(N'`O4fy=SOGz3=%POz%?_.28c.)s3)=/BXQ<`~A,0 3@< 2Dclqx+`Eշbv _k >r (xVBtyaU@dY*g 7 Gt|ւ' q$4xS[*g,omʡ>F xȏڴHM{몯:njG [*NO,`n꺔Aw|BƷj?ݞ(}S!>x]x hsdKĞ@-j6%CZnzhŏj@@c /o7 j= 'ZKl#+ Y9y, u_Y(ԏJ|݀ sa0L4pkpaTz0Zu:c N6\<7j(@Z L>sBay (H9Y xv&x͖LiUVvqm]/jFN GitX281? DDZڻf'πJ4yϤHbB,8 ~[9guz30+ѩ/I/ p󑽂L5ױ^偙L<}g>s3>sP9>S 4̔ji[Nrtx zٱ= Zz:Հ@hD?`L Ñv ˱iT(rn-1D["k/Vx7JwNtusѽ"X0bfsmįs 1<CnT⫥ky001 *akY>FTR8G40×P (0pF8q0@ ;ڹ{c<=F(v C|igXc|o250W,Zy/(æYϮQi6ZÁ$8_ּ KV`eU& 8P [ 6 O`hhm`IjӺ(E%'W@)'ùwհ<ːS̤XbB%ER,40|\ %Q+dI\t'-zC$ȗY7H02Dpl]mB'?ƚ0{71j4=Ko`Ŝ-/2/1>Fp1[E[[DIok,_nT߅Ȗ~l,| zQ|ɲ"b 0:>/* qd٪:[0O{cբǒE+ʛUݖ;S_.G&VeX( " hݣp`4kssWfݝs<fHx&*;|hٲC6,;ܖsSݸ=?XMVlZt_|-n$={2nd8(V= w!`td:jYuB-~|]x7Ap;Ufq9  xZƐO/1-SL ]x 4yÚ^i$& : ٣|]&@!0E(Ǧ"s̮P.>}cc|6=̮5q'(@!& a_-I0ZT (1pPX1qҋ=i̫>* c ۢnyNDL9LS^s!B0u.xϴE7吩jv`":̽GCxq0h?BeNc'y 껡gi<Եk \k6y9z3}/+W%ZX^f.!.O,NX̠xC#ަqr ]!w_5I 5(`"t"03&#P,U-@3(1/ @ E˻o>j I/k#KO7eXG8_ w牰LúZfCeޠ] f잞{^ זAzP \ɭ !{Z"H_yk.2M@ZԌxH3 5V"e6\y k Xl_x60|aMs|I0Z#@+fJi|Q܊hJ06=ec]aߡo~Ƌ5ٞTWȕp犇i)*= ~Vitl|xgC|^ B9PXrl| ݇!M@jjhjPrT%~[!C@D&)!V><>`u ƽc6˯YHz8ZSJxPuq va \DWO&ݏWqc݃X <1D5{3?8 aX'E}P =q.Pk X =vk 2I Ϭe b(e5Lp,w1=s\T3-i.{oYv'#ubsP-t* =2.kc_p~A(ϖ _SƸ%µ9ZFyjHr\\$! /'.Y2`2f:ہw>%`oc]wNG>jBdo}`a|Bx"Ŷ&@PȦ1 z GP^ѕ;{$W˅&Ň8/u\@ /bGSs1$IHM$t6,M( 8V"dzۤ'7]$:aY'<1Io so$-h,:1L,B.M>0;Zc?KÓbhU 1D׳YRbu3m!J7|gNՂJ8dqRT[ =#?o}Fл+R>aĒt(/{'_'2uQ%cvCL0Cٟ@b^T10bi6 ^a-Cc# t_™$c0|c~M3{}@w )!.s|I -ǘ:A xt \(bw/Tx"`.Ͷ9:{URʖ$uY\iYӕ{b~huÒꯘT ry23xtR]u)Is%>ra Qr~ aQrd".}<^*89UE]6퀸]4 ˯n#@w;+ t}UU+/(յh&ژs%w9=od>)o!pkg^+x>(˵\cy!6 PWU]7 \PP=fnHӜl 0 ,l 0JWꑢ"_Qۃ_?,JO/|H m "'k {b8>KofAGg2k+ecl#:[KvLՀjr&e~vN vuNmwnjߟ5!J G):)%ܣt?o EIr% 0?&,sbzY^K%WZJ!338J,$Z#.G-X/=;(`# 8 B= G;(?7 ݠuCcFAhFAxĢu AH~WmPxk"0+W1`ݳȔ"?׮R4M˨IsLⶼ ,0z3toyY>acu/#Dd8Gp5!``Z]'k߾ #vki^]Q(z2]rV{m x׳d<dR-`?^2>/_XiP/y1%>iX_-lj83ن#l'N v:ðy.m$8a?9[_My^ ybG{Y1[uLu륭IR9iQnq`.-T3>Uڔ~Y2.YP(DseLw gqK2 nz]xrG̈́Abk;1 |^\+TC ob]u3_,ilc\4h h h h h h hTMw8vHQj逡w"R<Ɓ>saGbdeju:`ȵ'/S/K49u g}亂֚T \,KVfE bx_ s/ Ԯ˹0!$?%ڹ˰B'm ijUQݗjn|%גZ[)L6k ac}37$^EŊtcnx.̹"9J.rU8'>!)O`@¥wæ\Ǒb/9 aYI$2OtpT^魥Yb,s߄T?=yPjza}@,~M<]>!4d\t{:]qE`OW|ϴ/Ag?wѨ:ؓX^w ~AFKEK3xU5b>:.ԤGrqQ˴jjQw#a}hAvl:C~ILgjAxvLj⁚Y)`n>u4A?֡Ѡ<[A=hXn0P_]{`?}Y)ۮN_\#tZ,k/頫K5֫V8o2x{%`ؾh'yZFlfqG.vCLrqQ,qyIwRٌy03Ǩ1vcBe%`txoó!)F n%r°v:~|.R> zvs /mNA?ZCȘwߎ}|fH2?Q-k .>0՝tH3̠tPmU@X;ҹ<$r*_51`i:Ci72=OreCX˺2=k 5;ىکSGT45I\bݖ3<əuOH$k~NoPS<)3u_OD>Ev#(ڼj ^N]c9EA)xv12 8UƐrE0Wdo>gvrv)F|NXr2/Y$۟_Ӌ_E̟ѫYJEj֮LLhYg*etv.P)h-WQG,JgĽJX4{-uT,ʛbFivGXV")ůyDoTK1)eUwY0Wүl}u'A&+3k.e&EWD]N&K*숨T{PLFE c0QTPkQ3z`WU(mu\tfldU9YƹPl.q8t=UN $ŭg u,et%(5?dkoAnYkE yNḊ)օ* ZwV鹱'nUVM'??mʽE㌬ePI5UeXW"*d_[^FsJl79QU.CJTx+SW屌 EP/_G#RU$EHl|R5I&Ug/%df:,\Tj/I~Y$i"HvˢQ=],ᥨ.5Tk{#3(UwhT.F,Mͺ/^6='t:[.%EMxE&'?Gb8Mc뷉Ϋo4SSy`edL3ip?L&{O,]\4JaNB/,f'p&7hz)2,P"2g&@_?.Ex,(99)ZNz"cXnФHHtY"%LϟV*M5ugSJCMǰ?[UaB;#M C?_?GB4[ Azs8L.y*FUW Nhq#i$=e+J_4hue V%Q K:Bҋ0窤Z⨚UKW̅6 Mʜ XKE릳cP_e+'W+5UP]Se*Jg>TUr2~rQ;30n0=AchjK֋U\X#f,_/jjnLRζő%DYnVf϶7MJTRs*>މ5hG~9-xM\'ŭ*Rf,R{]S^@HL鮜j[IJqyo࠱+_w֙^.iDcR$TgQ{wij+9]@@3xEZpku_J5;+*Vhڜ 'dVUڞvם_ ytd>"Ӯ_ע~Uf2tB䞚U.P'j @ΘU,-Cɜk5QeCNz X5ڷk-sG}^Qg6jϺ7FƺB7KKI,uB\(d*g׿4PwT&-P}1<"тXeFףn)+?Gm_vhf+T *c/*y85]FnU]?GT*i9gPV`wLu٢: uԞFݥpUթ+ݿ8֕}MҵM65PtqBDYC䴑mjsT"ߙ?bASoV 5Ro9&jDUfZΌ,.3WxF=顬G4-88ge0t FdV̞ ]D"X0LeEMMr;y79˪_ EЯhۍ*Eһs(+%7#*Wꬆ:BIBXg]yPBM9FճU±3oNH]0 ѤIWC.Qwa״Xgulpl h{Jemj`"Fo[$Ce31;+b%-!+wE6<3h@ xlʱlK3eَ~j3jm\ﻪm_];14OFǓƶroc/KBsh0TU)rԵo}le3x 4:ۛ.j>^'U^Lᨧhg%FWvwOT3;);P-CS_:=*e+ JUt'%v/$X6߼xNO׮zJ5p9 M8+r p(خ2Ґnb_$YNSGٻ*N鉞b."h7Sk.<_jyx d)Eb>R_;*]>|[=ͻ7?|/&?R.Mys{/bdY6Ys*Wmb6Kd̦MǗ<}K5$@_Mi!Gp;<"cÎZ\pe1kXY[x(RkGb&r()W}T@mkµ-V Mৃ"W܍4[ݲꫢFCsDDV9⨎}ڥO{`8(c}P-OTcz8TƋ=sQ{GT۫ j[gّ':2TUW g0\X?%v?Y*Vs~j*z3𧁞=ϓǟ7UCneup<GXeu#6 zφBAEIOq_$tYm Yww-kNބo6FÅb[8y#sOfRFS (?ަ9CV4b%b8bgXo#yK'D|J2HloN׶wweU l9 W+~d|w>Wm]/^g"]=W:sP"/)v6o]od݉2<}QLos>:2͡ϟɒmպ-uGs;e|÷Jt\Sַ9'Y:@3N|Cu\ Los&ɷ gemޗxȗ3O[b=O18e}#8/6z OtMy:BSe/Nvw©3nhSf`ݔѾ[ Mp 233ю RhT~ d]3LQNuSFeNul`b6먌R6\׋:oyhlWM VӟL>N,^8olxRwwM6}.♢ט%Lᳬ?HCZ; anr͑ ={o"H >o]ʬ}퀢apcȨ"F67Z73bdw{;meVv3j7*fӍHkp[wJ.1w ]4q:TRP;ݕFh}MKYxm>V3T6F^HEF1Z'Ez?~=#VΨYASv3f _vFNB?ޡklogr C{Ǯ;^>ʽ ogӫ*w̤:6at;uX=6{o|)ngf:{eFɈk@$>$N??L6OVi^ݲj!dFlaݟ~|)[7w{?nysF'RȐ7Pb/[*x#v|2:ޯG*FS^̴]mw˚sqASf4f/I6~}֘Qݤ'6u9V_?| 6ʴ-n>L&{>2;=@oڟlS3J;NGY~f/-"TSM]^t}jWۇ8/T/)W7<_oM9Wn $g,Qiȏ =y :O^cywW~>AAw-O2P}r)ޠ?=:>؆6kY߲׊hxM;|6nT*[ 8ѻH̓tv•iw|t Eax4T(8Z>mA?O/dcDi~cx?;[ߟjZlq$ef~$O4 chAîIq>+;~%9[_p;($jMY[2s 0ENW%w|hӖ 9r^|?pfܮI~.HD'(rrHW+ՍI<-Er^Y1\97D,8ǯIɗIeo4; xUp2<>D{G>_G|çȁp3h&s.*d\&E;r]WшBnwIY%*O4& Ꙥ鎜HڡZ Y/"c5{Վ DUSί)1kY($+WUkA ϩ(?#Tk0HV']BGv\^.qmW}h B$[OҕX/I+ICiw޳a^;8jO"ﻨ>ds;[[e8De;kS/|~:J3먾J]4t cV*DEkiH·^o<:]&YfVr(5\U:EA2Yl㮜i/;e[hf69٬/Rej%5:ۃ|r%o(/W 24m G˟t$V=]{anF{Ȣ71ePU]ԏ^-ojtpnƵQCg0^O^ y&g\ jڝs3Z9asq Z/kVlCmM];.K;E~J~I'o*+Þн?hDZ?j2n Mq[}sZL_et%Pn]iX]bQdzIړRz-E%:+ӋL,c>ב͜ɼ(}wp\% ~AKB^ɵzG$;Q+AqCREQ쎗( On1Lrq$PI`>4\>O/0ՇW'Ye:{\N+.8H݃X=X4C<=C3ZLq7w h0lLȸho-XeE(*y!%ۤP,HdX wd_[ǫx.Wf딻RfLlR-q %=hksU3W3{iCJ9b\#=v:E5fkB|]/G߽0H4=MW7JEmVvߜ%JNL2ˊ~TzL̆RF/뜆J.Q=JnLkTN}&񋉪jNQEՙoq%dqG>H.ؼڞe|kFv oUeknўۖq<,)G-dCVLiJwk;ƈ ?߻VeuW:ʪ^Jߥ?%+wq;w@dnujҮ3b4k*S{ ҚĔaCYQy*>lTT7*"X0~9דERY#ʯYbox?E|Q.#i4jܹkaCL^?Q6Væ->Zj@m%ŭI\\$V#1iEqngO9keu EI5ݛ9791JW-T^{0d\gZ/>~GVIW.kǭ/{-^]8jx}TZǯcU /zcnkz%7wXԃu{(=xw zǓ7^҅!(꣼oƋuY@ 8uR}X++-w:; z@~[?H(0'(%CMpZ c@ WS3n%j]KsogZöw5Qs_%p^ô׳?kZ|c|aW Wxhw w;T19n;"2ƵQ4Qn(2DWҏ7O* nu gKlkS'|>ҹf˖fu:Ww]IoD?V Xv_zT뗝^ F]Wt"U;, _ b|Ngu[ڲ;FmaQǢK]h0'ǮhaZ5<," Ǐ 4lƻK]㏀ts4]Rkpא5n|D#dPc j')HPc {A7):FQB)Y\46#E DhhavcȂ"i|k`n}0D O7uD̢?K36{x#A6UHp' Mo?`˅ ?]CMc~Pt<rv#i;tѼ<Ŵ𩦄V|18NJW}/1덭ѪFj̔=7S=Hy yj/HIpT>rt#C|иnԡyXTfFn(>8*?aq]{Bznc-1.9.1/modules/modpython.cpp0000644000175000017500000004352514641222733016752 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include #include #include "modpython/swigpyrun.h" #include "modpython/module.h" #include "modpython/ret.h" using std::vector; using std::set; class CModPython : public CModule { PyObject* m_PyZNCModule; PyObject* m_PyFormatException; vector m_vpObject; public: CString GetPyExceptionStr() { PyObject* ptype; PyObject* pvalue; PyObject* ptraceback; PyErr_Fetch(&ptype, &pvalue, &ptraceback); CString result; if (!pvalue) { Py_INCREF(Py_None); pvalue = Py_None; } if (!ptraceback) { Py_INCREF(Py_None); ptraceback = Py_None; } PyErr_NormalizeException(&ptype, &pvalue, &ptraceback); PyObject* strlist = PyObject_CallFunctionObjArgs( m_PyFormatException, ptype, pvalue, ptraceback, nullptr); Py_CLEAR(ptype); Py_CLEAR(pvalue); Py_CLEAR(ptraceback); if (!strlist) { return "Couldn't get exact error message"; } if (PySequence_Check(strlist)) { PyObject* strlist_fast = PySequence_Fast(strlist, "Shouldn't happen (1)"); PyObject** items = PySequence_Fast_ITEMS(strlist_fast); Py_ssize_t L = PySequence_Fast_GET_SIZE(strlist_fast); for (Py_ssize_t i = 0; i < L; ++i) { PyObject* utf8 = PyUnicode_AsUTF8String(items[i]); result += PyBytes_AsString(utf8); Py_CLEAR(utf8); } Py_CLEAR(strlist_fast); } else { result = "Can't get exact error message"; } Py_CLEAR(strlist); return result; } MODCONSTRUCTOR(CModPython) { CZNC::Get().ForceEncoding(); Py_Initialize(); m_PyFormatException = nullptr; m_PyZNCModule = nullptr; } bool OnLoad(const CString& sArgsi, CString& sMessage) override { CString sModPath, sTmp; #ifdef __CYGWIN__ CString sDllPath = "modpython/_znc_core.dll"; #else CString sDllPath = "modpython/_znc_core.so"; #endif if (!CModules::FindModPath(sDllPath, sModPath, sTmp)) { sMessage = sDllPath + " not found."; return false; } sTmp = CDir::ChangeDir(sModPath, ".."); PyObject* pyModuleTraceback = PyImport_ImportModule("traceback"); if (!pyModuleTraceback) { sMessage = "Couldn't import python module traceback"; return false; } m_PyFormatException = PyObject_GetAttrString(pyModuleTraceback, "format_exception"); if (!m_PyFormatException) { sMessage = "Couldn't get traceback.format_exception"; Py_CLEAR(pyModuleTraceback); return false; } Py_CLEAR(pyModuleTraceback); PyObject* pySysModule = PyImport_ImportModule("sys"); if (!pySysModule) { sMessage = GetPyExceptionStr(); return false; } PyObject* pySysPath = PyObject_GetAttrString(pySysModule, "path"); if (!pySysPath) { sMessage = GetPyExceptionStr(); Py_CLEAR(pySysModule); return false; } Py_CLEAR(pySysModule); PyObject* pyIgnored = PyObject_CallMethod(pySysPath, const_cast("append"), const_cast("s"), sTmp.c_str()); if (!pyIgnored) { sMessage = GetPyExceptionStr(); Py_CLEAR(pyIgnored); return false; } Py_CLEAR(pyIgnored); Py_CLEAR(pySysPath); m_PyZNCModule = PyImport_ImportModule("znc"); if (!m_PyZNCModule) { sMessage = GetPyExceptionStr(); return false; } return true; } EModRet OnModuleLoading(const CString& sModName, const CString& sArgs, CModInfo::EModuleType eType, bool& bSuccess, CString& sRetMsg) override { PyObject* pyFunc = PyObject_GetAttrString(m_PyZNCModule, "load_module"); if (!pyFunc) { sRetMsg = GetPyExceptionStr(); DEBUG("modpython: " << sRetMsg); bSuccess = false; return HALT; } PyObject* pyRes = PyObject_CallFunction( pyFunc, const_cast("ssiNNNN"), sModName.c_str(), sArgs.c_str(), (int)eType, (eType == CModInfo::GlobalModule ? Py_None : SWIG_NewInstanceObj(GetUser(), SWIG_TypeQuery("CUser*"), 0)), (eType == CModInfo::NetworkModule ? SWIG_NewInstanceObj(GetNetwork(), SWIG_TypeQuery("CIRCNetwork*"), 0) : Py_None), CPyRetString::wrap(sRetMsg), SWIG_NewInstanceObj(reinterpret_cast(this), SWIG_TypeQuery("CModPython*"), 0)); if (!pyRes) { sRetMsg = GetPyExceptionStr(); DEBUG("modpython: " << sRetMsg); bSuccess = false; Py_CLEAR(pyFunc); return HALT; } Py_CLEAR(pyFunc); long int ret = PyLong_AsLong(pyRes); if (PyErr_Occurred()) { sRetMsg = GetPyExceptionStr(); DEBUG("modpython: " << sRetMsg); Py_CLEAR(pyRes); return HALT; } Py_CLEAR(pyRes); switch (ret) { case 0: // Not found return CONTINUE; case 1: // Error bSuccess = false; return HALT; case 2: // Success bSuccess = true; return HALT; } bSuccess = false; sRetMsg += " unknown value returned by modpython.load_module"; return HALT; } EModRet OnModuleUnloading(CModule* pModule, bool& bSuccess, CString& sRetMsg) override { CPyModule* pMod = AsPyModule(pModule); if (pMod) { CString sModName = pMod->GetModName(); PyObject* pyFunc = PyObject_GetAttrString(m_PyZNCModule, "unload_module"); if (!pyFunc) { sRetMsg = GetPyExceptionStr(); DEBUG("modpython: " << sRetMsg); bSuccess = false; return HALT; } PyObject* pyRes = PyObject_CallFunctionObjArgs(pyFunc, pMod->GetPyObj(), nullptr); if (!pyRes) { sRetMsg = GetPyExceptionStr(); DEBUG("modpython: " << sRetMsg); bSuccess = false; Py_CLEAR(pyFunc); return HALT; } if (!PyObject_IsTrue(pyRes)) { // python module, but not handled by modpython itself. // some module-provider written on python loaded it? return CONTINUE; } Py_CLEAR(pyFunc); Py_CLEAR(pyRes); bSuccess = true; sRetMsg = "Module [" + sModName + "] unloaded"; return HALT; } return CONTINUE; } EModRet OnGetModInfo(CModInfo& ModInfo, const CString& sModule, bool& bSuccess, CString& sRetMsg) override { PyObject* pyFunc = PyObject_GetAttrString(m_PyZNCModule, "get_mod_info"); if (!pyFunc) { sRetMsg = GetPyExceptionStr(); DEBUG("modpython: " << sRetMsg); bSuccess = false; return HALT; } PyObject* pyRes = PyObject_CallFunction( pyFunc, const_cast("sNN"), sModule.c_str(), CPyRetString::wrap(sRetMsg), SWIG_NewInstanceObj(&ModInfo, SWIG_TypeQuery("CModInfo*"), 0)); if (!pyRes) { sRetMsg = GetPyExceptionStr(); DEBUG("modpython: " << sRetMsg); bSuccess = false; Py_CLEAR(pyFunc); return HALT; } Py_CLEAR(pyFunc); long int x = PyLong_AsLong(pyRes); if (PyErr_Occurred()) { sRetMsg = GetPyExceptionStr(); DEBUG("modpython: " << sRetMsg); bSuccess = false; Py_CLEAR(pyRes); return HALT; } Py_CLEAR(pyRes); switch (x) { case 0: return CONTINUE; case 1: bSuccess = false; return HALT; case 2: bSuccess = true; return HALT; } bSuccess = false; sRetMsg = CString("Shouldn't happen. ") + __PRETTY_FUNCTION__ + " on " + __FILE__ + ":" + CString(__LINE__); DEBUG(sRetMsg); return HALT; } void TryAddModInfo(const CString& sName, set& ssMods, set& ssAlready, CModInfo::EModuleType eType) { if (ssAlready.count(sName)) { return; } CModInfo ModInfo; bool bSuccess = false; CString sRetMsg; OnGetModInfo(ModInfo, sName, bSuccess, sRetMsg); if (bSuccess && ModInfo.SupportsType(eType)) { ssMods.insert(ModInfo); ssAlready.insert(sName); } } void OnGetAvailableMods(set& ssMods, CModInfo::EModuleType eType) override { CDir Dir; CModules::ModDirList dirs = CModules::GetModDirs(); while (!dirs.empty()) { set already; Dir.Fill(dirs.front().first); for (unsigned int a = 0; a < Dir.size(); a++) { CFile& File = *Dir[a]; CString sName = File.GetShortName(); if (!File.IsDir()) { if (sName.WildCmp("*.pyc")) { sName.RightChomp(4); } else if (sName.WildCmp("*.py")) { sName.RightChomp(3); } else { continue; } } TryAddModInfo(sName, ssMods, already, eType); } dirs.pop(); } } ~CModPython() override { if (!m_PyZNCModule) { DEBUG( "~CModPython(): seems like CModPython::OnLoad() didn't " "initialize python"); return; } PyObject* pyFunc = PyObject_GetAttrString(m_PyZNCModule, "unload_all"); if (!pyFunc) { CString sRetMsg = GetPyExceptionStr(); DEBUG("~CModPython(): couldn't find unload_all: " << sRetMsg); return; } PyObject* pyRes = PyObject_CallFunctionObjArgs(pyFunc, nullptr); if (!pyRes) { CString sRetMsg = GetPyExceptionStr(); DEBUG( "modpython tried to unload all modules in its destructor, but: " << sRetMsg); } Py_CLEAR(pyRes); Py_CLEAR(pyFunc); Py_CLEAR(m_PyFormatException); Py_CLEAR(m_PyZNCModule); Py_Finalize(); CZNC::Get().UnforceEncoding(); } }; CString CPyModule::GetPyExceptionStr() { return m_pModPython->GetPyExceptionStr(); } #include "modpython/pyfunctions.cpp" VWebSubPages& CPyModule::GetSubPages() { VWebSubPages* result = _GetSubPages(); if (!result) { return CModule::GetSubPages(); } return *result; } void CPyTimer::RunJob() { CPyModule* pMod = AsPyModule(GetModule()); if (pMod) { PyObject* pyRes = PyObject_CallMethod( m_pyObj, const_cast("RunJob"), const_cast("")); if (!pyRes) { CString sRetMsg = m_pModPython->GetPyExceptionStr(); DEBUG("python timer failed: " << sRetMsg); Stop(); } Py_CLEAR(pyRes); } } CPyTimer::~CPyTimer() { CPyModule* pMod = AsPyModule(GetModule()); if (pMod) { PyObject* pyRes = PyObject_CallMethod( m_pyObj, const_cast("OnShutdown"), const_cast("")); if (!pyRes) { CString sRetMsg = m_pModPython->GetPyExceptionStr(); DEBUG("python timer shutdown failed: " << sRetMsg); } Py_CLEAR(pyRes); Py_CLEAR(m_pyObj); } } #define CHECKCLEARSOCK(Func) \ if (!pyRes) { \ CString sRetMsg = m_pModPython->GetPyExceptionStr(); \ DEBUG("python socket failed in " Func ": " << sRetMsg); \ Close(); \ } \ Py_CLEAR(pyRes) #define CBSOCK(Func) \ void CPySocket::Func() { \ PyObject* pyRes = PyObject_CallMethod( \ m_pyObj, const_cast("On" #Func), const_cast("")); \ CHECKCLEARSOCK(#Func); \ } CBSOCK(Connected); CBSOCK(Disconnected); CBSOCK(Timeout); CBSOCK(ConnectionRefused); void CPySocket::ReadData(const char* data, size_t len) { PyObject* pyRes = PyObject_CallMethod(m_pyObj, const_cast("OnReadData"), const_cast("y#"), data, (Py_ssize_t)len); CHECKCLEARSOCK("OnReadData"); } void CPySocket::ReadLine(const CString& sLine) { PyObject* pyRes = PyObject_CallMethod(m_pyObj, const_cast("OnReadLine"), const_cast("s"), sLine.c_str()); CHECKCLEARSOCK("OnReadLine"); } Csock* CPySocket::GetSockObj(const CString& sHost, unsigned short uPort) { CPySocket* result = nullptr; PyObject* pyRes = PyObject_CallMethod(m_pyObj, const_cast("_Accepted"), const_cast("sH"), sHost.c_str(), uPort); if (!pyRes) { CString sRetMsg = m_pModPython->GetPyExceptionStr(); DEBUG("python socket failed in OnAccepted: " << sRetMsg); Close(); } int res = SWIG_ConvertPtr(pyRes, (void**)&result, SWIG_TypeQuery("CPySocket*"), 0); if (!SWIG_IsOK(res)) { DEBUG( "python socket was expected to return new socket from OnAccepted, " "but error=" << res); Close(); result = nullptr; } if (!result) { DEBUG("modpython: OnAccepted didn't return new socket"); } Py_CLEAR(pyRes); return result; } CPySocket::~CPySocket() { PyObject* pyRes = PyObject_CallMethod( m_pyObj, const_cast("OnShutdown"), const_cast("")); if (!pyRes) { CString sRetMsg = m_pModPython->GetPyExceptionStr(); DEBUG("python socket failed in OnShutdown: " << sRetMsg); } Py_CLEAR(pyRes); Py_CLEAR(m_pyObj); } CPyCapability::CPyCapability(PyObject* serverCb, PyObject* clientCb) : m_serverCb(serverCb), m_clientCb(clientCb) { Py_INCREF(serverCb); Py_INCREF(clientCb); } CPyCapability::~CPyCapability() { Py_CLEAR(m_serverCb); Py_CLEAR(m_clientCb); } void CPyCapability::OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) { PyObject* pyArg_Network = SWIG_NewInstanceObj(pNetwork, SWIG_TypeQuery("CIRCNetwork*"), 0); PyObject* pyArg_bState = Py_BuildValue("l", (long int)bState); PyObject* pyRes = PyObject_CallFunctionObjArgs(m_serverCb, pyArg_Network, pyArg_bState, nullptr); if (!pyRes) { CString sPyErr = ((CPyModule*)GetModule())->GetPyExceptionStr(); DEBUG("modpython: " << GetModule()->GetModName() << "/OnServerChangedSupport failed: " << sPyErr); } Py_CLEAR(pyRes); Py_CLEAR(pyArg_bState); Py_CLEAR(pyArg_Network); } void CPyCapability::OnClientChangedSupport(CClient* pClient, bool bState) { PyObject* pyArg_Client = SWIG_NewInstanceObj(pClient, SWIG_TypeQuery("CClient*"), 0); PyObject* pyArg_bState = Py_BuildValue("l", (long int)bState); PyObject* pyRes = PyObject_CallFunctionObjArgs(m_clientCb, pyArg_Client, pyArg_bState, nullptr); if (!pyRes) { CString sPyErr = ((CPyModule*)GetModule())->GetPyExceptionStr(); DEBUG("modpython: " << GetModule()->GetModName() << "/OnClientChangedSupport failed: " << sPyErr); } Py_CLEAR(pyRes); Py_CLEAR(pyArg_bState); Py_CLEAR(pyArg_Client); } CPyModule* CPyModCommand::GetModule() { return this->m_pModule; } void CPyModCommand::operator()(const CString& sLine) { PyObject* pyRes = PyObject_CallMethod( m_pyObj, const_cast("__call__"), const_cast("s"), sLine.c_str()); if (!pyRes) { CString sRetMsg = m_pModPython->GetPyExceptionStr(); DEBUG("oops, something went wrong when calling command: " << sRetMsg); } Py_CLEAR(pyRes); } CPyModCommand::~CPyModCommand() { Py_CLEAR(m_pyObj); } template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("modpython"); } GLOBALMODULEDEFS(CModPython, t_s("Loads python scripts as ZNC modules")) znc-1.9.1/modules/modpython/0000755000175000017500000000000014641222745016240 5ustar somebodysomebodyznc-1.9.1/modules/modpython/CMakeLists.txt0000644000175000017500000000741214641222733021001 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # TODO: consider switching to swig_add_library() after bumping CMake # requirements to 3.8, when that command started using IMPLICIT_DEPENDS set(modinclude_modpython PUBLIC ${Python3_INCLUDE_DIRS} "${CMAKE_CURRENT_BINARY_DIR}/.." PARENT_SCOPE) set(modlink_modpython PUBLIC ${Python3_LDFLAGS} PARENT_SCOPE) set(moddef_modpython PUBLIC "SWIG_TYPE_TABLE=znc" PARENT_SCOPE) set(moddepend_modpython modpython_functions modpython_swigruntime PARENT_SCOPE) if(APPLE) set(CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS "${CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS} -undefined dynamic_lookup") endif() if(SWIG_FOUND) add_custom_command( OUTPUT "pyfunctions.cpp" COMMAND "${PERL_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl" "${CMAKE_CURRENT_SOURCE_DIR}/functions.in" "pyfunctions.cpp" VERBATIM DEPENDS codegen.pl functions.in) add_custom_command( OUTPUT "swigpyrun.h" COMMAND "${SWIG_EXECUTABLE}" -python -py3 -c++ -shadow -external-runtime "swigpyrun.h" VERBATIM) add_custom_command( OUTPUT "modpython_biglib.cpp" "znc_core.py" COMMAND "${SWIG_EXECUTABLE}" -python -py3 -c++ -shadow "-I${PROJECT_BINARY_DIR}/include" "-I${PROJECT_SOURCE_DIR}/include" "-I${CMAKE_CURRENT_SOURCE_DIR}/.." -outdir "${CMAKE_CURRENT_BINARY_DIR}" -o "${CMAKE_CURRENT_BINARY_DIR}/modpython_biglib.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/modpython.i" DEPENDS "modpython.i" copy_csocket_h IMPLICIT_DEPENDS CXX "${CMAKE_CURRENT_SOURCE_DIR}/modpython.i" VERBATIM) else() add_custom_command( OUTPUT swigpyrun.h znc_core.py modpython_biglib.cpp pyfunctions.cpp COMMAND "${CMAKE_COMMAND}" -E tar xz "${CMAKE_CURRENT_SOURCE_DIR}/generated.tar.gz" VERBATIM) endif() add_custom_target(modpython_functions DEPENDS "pyfunctions.cpp") add_custom_target(modpython_swigruntime DEPENDS "swigpyrun.h") add_custom_target(modpython_swig DEPENDS "modpython_biglib.cpp" "znc_core.py") znc_add_library(modpython_lib MODULE modpython_biglib.cpp) add_dependencies(modpython_lib modpython_swig) target_include_directories(modpython_lib PRIVATE "${PROJECT_BINARY_DIR}/include" "${PROJECT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/.." "${CMAKE_CURRENT_SOURCE_DIR}/.." ${Python3_INCLUDE_DIRS}) target_link_libraries(modpython_lib ZNC ${Python3_LDFLAGS}) set_target_properties(modpython_lib PROPERTIES PREFIX "_" OUTPUT_NAME "znc_core" NO_SONAME true) target_compile_definitions(modpython_lib PRIVATE "SWIG_TYPE_TABLE=znc") if(CYGWIN) target_link_libraries(modpython_lib module_modpython) endif() install(TARGETS modpython_lib LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}/znc/modpython") install(FILES "znc.py" "${CMAKE_CURRENT_BINARY_DIR}/znc_core.py" DESTINATION "${CMAKE_INSTALL_LIBDIR}/znc/modpython") function(add_python_module mod modpath) install(FILES "${modpath}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/znc") endfunction() # This target is used to generate tarball which doesn't depend on SWIG. add_custom_target(modpython_dist COMMAND "${CMAKE_COMMAND}" -E tar cz "${CMAKE_CURRENT_SOURCE_DIR}/generated.tar.gz" "swigpyrun.h" "znc_core.py" "modpython_biglib.cpp" "pyfunctions.cpp" DEPENDS swigpyrun.h znc_core.py modpython_biglib.cpp pyfunctions.cpp VERBATIM) add_dependencies(modpython_dist copy_csocket_h) znc-1.9.1/modules/modpython/codegen.pl0000755000175000017500000003741114641222733020207 0ustar somebodysomebody#!/usr/bin/env perl # # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Parts of SWIG are used here. use strict; use warnings; use IO::File; use feature 'switch', 'say'; open my $in, $ARGV[0] or die; open my $out, ">", $ARGV[1] or die; print $out <<'EOF'; /* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Parts of SWIG are used here. */ /*************************************************************************** * This file is generated automatically using codegen.pl from functions.in * * Don't change it manually. * ***************************************************************************/ namespace { inline swig_type_info* ZNC_SWIG_pchar_descriptor(void) { static int init = 0; static swig_type_info* info = 0; if (!init) { info = SWIG_TypeQuery("_p_char"); init = 1; } return info; } // SWIG 4.2.0 replaced SWIG_Python_str_AsChar with SWIG_PyUnicode_AsUTF8AndSize. // SWIG doesn't provide any good way to detect SWIG version (other than parsing // `swig -version`), but it also introduced SWIG_NULLPTR in 4.2.0. // So let's abuse that define to do different code for new SWIG. #ifdef SWIG_NULLPTR // This is copied/adapted from SWIG 4.2.0 from pystrings.swg inline int ZNC_SWIG_AsCharPtrAndSize(PyObject *obj, char** cptr, size_t* psize, int *alloc) { #if PY_VERSION_HEX>=0x03000000 #if defined(SWIG_PYTHON_STRICT_BYTE_CHAR) if (PyBytes_Check(obj)) #else if (PyUnicode_Check(obj)) #endif #else if (PyString_Check(obj)) #endif { char *cstr; Py_ssize_t len; PyObject *bytes = NULL; int ret = SWIG_OK; if (alloc) *alloc = SWIG_OLDOBJ; #if PY_VERSION_HEX>=0x03000000 && defined(SWIG_PYTHON_STRICT_BYTE_CHAR) if (PyBytes_AsStringAndSize(obj, &cstr, &len) == -1) return SWIG_TypeError; #else cstr = (char *)SWIG_PyUnicode_AsUTF8AndSize(obj, &len, &bytes); if (!cstr) return SWIG_TypeError; /* The returned string is only duplicated if the char * returned is not owned and memory managed by obj */ if (bytes && cptr) { if (alloc) { //cstr = %new_copy_array(cstr, len + 1, char); cstr = (char *)memcpy((char *)malloc((len + 1)*sizeof(char)), cstr, sizeof(char)*(len + 1)); *alloc = SWIG_NEWOBJ; } else { /* alloc must be set in order to clean up allocated memory */ return SWIG_RuntimeError; } } #endif if (cptr) *cptr = cstr; if (psize) *psize = len + 1; Py_XDECREF(bytes); return ret; } else { swig_type_info* pchar_descriptor = ZNC_SWIG_pchar_descriptor(); if (pchar_descriptor) { void* vptr = 0; if (SWIG_ConvertPtr(obj, &vptr, pchar_descriptor, 0) == SWIG_OK) { if (cptr) *cptr = (char *) vptr; if (psize) *psize = vptr ? (strlen((char *)vptr) + 1) : 0; if (alloc) *alloc = SWIG_OLDOBJ; return SWIG_OK; } } } return SWIG_TypeError; } #else // TODO: at some point drop support for SWIG<4.2.0 (drop this branch of ifdef) // This is copied from some old SWIG version from pystrings.swg inline int ZNC_SWIG_AsCharPtrAndSize(PyObject *obj, char** cptr, size_t* psize, int *alloc) { #if PY_VERSION_HEX>=0x03000000 if (PyUnicode_Check(obj)) #else if (PyString_Check(obj)) #endif { char *cstr; Py_ssize_t len; #if PY_VERSION_HEX>=0x03000000 if (!alloc && cptr) { /* We can't allow converting without allocation, since the internal representation of string in Python 3 is UCS-2/UCS-4 but we require a UTF-8 representation. TODO(bhy) More detailed explanation */ return SWIG_RuntimeError; } obj = PyUnicode_AsUTF8String(obj); PyBytes_AsStringAndSize(obj, &cstr, &len); if(alloc) *alloc = SWIG_NEWOBJ; #else PyString_AsStringAndSize(obj, &cstr, &len); #endif if (cptr) { if (alloc) { /* In python the user should not be able to modify the inner string representation. To warranty that, if you define SWIG_PYTHON_SAFE_CSTRINGS, a new/copy of the python string buffer is always returned. The default behavior is just to return the pointer value, so, be careful. */ #if defined(SWIG_PYTHON_SAFE_CSTRINGS) if (*alloc != SWIG_OLDOBJ) #else if (*alloc == SWIG_NEWOBJ) #endif { *cptr = (char *)memcpy((char *)malloc((len + 1)*sizeof(char)), cstr, sizeof(char)*(len + 1)); *alloc = SWIG_NEWOBJ; } else { *cptr = cstr; *alloc = SWIG_OLDOBJ; } } else { #if PY_VERSION_HEX>=0x03000000 assert(0); /* Should never reach here in Python 3 */ #endif *cptr = SWIG_Python_str_AsChar(obj); } } if (psize) *psize = len + 1; #if PY_VERSION_HEX>=0x03000000 Py_XDECREF(obj); #endif return SWIG_OK; } else { swig_type_info* pchar_descriptor = ZNC_SWIG_pchar_descriptor(); if (pchar_descriptor) { void* vptr = 0; if (SWIG_ConvertPtr(obj, &vptr, pchar_descriptor, 0) == SWIG_OK) { if (cptr) *cptr = (char *) vptr; if (psize) *psize = vptr ? (strlen((char *)vptr) + 1) : 0; if (alloc) *alloc = SWIG_OLDOBJ; return SWIG_OK; } } } return SWIG_TypeError; } #endif inline int ZNC_SWIG_AsPtr_CString (PyObject * obj, CString **val) { char* buf = 0 ; size_t size = 0; int alloc = SWIG_OLDOBJ; if (SWIG_IsOK((ZNC_SWIG_AsCharPtrAndSize(obj, &buf, &size, &alloc)))) { if (buf) { if (val) *val = new CString(buf, size - 1); if (alloc == SWIG_NEWOBJ) delete[] buf; return SWIG_NEWOBJ; } else { if (val) *val = 0; return SWIG_OLDOBJ; } } else { static int init = 0; static swig_type_info* descriptor = 0; if (!init) { descriptor = SWIG_TypeQuery("CString" " *"); init = 1; } if (descriptor) { CString *vptr; int res = SWIG_ConvertPtr(obj, (void**)&vptr, descriptor, 0); if (SWIG_IsOK(res) && val) *val = vptr; return res; } } return SWIG_ERROR; } } EOF =b bool OnFoo(const CString& x) { PyObject* pyName = Py_BuildValue("s", "OnFoo"); if (!pyName) { CString s = GetPyExceptionStr(); DEBUG("modpython: username/module/OnFoo: can't name method to call: " << s); return default; } PyObject* pyArg1 = Py_BuildValue("s", x.c_str()); if (!pyArg1) { CString s = GetPyExceptionStr(); DEBUG("modpython: username/module/OnFoo: can't convert parameter x to PyObject*: " << s); Py_CLEAR(pyName); return default; } PyObject* pyArg2 = ...; if (!pyArg2) { CString s = ...; DEBUG(...); Py_CLEAR(pyName); Py_CLEAR(pyArg1); return default; } PyObject* pyArg3 = ...; if (!pyArg3) { CString s = ...; DEBUG(...); Py_CLEAR(pyName); Py_CLEAR(pyArg1); Py_CLEAR(pyArg2); return default; } PyObject* pyRes = PyObject_CallMethodObjArgs(m_pyObj, pyName, pyArg1, pyArg2, pyArg3, nullptr); if (!pyRes) { CString s = ...; DEBUG("modpython: username/module/OnFoo failed: " << s); Py_CLEAR(pyName); Py_CLEAR(pyArg1); Py_CLEAR(pyArg2); Py_CLEAR(pyArg3); return default; } Py_CLEAR(pyName); Py_CLEAR(pyArg1); Py_CLEAR(pyArg2); Py_CLEAR(pyArg3); bool res = PyLong_AsLong(pyRes); if (PyErr_Occured()) { CString s = GetPyExceptionStr(); DEBUG("modpython: username/module/OnFoo returned unexpected value: " << s); Py_CLEAR(pyRes); return default; } Py_CLEAR(pyRes); return res; } =cut while (<$in>) { my ($type, $name, $args, $default) = /(\S+)\s+(\w+)\((.*)\)(?:=(\w+))?/ or next; $type =~ s/(EModRet)/CModule::$1/; $type =~ s/^\s*(.*?)\s*$/$1/; my @arg = map { my ($t, $v) = /^\s*(.*\W)\s*(\w+)\s*$/; $t =~ s/^\s*(.*?)\s*$/$1/; my ($tb, $tm) = $t =~ /^(.*?)\s*?(\*|&)?$/; {type=>$t, var=>$v, base=>$tb, mod=>$tm//'', pyvar=>"pyArg_$v", error=>"can't convert parameter '$v' to PyObject"} } split /,/, $args; unless (defined $default) { $default = "CModule::$name(" . (join ', ', map { $_->{var} } @arg) . ")"; } unshift @arg, {type=>'$func$', var=>"", base=>"", mod=>"", pyvar=>"pyName", error=>"can't convert string '$name' to PyObject"}; my $cleanup = ''; say $out "$type CPyModule::$name($args) {"; for my $a (@arg) { print $out "\tPyObject* $a->{pyvar} = "; given ($a->{type}) { when ('$func$') { say $out "Py_BuildValue(\"s\", \"$name\");"; } when (/vector\s*<\s*.*\*\s*>/) { say $out "PyList_New(0);"; } when (/(?:^|\s)CString/) { # not SCString if ($a->{base} eq 'CString' && $a->{mod} eq '&') { say $out "CPyRetString::wrap($a->{var});"; } else { say $out "Py_BuildValue(\"s\", $a->{var}.c_str());"; } } when (/^bool/) { if ($a->{mod} eq '&') { say $out "CPyRetBool::wrap($a->{var});"; } else { say $out "Py_BuildValue(\"l\", (long int)$a->{var});"; } } when (/^std::shared_ptr/) { say $out "SWIG_NewInstanceObj(new $a->{type}($a->{var}), SWIG_TypeQuery(\"$a->{type}*\"), SWIG_POINTER_OWN);"; } when (/\*$/) { (my $t = $a->{type}) =~ s/^const//; say $out "SWIG_NewInstanceObj(const_cast<$t>($a->{var}), SWIG_TypeQuery(\"$t\"), 0);"; } when (/&$/) { (my $b = $a->{base}) =~ s/^const//; say $out "SWIG_NewInstanceObj(const_cast<$b*>(&$a->{var}), SWIG_TypeQuery(\"$b*\"), 0);"; } when (/(?:^|::)E/) { # Enumerations say $out "Py_BuildValue(\"i\", (int)$a->{var});"; } default { my %letter = ( 'int' => 'i', 'char' => 'b', 'short int' => 'h', 'long int' => 'l', 'unsigned char' => 'B', 'unsigned short' => 'H', 'unsigned int' => 'I', 'unsigned long' => 'k', 'long long' => 'L', 'unsigned long long' => 'K', 'ssize_t' => 'n', 'double' => 'd', 'float' => 'f', ); if (exists $letter{$a->{type}}) { say $out "Py_BuildValue(\"$letter{$a->{type}}\", $a->{var});" } else { say $out "...;"; } } } say $out "\tif (!$a->{pyvar}) {"; say $out "\t\tCString sPyErr = m_pModPython->GetPyExceptionStr();"; say $out "\t\tDEBUG".'("modpython: " << (GetUser() ? GetUser()->GetUsername() : CString("")) << "/" << GetModName() << '."\"/$name: $a->{error}: \" << sPyErr);"; print $out $cleanup; say $out "\t\treturn $default;"; say $out "\t}"; $cleanup .= "\t\tPy_CLEAR($a->{pyvar});\n"; if ($a->{type} =~ /(vector\s*<\s*(.*)\*\s*>)/) { my ($vec, $sub) = ($1, $2); (my $cleanup1 = $cleanup) =~ s/\t\t/\t\t\t/g; my $dot = '.'; $dot = '->' if $a->{mod} eq '*'; say $out "\tfor (${vec}::const_iterator i = $a->{var}${dot}begin(); i != $a->{var}${dot}end(); ++i) {"; say $out "\t\tPyObject* pyVecEl = SWIG_NewInstanceObj(*i, SWIG_TypeQuery(\"$sub*\"), 0);"; say $out "\t\tif (!pyVecEl) {"; say $out "\t\t\tCString sPyErr = m_pModPython->GetPyExceptionStr();"; say $out "\t\t\tDEBUG".'("modpython: " << (GetUser() ? GetUser()->GetUsername() : CString("")) << "/" << GetModName() << '. "\"/$name: can't convert element of vector '$a->{var}' to PyObject: \" << sPyErr);"; print $out $cleanup1; say $out "\t\t\treturn $default;"; say $out "\t\t}"; say $out "\t\tif (PyList_Append($a->{pyvar}, pyVecEl)) {"; say $out "\t\t\tCString sPyErr = m_pModPython->GetPyExceptionStr();"; say $out "\t\t\tDEBUG".'("modpython: " << (GetUser() ? GetUser()->GetUsername() : CString("")) << "/" << GetModName() << '. "\"/$name: can't add element of vector '$a->{var}' to PyObject: \" << sPyErr);"; say $out "\t\t\tPy_CLEAR(pyVecEl);"; print $out $cleanup1; say $out "\t\t\treturn $default;"; say $out "\t\t}"; say $out "\t\tPy_CLEAR(pyVecEl);"; say $out "\t}"; } } print $out "\tPyObject* pyRes = PyObject_CallMethodObjArgs(m_pyObj"; print $out ", $_->{pyvar}" for @arg; say $out ", nullptr);"; say $out "\tif (!pyRes) {"; say $out "\t\tCString sPyErr = m_pModPython->GetPyExceptionStr();"; say $out "\t\tDEBUG".'("modpython: " << (GetUser() ? GetUser()->GetUsername() : CString("")) << "/" << GetModName() << '."\"/$name failed: \" << sPyErr);"; print $out $cleanup; say $out "\t\treturn $default;"; say $out "\t}"; $cleanup =~ s/\t\t/\t/g; print $out $cleanup; if ($type ne 'void') { say $out "\t$type result;"; say $out "\tif (pyRes == Py_None) {"; say $out "\t\tresult = $default;"; say $out "\t} else {"; given ($type) { when (/^(.*)\*$/) { say $out "\t\tint res = SWIG_ConvertPtr(pyRes, (void**)&result, SWIG_TypeQuery(\"$type\"), 0);"; say $out "\t\tif (!SWIG_IsOK(res)) {"; say $out "\t\t\tDEBUG(\"modpython: \" << (GetUser() ? GetUser()->GetUsername() : CString(\"\")) << \"/\" << GetModName() << \"/$name was expected to return '$type' but error=\" << res);"; say $out "\t\t\tresult = $default;"; say $out "\t\t}"; } when ('CString') { say $out "\t\tCString* p = nullptr;"; say $out "\t\tint res = ZNC_SWIG_AsPtr_CString(pyRes, &p);"; say $out "\t\tif (!SWIG_IsOK(res)) {"; say $out "\t\t\tDEBUG(\"modpython: \" << (GetUser() ? GetUser()->GetUsername() : CString(\"\")) << \"/\" << GetModName() << \"/$name was expected to return '$type' but error=\" << res);"; say $out "\t\t\tresult = $default;"; say $out "\t\t} else if (!p) {"; say $out "\t\t\tDEBUG(\"modpython: \" << (GetUser() ? GetUser()->GetUsername() : CString(\"\")) << \"/\" << GetModName() << \"/$name was expected to return '$type' but returned nullptr\");"; say $out "\t\t\tresult = $default;"; say $out "\t\t} else result = *p;"; say $out "\t\tif (SWIG_IsNewObj(res)) delete p;"; } when ('CModule::EModRet') { say $out "\t\tlong int x = PyLong_AsLong(pyRes);"; say $out "\t\tif (PyErr_Occurred()) {"; say $out "\t\t\tCString sPyErr = m_pModPython->GetPyExceptionStr();"; say $out "\t\t\tDEBUG".'("modpython: " << (GetUser() ? GetUser()->GetUsername() : CString("")) << "/" << GetModName() << '."\"/$name was expected to return EModRet but: \" << sPyErr);"; say $out "\t\t\tresult = $default;"; say $out "\t\t} else { result = (CModule::EModRet)x; }"; } when ('bool') { say $out "\t\tint x = PyObject_IsTrue(pyRes);"; say $out "\t\tif (-1 == x) {"; say $out "\t\t\tCString sPyErr = m_pModPython->GetPyExceptionStr();"; say $out "\t\t\tDEBUG".'("modpython: " << (GetUser() ? GetUser()->GetUsername() : CString("")) << "/" << GetModName() << '."\"/$name was expected to return EModRet but: \" << sPyErr);"; say $out "\t\t\tresult = $default;"; say $out "\t\t} else result = x ? true : false;"; } default { say $out "\t\tI don't know how to convert PyObject to $type :("; } } say $out "\t}"; say $out "\tPy_CLEAR(pyRes);"; say $out "\treturn result;"; } else { say $out "\tPy_CLEAR(pyRes);"; } say $out "}\n"; } sub getres { my $type = shift; given ($type) { when (/^(.*)\*$/) { return "pyobj_to_ptr<$1>(\"$type\")" } when ('CString') { return 'PString' } when ('CModule::EModRet') { return 'SvToEModRet' } when (/unsigned/) { return 'SvUV' } default { return 'SvIV' } } } znc-1.9.1/modules/modpython/cstring.i0000644000175000017500000003123014641222733020057 0ustar somebodysomebody/* SWIG-generated sources are used here. This file is generated using: echo '%include ' > foo.i swig -python -py3 -c++ -shadow -E foo.i > string.i Remove unrelated stuff from top of file which is included by default s/std::string/CString/g s/std_string/CString/g Add "%traits_ptypen(CString);" */ // // String // %fragment(""); %feature("naturalvar") CString; class CString; %traits_ptypen(CString); /*@SWIG:/swig/3.0.8/typemaps/CStrings.swg,70,%typemaps_CString@*/ /*@SWIG:/swig/3.0.8/typemaps/CStrings.swg,4,%CString_asptr@*/ %fragment("SWIG_" "AsPtr" "_" {CString},"header",fragment="SWIG_AsCharPtrAndSize") { SWIGINTERN int SWIG_AsPtr_CString (PyObject * obj, CString **val) { char* buf = 0 ; size_t size = 0; int alloc = SWIG_OLDOBJ; if (SWIG_IsOK((SWIG_AsCharPtrAndSize(obj, &buf, &size, &alloc)))) { if (buf) { if (val) *val = new CString(buf, size - 1); if (alloc == SWIG_NEWOBJ) delete[] buf; return SWIG_NEWOBJ; } else { if (val) *val = 0; return SWIG_OLDOBJ; } } else { static int init = 0; static swig_type_info* descriptor = 0; if (!init) { descriptor = SWIG_TypeQuery("CString" " *"); init = 1; } if (descriptor) { CString *vptr; int res = SWIG_ConvertPtr(obj, (void**)&vptr, descriptor, 0); if (SWIG_IsOK(res) && val) *val = vptr; return res; } } return SWIG_ERROR; } } /*@SWIG@*/ /*@SWIG:/swig/3.0.8/typemaps/CStrings.swg,48,%CString_asval@*/ %fragment("SWIG_" "AsVal" "_" {CString},"header", fragment="SWIG_" "AsPtr" "_" {CString}) { SWIGINTERN int SWIG_AsVal_CString (PyObject * obj, CString *val) { CString* v = (CString *) 0; int res = SWIG_AsPtr_CString (obj, &v); if (!SWIG_IsOK(res)) return res; if (v) { if (val) *val = *v; if (SWIG_IsNewObj(res)) { delete v; res = SWIG_DelNewMask(res); } return res; } return SWIG_ERROR; } } /*@SWIG@*/ /*@SWIG:/swig/3.0.8/typemaps/CStrings.swg,38,%CString_from@*/ %fragment("SWIG_" "From" "_" {CString},"header",fragment="SWIG_FromCharPtrAndSize", fragment="StdTraits") { SWIGINTERNINLINE PyObject * SWIG_From_CString (const CString& s) { return SWIG_FromCharPtrAndSize(s.data(), s.size()); } } /*@SWIG@*/ /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,201,%typemaps_asptrfromn@*/ /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,190,%typemaps_asptrfrom@*/ /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,160,%typemaps_asptr@*/ %fragment("SWIG_" "AsVal" "_" {CString},"header",fragment="SWIG_" "AsPtr" "_" {CString}) { SWIGINTERNINLINE int SWIG_AsVal_CString (PyObject * obj, CString *val) { CString *v = (CString *)0; int res = SWIG_AsPtr_CString (obj, &v); if (!SWIG_IsOK(res)) return res; if (v) { if (val) *val = *v; if (SWIG_IsNewObj(res)) { delete v; res = SWIG_DelNewMask(res); } return res; } return SWIG_ERROR; } } /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,28,%ptr_in_typemap@*/ %typemap(in,fragment="SWIG_" "AsPtr" "_" {CString}) CString { CString *ptr = (CString *)0; int res = SWIG_AsPtr_CString($input, &ptr); if (!SWIG_IsOK(res) || !ptr) { SWIG_exception_fail(SWIG_ArgError((ptr ? res : SWIG_TypeError)), "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } $1 = *ptr; if (SWIG_IsNewObj(res)) delete ptr; } %typemap(freearg) CString ""; %typemap(in,fragment="SWIG_" "AsPtr" "_" {CString}) const CString & (int res = SWIG_OLDOBJ) { CString *ptr = (CString *)0; res = SWIG_AsPtr_CString($input, &ptr); if (!SWIG_IsOK(res)) { SWIG_exception_fail(SWIG_ArgError(res), "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } if (!ptr) { SWIG_exception_fail(SWIG_ValueError, "invalid null reference " "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } $1 = ptr; } %typemap(freearg,noblock=1) const CString & { if (SWIG_IsNewObj(res$argnum)) delete $1; } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,53,%ptr_varin_typemap@*/ %typemap(varin,fragment="SWIG_" "AsPtr" "_" {CString}) CString { CString *ptr = (CString *)0; int res = SWIG_AsPtr_CString($input, &ptr); if (!SWIG_IsOK(res) || !ptr) { SWIG_exception_fail(SWIG_ArgError((ptr ? res : SWIG_TypeError)), "in variable '""$name""' of type '""$type""'"); } $1 = *ptr; if (SWIG_IsNewObj(res)) delete ptr; } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,68,%ptr_directorout_typemap@*/ %typemap(directorargout,noblock=1,fragment="SWIG_" "AsPtr" "_" {CString}) CString *DIRECTOROUT ($*ltype temp, int swig_ores) { CString *swig_optr = 0; swig_ores = $result ? SWIG_AsPtr_CString($result, &swig_optr) : 0; if (!SWIG_IsOK(swig_ores) || !swig_optr) { Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError((swig_optr ? swig_ores : SWIG_TypeError))), "in output value of type '""$type""'"); } temp = *swig_optr; $1 = &temp; if (SWIG_IsNewObj(swig_ores)) delete swig_optr; } %typemap(directorout,noblock=1,fragment="SWIG_" "AsPtr" "_" {CString}) CString { CString *swig_optr = 0; int swig_ores = SWIG_AsPtr_CString($input, &swig_optr); if (!SWIG_IsOK(swig_ores) || !swig_optr) { Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError((swig_optr ? swig_ores : SWIG_TypeError))), "in output value of type '""$type""'"); } $result = *swig_optr; if (SWIG_IsNewObj(swig_ores)) delete swig_optr; } %typemap(directorout,noblock=1,fragment="SWIG_" "AsPtr" "_" {CString},warning= "473:Returning a pointer or reference in a director method is not recommended." ) CString* { CString *swig_optr = 0; int swig_ores = SWIG_AsPtr_CString($input, &swig_optr); if (!SWIG_IsOK(swig_ores)) { Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError(swig_ores)), "in output value of type '""$type""'"); } $result = swig_optr; if (SWIG_IsNewObj(swig_ores)) { swig_acquire_ownership(swig_optr); } } %typemap(directorfree,noblock=1) CString* { if (director) { director->swig_release_ownership(SWIG_as_voidptr($input)); } } %typemap(directorout,noblock=1,fragment="SWIG_" "AsPtr" "_" {CString},warning= "473:Returning a pointer or reference in a director method is not recommended." ) CString& { CString *swig_optr = 0; int swig_ores = SWIG_AsPtr_CString($input, &swig_optr); if (!SWIG_IsOK(swig_ores)) { Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError(swig_ores)), "in output value of type '""$type""'"); } else { if (!swig_optr) { Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ValueError), "invalid null reference " "in output value of type '""$type""'"); } } $result = swig_optr; if (SWIG_IsNewObj(swig_ores)) { swig_acquire_ownership(swig_optr); } } %typemap(directorfree,noblock=1) CString& { if (director) { director->swig_release_ownership(SWIG_as_voidptr($input)); } } %typemap(directorout,fragment="SWIG_" "AsPtr" "_" {CString}) CString &DIRECTOROUT = CString /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/ptrtypes.swg,143,%ptr_typecheck_typemap@*/ %typemap(typecheck,noblock=1,precedence=135,fragment="SWIG_" "AsPtr" "_" {CString}) CString * { int res = SWIG_AsPtr_CString($input, (CString**)(0)); $1 = SWIG_CheckState(res); } %typemap(typecheck,noblock=1,precedence=135,fragment="SWIG_" "AsPtr" "_" {CString}) CString, const CString& { int res = SWIG_AsPtr_CString($input, (CString**)(0)); $1 = SWIG_CheckState(res); } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,254,%ptr_input_typemap@*/ /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,117,%_ptr_input_typemap@*/ %typemap(in,noblock=1,fragment="SWIG_" "AsPtr" "_" {CString}) CString *INPUT(int res = 0) { res = SWIG_AsPtr_CString($input, &$1); if (!SWIG_IsOK(res)) { SWIG_exception_fail(SWIG_ArgError(res), "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } res = SWIG_AddTmpMask(res); } %typemap(in,noblock=1,fragment="SWIG_" "AsPtr" "_" {CString}) CString &INPUT(int res = 0) { res = SWIG_AsPtr_CString($input, &$1); if (!SWIG_IsOK(res)) { SWIG_exception_fail(SWIG_ArgError(res), "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } if (!$1) { SWIG_exception_fail(SWIG_ValueError, "invalid null reference " "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } res = SWIG_AddTmpMask(res); } %typemap(freearg,noblock=1,match="in") CString *INPUT, CString &INPUT { if (SWIG_IsNewObj(res$argnum)) delete $1; } %typemap(typecheck,noblock=1,precedence=135,fragment="SWIG_" "AsPtr" "_" {CString}) CString *INPUT, CString &INPUT { int res = SWIG_AsPtr_CString($input, (CString**)0); $1 = SWIG_CheckState(res); } /*@SWIG@*/ /*@SWIG@*/; /*@SWIG@*/ /*@SWIG:/swig/3.0.8/typemaps/valtypes.swg,183,%typemaps_from@*/ /*@SWIG:/swig/3.0.8/typemaps/valtypes.swg,55,%value_out_typemap@*/ %typemap(out,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString, const CString { $result = SWIG_From_CString(static_cast< CString >($1)); } %typemap(out,noblock=1,fragment="SWIG_" "From" "_" {CString}) const CString& { $result = SWIG_From_CString(static_cast< CString >(*$1)); } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/valtypes.swg,79,%value_varout_typemap@*/ %typemap(varout,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString, const CString& { $result = SWIG_From_CString(static_cast< CString >($1)); } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/valtypes.swg,87,%value_constcode_typemap@*/ %typemap(constcode,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString { SWIG_Python_SetConstant(d, "$symname",SWIG_From_CString(static_cast< CString >($value))); } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/valtypes.swg,98,%value_directorin_typemap@*/ %typemap(directorin,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString *DIRECTORIN { $input = SWIG_From_CString(static_cast< CString >(*$1)); } %typemap(directorin,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString, const CString& { $input = SWIG_From_CString(static_cast< CString >($1)); } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/valtypes.swg,153,%value_throws_typemap@*/ %typemap(throws,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString { SWIG_Python_Raise(SWIG_From_CString(static_cast< CString >($1)), "$type", 0); SWIG_fail; } /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,258,%value_output_typemap@*/ /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,175,%_value_output_typemap@*/ %typemap(in,numinputs=0,noblock=1) CString *OUTPUT ($*1_ltype temp, int res = SWIG_TMPOBJ), CString &OUTPUT ($*1_ltype temp, int res = SWIG_TMPOBJ) { $1 = &temp; } %typemap(argout,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString *OUTPUT, CString &OUTPUT { if (SWIG_IsTmpObj(res$argnum)) { $result = SWIG_Python_AppendOutput($result, SWIG_From_CString((*$1))); } else { int new_flags = SWIG_IsNewObj(res$argnum) ? (SWIG_POINTER_OWN | 0 ) : 0 ; $result = SWIG_Python_AppendOutput($result, SWIG_NewPointerObj((void*)($1), $1_descriptor, new_flags)); } } /*@SWIG@*/ /*@SWIG@*/; /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,258,%value_output_typemap@*/ /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,175,%_value_output_typemap@*/ %typemap(in,numinputs=0,noblock=1) CString *OUTPUT ($*1_ltype temp, int res = SWIG_TMPOBJ), CString &OUTPUT ($*1_ltype temp, int res = SWIG_TMPOBJ) { $1 = &temp; } %typemap(argout,noblock=1,fragment="SWIG_" "From" "_" {CString}) CString *OUTPUT, CString &OUTPUT { if (SWIG_IsTmpObj(res$argnum)) { $result = SWIG_Python_AppendOutput($result, SWIG_From_CString((*$1))); } else { int new_flags = SWIG_IsNewObj(res$argnum) ? (SWIG_POINTER_OWN | 0 ) : 0 ; $result = SWIG_Python_AppendOutput($result, SWIG_NewPointerObj((void*)($1), $1_descriptor, new_flags)); } } /*@SWIG@*/ /*@SWIG@*/; /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,240,%_ptr_inout_typemap@*/ /*@SWIG:/swig/3.0.8/typemaps/inoutlist.swg,230,%_value_inout_typemap@*/ %typemap(in) CString *INOUT = CString *INPUT; %typemap(in) CString &INOUT = CString &INPUT; %typemap(typecheck) CString *INOUT = CString *INPUT; %typemap(typecheck) CString &INOUT = CString &INPUT; %typemap(argout) CString *INOUT = CString *OUTPUT; %typemap(argout) CString &INOUT = CString &OUTPUT; /*@SWIG@*/ %typemap(typecheck) CString *INOUT = CString *INPUT; %typemap(typecheck) CString &INOUT = CString &INPUT; %typemap(freearg) CString *INOUT = CString *INPUT; %typemap(freearg) CString &INOUT = CString &INPUT; /*@SWIG@*/; /*@SWIG@*/ znc-1.9.1/modules/modpython/functions.in0000644000175000017500000001474514641222733020610 0ustar somebodysomebodybool OnBoot() bool WebRequiresLogin() bool WebRequiresAdmin() CString GetWebMenuTitle() bool OnWebPreRequest(CWebSock& WebSock, const CString& sPageName) bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) bool ValidateWebRequestCSRFCheck(CWebSock& WebSock, const CString& sPageName) VWebSubPages* _GetSubPages()=nullptr void OnPreRehash() void OnPostRehash() void OnIRCDisconnected() void OnIRCConnected() EModRet OnIRCConnecting(CIRCSock *pIRCSock) void OnIRCConnectionError(CIRCSock *pIRCSock) EModRet OnIRCRegistration(CString& sPass, CString& sNick, CString& sIdent, CString& sRealName) EModRet OnBroadcast(CString& sMessage) void OnChanPermission2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, unsigned char uMode, bool bAdded, bool bNoChange) void OnOp2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) void OnDeop2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) void OnVoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) void OnDevoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) void OnMode2(const CNick* pOpNick, CChan& Channel, char uMode, const CString& sArg, bool bAdded, bool bNoChange) void OnRawMode2(const CNick* pOpNick, CChan& Channel, const CString& sModes, const CString& sArgs) EModRet OnRaw(CString& sLine) EModRet OnStatusCommand(CString& sCommand) void OnModCommand(const CString& sCommand) void OnModNotice(const CString& sMessage) void OnModCTCP(const CString& sMessage) void OnQuit(const CNick& Nick, const CString& sMessage, const vector& vChans) void OnNick(const CNick& Nick, const CString& sNewNick, const vector& vChans) void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) EModRet OnJoining(CChan& Channel) void OnJoin(const CNick& Nick, CChan& Channel) void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) EModRet OnInvite(const CNick& Nick, const CString& sChan) EModRet OnChanBufferStarting(CChan& Chan, CClient& Client) EModRet OnChanBufferEnding(CChan& Chan, CClient& Client) EModRet OnChanBufferPlayLine(CChan& Chan, CClient& Client, CString& sLine) EModRet OnPrivBufferPlayLine(CClient& Client, CString& sLine) void OnClientLogin() void OnClientDisconnect() EModRet OnUserRaw(CString& sLine) EModRet OnUserCTCPReply(CString& sTarget, CString& sMessage) EModRet OnUserCTCP(CString& sTarget, CString& sMessage) EModRet OnUserAction(CString& sTarget, CString& sMessage) EModRet OnUserMsg(CString& sTarget, CString& sMessage) EModRet OnUserNotice(CString& sTarget, CString& sMessage) EModRet OnUserJoin(CString& sChannel, CString& sKey) EModRet OnUserPart(CString& sChannel, CString& sMessage) EModRet OnUserTopic(CString& sChannel, CString& sTopic) EModRet OnUserTopicRequest(CString& sChannel) EModRet OnUserQuit(CString& sMessage) EModRet OnCTCPReply(CNick& Nick, CString& sMessage) EModRet OnPrivCTCP(CNick& Nick, CString& sMessage) EModRet OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage) EModRet OnPrivAction(CNick& Nick, CString& sMessage) EModRet OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) EModRet OnPrivMsg(CNick& Nick, CString& sMessage) EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) EModRet OnPrivNotice(CNick& Nick, CString& sMessage) EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) bool OnServerCapAvailable(const CString& sCap) bool OnServerCap302Available(const CString& sCap, const CString& sValue) void OnServerCapResult(const CString& sCap, bool bSuccess) void OnClientAttached() void OnClientDetached() EModRet OnTimerAutoJoin(CChan& Channel) bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet) EModRet OnDeleteNetwork(CIRCNetwork& Network) EModRet OnSendToClient(CString& sLine, CClient& Client) EModRet OnSendToIRC(CString& sLine) EModRet OnRawMessage(CMessage& Message) EModRet OnNumericMessage(CNumericMessage& Message) void OnQuitMessage(CQuitMessage& Message, const std::vector& vChans) void OnNickMessage(CNickMessage& Message, const std::vector& vChans) void OnKickMessage(CKickMessage& Message) void OnJoinMessage(CJoinMessage& Message) void OnPartMessage(CPartMessage& Message) EModRet OnChanBufferPlayMessage(CMessage& Message) EModRet OnPrivBufferPlayMessage(CMessage& Message) EModRet OnUserRawMessage(CMessage& Message) EModRet OnUserCTCPReplyMessage(CCTCPMessage& Message) EModRet OnUserCTCPMessage(CCTCPMessage& Message) EModRet OnUserActionMessage(CActionMessage& Message) EModRet OnUserTextMessage(CTextMessage& Message) EModRet OnUserNoticeMessage(CNoticeMessage& Message) EModRet OnUserJoinMessage(CJoinMessage& Message) EModRet OnUserPartMessage(CPartMessage& Message) EModRet OnUserTopicMessage(CTopicMessage& Message) EModRet OnUserQuitMessage(CQuitMessage& Message) EModRet OnCTCPReplyMessage(CCTCPMessage& Message) EModRet OnPrivCTCPMessage(CCTCPMessage& Message) EModRet OnChanCTCPMessage(CCTCPMessage& Message) EModRet OnPrivActionMessage(CActionMessage& Message) EModRet OnChanActionMessage(CActionMessage& Message) EModRet OnPrivTextMessage(CTextMessage& Message) EModRet OnChanTextMessage(CTextMessage& Message) EModRet OnPrivNoticeMessage(CNoticeMessage& Message) EModRet OnChanNoticeMessage(CNoticeMessage& Message) EModRet OnTopicMessage(CTopicMessage& Message) EModRet OnSendToClientMessage(CMessage& Message) EModRet OnSendToIRCMessage(CMessage& Message) EModRet OnAddUser(CUser& User, CString& sErrorRet) EModRet OnDeleteUser(CUser& User) void OnClientConnect(CZNCSock* pSock, const CString& sHost, unsigned short uPort) void OnFailedLogin(const CString& sUsername, const CString& sRemoteIP) EModRet OnUnknownUserRaw(CClient* pClient, CString& sLine) EModRet OnUnknownUserRawMessage(CMessage& Message) bool IsClientCapSupported(CClient* pClient, const CString& sCap, bool bState) void OnClientCapRequest(CClient* pClient, const CString& sCap, bool bState) EModRet OnModuleLoading(const CString& sModName, const CString& sArgs, CModInfo::EModuleType eType, bool& bSuccess, CString& sRetMsg) EModRet OnModuleUnloading(CModule* pModule, bool& bSuccess, CString& sRetMsg) EModRet OnGetModInfo(CModInfo& ModInfo, const CString& sModule, bool& bSuccess, CString& sRetMsg) void OnGetAvailableMods(std::set& ssMods, CModInfo::EModuleType eType) void OnClientCapLs(CClient* pClient, SCString& ssCaps) EModRet OnLoginAttempt(std::shared_ptr Auth) znc-1.9.1/modules/modpython/modpython.i0000644000175000017500000002256714641222733020444 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ %module znc_core %{ #include #include "znc/Utils.h" #include "znc/Threads.h" #include "znc/Config.h" #include "znc/Socket.h" #include "znc/Modules.h" #include "znc/Nick.h" #include "znc/Chan.h" #include "znc/User.h" #include "znc/IRCNetwork.h" #include "znc/Query.h" #include "znc/Client.h" #include "znc/IRCSock.h" #include "znc/Listener.h" #include "znc/HTTPSock.h" #include "znc/Template.h" #include "znc/WebModules.h" #include "znc/znc.h" #include "znc/Server.h" #include "znc/ZNCString.h" #include "znc/FileUtils.h" #include "znc/ZNCDebug.h" #include "znc/ExecSock.h" #include "znc/Buffer.h" #include "modpython/module.h" #include "modpython/ret.h" #define stat struct stat using std::allocator; %} %apply long { off_t }; %apply long { uint16_t }; %apply long { uint32_t }; %apply long { uint64_t }; // Just makes generated python code slightly more beautiful. %feature("python:defaultargs"); // Probably can be removed when swig is fixed to not produce bad code for some cases %feature("python:defaultargs", "0") CDir::MakeDir; // 0700 doesn't work in python3 %feature("python:defaultargs", "0") CUtils::GetNumInput; // SyntaxError: non-default argument follows default argument %feature("python:defaultargs", "0") CModules::GetAvailableMods; // NameError: name 'UserModule' is not defined %feature("python:defaultargs", "0") CModules::GetDefaultMods; // NameError: name 'UserModule' is not defined %begin %{ #include "znc/zncconfig.h" %} %include %include %include %include %include %include %include %shared_ptr(CAuthBase); %shared_ptr(CWebSession); %shared_ptr(CClientAuth); %include "modpython/cstring.i" %template(_stringlist) std::list; %typemap(out) CModules::ModDirList %{ $result = PyList_New($1.size()); if ($result) { for (size_t i = 0; !$1.empty(); $1.pop(), ++i) { PyList_SetItem($result, i, Py_BuildValue("ss", $1.front().first.c_str(), $1.front().second.c_str())); } } %} %template(VIRCNetworks) std::vector; %template(VChannels) std::vector; %template(MNicks) std::map; %template(SModInfo) std::set; %template(SCString) std::set; typedef std::set SCString; %template(VCString) std::vector; typedef std::vector VCString; %template(PyMCString) std::map; %template(PyMStringVString) std::map; class MCString : public std::map {}; %template(PyModulesVector) std::vector; %template(VListeners) std::vector; %template(BufLines) std::deque; %template(VVString) std::vector; %template(VClients) std::vector; %template(VServers) std::vector; %template(VQueries) std::vector; #define REGISTER_ZNC_MESSAGE(M) \ %template(As_ ## M) CMessage::As; %typemap(in) CString& { String* p; int res = SWIG_IsOK(SWIG_ConvertPtr($input, (void**)&p, SWIG_TypeQuery("String*"), 0)); if (SWIG_IsOK(res)) { $1 = &p->s; } else { SWIG_exception_fail(SWIG_ArgError(res), "need znc.String object as argument $argnum $1_name"); } } %typemap(out) CString&, CString* { if ($1) { $result = CPyRetString::wrap(*$1); } else { $result = Py_None; Py_INCREF(Py_None); } } %typemap(typecheck) CString&, CString* { String* p; $1 = SWIG_IsOK(SWIG_ConvertPtr($input, (void**)&p, SWIG_TypeQuery("String*"), 0)); } /*TODO %typemap(in) bool& to be able to call from python functions which get bool& */ %typemap(out) bool&, bool* { if ($1) { $result = CPyRetBool::wrap(*$1); } else { $result = Py_None; Py_INCREF(Py_None); } } #define u_short unsigned short #define u_int unsigned int #include "znc/zncconfig.h" #include "znc/ZNCString.h" %include "znc/defines.h" %include "znc/Translation.h" %include "znc/Utils.h" %include "znc/Threads.h" %include "znc/Config.h" %include "znc/Csocket.h" %template(ZNCSocketManager) TSocketManager; %include "znc/Socket.h" %include "znc/FileUtils.h" %include "znc/Message.h" %include "znc/Modules.h" %include "znc/Nick.h" %include "znc/Chan.h" %include "znc/User.h" %include "znc/IRCNetwork.h" %include "znc/Query.h" %include "znc/Client.h" %include "znc/IRCSock.h" %include "znc/Listener.h" %include "znc/HTTPSock.h" %include "znc/Template.h" %include "znc/WebModules.h" %include "znc/znc.h" %include "znc/Server.h" %include "znc/ZNCDebug.h" %include "znc/ExecSock.h" %include "znc/Buffer.h" %include "modpython/module.h" /* Really it's CString& inside, but SWIG shouldn't know that :) */ class CPyRetString { CPyRetString(); public: CString s; }; %extend CPyRetString { CString __str__() { return $self->s; } }; %extend String { CString __str__() { return $self->s; } }; class CPyRetBool { CPyRetBool(); public: bool b; }; %extend CPyRetBool { bool __bool__() { return $self->b; } } %extend Csock { PyObject* WriteBytes(PyObject* data) { if (!PyBytes_Check(data)) { PyErr_SetString(PyExc_TypeError, "socket.WriteBytes needs bytes as argument"); return nullptr; } char* buffer; Py_ssize_t length; if (-1 == PyBytes_AsStringAndSize(data, &buffer, &length)) { return nullptr; } if ($self->Write(buffer, length)) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } } %extend CModule { CString __str__() { return $self->GetModName(); } MCString_iter BeginNV_() { return MCString_iter($self->BeginNV()); } bool ExistsNV(const CString& sName) { return $self->EndNV() != $self->FindNV(sName); } void AddServerDependentCapability(const CString& sName, PyObject* serverCb, PyObject* clientCb) { $self->AddServerDependentCapability(sName, std::make_unique(serverCb, clientCb)); } } %extend CModules { bool removeModule(CModule* p) { for (CModules::iterator i = $self->begin(); $self->end() != i; ++i) { if (*i == p) { $self->erase(i); return true; } } return false; } } %extend CUser { CString __str__() { return $self->GetUsername(); } CString __repr__() { return "GetUsername() + ">"; } std::vector GetNetworks_() { return $self->GetNetworks(); } }; %extend CIRCNetwork { CString __str__() { return $self->GetName(); } CString __repr__() { return "GetName() + ">"; } std::vector GetChans_() { return $self->GetChans(); } std::vector GetServers_() { return $self->GetServers(); } std::vector GetQueries_() { return $self->GetQueries(); } } %extend CChan { CString __str__() { return $self->GetName(); } CString __repr__() { return "GetName() + ">"; } std::map GetNicks_() { return $self->GetNicks(); } }; %extend CNick { CString __str__() { return $self->GetNick(); } CString __repr__() { return "GetHostMask() + ">"; } }; %extend CMessage { CString __str__() { return $self->ToString(); } CString __repr__() { return $self->ToString(); } }; %extend CZNC { PyObject* GetUserMap_() { PyObject* result = PyDict_New(); auto user_type = SWIG_TypeQuery("CUser*"); for (const auto& p : $self->GetUserMap()) { PyObject* user = SWIG_NewInstanceObj(p.second, user_type, 0); PyDict_SetItemString(result, p.first.c_str(), user); Py_CLEAR(user); } return result; } }; /* To allow module-loaders to be written on python. * They can call CreatePyModule() to create CModule* object, but one of arguments to CreatePyModule() is "CModule* pModPython" * Pointer to modpython is already accessible to python modules as self.GetModPython(), but it's just a pointer to something, not to CModule*. * So make it known that CModPython is really a CModule. */ class CModPython : public CModule { private: CModPython(); CModPython(const CModPython&); ~CModPython(); }; /* Web */ %template(StrPair) std::pair; %template(VPair) std::vector >; typedef std::vector > VPair; %template(VWebSubPages) std::vector; %inline %{ void VPair_Add2Str_(VPair* self, const CString& a, const CString& b) { self->push_back(std::make_pair(a, b)); } %} %extend CTemplate { void set(const CString& key, const CString& value) { DEBUG("WARNING: modpython's CTemplate.set is deprecated and will be removed. Use normal dict's operations like Tmpl['foo'] = 'bar'"); (*$self)[key] = value; } } %inline %{ TWebSubPage CreateWebSubPage_(const CString& sName, const CString& sTitle, const VPair& vParams, unsigned int uFlags) { return std::make_shared(sName, sTitle, vParams, uFlags); } %} /* vim: set filetype=cpp: */ znc-1.9.1/modules/modpython/module.h0000644000175000017500000003675514641222733017713 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once // This class is used from python to call functions which accept CString& // __str__ is added to it in modpython.i class String { public: CString s; String(const CString& s = "") : s(s) {} }; class CModPython; class ZNC_EXPORT_LIB_EXPORT CPyModule : public CModule { PyObject* m_pyObj; CModPython* m_pModPython; VWebSubPages* _GetSubPages(); public: CPyModule(CUser* pUser, CIRCNetwork* pNetwork, const CString& sModName, const CString& sDataPath, CModInfo::EModuleType eType, PyObject* pyObj, CModPython* pModPython) : CModule(nullptr, pUser, pNetwork, sModName, sDataPath, eType) { m_pyObj = pyObj; Py_INCREF(pyObj); m_pModPython = pModPython; } PyObject* GetPyObj() { // borrows return m_pyObj; } PyObject* GetNewPyObj() { Py_INCREF(m_pyObj); return m_pyObj; } void DeletePyModule() { Py_CLEAR(m_pyObj); delete this; } CString GetPyExceptionStr(); CModPython* GetModPython() { return m_pModPython; } bool OnBoot() override; bool WebRequiresLogin() override; bool WebRequiresAdmin() override; CString GetWebMenuTitle() override; bool OnWebPreRequest(CWebSock& WebSock, const CString& sPageName) override; bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) override; bool ValidateWebRequestCSRFCheck(CWebSock& WebSock, const CString& sPageName) override; VWebSubPages& GetSubPages() override; void OnPreRehash() override; void OnPostRehash() override; void OnIRCDisconnected() override; void OnIRCConnected() override; EModRet OnIRCConnecting(CIRCSock* pIRCSock) override; void OnIRCConnectionError(CIRCSock* pIRCSock) override; EModRet OnIRCRegistration(CString& sPass, CString& sNick, CString& sIdent, CString& sRealName) override; EModRet OnBroadcast(CString& sMessage) override; void OnChanPermission2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, unsigned char uMode, bool bAdded, bool bNoChange) override; void OnOp2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) override; void OnDeop2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) override; void OnVoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) override; void OnDevoice2(const CNick* pOpNick, const CNick& Nick, CChan& Channel, bool bNoChange) override; void OnMode2(const CNick* pOpNick, CChan& Channel, char uMode, const CString& sArg, bool bAdded, bool bNoChange) override; void OnRawMode2(const CNick* pOpNick, CChan& Channel, const CString& sModes, const CString& sArgs) override; EModRet OnRaw(CString& sLine) override; EModRet OnStatusCommand(CString& sCommand) override; void OnModCommand(const CString& sCommand) override; void OnModNotice(const CString& sMessage) override; void OnModCTCP(const CString& sMessage) override; void OnQuit(const CNick& Nick, const CString& sMessage, const std::vector& vChans) override; void OnNick(const CNick& Nick, const CString& sNewNick, const std::vector& vChans) override; void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) override; EModRet OnJoining(CChan& Channel) override; void OnJoin(const CNick& Nick, CChan& Channel) override; void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) override; EModRet OnInvite(const CNick& Nick, const CString& sChan) override; EModRet OnChanBufferStarting(CChan& Chan, CClient& Client) override; EModRet OnChanBufferEnding(CChan& Chan, CClient& Client) override; EModRet OnChanBufferPlayLine(CChan& Chan, CClient& Client, CString& sLine) override; EModRet OnPrivBufferPlayLine(CClient& Client, CString& sLine) override; void OnClientLogin() override; void OnClientDisconnect() override; EModRet OnUserRaw(CString& sLine) override; EModRet OnUserCTCPReply(CString& sTarget, CString& sMessage) override; EModRet OnUserCTCP(CString& sTarget, CString& sMessage) override; EModRet OnUserAction(CString& sTarget, CString& sMessage) override; EModRet OnUserMsg(CString& sTarget, CString& sMessage) override; EModRet OnUserNotice(CString& sTarget, CString& sMessage) override; EModRet OnUserJoin(CString& sChannel, CString& sKey) override; EModRet OnUserPart(CString& sChannel, CString& sMessage) override; EModRet OnUserTopic(CString& sChannel, CString& sTopic) override; EModRet OnUserTopicRequest(CString& sChannel) override; EModRet OnUserQuit(CString& sMessage) override; EModRet OnCTCPReply(CNick& Nick, CString& sMessage) override; EModRet OnPrivCTCP(CNick& Nick, CString& sMessage) override; EModRet OnChanCTCP(CNick& Nick, CChan& Channel, CString& sMessage) override; EModRet OnPrivAction(CNick& Nick, CString& sMessage) override; EModRet OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) override; EModRet OnPrivMsg(CNick& Nick, CString& sMessage) override; EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) override; EModRet OnPrivNotice(CNick& Nick, CString& sMessage) override; EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) override; EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) override; bool OnServerCapAvailable(const CString& sCap) override; bool OnServerCap302Available(const CString& sCap, const CString& sValue) override; void OnServerCapResult(const CString& sCap, bool bSuccess) override; void OnClientAttached() override; void OnClientDetached() override; EModRet OnTimerAutoJoin(CChan& Channel) override; bool OnEmbeddedWebRequest(CWebSock&, const CString&, CTemplate&) override; EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet) override; EModRet OnDeleteNetwork(CIRCNetwork& Network) override; EModRet OnSendToClient(CString& sLine, CClient& Client) override; EModRet OnSendToIRC(CString& sLine) override; EModRet OnRawMessage(CMessage& Message) override; EModRet OnNumericMessage(CNumericMessage& Message) override; void OnQuitMessage(CQuitMessage& Message, const std::vector& vChans) override; void OnNickMessage(CNickMessage& Message, const std::vector& vChans) override; void OnKickMessage(CKickMessage& Message) override; void OnJoinMessage(CJoinMessage& Message) override; void OnPartMessage(CPartMessage& Message) override; EModRet OnChanBufferPlayMessage(CMessage& Message) override; EModRet OnPrivBufferPlayMessage(CMessage& Message) override; EModRet OnUserRawMessage(CMessage& Message) override; EModRet OnUserCTCPReplyMessage(CCTCPMessage& Message) override; EModRet OnUserCTCPMessage(CCTCPMessage& Message) override; EModRet OnUserActionMessage(CActionMessage& Message) override; EModRet OnUserTextMessage(CTextMessage& Message) override; EModRet OnUserNoticeMessage(CNoticeMessage& Message) override; EModRet OnUserJoinMessage(CJoinMessage& Message) override; EModRet OnUserPartMessage(CPartMessage& Message) override; EModRet OnUserTopicMessage(CTopicMessage& Message) override; EModRet OnUserQuitMessage(CQuitMessage& Message) override; EModRet OnCTCPReplyMessage(CCTCPMessage& Message) override; EModRet OnPrivCTCPMessage(CCTCPMessage& Message) override; EModRet OnChanCTCPMessage(CCTCPMessage& Message) override; EModRet OnPrivActionMessage(CActionMessage& Message) override; EModRet OnChanActionMessage(CActionMessage& Message) override; EModRet OnPrivTextMessage(CTextMessage& Message) override; EModRet OnChanTextMessage(CTextMessage& Message) override; EModRet OnPrivNoticeMessage(CNoticeMessage& Message) override; EModRet OnChanNoticeMessage(CNoticeMessage& Message) override; EModRet OnTopicMessage(CTopicMessage& Message) override; EModRet OnSendToClientMessage(CMessage& Message) override; EModRet OnSendToIRCMessage(CMessage& Message) override; // Global Modules EModRet OnAddUser(CUser& User, CString& sErrorRet) override; EModRet OnDeleteUser(CUser& User) override; void OnClientConnect(CZNCSock* pSock, const CString& sHost, unsigned short uPort) override; void OnFailedLogin(const CString& sUsername, const CString& sRemoteIP) override; EModRet OnUnknownUserRaw(CClient* pClient, CString& sLine) override; EModRet OnUnknownUserRawMessage(CMessage& Message) override; bool IsClientCapSupported(CClient* pClient, const CString& sCap, bool bState) override; void OnClientCapRequest(CClient* pClient, const CString& sCap, bool bState) override; virtual EModRet OnModuleLoading(const CString& sModName, const CString& sArgs, CModInfo::EModuleType eType, bool& bSuccess, CString& sRetMsg) override; EModRet OnModuleUnloading(CModule* pModule, bool& bSuccess, CString& sRetMsg) override; virtual EModRet OnGetModInfo(CModInfo& ModInfo, const CString& sModule, bool& bSuccess, CString& sRetMsg) override; void OnGetAvailableMods(std::set& ssMods, CModInfo::EModuleType eType) override; void OnClientCapLs(CClient* pClient, SCString& ssCaps) override; EModRet OnLoginAttempt(std::shared_ptr Auth) override; }; static inline CPyModule* AsPyModule(CModule* p) { return dynamic_cast(p); } inline CPyModule* CreatePyModule(CUser* pUser, CIRCNetwork* pNetwork, const CString& sModName, const CString& sDataPath, CModInfo::EModuleType eType, PyObject* pyObj, CModPython* pModPython) { return new CPyModule(pUser, pNetwork, sModName, sDataPath, eType, pyObj, pModPython); } class ZNC_EXPORT_LIB_EXPORT CPyTimer : public CTimer { PyObject* m_pyObj; CModPython* m_pModPython; public: CPyTimer(CPyModule* pModule, unsigned int uInterval, unsigned int uCycles, const CString& sLabel, const CString& sDescription, PyObject* pyObj) : CTimer(pModule, uInterval, uCycles, sLabel, sDescription), m_pyObj(pyObj) { Py_INCREF(pyObj); m_pModPython = pModule->GetModPython(); pModule->AddTimer(this); } void RunJob() override; PyObject* GetPyObj() { return m_pyObj; } PyObject* GetNewPyObj() { Py_INCREF(m_pyObj); return m_pyObj; } ~CPyTimer(); }; inline CPyTimer* CreatePyTimer(CPyModule* pModule, unsigned int uInterval, unsigned int uCycles, const CString& sLabel, const CString& sDescription, PyObject* pyObj) { return new CPyTimer(pModule, uInterval, uCycles, sLabel, sDescription, pyObj); } class ZNC_EXPORT_LIB_EXPORT CPySocket : public CSocket { PyObject* m_pyObj; CModPython* m_pModPython; public: CPySocket(CPyModule* pModule, PyObject* pyObj) : CSocket(pModule), m_pyObj(pyObj) { Py_INCREF(pyObj); m_pModPython = pModule->GetModPython(); } PyObject* GetPyObj() { return m_pyObj; } PyObject* GetNewPyObj() { Py_INCREF(m_pyObj); return m_pyObj; } ~CPySocket(); void Connected() override; void Disconnected() override; void Timeout() override; void ConnectionRefused() override; void ReadData(const char* data, size_t len) override; void ReadLine(const CString& sLine) override; Csock* GetSockObj(const CString& sHost, unsigned short uPort) override; }; inline CPySocket* CreatePySocket(CPyModule* pModule, PyObject* pyObj) { return new CPySocket(pModule, pyObj); } inline bool HaveIPv6_() { #ifdef HAVE_IPV6 return true; #endif return false; } inline bool HaveSSL_() { #ifdef HAVE_LIBSSL return true; #endif return false; } inline bool HaveCharset_() { #ifdef HAVE_ICU return true; #endif return false; } inline int GetSOMAXCONN() { return SOMAXCONN; } inline int GetVersionMajor() { return VERSION_MAJOR; } inline int GetVersionMinor() { return VERSION_MINOR; } inline double GetVersion() { return VERSION; } inline CString GetVersionExtra() { return ZNC_VERSION_EXTRA; } class MCString_iter { public: MCString_iter() {} MCString::iterator x; MCString_iter(MCString::iterator z) : x(z) {} void plusplus() { ++x; } CString get() { return x->first; } bool is_end(CModule* m) { return m->EndNV() == x; } }; class CModulesIter { public: CModulesIter(CModules* pModules) { m_pModules = pModules; m_it = pModules->begin(); } void plusplus() { ++m_it; } const CModule* get() const { return *m_it; } bool is_end() const { return m_pModules->end() == m_it; } CModules* m_pModules; CModules::const_iterator m_it; }; class ZNC_EXPORT_LIB_EXPORT CPyModCommand : public CModCommand { CPyModule* m_pModule; CModPython* m_pModPython; PyObject* m_pyObj; void operator()(const CString& sLine); public: CPyModCommand(CPyModule* pModule, const CString& sCmd, const COptionalTranslation& sArgs, const COptionalTranslation& sDesc, PyObject *pyObj) : CModCommand(sCmd, [=](const CString& sLine) { (*this)(sLine); }, sArgs, sDesc), m_pModule(pModule), m_pModPython(pModule->GetModPython()), m_pyObj(pyObj) { Py_INCREF(pyObj); pModule->AddCommand(*this); } virtual ~CPyModCommand(); CPyModule* GetModule(); }; inline CPyModCommand* CreatePyModCommand(CPyModule* pModule, const CString& sCmd, const COptionalTranslation& sArgs, const COptionalTranslation& sDesc, PyObject* pyObj) { return new CPyModCommand(pModule, sCmd, sArgs, sDesc, pyObj); } class ZNC_EXPORT_LIB_EXPORT CPyCapability : public CCapability { public: CPyCapability(PyObject* serverCb, PyObject* clientCb); ~CPyCapability(); void OnServerChangedSupport(CIRCNetwork* pNetwork, bool bState) override; void OnClientChangedSupport(CClient* pClient, bool bState) override; private: PyObject* m_serverCb; PyObject* m_clientCb; }; znc-1.9.1/modules/modpython/ret.h0000644000175000017500000000233114641222733017177 0ustar somebodysomebody/* * Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once class CPyRetString { public: CString& s; CPyRetString(CString& S) : s(S) {} static PyObject* wrap(CString& S) { CPyRetString* x = new CPyRetString(S); return SWIG_NewInstanceObj(x, SWIG_TypeQuery("CPyRetString*"), SWIG_POINTER_OWN); } }; class CPyRetBool { public: bool& b; CPyRetBool(bool& B) : b(B) {} static PyObject* wrap(bool& B) { CPyRetBool* x = new CPyRetBool(B); return SWIG_NewInstanceObj(x, SWIG_TypeQuery("CPyRetBool*"), SWIG_POINTER_OWN); } }; znc-1.9.1/modules/modpython/znc.py0000644000175000017500000007071314641222733017411 0ustar somebodysomebody# # Copyright (C) 2004-2024 ZNC, see the NOTICE file for details. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # _cov = None import os if os.environ.get('ZNC_MODPYTHON_COVERAGE'): import coverage _cov = coverage.Coverage(auto_data=True, branch=True) _cov.start() from functools import wraps import collections.abc import importlib.abc import importlib.machinery import importlib.util import re import sys import traceback from znc_core import * class Socket: ADDR_MAP = { 'ipv4': ADDR_IPV4ONLY, 'ipv6': ADDR_IPV6ONLY, 'all': ADDR_ALL } def _Accepted(self, host, port): return getattr(self.OnAccepted(host, port), '_csock', None) def GetModule(self): return AsPyModule(self._csock.GetModule()).GetNewPyObj() def Listen(self, addrtype='all', port=None, bindhost='', ssl=False, maxconns=GetSOMAXCONN(), timeout=0): try: addr = self.ADDR_MAP[addrtype.lower()] except KeyError: raise ValueError( "Specified addrtype [{0}] isn't supported".format(addrtype)) args = ( "python socket for {0}".format(self.GetModule()), bindhost, ssl, maxconns, self._csock, timeout, addr ) if port is None: return self.GetModule().GetManager().ListenRand(*args) if self.GetModule().GetManager().ListenHost(port, *args): return port return 0 def Connect(self, host, port, timeout=60, ssl=False, bindhost=''): return self.GetModule().GetManager().Connect( host, port, 'python conn socket for {0}'.format(self.GetModule()), timeout, ssl, bindhost, self._csock ) def Write(self, data): if (isinstance(data, str)): return self._csock.Write(data) raise TypeError( 'socket.Write needs str. If you want binary data, use WriteBytes') def Init(self, *a, **b): pass def OnConnected(self): pass def OnDisconnected(self): pass def OnTimeout(self): pass def OnConnectionRefused(self): pass def OnReadData(self, bytess): pass def OnReadLine(self, line): pass def OnAccepted(self, host, port): pass def OnShutdown(self): pass class Timer: def GetModule(self): return AsPyModule(self._ctimer.GetModule()).GetNewPyObj() def RunJob(self): pass def OnShutdown(self): pass class ModuleNVIter(collections.abc.Iterator): def __init__(self, cmod): self._cmod = cmod self.it = cmod.BeginNV_() def __next__(self): if self.it.is_end(self._cmod): raise StopIteration res = self.it.get() self.it.plusplus() return res class ModuleNV(collections.abc.MutableMapping): def __init__(self, cmod): self._cmod = cmod def __setitem__(self, key, value): self._cmod.SetNV(key, value) def __getitem__(self, key): if not self._cmod.ExistsNV(key): raise KeyError return self._cmod.GetNV(key) def __contains__(self, key): return self._cmod.ExistsNV(key) def __delitem__(self, key): self._cmod.DelNV(key) def keys(self): return ModuleNVIter(self._cmod) __iter__ = keys def __len__(self): raise NotImplemented class Module: description = '< Placeholder for a description >' module_types = [CModInfo.NetworkModule] wiki_page = '' has_args = False args_help_text = '' def __str__(self): return self.GetModName() @classmethod def t_s(cls, english, context=''): domain = 'znc-' + cls.__name__ return CTranslation.Get().Singular(domain, context, english) @classmethod def t_f(cls, english, context=''): fmt = cls.t_s(english, context) # Returning bound method return fmt.format @classmethod def t_p(cls, english, englishes, num, context=''): domain = 'znc-' + cls.__name__ fmt = CTranslation.Get().Plural(domain, context, english, englishes, num) return fmt.format @classmethod def t_d(cls, english, context=''): return CDelayedTranslation('znc-' + cls.__name__, context, english) def OnLoad(self, sArgs, sMessage): return True def _GetSubPages(self): return self.GetSubPages() def CreateSocket(self, socketclass=Socket, *the, **rest): socket = socketclass() socket._csock = CreatePySocket(self._cmod, socket) socket.Init(*the, **rest) return socket def CreateTimer(self, timer, interval=10, cycles=1, label='pytimer', description='Some python timer'): t = timer() t._ctimer = CreatePyTimer(self._cmod, interval, cycles, label, description, t) return t def GetSubPages(self): pass def OnShutdown(self): pass def OnBoot(self): pass def WebRequiresLogin(self): pass def WebRequiresAdmin(self): pass def GetWebMenuTitle(self): pass def OnWebPreRequest(self, WebSock, sPageName): pass def OnWebRequest(self, WebSock, sPageName, Tmpl): pass def OnPreRehash(self): pass def OnPostRehash(self): pass def OnIRCDisconnected(self): pass def OnIRCConnected(self): pass def OnIRCConnecting(self, IRCSock): pass def OnIRCConnectionError(self, IRCSock): pass def OnIRCRegistration(self, sPass, sNick, sIdent, sRealName): pass def OnBroadcast(self, sMessage): pass def OnChanPermission(self, OpNick, Nick, Channel, uMode, bAdded, bNoChange): pass def OnOp(self, OpNick, Nick, Channel, bNoChange): pass def OnDeop(self, OpNick, Nick, Channel, bNoChange): pass def OnVoice(self, OpNick, Nick, Channel, bNoChange): pass def OnDevoice(self, OpNick, Nick, Channel, bNoChange): pass def OnMode(self, OpNick, Channel, uMode, sArg, bAdded, bNoChange): pass def OnRawMode(self, OpNick, Channel, sModes, sArgs): pass def OnRaw(self, sLine): pass def OnStatusCommand(self, sCommand): pass def OnModCommand(self, sCommand): self.HandleCommand(sCommand) def OnModNotice(self, sMessage): pass def OnModCTCP(self, sMessage): pass def OnQuit(self, Nick, sMessage, vChans): pass def OnNick(self, Nick, sNewNick, vChans): pass def OnKick(self, OpNick, sKickedNick, Channel, sMessage): pass def OnJoining(self, Channel): pass def OnJoin(self, Nick, Channel): pass def OnPart(self, Nick, Channel, sMessage=None): pass def OnInvite(self, Nick, sChan): pass def OnChanBufferStarting(self, Chan, Client): pass def OnChanBufferEnding(self, Chan, Client): pass def OnChanBufferPlayLine(self, Chan, Client, sLine): pass def OnPrivBufferPlayLine(self, Client, sLine): pass def OnClientLogin(self): pass def OnClientDisconnect(self): pass def OnUserRaw(self, sLine): pass def OnUserCTCPReply(self, sTarget, sMessage): pass def OnUserCTCP(self, sTarget, sMessage): pass def OnUserAction(self, sTarget, sMessage): pass def OnUserMsg(self, sTarget, sMessage): pass def OnUserNotice(self, sTarget, sMessage): pass def OnUserJoin(self, sChannel, sKey): pass def OnUserPart(self, sChannel, sMessage): pass def OnUserTopic(self, sChannel, sTopic): pass def OnUserTopicRequest(self, sChannel): pass def OnUserQuit(self, sMessage): pass def OnCTCPReply(self, Nick, sMessage): pass def OnPrivCTCP(self, Nick, sMessage): pass def OnChanCTCP(self, Nick, Channel, sMessage): pass def OnPrivAction(self, Nick, sMessage): pass def OnChanAction(self, Nick, Channel, sMessage): pass def OnPrivMsg(self, Nick, sMessage): pass def OnChanMsg(self, Nick, Channel, sMessage): pass def OnPrivNotice(self, Nick, sMessage): pass def OnChanNotice(self, Nick, Channel, sMessage): pass def OnTopic(self, Nick, Channel, sTopic): pass def OnServerCapAvailable(self, sCap): pass def OnServerCap302Available(self, sCap, sValue): return self.OnServerCapAvailable(sCap) def OnClientAttached(self): pass def OnClientDetached(self): pass def OnServerCapResult(self, sCap, bSuccess): pass def OnTimerAutoJoin(self, Channel): pass def OnEmbeddedWebRequest(self, WebSock, sPageName, Tmpl): pass def OnAddNetwork(self, Network, sErrorRet): pass def OnDeleteNetwork(self, Network): pass def OnSendToClient(self, sLine, Client): pass def OnSendToIRC(self, sLine): pass # Command stuff def AddCommand(self, cls, *args, **kwargs): cmd = cls(*args, **kwargs) cmd._cmodcommand = CreatePyModCommand(self._cmod, cls.command, COptionalTranslation(cls.args), COptionalTranslation(cls.description), cmd) return cmd # Global modules def OnAddUser(self, User, sErrorRet): pass def OnDeleteUser(self, User): pass def OnClientConnect(self, pSock, sHost, uPort): pass def OnLoginAttempt(self, Auth): pass def OnFailedLogin(self, sUsername, sRemoteIP): pass def OnUnknownUserRaw(self, pClient, sLine): pass def OnClientCapLs(self, pClient, ssCaps): pass def IsClientCapSupported(self, pClient, sCap, bState): pass def OnClientCapRequest(self, pClient, sCap, bState): pass def OnModuleLoading(self, sModName, sArgs, eType, bSuccess, sRetMsg): pass def OnModuleUnloading(self, pModule, bSuccess, sRetMsg): pass def OnGetModInfo(self, ModInfo, sModule, bSuccess, sRetMsg): pass def OnGetAvailableMods(self, ssMods, eType): pass # In python None is allowed value, so python modules may continue using OnMode and not OnMode2 def OnChanPermission2(self, OpNick, Nick, Channel, uMode, bAdded, bNoChange): return self.OnChanPermission(OpNick, Nick, Channel, uMode, bAdded, bNoChange) def OnOp2(self, OpNick, Nick, Channel, bNoChange): return self.OnOp(OpNick, Nick, Channel, bNoChange) def OnDeop2(self, OpNick, Nick, Channel, bNoChange): return self.OnDeop(OpNick, Nick, Channel, bNoChange) def OnVoice2(self, OpNick, Nick, Channel, bNoChange): return self.OnVoice(OpNick, Nick, Channel, bNoChange) def OnDevoice2(self, OpNick, Nick, Channel, bNoChange): return self.OnDevoice(OpNick, Nick, Channel, bNoChange) def OnMode2(self, OpNick, Channel, uMode, sArg, bAdded, bNoChange): return self.OnMode(OpNick, Channel, uMode, sArg, bAdded, bNoChange) def OnRawMode2(self, OpNick, Channel, sModes, sArgs): return self.OnRawMode(OpNick, Channel, sModes, sArgs) def OnRawMessage(self, msg): pass def OnNumericMessage(self, msg): pass # Deprecated non-Message functions should still work, for now. def OnQuitMessage(self, msg, vChans): return self.OnQuit(msg.GetNick(), msg.GetReason(), vChans) def OnNickMessage(self, msg, vChans): return self.OnNick(msg.GetNick(), msg.GetNewNick(), vChans) def OnKickMessage(self, msg): return self.OnKick(msg.GetNick(), msg.GetKickedNick(), msg.GetChan(), msg.GetReason()) def OnJoinMessage(self, msg): return self.OnJoin(msg.GetNick(), msg.GetChan()) def OnPartMessage(self, msg): return self.OnPart(msg.GetNick(), msg.GetChan(), msg.GetReason()) def OnChanBufferPlayMessage(self, msg): modified = String() old = modified.s = msg.ToString(CMessage.ExcludeTags) ret = self.OnChanBufferPlayLine(msg.GetChan(), msg.GetClient(), modified) if old != modified.s: msg.Parse(modified.s) return ret def OnPrivBufferPlayMessage(self, msg): modified = String() old = modified.s = msg.ToString(CMessage.ExcludeTags) ret = self.OnPrivBufferPlayLine(msg.GetClient(), modified) if old != modified.s: msg.Parse(modified.s) return ret def OnUserRawMessage(self, msg): pass def OnUserCTCPReplyMessage(self, msg): target = String(msg.GetTarget()) text = String(msg.GetText()) ret = self.OnUserCTCPReply(target, text) msg.SetTarget(target.s) msg.SetText(text.s) return ret def OnUserCTCPMessage(self, msg): target = String(msg.GetTarget()) text = String(msg.GetText()) ret = self.OnUserCTCP(target, text) msg.SetTarget(target.s) msg.SetText(text.s) return ret def OnUserActionMessage(self, msg): target = String(msg.GetTarget()) text = String(msg.GetText()) ret = self.OnUserAction(target, text) msg.SetTarget(target.s) msg.SetText(text.s) return ret def OnUserTextMessage(self, msg): target = String(msg.GetTarget()) text = String(msg.GetText()) ret = self.OnUserMsg(target, text) msg.SetTarget(target.s) msg.SetText(text.s) return ret def OnUserNoticeMessage(self, msg): target = String(msg.GetTarget()) text = String(msg.GetText()) ret = self.OnUserNotice(target, text) msg.SetTarget(target.s) msg.SetText(text.s) return ret def OnUserJoinMessage(self, msg): chan = String(msg.GetTarget()) key = String(msg.GetKey()) ret = self.OnUserJoin(chan, key) msg.SetTarget(chan.s) msg.SetKey(key.s) return ret def OnUserPartMessage(self, msg): chan = String(msg.GetTarget()) reason = String(msg.GetReason()) ret = self.OnUserPart(chan, reason) msg.SetTarget(chan.s) msg.SetReason(reason.s) return ret def OnUserTopicMessage(self, msg): chan = String(msg.GetTarget()) topic = String(msg.GetTopic()) ret = self.OnUserTopic(chan, topic) msg.SetTarget(chan.s) msg.SetTopic(topic.s) return ret def OnUserQuitMessage(self, msg): reason = String(msg.GetReason()) ret = self.OnUserQuit(reason) msg.SetReason(reason.s) return ret def OnCTCPReplyMessage(self, msg): text = String(msg.GetText()) ret = self.OnCTCPReply(msg.GetNick(), text) msg.SetText(text.s) return ret def OnPrivCTCPMessage(self, msg): text = String(msg.GetText()) ret = self.OnPrivCTCP(msg.GetNick(), text) msg.SetText(text.s) return ret def OnChanCTCPMessage(self, msg): text = String(msg.GetText()) ret = self.OnChanCTCP(msg.GetNick(), msg.GetChan(), text) msg.SetText(text.s) return ret def OnPrivActionMessage(self, msg): text = String(msg.GetText()) ret = self.OnPrivAction(msg.GetNick(), text) msg.SetText(text.s) return ret def OnChanActionMessage(self, msg): text = String(msg.GetText()) ret = self.OnChanAction(msg.GetNick(), msg.GetChan(), text) msg.SetText(text.s) return ret def OnPrivTextMessage(self, msg): text = String(msg.GetText()) ret = self.OnPrivMsg(msg.GetNick(), text) msg.SetText(text.s) return ret def OnChanTextMessage(self, msg): text = String(msg.GetText()) ret = self.OnChanMsg(msg.GetNick(), msg.GetChan(), text) msg.SetText(text.s) return ret def OnPrivNoticeMessage(self, msg): text = String(msg.GetText()) ret = self.OnPrivNotice(msg.GetNick(), text) msg.SetText(text.s) return ret def OnChanNoticeMessage(self, msg): text = String(msg.GetText()) ret = self.OnChanNotice(msg.GetNick(), msg.GetChan(), text) msg.SetText(text.s) return ret def OnTopicMessage(self, msg): topic = String(msg.GetTopic()) ret = self.OnTopic(msg.GetNick(), msg.GetChan(), topic) msg.SetTopic(topic.s) return ret def OnUnknownUserRawMessage(self, msg): pass def OnSendToClientMessage(self, msg): pass def OnSendToIRCMessage(self, msg): pass class Command: command = '' args = '' description = '' def __call__(self, sLine): pass def GetModule(self): return self._cmodcommand.GetModule().GetNewPyObj() def make_inherit(cl, parent, attr): def make_caller(parent, name, attr): return lambda self, *a: parent.__dict__[name](self.__dict__[attr], *a) while True: for x in parent.__dict__: if not x.startswith('_') and x not in cl.__dict__: setattr(cl, x, make_caller(parent, x, attr)) if parent.__bases__: # Multiple inheritance is not supported (yet?) parent = parent.__bases__[0] else: break make_inherit(Socket, CPySocket, '_csock') make_inherit(Module, CPyModule, '_cmod') make_inherit(Timer, CPyTimer, '_ctimer') make_inherit(Command, CPyModCommand, '_cmodcommand') class ZNCModuleLoader(importlib.abc.SourceLoader): def __init__(self, modname, pypath): self.pypath = pypath def create_module(self, spec): self._datadir = spec.loader_state[0] self._package_dir = spec.loader_state[1] return super().create_module(spec) def get_data(self, path): with open(path, 'rb') as f: return f.read() def get_filename(self, fullname): return self.pypath class ZNCModuleFinder(importlib.abc.MetaPathFinder): @staticmethod def find_spec(fullname, path, target=None): if fullname == 'znc_modules': spec = importlib.util.spec_from_loader(fullname, None, is_package=True) return spec parts = fullname.split('.') if parts[0] != 'znc_modules': return def dirs(): if len(parts) == 2: # common case yield from CModules.GetModDirs() else: # the module is a package and tries to load a submodule of it for libdir in sys.modules['znc_modules.' + parts[1]].__loader__._package_dir: yield libdir, None for libdir, datadir in dirs(): finder = importlib.machinery.FileFinder(libdir, (ZNCModuleLoader, importlib.machinery.SOURCE_SUFFIXES)) spec = finder.find_spec('.'.join(parts[1:])) if spec: spec.name = fullname spec.loader_state = (datadir, spec.submodule_search_locations) # It almost works with original submodule_search_locations, # then python will find submodules of the package itself, # without calling out to ZNCModuleFinder or ZNCModuleLoader. # But updatemod will be flaky for those submodules because as # of py3.8 importlib.invalidate_caches() goes only through # sys.meta_path, but not sys.path_hooks. So we make them load # through ZNCModuleFinder too, but still remember the original # dir so that the whole module comes from a single entry in # CModules.GetModDirs(). spec.submodule_search_locations = [] return spec sys.meta_path.append(ZNCModuleFinder()) _py_modules = set() def find_open(modname): '''Returns (pymodule, datapath)''' fullname = 'znc_modules.' + modname for m in _py_modules: if m.GetModName() == modname: break else: # module is not loaded, clean up previous attempts to load it or even # to list as available modules # This is to to let updatemod work to_remove = [] for m in sys.modules: if m == fullname or m.startswith(fullname + '.'): to_remove.append(m) for m in to_remove: del sys.modules[m] try: module = importlib.import_module(fullname) except ImportError: return (None, None) if not isinstance(module.__loader__, ZNCModuleLoader): # If modname/ is a directory, it was "loaded" using _NamespaceLoader. # This is the case for e.g. modperl. # https://github.com/znc/znc/issues/1757 return (None, None) return (module, os.path.join(module.__loader__._datadir, modname)) def load_module(modname, args, module_type, user, network, retmsg, modpython): '''Returns 0 if not found, 1 on loading error, 2 on success''' if re.search(r'[^a-zA-Z0-9_]', modname) is not None: retmsg.s = 'Module names can only contain letters, numbers and ' \ 'underscores, [{0}] is invalid.'.format(modname) return 1 pymodule, datapath = find_open(modname) if pymodule is None: return 0 if modname not in pymodule.__dict__: retmsg.s = "Python module [{0}] doesn't have class named [{1}]".format( pymodule.__file__, modname) return 1 cl = pymodule.__dict__[modname] if module_type not in cl.module_types: retmsg.s = "Module [{}] doesn't support type.".format(modname) return 1 module = cl() module._cmod = CreatePyModule(user, network, modname, datapath, module_type, module, modpython) module.nv = ModuleNV(module._cmod) module.SetDescription(cl.description) module.SetArgs(args) module.SetModPath(pymodule.__file__) _py_modules.add(module) if module_type == CModInfo.UserModule: if not user: retmsg.s = "Module [{}] is UserModule and needs user.".format(modname) unload_module(module) return 1 cont = user elif module_type == CModInfo.NetworkModule: if not network: retmsg.s = "Module [{}] is Network module and needs a network.".format(modname) unload_module(module) return 1 cont = network elif module_type == CModInfo.GlobalModule: cont = CZNC.Get() else: retmsg.s = "Module [{}] doesn't support that module type.".format(modname) unload_module(module) return 1 cont.GetModules().append(module._cmod) try: loaded = True if not module.OnLoad(args, retmsg): if retmsg.s == '': retmsg.s = 'Module [{0}] aborted.'.format(modname) else: retmsg.s = 'Module [{0}] aborted: {1}'.format(modname, retmsg.s) loaded = False except BaseException: if retmsg.s == '': retmsg.s = 'Got exception: {0}'.format(traceback.format_exc()) else: retmsg.s = '{0}; Got exception: {1}'.format(retmsg.s, traceback.format_exc()) loaded = False except: if retmsg.s == '': retmsg.s = 'Got exception.' else: retmsg.s = '{0}; Got exception.'.format(retmsg.s) loaded = False if loaded: if retmsg.s == '': retmsg.s = "[{0}]".format(pymodule.__file__) else: retmsg.s = "[{1}] [{0}]".format(pymodule.__file__, retmsg.s) return 2 print(retmsg.s) unload_module(module) return 1 def unload_module(module): if (module not in _py_modules): return False module.OnShutdown() _py_modules.discard(module) cmod = module._cmod if module.GetType() == CModInfo.UserModule: cont = cmod.GetUser() elif module.GetType() == CModInfo.NetworkModule: cont = cmod.GetNetwork() elif module.GetType() == CModInfo.GlobalModule: cont = CZNC.Get() cont.GetModules().removeModule(cmod) del module._cmod cmod.DeletePyModule() del cmod return True def unload_all(): while len(_py_modules) > 0: mod = _py_modules.pop() # add it back to set, otherwise unload_module will be sad _py_modules.add(mod) unload_module(mod) if _cov: _cov.stop() def gather_mod_info(cl, modinfo): translation = CTranslationDomainRefHolder("znc-" + modinfo.GetName()) modinfo.SetDescription(cl.description) modinfo.SetWikiPage(cl.wiki_page) modinfo.SetDefaultType(cl.module_types[0]) modinfo.SetArgsHelpText(cl.args_help_text); modinfo.SetHasArgs(cl.has_args); for module_type in cl.module_types: modinfo.AddType(module_type) def get_mod_info(modname, retmsg, modinfo): '''0-not found, 1-error, 2-success''' pymodule, datadir = find_open(modname) if pymodule is None: return 0 if modname not in pymodule.__dict__: retmsg.s = "Python module [{0}] doesn't have class named [{1}]".format( pymodule.__file__, modname) return 1 cl = pymodule.__dict__[modname] modinfo.SetName(modname) modinfo.SetPath(pymodule.__file__) gather_mod_info(cl, modinfo) return 2 CONTINUE = CModule.CONTINUE HALT = CModule.HALT HALTMODS = CModule.HALTMODS HALTCORE = CModule.HALTCORE UNLOAD = CModule.UNLOAD HaveSSL = HaveSSL_() HaveIPv6 = HaveIPv6_() HaveCharset = HaveCharset_() Version = GetVersion() VersionMajor = GetVersionMajor() VersionMinor = GetVersionMinor() VersionExtra = GetVersionExtra() def CreateWebSubPage(name, title='', params=dict(), admin=False): vpair = VPair() for k, v in params.items(): VPair_Add2Str_(vpair, k, v) flags = 0 if admin: flags |= CWebSubPage.F_ADMIN return CreateWebSubPage_(name, title, vpair, flags) CUser.GetNetworks = CUser.GetNetworks_ CIRCNetwork.GetChans = CIRCNetwork.GetChans_ CIRCNetwork.GetServers = CIRCNetwork.GetServers_ CIRCNetwork.GetQueries = CIRCNetwork.GetQueries_ CChan.GetNicks = CChan.GetNicks_ CZNC.GetUserMap = CZNC.GetUserMap_ def FreeOwnership(func): """ Force release of python ownership of user object when adding it to znc This solves #462 """ @wraps(func) def _wrap(self, obj, *args): # Bypass if first argument is not an SWIG object (like base type str) if not hasattr(obj, 'thisown'): return func(self, obj, *args) # Change ownership of C++ object from SWIG/python to ZNC core if function was successful if func(self, obj, *args): # .thisown is magic SWIG's attribute which makes it call C++ "delete" when python's garbage collector deletes python wrapper obj.thisown = 0 return True else: return False return _wrap CZNC.AddListener = FreeOwnership(func=CZNC.AddListener) CZNC.AddUser = FreeOwnership(func=CZNC.AddUser) CZNC.AddNetworkToQueue = FreeOwnership(func=CZNC.AddNetworkToQueue) CUser.AddNetwork = FreeOwnership(func=CUser.AddNetwork) CIRCNetwork.AddChan = FreeOwnership(func=CIRCNetwork.AddChan) CModule.AddSocket = FreeOwnership(func=CModule.AddSocket) CModule.AddSubPage = FreeOwnership(func=CModule.AddSubPage) class ModulesIter(collections.abc.Iterator): def __init__(self, cmod): self._cmod = cmod def __next__(self): if self._cmod.is_end(): raise StopIteration module = self._cmod.get() self._cmod.plusplus() return module CModules.__iter__ = lambda cmod: ModulesIter(CModulesIter(cmod)) # e.g. msg.As(znc.CNumericMessage) def _CMessage_As(self, cl): return getattr(self, 'As_' + cl.__name__, lambda: self)() CMessage.As = _CMessage_As def str_eq(self, other): if str(other) == str(self): return True return id(self) == id(other) CChan.__eq__ = str_eq CNick.__eq__ = str_eq CUser.__eq__ = str_eq CIRCNetwork.__eq__ = str_eq CPyRetString.__eq__ = str_eq znc-1.9.1/modules/modpython/generated.tar.gz0000644000175000017500000132163114641222745021334 0ustar somebodysomebody%f{۶0ƟuI$Wql7mO&}im-'=nziy":$K .(Qez46 `0d>ׯ~666|"n~Wl}/_lnmm}b+B`(b?,SAJdp_VowO|-xğ`-82Fm4OFa>Q ?|||v=ҸWu@>J8/ַ7E%8ɃQ! *ʂ< rt<,Oqr^9Q0HU,%( Yc z=_YY2ב~2`dQO$MnAd$I08 J > t=vZ5dt80'Np1̓Atiz'[ -~*xg qW>Z-^|o~{prO^ 6vџ ͟rkcsss R"פĪŻqŒIyi-U$V>YcVԋ!8̅r1BſF4,$˃2Hi0l(M0:(GQ0I]($ p$ՃO!UCO`W Z,|Ĕ2_IOfQ?Oy(iȸOJi^Q .FQ~ ߄i !A A<4#eR LsK4%҇y4 aoC&SA @p XE#+ 9ȗÝћ휟]Σ:47/Bū#*GCO{>&2ŗS2 @fq77;V`< na1 S8F),B:51;hы(PB? a8f~Ձ8o!h)uxD\Q̇z81??;=cG^N,!Ia).61`rN&X(ā0E)s]w%>|qء*Wbiy‚3Z^HX&JDmGQ%Q4/5/tﮰ az^@^(ph:E6/0/E{ZzZRӝ0E/Ut'ˢ42MFn^$gi84/jɝU%&IWV_a A>ݹXmDXoy_6xGz2e[%A{ 05 OŰyJ3Ca /`!e<ĂMOY-&z{RoqkAM?L- OQ2P+1ӋuAs,Fͯhpa X#{'G`FM/k.6gE;<<=? p҉ʆY 4Fs(Xpy84W7¦& f Ŧ'JB~ @Ͼ!ߍ 8V@?{T:8u-)AU|Au^8u'k |ًa-Ko;JSD0WN8ͩuO& ҎZUr9}$XҙUg?? N`Wʟ s~e1A:P}+_ m(b~Az`uPًǗw)z9j4 dLm{IGO?4V_"ռO7b99^u>25|rT;9G/wvSA|;{=[ׇviD@>1?%SB$)$K[ p!djfAA&HX|^1 T+(hbk8 W$91|JM>]_hހȁtIX2.7Q Z:Au/Ǔ4+H,d܃Y7ٴ=?Za>pC\fbz6]OnM`~sw{^]yf_͍axqC{w vw=X-uwvݝyxOږNaU^ѿz}YAo_g;ѭfIr񏨟BC=\oS@ E[h`9w ΢aæ{ {/5O`8o3OыO;Dw!ږXyJ,Q./d$SX)[Eh%SSl8T*:Ndzt06g*ovzrKY<mOK`l;CKOem 7pWH]%*5 iw ;Gbf}\e3"I 'hL.-n_<<8:87$m|ZkQ}#ђO:'Gc-v=G;]K[>Z)Is)/aNdtαh4IpYfRӭw1Fi$t,16μ>,CǶjbo< p;jI'T(Rc>瞖R\-L"BShXۓ@g̾v D>;)ɸg=l2m~2th@<-9).x@q :Hp-‡U_;0̲oĠj'>Ⱦ٠m{F0DfM}<D~'\ nvr޵fgdت" {ku Ά&[V{;3.jf1d Ph۩eWV-`-_#4kО45 4%Zf,x[hJ)-`!Gppޡn|owɯG[P*0xBE'PU3 {| 7 צoESRљr댡`:  Ȣ,Eh>rf܇myU7:Ȥ4 )zM=õm0Q u+fcuTh5&Fk6%n{AŰ< qb>-B^C[^'MhAs!&Re/DeVJvWWETi1F#@+nX2 ynWs@Y-sჇ_xHbwGpD+IXiFaN0&dǙ@]2mr A41v?cO tB1-‹V p$Sȡ¤I0Yί3nGCjVI _mK`K_Z3 C3׃=tsiaJǸoA ~ m:|I c0;d 0fBuhdEiaK)Sٹ4۪XV8L2\8wAMB&E.efǶ6Rȳrb8׃\_P)Xot] Iug<'-sEs&bW+oG2qj6e$6|Pbn@P 4vZm̪ ߂Bvut?L؄鑈D`VvL/u8oB*aj`QJL0,<65b;#8.6[@HKOh3Z\ )F'O>op<(F=f/_ jG-前 ۅKa\+״wUӍ`R澓wNZ*-m\*f?ˆ\y ;/q]Z!P!+Iqb*4ǹ߆@MiwGG`M*:,Dad~n8@ 6GȊ*wrRcӟbWa3hp:zN,e<Gs &0 N%#XF ~QJvE1dJnZjXK@ZrCȱ~Mc"C97 /bB]zg[ }& ~-]m'1u:BQ!$P} I!UFs` %Ha fJyx&rZ6mU@y&>qgbTc =IK]_jfpvlTr dPڅTfMrzij *:&UQfVnR">/&@뀪 b2Īъe>9l_[$f+|"rQ SX&Uf{_H9tj6mrCxkF 1\ICQML @:YR؎@E *cKǤ:y_DW76>ŗ_/3`@UQSXv o9x!" p+Tc= 6.7p~񋧎3οO\R„r8ZXlDl{l%JO7_?mHk7/dv(C e-ls]+!MqL*5"_QݾlH0ꎧ$kOm!n9y/ %rB3}E jjmARAܞ0_Q0mSO{OђSˣVoinul|zUǙAp%?@PV`O^ʯ:^s73k}Q *V1yHsTꮕt. "~n-~Ijp1RA ч M.Љ ‡U#Z:v(îl83y.i3읽,[F(r<{Px8dNe6ݵ2}~UO:MGg9ǠmQtz8sR_ļxrzV'm`K\w0_鿝j;|etS0NSTׅ K$}eWj8kxv c[#؀pú;Da*-Am]X~|yÝﺔ;7;}Ȣ!z|2ҕBtP,i%Ѕ^'mt x s ӹn͜[$VjK8f*'#4P6R k?oJ5;KC ‚\S\%Uf4dǢ6/ܫ z2&X#+XLp-3Qg `v^*˺@&T -.' 0 |W뿢*teEQ1~ekYJBMjȣ.ZFm I $kZ H8!r| ɴZEosZ+>KKULw]BFg^n7ȓ7t`ߛw( 9??$K~8/v1y<^-KTHTw)Ny!͏Vw'{Y-IZJVۻ0|CEHK^SZ`j"mWbd ԊB{3]wfװˁf'S\Rϧ1l#Sy4J>vcE;22 9{C~]>7 "9TO$AvGTN{n}$,ޯ'2$3 @ 8U ݽO+H_*5\ ·8>A= :<&5)[yUڲK$U;=iw÷.TWh%qg9(],:INaNitSf |hſ2 &i kVv*潽IG8,:Ai;HXc؏b\ jMYd9L q r0R8nZ~eSړ?r>HmKnȚ:E)Z+i`zW#%?z'iU{vfTE&wM4KBoQ^;´: ,:I7 hFwzQtR`i@. :=FᐸpuoX,CywH@BOy3N[ PtY^1j>XF_gl[[w@B^iCkN)ըϳʵbYTWBEXAD3A1) yAʡHFϏOzߟuգ)Z 0'e*|1xY6dR  ;81pzH7^u{R9ދ0سA/2tf:N;bWA~<=)b9(L8'>g-{,.> eZ8~>+}_F6Շs#QM l,I3ͶMa{޷٧|dz!vx/M}<twb"aMdLWheBO]QYvR[Q%VRBt-8 *P6|ܞHTTx- oc-%xRR-ݨ$R㤀nI]DZ ;NE-{qC(n.h6=8`XGz뒸sxxcO0_W=K?'s 34 i^V{}L"^vxAέbcmGA9r orBX<AUBo7YXZPQpj֞!-8b!OMftݒg<'JWL%W9쵇&k3V1,|Pz`&$yT2=ϸnn1Bý@Z;A"~RoE?#_ؘx6@s?,@\W3(C`Q;imKkkt߮{|{I-Y"ipׇIR"tz4-Wa&PFkUO6=h'sk ,W/*I|J%K2x~U녷*K27 C EgSaRj*᭕ Оfd48Mrȷ?J'N!/x<{`e0&`!Е +ut+3A;}(hm',=|ړo`h9 `D9`֌kX `N~>>Ei uӈQ 1AWN!Jnb j)>.+ V#I 'DO*tyB]$vP_cT.>*ܳfJ; :*Czb( ]e-cn>"L+:K/²'GXd/Q$j.=Yk٘*v)fĒ_͂PnpAX:,PJg}(姴 Q#SidL[ց7iXܤd)uLl+<[  KNsy/`15%|<(/'a9擩DchٰIJ,f'q2f`v*sW)t1NX =[ }7&%rMaEP̆-}"(NJfJ4=eKt[$"L R.:YbAͤ'-5uuJPO!]^dݬ!?ȋ&q|=h~\ABA!PwZ@XACm V<I?NzTIa5a{K4e &0d)bctU ~8|ZBvw2*>HJŧjbH(sɇ[ے9 u4*ZE;6 bꁑ>*ےJŬ/MѦj]Ξ.svW1h3~ć#mj&#6J=H6FPU ~3&Fp?ȝ6O/󣰭! Mo:*4 U XHbu-X/vm_f7SW7:ŝ%zN f`FɑT<[+6LY7wfq_=ӴXp{D/Q2 o/X>s|k3CNaj`)4D@;\X%bCҒ+֨1'+l?&*lw~̎bx$2 sbX^g;Y ^4B(*rbﶽ"t GmIG5Gm<$ ɨS{owKp3ծzʬ%9RԴMknhaT)\*eN Sr9O#P*ݦyEyO j:kV(;cވظq?rmL|+J|\wͫē)1@=yuCnhO>&a.TUPzCфY":1~eOG!2+ T9c|WM@|aneV2&VE%XhvxHR AΞfɏT7f kyooK4A6-(=G@nc,! ]wE*DzYk374D{H[y%7Oo5hs/ -ٷfKB Qd1JZT3^cDu^CwiqIϓ"?Ӹ6"n\1#}<{ͻ%ĚV=g r\1D R*Kq+Y` B}ZڢqzXMfyT6!Ud}>Dl{:y -uU6r8R2 Q@ #1.!*վUU/vzv ]A@ U8C[R._)ciߋB)) B~Ab!TZS@Anȃ7#k.ςod kͭ? ϿSH-b[ 1ԯV(+x:R5n`?ggg֕M4{ւIŬ5S4+(j# 6ǸP'˺בWJ|+'O9.,2yL~biP,ַLm~<S%~Mo}@z'TƷNpLH p Z!~m]^dbk~ <+w@?K,A\~`0 7I24olW dd)Ef5s?@+Se\efQJ=Nҗ,3 !|~vPp~y_[+Ц| @.c_("*?n$ ֱqDWقƄ &vyaM̢7n6 dxΆmAՙi0Ҡ *#Ƃ$e ~ŚrZ4zH>?;F2Ds~#K@)N䣌1ȗ[ @~lr{-m\ (I ymRބM70ԓ4^oW>!zz^-̱nN8M0|,WGTmiQoo~(Oܫb 0 &Uno{}u|}Ξ]R|"$]A$cx dbLECN(d=p\;whazou<0ln4 n_?j ![|d4OR''Lx8IWא@6ǧ?&}L 4¨0$!x'_0HJoa@\gʍ?zc9(FfxF upN|ifR9EOV]YI (W'<wh<~zyA^_0Zkf57mC(j=fmnqs5aǴwJ}{p|;?:X[ӱЭ z }+f !^(:+^e3mrcgcw8u5QږVa£fe2f7Ӎ+O/PBir{|*cEL̖?4㬀&74޿ٔ|jg:G tna.7WV;:XdT =auXb`5[VvB;XɆ\i-NL9sNUf'x\OLwLGhE+> *52=R<>> _қZlТ4'Վ!UUէqx% @+$8=Ϣ͸(xAŊ'ꂆ$Y<2,q# Eze܏XƦcy$ۃu x,6]k642x2UNGb|cM^,a/rd \Dw_5$5U&IϨ^dp*L %_456:mĘa(CZAl=wI):c@~}w ; 'FZʍ׃R<7rM L䫙g%;UYZa׿`?9d(v#~*+bq>]z بEE`gb]r9}bW_{QX{(}۾ E>dA?H90ʂQcv,FM!ַVAhuSb1dFp11-,&E%*Q8XU:;D츽3p<[ch*G kWo&ǜsqCKNܗ^f/YFĥNV{Qr4Q1iɂf: sQG/:)Re;|ٓ^7Mς-,+cԋAFY?/c|!Haa.wgֻZ t-6`O>¸(͋Z8 m2rـ*P@ r5 x^ٙ 0!6N gQ ߖ:SPaS)(1 %xe.ێ-,ʘkCӖ8yK4dCH|6В#l-lF}EZ P@\2*h004a0NHFutsoúlѵRN.rn xO,m*m/]*$VɯC|BB<!.$D R\C|IEFgQ4˧E} 1gDAjd & vV~, )\~|D8Fe+Ja@o˷>XNxbop^՟XJ§{S`.?+ql 3yI3ݿN"?I@w<ufͱ:^8,հU\J3<rk5O^ye>8罪X="~}ݻo5ng$.9 1%/=`8tXVW5QIvd'ĵH[Ҥ+; hC}]͞cZQ8'!?I5MLƌ=7h;͔YZ/,"'?BȚ{A-ꞟ일 A2~#|,2Ce(,Vu?uZZty'1(bIzSځsg5Nx } Y$Ne2€V2&!ۏOKj4MW5QHsBŮB›7{.2;>.bU2cJZVq} \ld s:L Ufj*3l|/W \. UOD¬"?~⢡<!VpLi Rf:okH#9X۠lvzi@}]!m=9`xHKvVn͖Gg٫ϕQsHFC%q,.5|3{;4qbH&WSmiA9&YcTφ*+G(ޯjJRʉ!VχyְXX*NQ?6#8EX0Pzh&E7 0sJ V'VfV~f;)*Gѓ|8IlW6o2 vvrf6 T5R"~Oz*<g*so %_N~Ib%ړ, KM0mn7:i\F8)d@ fi}B`AÈ4Va`\W$ *,Z|TAJYU{4ZnX&7„5GQ.KxgGڻ#O֮R%Zz:ʞVl>ᙙ;WnٛY&֖Ypьy7TtՆ  ٙ3L I7 ɦFə:.+ YGy) }n=(Դ=;*|}'{woBu_ؑ9ԟ za xvW^ÝD{b^B"|S#w`s_Kv3f,4c-nn~inEe0]Ux7n%nk0. I8%Xj[TЙb qkvˎꌶ~e?vl{,!0{oHEz[1M-gsv'X1$W<Q8,is-! ,Knje|ɨX.Kǒqi; !<ܧN|,ŃyW[:{') NyFQ>d./Ge\3#^=i Vx3Op+lydr69FV$QXL^r"gP4%n՚t̪'%TCiӤDR= ꖚCȲ;{'?֌4=iUAS(A&}j|\"C^<#T4 M]ߝR.om0^ Q.;ñ[k [w%}]i6{F?'پB3w JPPUKY<;ŗ!ޏRi(ŗ@j?bb yν> ʭ/p~l.i6!^ ʸ2^M/P],8$3ɗd䉑Ü&ʥC7.z/ۼEkIowpwsϹfOVq bK' T@>{nc0zE> cS˧pZILCpۊWb E' >,|bѬPJj)ĉ{cuK;:p$34sˆ,QЫ:AjDF:aVIs;gM\bJ< J)m("VL-m$xj~}ԙma甑\؛ 6m_͇!m.h3u wET,$OR5li>%V-U{k?bآ/7zMШ)衩;-dr@q%J%5~F+{+8Sa G/a,d]Q޿n==Axn?=\O!ex`.`.T/abò2LEi 0QG-ez0ԉ):F^EL%0gӴ$Bi r"2*˼+ӨK<`%!qei5ilsgj) f^&ͰQ @4;r3# y/ecJ"'@FfU|^Ȟ) u*6~@l^Б`B|b\%pw8Gw!LܒOhlm^Yy!jv/u>WɥMk,\8\˜w:.:Ù sʂȎ %(B 6A :ĵs1C;i, GW =lq`^Z yM]*'i3_hWXy84m 2@\ .M3;+y?M;x؎:??~Fkccc/W_lo[_}e[[[_"|՗[l<EG}a*HɒQt J0;?ϧ^PoaN\p><sqOKa:'oooa 'U{])yb}k}sSQ= ?D0WbyR*[xAg0{N/Ǹ aK`Z> $^DztK6wN0}6n|tJ VWQeu}s7^! D+zj֧O+yzo}\ ɿzKZ>1+hQ5GQ[ߑQkjFC×"ZdKKX[EH<g,x<0&zjMu'sGaWx?[2q2ж.n43wxJI rS[O<צSBh/"za's{uh' uC vmwg)t#Ӹˊ@ $J+U?i *VSѡ*IFa(f-ERdEU ⩮E,~xjjUdSvbwMXi:oQ ?o]le ±%;Cm/Gg&0A4L(,>߭[J.c쐡X|fzM̟OڢL7.e!Dl"HBg/PGp&!8nS&Vg{Z{൩)@:_h?o"Zw2O3dE2 UthsG(-rTAܽ :E9a}I9 |W+ZJTښ_;K N*>_mOS mLSr4 |Ef2j=d0^ ^m΁2|b/r|Tn.s@i|*c=y.x7/.\d͋φ3 ]Hz^31Z@ lz(Vc]rJykkʝ͙e"pu] z0˕R@eE^~s;=W=-^l:0kJ-ga]?boW=&Z`7[v h eR-O=hXנDOƿFiRCcr2 pE ǁ8a4Qa7 *nMqynPϛQ>EwFI~pg4gA2Ku}fPV+G@1IE0h2 }`HnÉ$6cP]f<A qG6oT}"KK:/1|x.,  ̽y уybcY4.Vo241TG=n:ΐivm:7|/=bo)_;B+򙢵˜C~̭Z-刌2ؐ| 0 փ;Y7C[ Gm~~kb9>i[t,w63d˲a;M<4~e[őX_ƶoajh[7-4M}Q´ogaB,l[h`[cӥo2 GX+,ifیil[75g,Ͳ h4JojZpl‡ϫm:aC^.d_;{hWeIPda?U?YM]/Ө0r5ihXid54 ´<жTȠ]i4Ra)\զF&tqHhf5449L#fqH⦑B48FC0%F rM#uL#.nIi$ M#i$KMd,dFeIe$a$-5ɍ&fRI}R&9/)5 e׍llッJVjucZX[X`aIVFIJ\ 4S`15l9B$l4@R1b mfOHgc#}&nHCdH5 ߪ",g|Re|0 \7L=oF2 p ]3BW܁,i^b$8hj8ɰ4 '2(!XfͷeR|\f0Ŋ%T2 *J- :YKo8 6lVl7m/ o7eCöLN20-[ACԣQPcwTM9_vfwmZp6 ,p]r6el 6XҞ% \%"\,o/acnWK F{ v.77llMv=lTv &rv#6ΥIR3qx9v)6eUlMw,6wb}ڽ$X_uBa?6Z$C=of{}mM @2[ c;%mwweE\1?DU*aXFeeyqlQaunajh۸&ͫ&anjb[c[(O W-[YXIc\fdȰR\ZXqz--N`-)\CrӨjX,xAYaJou籰ӋⅡb)W/8/8]HoA`0XE g_0]0x^0\0H]`h]0x e2glKaM%\`÷K\4isE ƐW5 5جj|]]dyW6iKm /o\~3<џV}=mlM2,l}-˺f(¶f²f/۪9lf|cZkdOk, igq[ZhbI$Mhea+caZh`A gk93-g3d˰ VX8hf3F45e3{ZF;-Vf3bi1%KxIuBbYʪNUEϸW!r.97Kps/RC4ٿ||uk`; pmߩXc~Dz-B̩_Fo6y%o~akw`)uZ:qmwZem,amf7% ɾ5;-/F075ɾ?HM}79痟}3~ 7%7%7%&7%f7f7%7?dߔ(eT8dߔ(}SZVM3}S rojA̾)7#}Sj}S͞7vZbM=#}SGK˾il}SEo} +o2g7T]ݟ%UHX[ĪU hpGge]W0EæTXTiWX*REUKI+(vBԓMw73FA<@Y(졲>V`3\7jB+d8+LY:?\|ҹԫ55}]Th5.z3c=/pϢ,X<[ŧrq)b-'3oOfKEăʖOr!)cO -&+??ד8mAZB:Vyd|%fвUOx)xŏ,$>Ȋ\tWކìvhfSp&I>wG]`zqf_ʗ_ C70>m&DmUAϐtӄZ8he@y@i*g~g/o ~vˋ9_kb Ro,Yzt<M hx: f@hz{b=5%+Ã^d{shSGw:LL// \qW~c {L2̩:2-*m([ XrQpl}Ѧe?E?%ȟ5s B㟜 uՃ݊Qa mi8@Dȉ( K.Nr%/5Y &U0}FX]|_wޞEƩZx~5k\Jmj:sl9LլPdfjLk%&J7>1nI'gG^%Ǻ7CqFT3d8M 7̶K~rDe|8-VbD}qpΪ=8%qFۚn`p*CV#`0,67 V'ϭfiUA0Dozv\ Uǀuυɇ,4(4g585 EwQ4׷ qRF; ?!\t&vn.GsO3ө:~ʏ"g9w8r X𖢫C)&8 Y Y]xVm+>! V6aBk ,d 'u0I xv BeնU}zLmuI =@6]]~6}Фq&U&uŊUv>iUTVm㓂I oOve:@{o?ko::_1) H@$q7'd8>ܨduKse^v~i|uѠX9=Ķ ś`%۽޻q#i+ώI7%Sewb∷&)iه'Y$U\2Y%޳X KO̔ItpW[#"*?[U:.c XcPkF3f LjCyreYN;Zg] 갾j.]+hcal5n^>* WdzgH,6c932Xw+҈9 _-Ԙ/:j8xYPf8ipo w:"T3:ӂ"l,/>2wa73A"h&e _^틈q/=~)1! cB(e:P/1T)`*z-.,<kD}=vVR "ú"([,7uYy Q$z4DEl/|}XLAqBiaݜmU; t3y 7|C:DbKd㥔/fc Ә]ØکC`͚h8zn>-|~!d; Rk>IDUՑNspA]S}f@xü?5B7!8o d Ake׳S0":J 282sWwD&_)E>iLQ,V \N2%9 x=XSWIBG06o,apCkY5UYǁ k"-ce <H [grGMlaK~(#Lݾ,qr '39(2gRc1R[WPO{1i/*bsX$>\wxV) 11h/|Zxiuc:+3 FYΫٸ<,%t`о=wp1<=\ X?+r x4j6~}5|(Xm}BcW*`?[tZ wSQ]~$ {QcM͌ -VQ˅D;9U~/ LsI桦2(p@3^0z"tKzփGݗx ;<٣uzVr5N(ݒe vf<}칛qYsǙbVT/ae]ZiиO@ 6o ۺ`[4\i,R=V ܄%ЪPn؍ر AK5v#鈐~pXtMl*-l)Ζ63>_.-oy qdfBaLBgN\=Cla0c0]JC/0]ja´F`6 &#vb~ 뛉H:i[QO Dp:L LUcAL~d @NJ@͊c.÷+s{ tXyZю!r,Wm`WzĪv s"QThxqOZ1(FoG-b쯑qlj0?}ڜ6M2&EsY1@栍eR"#!Be9,ס_!\'c\W3|~YD> )bd$k2lu14"K"vRRI4K3Pv*.=+ !Sh6RK4-ծXIdOG6y#w8:U_3bJfI~J]Lh+J^"9dei %Ǻ=)`0\uv z/ϫ,X~p}XdQd0Մ@R7w NM/%HM2q,@)v!d$4.i8$cȉܪm2,U>̿u "ĝ971h ʫ:Zzvǃ.N~sr|H_"!6wx߹xSLu*H۞v@cP9NyΥֽe<hLkHL"Ɛ2ǰtGsV)aY);N XaZ%:7=Q,kǹ ['apu_iv]CW.gD8QOVSE!bV.sBԎ`{o{-@GguEk~NW>͗6M>}YP2ˋ˳o}h 4qFËc`jlox{7m/ǕA&˃'hbb' 6xx谞tn?I6K\ݰFIB0~w>|fLb|{YNl g!" ?+ę#hb3zӔ`ӲA{<*Ă #m< 2pa:{s%ڪ{Q<-I6ZZIJ3qL#>0zȧMAbwE3VTOv؅ iK`_*|mGm0)QVۻzDPzuD@+.٘@(G1Euy2M7VyjYmui"ȇ 3Q%jv6i!19wғ Dx,NhQ guśY9+ E& 3yݠLרVgz)ONQFD>m_w%"b:*:A㸎"XUbS"XL"ZC~[0"@7cِBSd\,&(ʢ$A5 U-!@2c;Eϗ1͘J BDFYC7>]Gnj3jOݔNS[u⽌)Q O_;Ӌqtme<:Hi7DGȣi_dc,9Myؼ2E8q a_c`ކC]b^\Ujqbx.&Zbp`v%Uj!v_W-!En]vc|\>ƭ" \T6 ŢM Eq[d"k/-"_"C0\[kDYϛANcULmٮ,WrOHizXܡ !MI~wY[ SybiNʐO 4!7;>{ahmhPc}wg[chm:I~0[50VެُOqF91uF(Da=wר8M$A'+ 1 bx:/y)},bBA4_ t8Η|;_̅H?Ǿ.(?(Lm1]|0OWro:-.\F}?պ7 Q0*}>_.v@:^!EFcXQkIJDU.͒- (V ^(T/Hyaiԋ7R^X ^㕋~!V˲~,eݷF a/Hya׋7z=[, kqFf }"_㩙PL ͙ جMthhEN&`Ą~eP'SvK(m!"D҂Ӊ BET/#-1u )%Q2A$1t8Qvq #NIʼn)̧'f +RW |-L8C01׆S-]$q#$QqDiaDXAĚD!b)-;4CD|e1_L.6IrؠsbBܶ,ZX=jrG<^p"Kf.+^MSTxcp$:9p޲, tq2}Feq^݇^}@{/M &N %nD8*( -~P'4G]G )]Q!" %F j Hp'j |P I"e$DMk0 kqa^Q@LGhɜ^ȇRrftVJ0ܟES3gx90k Srnbrh=9NM9?__G&|;+WQ6+xwt=qdE)G; H?'(Ċ;ޒ{WQQ6+xwt=Ad킎(G : H ?'(mP=G=gSƒ)Lֱ%&kCHвmE'<)C|P.) R?qxY3 ascqǢ% < ix?HQmXMlxx~+U6l2~i^")Kiq(rR^r**JsXͫiy"6T)#]/MzcE\HoMlr϶`+;p?aB{ q(Bh[dv^.˅J;9_ &sggpu,e)Όǩdp;,7('PHN]8CNkzjMNd?])*ZU;Sa<-xX\Ӥ\K_k'Hʘ/ 8:?OƂ@sxrO~4 "s:z$yG]eYNOY[YVofӚƸQ[k\-m' kAѺ]yuVǪZa-.M# ]WGluQ-9FMucLDE8\nz0z8ޢ)S< #+ݔ/Yq`rywݼ^&bZMe~ыo+ߜ[H3OWZE40`Y~2)'|Z))E2aƒ~Ռ3%I =a?!9r# ~wz /Ql/ l9ʲsoN7}~d_`\Y$m1;-wUs6Uu2WSH385֝tPq 2A`{ξf23%yW d0f4L#F$gm] WK&gVL#mOP7٬,mXp8qv` Qb=$UhO~ͳC{[me-3Tge%kS^lĻ%8dZȑe\5}ZN:i8ND?qfZ,^^|Wo1,]ZQCcn?QMz'NCzdgIiDi_Dip;$ y7 sny8Sm̙#t\^qOF~" E9iH,'!v*XN /+b[,̈7TGպN>k(0d#y`y'L}H-}T*yB'Oh[I)tDv ca{Fbyr#mdP@hE5a !^C[@nL'? wFQ@q%L! ]Q6x뗲OG)~bGl# `Q6xOvͥP;E,1kkY~bq ?5GlJ0M@->]0M.]0K4IzȤZFw &_zYS`m@P+|=2sD;E,5N/H,Y;E,Mw\~Ů.u\-H ^]$7զ,6T)Ĝ^׷9 ~ծYv ?1?Wk4b7_k+]$l6z.aI15ed:%QnVH@zڂEP!Ųدx^BAKTcUfEqE97i5{c̱V$ѿfTQa=)1"#c&7gF,Vm.+9By݆47>(ǹ&wHbI?u[S~4og;5 Q9i}C1Dž=\T}J -͖p+KxAK}&OkdBȐɅYqWBl0:PF/rч0$^ܼ`uZ/g͙d8|rY doS_`ˢw Ujat%PystZa"%Zfy2#g˞ :{%4.у^Iһ,dsFꝙLF(/5Lj]A V%5K_d5)Vsx2PfTgr6Qlvv3 f|}afx##)I &[7XVlάd ٜrJ/x.Cr>L,e| 6Y0.Fqto,|5jg0V}񊻁֛?aї=h1B^,yޱ{:FO*ľLV9VIV6pž_ XMqV$Ulڙki6ʝk~^Gv<:oܩkߒW0=y߬[umcyv5yS/w<*߮67df]GOmi󨼑(-O59홀׾Febn6ǫέ56rڰw|r :&&Tyoju^M ): l_ *){~Q:cwM?uKvj׿UvǫJ3BI.Pi|는fWED,-ytm3p|*x.XO%sö o&~P$/ UMWµ$3&+zQM9Qe{]7I29zh!դw)>RMyNR]7]GNR~`{;e% fxegxJ]+N("CXYўyRScܥITYwMw{TS\o)ZtCRMfKuu5 NSM% $˺M557B-k;_9M5u_X"=76TnTZ]ބ &ۇz$AZD_܃hmz&L{u-햛n=]JiQJZ|s9o#o}M}$qލWvn4qRK?\SE.ϕ|θw˵2$s+%vgeZUhm=F珂{9duHtp|ev^KQڑ~y]VlKIwy)R5qώd_-V=j-H/Og$7wR_7Rk:Gj~5Ha\n=H~-oCͣr7D*j!qyD.|C 1ݗD<6\F1G M&jQbR9+0(@\m](,w3*6 =,|^bvS&pxa:t RG3=DHOvss*a@5&xy2+^#QT a{j LT`G(X6Ly<*(:jgfx{u0ֳVkbzh.ec g*T/j&]$S@d)cqq"^ WREQH/;i92("`OQ jGLr}w[[1?#; YWQ#0u?6#EцI(u8_л!݌L@^xSR1!(\>@7RѪQiwik`6)ʧP#L'R=$D6xa,`(,{\6^΂rQm"n&ߌ_uqxPyG^L &(j3nLlvcJ=2?1(Ӛ<> Tbò&+:,*5BzdXY %)/j8)m`^+17\2$kttOd=8uZ;"E)]fgZUc2Z8#28pd,ecXw^,*B<%x G ed_h@'򥾌%K,6S@p4l('8=b{FܕhD" Y$(zHf]>„v89 Yc u2آ=sAN{FeL B^,4:>V(RL5fHrBfT=g8Bi=%3=ŧ#aiO܆Pn Ki7,Quҍ"s~5;AFoi2{,"JRG[L泓A;uy|r@ϧ!QJa:+ LLOz\LÃScЎy1S7 ,$s#4<{v#|d`ܣr? )!ދrQ>^ԫٸC^ 4־Ie%DrYy} WcbQ۶[,{|6Cb ]˵zc_Gƾmc(ݑ{pcE Dn ^Czӳ$92 ()"eآưTgS`.4}h?Y6tx>4FoVeI2J'>]Ǭ݀-?g2d5J %x>L} /oHQY8rVT:= ,HorT;6s3^8"^b$bvr^Hx])Q ,EU):  E]r6}鹅ko/آ@O7J3X!jGe/KouhMEb}(1mZf4e-c B`<θ &ι Y 2F`32ȽJ2m3- ~Km YK+IAS"K1E2TdQ= X|F5 GFQ)#tT=HE#zCCb !.g X&`U1s^8/&b#L.qt7($W1pN tt6NPAd)`恹A 0D9RI͒Υ !DJ::o ki=lROW&-m*ҸT+(?%7u hHߒ!68GЉ7 ǺzT *aBFeY)tdEnKUf B$v{ Kjor_5]֚YŢ9]+%bӪX!~WΜhTN6Ū C!7bz,DVX:_RJCz&kX[$@kC7;vFsaC (c/Ӑ{ O> D"އQN!mC% QD/xVR{`^[+)JJ;-ai17ĢF.Fa5\~z}yL'jU]~h+(Xk{cxB If#=IOh4,_]obYJc1^n ]oX2^`~$*[/VOIulݴ:Xj:EZ9,xOs&K_Mb@)[OSʖ3A6z1ن߈ ʹ!!uN8(qnj{xs)ovӺ 3WX-򻪏$U@\B&r3X/ɥhH^~9\@R &R2BB6&8S.1R[H)8,;kl-:7tgLGvEMarنR`8ncEk%\ro:C ).=MIqP>e`)_ymӲpnW䅟ɃS#Ų,fGqXХsU>k*U}aQQnESo,֝rs!ŝ-َ _MX^U%)EuZ`\DO\Y9<U͠x:/yk1̮sƣ>  mwoLNԳ[b5xM4ug)qi}qlьٙaw0޴Up)C60vC裐v_ jkχӶ"l.SlDpO>1,z@B<#08B KGbzJp>;pIoʐ)E(_1ưcCڞl{Ԝ>+iS¼h,]a{a$RPJH GygL5& /oAONCHc߃u=/yMtZ<\{eu8"`ƕxJM;E[8"(BzDZz5NX9`8|)>'\5e'?6ՄCw崝-ōC|ǡtS9_b(EQuen\։Y`AZ)&w_, ޺&k?8sUyֽwVph(<fHII+yN%[\UX_iKnQ}$>'ErT À,0笚c_ٝ&yMc[2Jr֓>13o6q K_B<w9AɊ`3.|N\ N 70ס깥[1W7]p!Ϻ08KELƖRBdMHnpCntGwlI!^9EJVKP=`ɛ9#f@(ㆿl~Ջ2gfKi4??mהNRs9t}EezS5uT`[Dȣ&!{oЕ㿺8m@ ;ѝ% +Vbhv;M=-nJR:Gj+a,y:%,:L+U yXd[ҟ7|&.b[龎 حGRwߵ5@ L:b(%,F(^Y ⡼21cx+AMs0ՠ6wȳN`2hxrgP^Lu$H֋@Aߝ\:2N};:{qr>bQjtha^# f>XWp\v#twjYVK%~vrp~qf) 崨BVad>(lSX\Y'p}ގFw*Ev:⩴jaM{{`]n ¿`b?%v]. Hy*Hܼwy~g8.?!+P<7xfbiIP;ɻhnid^*icQ߯*̽nvKxm̘sU#ŝu=nEL)T\̐Z2ٞ2}`[YR(3fnXUβvY dXcI'gH:'`[MZi3#yJn9sKjGbz>g E^u7 kf1 <K x1DI^-m1=cHMMR& oE/\^BغTꢶSxlKB Ř뿬J{rN6f(ȷu ?S'xW`NF|=8Nfzrn.fu>c@" 53՜3f64:Uq "NG)n| okWwƖ;]åɫw9J~xߞ;U#c?zzS.gN,U =YGxM|sW^%a HN̥nb+Qk7=hmtUxJioSɣsvx-IZ4jߤź (&GȷdfMvaw* P _ӧN+S!cWtWqp=E~E eqxK^総qZIvUXGP!9F˱GnbM;j_+w~vWV h_WSsA:nf,kL16{n}4_Ͱ+f/ll.iݔrZM"@^,&U 6ONb>bu-[}4UZf/km5 t }Dnou^z]uݰ4Ǫǚh?lc8bUU0 Q\GY+{ skDSE%4DzC9%6T\2 'X6Eܽf15:(AtnL_~[ }<ިtP`Ǘ9_c(tc0DÛ@ԣxٟ?`~Lǁ s@*v͗#v$(6T`gl2|6yԮO!ڙ h]qTV/؇|ookm㠮1 .23hQG4lyT$ =>sCr1NfދUL#n~4prway[ⓟ)~gki,af:ČQá0exrX߷P:L7Oƚ :E :"u0pj50g|j=] il *ks@ &.6f[V#/ʌu I"-}phq]OxHS>cXap^մn^o 0T<{??x۴^t *|P7U1?_uBm>/٬QC$qLNȘ`8 n;~ΑJZ5nɲ5 f6%\5s3̮k#׎qUFzq{vzFf~ "ɶKud娸\ws<7\Y KkoL($pOPGsyWzh"&9Ď==R)W3t>aq%7fmC] RY rzjogCJ wTl%nft\I[ۺv½ŽoͶvkl =btv$]jd47$ڎl+k:5_Fz:"(z)EcfYL`.B5 /k$&sj<@۸" UuT|whZTM9[^r/q=3!p^XeO5:BeS$!/SbbڤBeE]O&!WmqyK|抇䠘LlɐiGŐvKWU **J"-6N7lu E+ˉMb d8eW`h>M)ZuΧueT0-gS(mK=UMƩU{54`V/[2`]MˆӦLUyZ,'B5]q #-A@q^|7͏ZH>V츞NFD?=Z-#pg6. T+˨>[_ ޖ`.1}~˺ۙK `$Hv ho %g@1&22fZKשvŁJct> `HL.:Z|ǁ5fَ?kKqg-cNeR|,/M( /#*Y9(2CyLBS͇bu`Uq(P\T kENh ~Yi(8tZ3U$EG('HMz[庂[WV !zQ3Q<(.F5Ps}js;4Aˬ)yI5d=@=lmmUh[e-Q4VC*8я)j2%ۘLi?L$3MSA פظB4z5\OLj٘k eӬsTo_'_4ㄧZi6c )^wـNr#*;6k8k*b1 owwo˷_DooϾn:#eYiL|o{+ދe+zv-~gP2_oGg'ǃݏ>}FBOzԷދgmekn9, Qw)S/wl&_ aRz08g,kB {7`]QΖ5)=:CsŢ^@ LV܁hWVAA@0I P/ýыWumgl]^9>=;aT| DymyDdI:,?,'slZ2xH 1aftw|~ =Bi)Q{@T .ZqB[1B;dߔ D?8l +~F+ۃ R6Q_ /.bEl^Y *O'Vu!ͬjh zPdj7 xΧh/.̗V6Tuȃ_?ŀv otBض"SEui+,VTzk"m"\G7oNOF|WC*_EqsWښT ? =-rj[1]_/ʻTUKTNΎd6nDU3UKpB4UYx1:3$ 奞Ln;.HHwW2.# tLGw*ud_pÃaJtIم'~.s (H@ :ػ8^|=ڏ 6TW9yr]6"R.7V7Se2=T۟gy󃿍./.H!DXր臐Tj_>+BU)Eq6%OޙMaRԯ'ϟ,& ,.X^4>nhZJR` 2ĤM6"i9Bҷ, t-5CVϿ$I 6:(*W qV FH1a؞}ٮ5k~'{4^, Vrՠ2#qrίWEU'zMn#0{ʀ` fqb^K[܌xîF9~otLbfz&+/Z6)`zbNS܊\czf&>Q( lHu!)ٰie&x8?{s|qpdu|.s\TQqyw4s/sTzA7'#is=ybw Ǎxh?b;zh! $گ7tqֆ;Y1l"èRrRΙmzR K?@$^W$fe9 !*/9 r 'f):lJB,3FS(%wbgKYo{W=0[Zݦ51b"ue@v@?hś/ż'(dl7ZnaGYvwYwG y9;<<F{gS)lo@9D5l$❏t UǬ`DKNW ])Ex) P e#E{pa}cف0Ŏ9Q̤M,S-d/sL`RgK4#8r4'f$Y/" ͫʪIaqV3TbڈVك/E.`fKn!~? #kӧO?:r2*itGVW\YQ`bsu= e<S3'^@}lpr-Z4&;/8qN Z\>U/ThX @Eẅt L^l!*\BLc) ֮w'/ IFN}IC_Q?Xv\M,/vobLĺۍnHN2޽cF9ѪApG(&'j=lھv{%^@s~h~!m֋b9 8&C57XJ֤^]s{%LN ^BŠ.;M}ų/l.rJ+֧4PDJU,٤mTb ]iJ8ʳn < TՈdB. DSu&x># gl(1ViFo=a2g:e;Zh:ac#/QO >1+i|袵e4Co^6%QS϶_ 0:nӸ{O#Jz{jM̗dLHau'oS<4[1AJ `Ʃ\V(yxptpl?uۤ(ʣy/*^h|5&JI@Sm-丼ࣽs"[<9TJW`S[]1G;*3_&srgS?'Јf€WACt۱jKE6kXfd$"RdZ2!BKqbE:fZ~9e9o;C$ϧ;dWNTrL9SNDd(8.y8SEՉ":ja2:DIQyE uR0'мԣ[ϷI![1k\^Sؚ۟羬mbR1V#l9(?A4zghRW:D$(R؅mnVV{"';#mM"c%eBH7J;G87|(AÔtKiVǃuGٗB4 @ϷT$pg1e-21ީuמt_›?wI uf 1XK:4HSܮ䚶 h_~D~,=n:*t?}tȒSyfi5{L!+0JD[gT6H/[_x E6Z%DPD 8rETc^DQK(pRd Bn Z{Q2B%W4.[/I׸BBdeB?VЦe#KOL>$GNia6"0\Ey)2EHU̢6ma܋U JNP-6ÂOá0uz#v|a,NAύ};140-Jg9|9l ޟ[ϟEO XڶΔ~-#9)qܹMT3%2\l~|!7vH3 [(h~'/v>&q{q+ aL? G`}nn@֖H*FdY ŶE~N#dpʣԶ'Nv(ē9-RK JNlK5z&N~/Kmk x3u%Ijmȡ׆+>+U p2.&_W8(? ̮n E'*yBQe,i]n -_%59 7+wd%-KS#Ӈd&TmA+:nCv ȴo`I;BbeIT;/= qzxTq81'/'HO*ã5lA!&_f/+)uR2?W<$g G;΋h,> UF!wi," R_f{H7_о@n=QT,q\Η֠/K~L{<\5B]jN2d?ېߡ4YA\ՙUfk $SۯAI%|aZ>舰7]yW/Ȱ{I4{Y~,$<%I+`_8l?S4sJ WZ M)4z;JRtņOQqt@tLtTTzddWzև'J[jm]KXTxː4P|*c3c 1KÚrVMbnA>\WtN(CȄJ٠UAS :EOD܊)Huʡp>,ͱ+A^91knVbuSOR#IK{Ns+͡8 DȦvJft9Df$ m0bNb0Ȧq`?њސ6hG3Hj:oWf\*UI Fnƥr[XC f%P=KYeǎF4,eY/U'7QK~~TS~&>7[}!(HeliDm'Ur @'M_]*3qCЫ9BjZ9/aҢ2;dԓi}34H/qM2/b)Yw%5,4F}Diq`REC]Xߜ'Ϧ?<3$+ 9Yհ;Ock g ʞ2ebe.CG&Y0V.+ 1`" c`+E >Jނ$tewc1h2Ńo<ɗQyv7 .ol~W_w,mTEcLttrȠI2wߠ\)"n ;UDmBƔpިuŕE5i%Y>]#_uT_Kap W`B>#ll%J[=ԇVӧɗr -;.;VUFu_R)$qg[q^PFi(B;=9Q4*Fǡ&E %4VgUmSiv{Bަv ¶Ӷ}>DINմӧ|Τ>P[C>U~Vsƞyhta)sEtˠYv_Ǥ[ƨ>7TxC|eduAth D Sf w|q?8:_;uąt_ɾ7NpҹSG@w^?$ppTӅJ.ࣗӓ`pB̪_,SDPasP~ oy}O!]A"qp[#qIamZS_;]y{Kɢ'k ߝj;|etS r*+ Tn!qB֠ۛ7e~A ij?$mݏ~Bv^*0 oK1j/LJ0u߾:yݢnÕiA"+TM.yaCgMBȒZ>#TޝHoT~s6= tW9.A*0v uJүB_Q@-bگZqg_?_J! -!Ζ P HflY5)K)]gCSH11zF@/^_ȵºݤ螴26 vѣu~ۃJx9 N?6d% K2C:ب $}76;eI 6;l4lx>zɸZ>9̆-K/eLw-b] Rvàu)p|OjGtmz0&󚩆1LM]wWhӃTZ/і\h?G cVFoֶ"J]Yڌ@Oh/L}`!$V:+8HS xd&ķu;~S1q\dHfx~d,4 2-۝#gU;zJW[Gkz҂AgčB8+ko9]޾L.FZњ؃\8tv1O.JR'#F%@BEyL&h=S-[ͅfCR9ܠR"Ә7]e˒D& ']:զSYQq:pe_{YA.]mu +e490<c]q1p P;&e+bP,C\ywH@Doyn w$QpYj2-CK;?Crl-}!vzQbVR6=sAͻ۬{)Tg/j.X)WPXKwC@:hFhdY!sM9.$s" v^TnP-^%q߽TdQU#fU/c82@Ivʇ #T?݉7)Or77#nm0|ps,bJ*? 2mBx.jz$ӬjBn x$8]HՅ#KUM/`mj;[Ԡ=4t{XWMO$>bgM0 =ocƑ Fu’FaN&cJ]7,=Izߒ.' frxU%'*n`Kd4F zic!aR~[giD-\*}z0}qpC')R z8 Vq`j @DlW9l%(9xi^T?܈w6,]w{0h:G<(:u6Ɨ '98Z$KpseŖI^6 4%mL3] #^H*n)ņa(׌hCWEMl/`ٕ vl7#ÑIp34$|+|7v3F~0pβؿL p%ZVeȴ}܍l=2鎑 2AW<X;Ӆ0L*dI_RBiV+I H NJRIeN`۰91*2oY;܅ɁN5Ee^eͼzαIٜt/2H&K ea,uL!-l6}M5S 6{n4ўM3U?-AՆdAJ}'}!,<֛,AhQtwhƙ_>8Z=[ 'cJ2 U(xTUTtߒRg]0^"kZ޼/&OT5,'9j\A'd-dIc˱eV!0 !L fBh:5BP!y,/'O$I, /*NIy$X2')`O%0mqYa)Thß! tJ1ICaї M@Am7,B 1YXN"uCeaY-m(Ipw3c9  |[O!>8H?Հ e mm"3F_wOe+c ڮ<nq`USwWoMO uU1(YMȜr| F0lu(q.uPw1GYY9t/C"tow^͏1>D+mKȸDAܷ1vov }[SH+7"4XF-'*O>:'s@1ӯ(Fuz=elv&?9\uX@IEM #7ZJyt/>;34&4, .V2Ǡ; 44PKFy5&l;8WkA0p 'Gi.Z "Ew~R/ѯcV4xt5;|{vME=mSR{oyW;anT‚!o@ZC=+9)X,?959(4(Ӿ\FD_2NyB*)bDT*J̜GeR}Bra@]; UA e6O~]%N(™ {/Am&Y=Hsv46c[#n5-{L% K:Jcv+"8˼M 4rӛJ #yS]oGmlyf.:OϾ9t0V1b^f<92%fT5ynۤu =Q Uv~^<[X@O+Rd,e+93}4 gdmVnϱl~N)խdZ+p4nQl&C)jjH[ s`"|+Izqf0iK:tdfSOyW71nb,S(eb.IMDH:rW"ڴnk2tQVS(YdN)b)fe]E /:-d7(^[TRRHmr)r"4ϋp/3Wwɸ QQf,p{;z+!3CC>+[!ňʹ̺0qVx:ҷ+3$Wl9(b $@bpOBrZi5"i!-d4*JF~}" G-QxND4*˽W;ڲ9W2UJ dH\G'ZU!IS iQ't0/v' ͙}_M#dE@1'k({cڈr8﹇;_pFT.4`twͳKē!(1Or]Ɠyh L*Z! 5Y;^P ~ | ș^(/5]R,:pqTH\1_N {v:ЪR>;}WEliIܒ1*2(m@##IQ2D"ɏG?RӧF=j@uP߼=oG?oL݃Gj#Zn VN9T#rwE*XCvFt4N!i$'2&VDHb :\yGnKq+S&̆T]BۓC#dz6B>n# V{Сo}&>>dV9yk)=3b6YRG 5K-+ʄ~O`-HVL+k|Oc91- wQ="B"~?qc"ɹ(BYW4T$OJdSZ.nn#rHfi64grLW^H:{r0+"M;6. *pe/vvrAx^gVINuy6"p]K/B<8$U6·DO`)+iee2@&[zEvvHS y6vՂ+ɛQ8۩L5T:2Q3B%航.Az9i0&Gmy= !1)@iP_T-CI%<Ò`XBH*$41Ҕ]/U߱P$x&Yi'pSӇV\A Xb4︗ѐmݬ! L2ozIs:_j*ԣI ul ev\lac68V\ȊNA;9KXXՂ&tQI|b_UPv+i4%kݦ,Ϯ޴-<ԧhn+EywRJ!ZZ<_n^V'z1홃CEpx+ `K76=LL*"8')3I>m/7uJ\I<%C!tRohaPKRsmQN8y&H(a9dJo3*| [% h͉cNT>dChtwh֒CO:\=dŨ5i ~(j! 6&5Z*˚WO~i1/s+Ozb)PWL]P\}C'IMModҟד m\Avb9(tm]?!ŵ1+9Yz~GiHTze~@]l!SР"? XtlԪ6PaH1`M{w&G.-1#5=e?!![ih68;6Ͳ4HIf fH $PHH%c$MT ,Ji.w$32p"E#_.- ?9iˁbxk)QFZ`XvN&\|Żv7եXI/ݕiDW@b!>wr9=8𰋗XefXϞOTŚ%iQ7_?&>jUn0W &UW~99~rt}ΝUSL Q>SCR<)&Qxb0r9ɧ$bn!o/d|ߘڞD6W8K=D׉)rڑF'tӚ}'D`Bk?;@B=X17]j!F2C"QC+&1gSր5oHAU= S XM_k%/B/2끏+B8'wdh{JƓlJ1ˀ 9,=Iu L*W~xKɻ  5Au1sq}!Kkt5`z킢_ l!T Xwq_cCX$53iSi]:*Z{ޛ: ڠЏ@O[1"LrARe@8{t[2݆ Wn1?éIFZ׶z\^J4JmVVP,.C=vB.vf IM/mz'>Z4E86ExBf4LJI c֊y3(Lgf) ߄Y]i)/n_C12h,T9 uۂQV -sۛŎ^ C ufCo>l Jjk4b/*th/BLq@F`[&9U0D^su1<2<#1AqevҘJߗi .J $|jpAy'Jh⸌&//۪>ۯ,ݯ!M$JxC"TTpq4duBC*a'?cM //+~4Es.7}v3ީZV˲u܈qDh3[X3]b-A\`8Tq>D72xگS9-z->|~0}@u>ba5I`ntx/M5IUrhr׽(x4lx:;0?W"ϳkUYX+鋵3'wq8[)Oe%qӸ{5!땠(H`^,-F\?<|~t7/eG>E7%TWGWhJ `c=Ɩ8\+4^eVtm/7|lM 49}v_:P&8 M{tD]o+Fhby{ 1-~ /K/_Ys[:;x U^ٝ4+߳zo04!Q >J'Jg-\qjJY.% AEc~it+Wt(Rԇ\đWD?Srqo#׎o&,U,vy%kZvDgH[¤Z+ٝ$F=tEW*a3쏦|p6yKEqC4`3t#F3LmQD~0)5~ -궏^=I^>|8I hGr.,"iGH?uX Jp'6(VqWDVb#~Y>^ S0 ) v_,)i@{CƮBۖU_Bܝ11hj1$RXK8 \ޗO.6lrsj鈪5Fq9Y6 [>U)KE/ .S=ƏC\ ?.WpLRz1h)<0p^Urq$pK=1Q>ڪtG\0kLf'F˃pz'-㱲?jz.0RHt(!(G`C`'&4!P|1`KV= x̢0Ijh:fMh NXKos/*lcTsf,%;DpW_4WQMrrDs dG슀& ٕgE?deMM@CsE7ۭ0Ti!!D;: `iN: 2KvYV_@\PA,$W)pZl ؚD-,?NƞǥlOk~su7^ o㽃^;D7&̖`&m8^Zmtz' f N+9x8Z n,ia&V(DA}!MV\!?ߔLqO悲9 r֢IR &-ۑi9`$1CrŸdԜ\Ȭ^?:#jn6-L1g%וX>N+&y=G?`lWYCoΰM7F}ƍcXA;d&6mYM8jg?:h!z1u#FenX&_7G„? QFvM;+Y۬ɺ5@\WR9p*Xmhnǝ:ݴ= V6/ pOvFΑ#m#dqlď"xT|Χ9r'furp,.} -s`8f3BBrd' %7ux_TadTIiJ_~^?'vVJT| 6D0n-Dw`{3]1KmjYhAoX* E˽vR2f/HG"ߘݧ57cV53ȁ/%Ͻ2d0pt UʹA5Ko)r/&qQ*kLIvQH!Cgix]o]E$l6cדT&t#+Pg\..(փ͈W5҄RZ%=18SN=rIMP|lm #K|l*&8!qmVKFgV);6Mj $U%@V>BƬKZd,Iww޶쥇;oԁ\f9x,ÉV^b!vt$g㴛O04> vzZgUL P!8XCK (Ⱥ0ɘfDX "4S@OAA?Y<"3NiM-jxA* ʗ 9aXxgb '|%KSc?am7(V]@I(QC 6FJq,GuoAdְkco;wM=vǎIi`1,7HnW4-y7{JEg7K3Ro7EFcA,[|楀b~~JTR. @iM~Gv~|DnՂ|C8sݜZ(]% fQِ]oemX*n>cCi^ XI\8Zj@\GU6Vhb+^ɡW=L[^u6grD,Y6 ԑ<ǿ$C825Y^TsM#JkHΉV,~^$XV]p\,k+[Q'ζZY>w~So@д;W{Y!;'`)(ͯESx+-*F;OUޓemȾ^7!Mx_iNY:,4 !o%}t.cmJc8wحb,ϖ@C76Oh&+ݪFZ6|wɕᥱsbjo\2@;vD0?p</]3_ CͮGGQq?ҕWR"%)E0㵆ECf/rcDV>ۤN?CS$'yBReD `k ?/JZf%EǬ2.3e$"A]fs%)i:T=*]Jܕm 3tp&P⛹eW.ED6H8q=hXAJ~AtXQ`ُe ,~Hٙ|_ވ*ke;b(!/X0urɅ[X]kup垖&AhޯЩC+2LBJ`3;;)FtrM/Pj~ γኻݷX:R*|.!VH5|E-╿T·3 {C$īGKT ^l[Hވ,n>Lj6"˸;zU|LF+ :V@ZEVeFJd=+Gs:kDV[oa6# 8Y@V yu~AYlrڑ#'fڌپ8/fd K܂c3?տAgx6u[|d7&X@]ndECpDVS رڑ]2"/+GSTB@G2fd,ϖʪ(,a&/#Kw&/#wO꾌b8ZDXga_Fmg< YR|M _F_ede%Dq6yrda2QV [_EW\\^UdYmӪYCUduq<_Eo;Ud[aUd[!PbM>OV2Z00_GV U9-He׌$ tYN3iwYN-$ u[Jy+H: Y؍,kl\mwgYֿ,h; ׍{#A60u"OΙo"+;gC@{5,t[zd=>Md(xx|YȡmlqME?%^k\E8~c|zGmاE.V :li!9*q[jo!&9*Nyٍ|L??jN?TLV#x1hn_@IF|wj4Bw4 6mrG9GD͉-U\6J 驍WnW%O6AVm#8 K_6  reaJhYlP8c';**HĶKNН?2S&iK\\V}(ay%ڑb={TaBD^e*ٙԌQ6Ī,4ޒ-㞚 e3]f.m_Y\!ݔ`7lWzt{(KCW# +0"pNqن!%ȃgF$gC"zv.d3k[7#v_Af]й 5*MnYV 6qBo +˽>+n;6#ϑVd3>)] ^]y[+膂<"[+penF^^13Bx@S=f3:bn0 O'U.Sd0lz:yUm=e(]Ԩj_emX2&ܲbq(](ݬyY} Y/صq祽×ͨGZG2ɡ,ՍSџ6AgI۰+NqVBgJGARY~|i/KN'h G xަɆn3 'TrErLЁӥB]'IL<1lFFb' Ywc@Wf/'O. J[x*Th?  :V[ IN`C6F(2yw0a#'a~x5M IthLN;k5#S%fnoT詌V$j],gf|)nCR K\Q? RxK @v?(5Eyp'OIVQꗃ>jOP _r/xL*<&Y^քd2n"[@x4}}n7M! Tq6!L,xxt<}|5LO:aS@f`w@j-!e>/I> NLIXR{XJ"1[b;xB?[q\'@O!qARB؛:%&!-էM_0 U>v !(e\f̐in ^8`MOr>z$i@*2ư8rEE5X? {\TO\.G@LF3b fhO\W™ HcȌ:KܕfJjd$rчqFC%ٌ,Vz3f]f£gL#wX/YLx'cxR@}{jwB1e:v1KNayBV&'_jSó2 tJs>4 &v:iʀ>^CCFT$IȤiZRN UC%G[o*9^9^hR;nb)JfdtNYAbh.'ς&!iE_t&Tfwngl\S7Eh~\ט^m k\OP_]8n&]ߘV;SOER:BI7kd 4'өFl@JmꘪV:%{/ԓ;aE; uڀL/.:38:aݞ=0fn1Q;T_&v}]T$PQG%LtsH!þTLE5Z&4⁓,s9=Qt7lתrY_㑧b3b1wS`FL6: R秹[}XBl9G̷.NdEcqQwbGNS\)'Е[M :EpS7]vT5 0@dRAuԨ,zt*\ȵ5P|ꉐc_?߮;8˫Ӊ>j)in~Nl6ꨪ8tpNGK7ߊ$ N.)BG?~Z1Pt]",Q8z&  camqaпO ~Ư)QzI2L=%DbeB4( )&Ud5ـ| ;4MXG;=M+=5%wԳ&xLspVU:t~T */_}z#q.Z#bdb;$O& ½9l/_C%6) MRV_x"O<8T]=c#۷DW٬aDjonas3L^J'%v+YaZev yS9ZKN2ȄL$N%IF dwg0ҫFyXŕLtæDⲾD\42gB&-7mlVFUjJ|[M/8bW>ʻh⒟%| އ4`v)l)aRR|z{q !5=O6+4%%(l$N+V_WQ_VKw_*_&?E޿;4O4ֶ8Vٻ/hs7+80i;V TMD!pzEo7;xF{fpWRoU57O5_@C#맑stʒqmr-3/3D+ٝ(V|C qr~a"S<<ڮ!Ėt3cwV37&)6cɳ{Ivs;I!W,qC4|nJW`S}p;5,(X ='>p8[3E?_:9y'G9> A!6RU؜,i6E؁Y 3/1=T #PP hT`0hr0DP\ } %Ic9tz,ֺVGέ?K+Z=ڠ @8@>ie]pyA̖X$5 h-BM4@k#LMBu&YYd=v~ز-8ZZs69g%6)6z C* Ӌ rZnNv3iSjiJ]IvJIRst'hI+ Ss~(Y0 `[|6>H.f2affV^2CN!Z=OT[<`U`I4v{GNJq_P7Z'jD<> 4؎م]NаKE>Ɠΰwt5d4+&k/+=:{+D@9O雽7G^כ~aDَwյBƺ?ڛVm%6L F݃֡.H)ʯ"Ӿ(Cf!>0b+# KI G2R]٥ͽwaI3`U2bn*sqvá",E2^2M x`y؄$Q?FR:i}$PLDrg\-o'JQQGdY)W26sˊY%TeP)Z@\<{Mrh 6f~0+`/'){c8Y-ӓ$Oж%wy7߲`KO:[Gjlw7.)" g,fiֲzI̗A\%+L6bAZFx]Fn8|"2ϖn-ݪOCԋ (0\'Uhjc$b([y@-1vhx|36"s ;ICK ɉb6Eu}G/Ttw8 ":N;"@@]?lR6 %e'S/{6z|v[V5(j'O.MpvcqP5t5MI>8P~T3|Lܔ,=O5zv F'Z^})GT2%зϊgm^ h:"!n3Rb3uL7\ bb>, $ )a6@gUXT׬nFtgg#VC6AOz.!Ɏp!IS#AshQu-K`wFW?73sr)-S}j-'b{M%=.֨i0*(+)7.5vWk M\2hmK!gGW x֘sˆ;ZJ!g# ^CEAt\Z;-LtGmy4:/5IA /SIcɤ&|g3DVZep!LN% n [. :pCz9"W?QYxlkM;nL[v?@rhe?3ɲt z\/NX .rP>ĉ _x< “M0|LQQxSµ^ݬ$ZGDN@ҢͰG0]iƣ`QQLsejB>ק^E N'7y!eѽ1':E{G"{KbEF>:.:(&fAKw&+E)]L|_K{ObZ#ED !U{N$<3@Y/Qr y`dX] M-<]1/m zF3QFq7ppg4h9;8P7ءp„bsYhSЬ’ P#A.V7B@Q[\`\5%4SkK "F|0 ьQBmNr# FMGB%Wi jBP#Ev~]a猒P2j̦SϿIX51ZWܺ!BDfBk [Q_٥Υ2vn2DlNN2d7_FĀ4=`\"m E]2 UTm+{Ӊ65L?2X='IcpnYZJy䆢uȶj&2ᢻ}s@qvl~#!-SfmDz;HR}ҏQj׵sԆc6z3+I)y7$o}K']J}fE ?tyOP}=9:|p8b!y5Մe` &l$#u0?,:.E?Q\~'RO=RB&-@M nj`Hk}?ٮիL۵.-jsmP{@dhbمLU$b e6q=2t7^mAbQA7)$Kҫ}Ήgo71˟LVEil\(JP gq/6 pZA %H08)iŨT, |0{U Fd:k:«DgT/ P-,lxWֳBkZAT u6~|7uOq`vSJ~\tn'O:O! sO= dgkyG 9K=󝞠dii;̟IL9s(U.%3qe5S3[7&?,ą,iך(t6j_hw{?-&o ,Jne,Zg BpQZqe6b H{"HFZ&^_E!Y >WZ5^tC up ʟs'Fm'%gu)K޳DF+`iu*%՗]T[JOF!SQ [g'~>WC|D9W;G(Uh鏫ҾI(}LqxըCGQ*MC ZAB{uxT SP,^j0{5dg@*?UM_B-!l"ߓ,$zóBg,:2"a=,G#{28~Y-b|&4/9QS!ڮ2כPD(>eB? Vnj E- ,50CATR,=&RM1DgF"OL UWQQp#trt 2+g/IǍ{DuI'BBLQ3D3|L Y8 2883L'eq@H,X- F o7kEph/\ex\#Ӳۀf,l4ː%b bgR&qsݨA52iMX-ٷDc%.f" H|3dbCw.4>Pw£o OlR>= &6;2 7Pʺv efALf6d:S P_l˩qRw?kֱ?+La076X+_aܩ=,&j[tCw:;Eo&;?&2@Va1~WY论+ 9韄:&;(+ f79ݥļ"ߴ7-uw^3]0O|y (wE7=??ކQ5گW>mfK]O8U-vvdWڻZйyg9v;{jnU$ygxo0@5q =|>N1 G4u_̚RLdw4|Ӓ1УXz}mR,yrh3Y"qK'TBˇD60lRGtH'1,!ݕ73dsr?̽V=OS?KEZcL5o\R;xX r@;x:s% ,MEʻH| ])AӼ~~]`Jl''Tw!:vEr'Xf K.sgU҃FC_ ",wFV6>Ix:±Ehu"q;fKGDǝ }]?PzŚ/6;O"V1kW 8TwjYM`] zM7W,]ūG/6~`Ju𚅰_o6$iQ Eҥ"=4҄8Ww.I @ad9=`,"tWHp }_Ek7՝|u3 /˫H HOwFL'V}í.KW"ا{ų{Dڱ7nJf eD=?FŏED!Is'$BWeJ1 Z(^zL.4W"5u{hι^eӗ?Nb%H@~YyX;~KpH64o:#U3ms,kvyS =dSCm@!,HMy>lU0уJ5|5l1]X]U)C,1gQ ߖOgH^XdBXZTkn[wHpmhiD CiSݬm&N9nyٰ`!AE p c_!PѰ{hаDžѝҡ_៻$GBK2AsAS??{ݶ%+`9 3"9QOXˉw,B27)+*@(oWn4GVbng~? O?yN"r&‰Ahz,7 s/m$%?Ŏvw\ YR{üB$?.e4# o,/s)}$-Z@-qv߾ʙsZЊM0JOpڶ7Rw;*ϽORtwP.8C 7 ޥA~1{s4) KuC%)A`RdH9 Tt#OqcAq#K^`.$d߂oacEH|StŴ0шUOQV)/e+Fe'p;+F ,XL8[Md)ň{e1>?S 2a s\Dڏ6 MyM WFYˁ@4nϑAVigKGS]-wInRm[X޲\ i^ZɛaP~-(혫(+@f2`W(22Vnǡ2FA+ zGCNVsDh~Zg9|-%:~}ȘDX`wZVe ywuV A+,&, %Iÿ.[Rړd2t %t`;ˬk9GiSr3RLn$(׋uI| -*Ej/Y‴_/<0dC ޲?]6l LkDŽÀcڊ/ r)[3H!no!XA-,oY8'ey #@h ا_%l':4YOP>wa鿁_7.$^[ N=eNZ=V{Ҽ)tw9KN$鲓שi8Yo6 k?}cTiP6un"_{tuIW憮]Ɗ} [ͷu%|CNl*41,y-,gƏDgAyiFl&'$&J %tyK=QI-)⽓nn=Q|j1w!K|J[ %rhN0aEԗne]ũ`_n8>ɞJCw2oʛ?H6t(9d`xvX;0(۟Cc3oZ~K(̆*f_g?Vzc藸 =Ϸ7s ?m[O}e{1VF^gC M`ۨw3zqz=L9u7@՚diNB]Bۆ^[~6xeQɭ"Sn1|Kxꆃ ECMPTB9BGI)ueH@IGׅU Ej"MS^We4WHLapU,\ 5՞OOD.Fu!!3U"Fޟ[|'uwY;W|ȫvw1ww3 Sa2*\ -@s.!`t@^ P,f~Mi%C|; *%wU/ww[ äTa20 c)jg0e!#N1NvzGHceDdD.?/hp5hW~ Ɔ:f 95p"d0!z=CS4|Fp+4q7(o8S:Q40xqX!iؓ,8g/mL59=ʇ:>v2=>tosbhM~05Z ._#8w*IÍ˿016Ńl.(8 t@ic`ܱ6*myͦRRu}4h'iJM5g"WO!VRN"RH2OM@l-Zڎ0jm/եYOy-%JAe{v,K[;[5[kfX=}? qd)b 044_y.b eI>[ q,eBî `(1ƆU. ^ QEq!jlpj V@({5 Q"u'9 F^x'R2Rs>⡍K0jìXa(#,Y:&5+0 `t`Zb f`*fKT[ho[%ALW4t~ʿBWnh z"+ӎ%0XN.%{LAfqeֶ ZV2Cd TcT0F*\X1rTӧd϶2r2rКGT#) ;g< C{fyI6e ̢t2IYsvG*%Cg<#L4؃QZm,[Vw33bMha8s p#kK+NhbtrPij%W!4's`K\%-$ty):9´3leUj0;: N)/,IpKn(S,ZJaI‡2T\^ӭ}8*?=Am)) Lv>A BrULi)CU)u$dfA˶,YkdQG:e&^H 5L`g!,$4?gkqjB}\PRgIڴ&wj2MqNb+Į*JeT@% !|DC $MB  4Հ|0!%1U SRm~hV3SĔbWwdzG}֣xݧ8OH~O9ds=9)/#qӧ(Dx~K%/ 6zԇ>)R@1ޙ|(B b7wIWZ=w765J!krr%,TVa~9rO3*g1,_Y#ajbOĔ"\OK`Q#ugBTEN#1}RVss"Ԕr1IL(^ޠ1lroV ,vj3V`ق-$ j0!Z .)`g2]9@9EQQ?b]HN[& 1]9o1bQL1yKiy/3o92eS]D ߜ'ofƒ=V?UD^ YXNJQPi*OR7#2i '&n=4jJlSøP-ڡYXWd Sk `0i2VVtL$V1M(dM[~͠n Sm65[ҎzizՔ~8E'rM#4&ve]/5Vpd6aN'G [;lQ nVKsυkIJQɆsGg--mEJê~U{R5qz^3&;` /Qk=Լ 4P56Zyo[ug- ra1nc>+vC2Y6+Z%Pu<4*[\j\.Aʥ ?( \k[-+[!o@M>%?JV8VՐٳĭ@9&p3U+%\5vʃBŽY5'۞[x!M?֢4z0Nj(0q%nSǒslU2`Fiy4Vi=4ψ7Sd`{XRGն[R[vepO)ݍ-;ր;l  5-m]6 hnf>ham pgkK+btrPg%N\&זAįh!%sT'sdN+*CdɜXddNed-$s*;%s6hɖdiSp$SX1rTV9(ɜΡ5IdVJjd;ev*xdNOdNO,OndEh`b\&stY&sxX~pߡ3d{0J˳dҼ{9h;o℀yypQ*r2%z2ɜ2%s4dN WdwJk˒9XH4|"Mp̩4SX\0#䠈9m's]>Aoh!er0H'r0 Q `v?_}PX3_>ivtQqv> \2Go0[Ji؛)ތb(O a}X *#F쌂ǧGe`/[czd*>IV(Bc@%Owf,K>f7X5,Uead=LlR.|f 6T(RCŚ>iN{8s^ 7omUۻ~]%̞_oJެe>]pw7_CY"I%Y^ayn-;pn%_D߄x_b==%'5 &dEEW7 `~2?ÝH~R&`_* 6#5fܬ61#8MYvIp_cU|NIru}~?{t }ۣbJekqH~:xr?>O-S z7/I-fI?Ⱦ$'`#H7v;w7ex} %lr2P5[_M> .ʈnp^ey!~~ ESR }6>-#)뾹<~}AwwkENт2I_Τa߲^, i,JB e1@1B2{@ ?x FPc CV&U]/^ _Xj}O gfH$?"ԔAAlei# 0qa;,#11d_1b&# 0Њ7 ?}?d\|S)a&JM"yԤp7Bt9@x.= H+& ߖ/6d >C!Iow8 W}Thkn_^0P=Ao<;A 5_ {+"yI`A?m/Y*?|]~7!8Cr\`RrM* +6b}ZÂU`Sۀ?f(6bQz2!z;%7@7zh}PÖ >n{󢇜ddq4~(J|s>ɋ+'3mW~ W͖" XxZR(_`0w 8j&nt9\.6Hy_~18 9 "oϞ'^_x^UǦmr{} c_<\O 0j .o NOuX%$#y"Y%ov@Uzܿw!j?Xs,QR+K_UEnTgYD{1{˗X8WUf9>3aq&s`'|?~@cS_/N ],MVSRX>ArLS AkgJ:k\sYܭhK~mXOh,cadW}Oro| Zϝ-spetpUX^ ,w%?^~/:g8^%0x^P^(v9L}t},1ޟxa@uw&5Rp2vp[^hG βH>t$E?.4OPo'oK?ĄsG?9 ADRLaZ)؍{g\O Uyg/%`c#S:>A)WvOru@ylv/[%ܽ}_ř?!Po/!$x P7Nȱ@|L-p ,㕬ᕿ +bq nŇFSy5o1о >;5ٓ`(_]9u3T~mƮ9J1'qâ1ov0@{\J y\N௖Nd'H!R0(#A-d7[7~Σ)?#&]W~ NK&Sf_߿CG}z da(Rj|MT)~w!;.Ji^7ǽi?l+׼qßr+UY?˃1D\Y7ٵׇΤ]P=qk _o^k~aϠ*j7'lhDhi6uDܧ!&ulw5C1x 8.+X%QjonuTuko;5<Ȇ baqDm F{vϮY_fT0yYJ VkA>kK>>2;DG0a1 ڍ>xE67a >96| W09-F5E# 1cZ)a۴%4>M#-UCT/*{p6+e0H;5 S &Qy}Mҙg }{u \b/;Wg6Xb-5cώcZGyb'R/\UMvq|,y>S'sNmXJnvE Tl!w/} ZVm@9wo[ʺ,n3R٥|y 2@=,7q_h~|`?/DGA\3Vqh(] Slx+4 EIdZӏ*m(>N]?fE0B_R<'W0d75<=>s͝ݧ񖌫ؼX P ' 4Q UoI>ȼj`oFyQ>@ 'm]#Al'p |~Jނh_pHfeSy^]\6@ :'fL~7t:M_6'.18( O|<>  *.~98 ,!P9`=DazQ)r]Zfkv?RJ8'-375ydq>} m:sG6m y 3)Z,[Bۃ6pQρΜ{QN>/='G_.uK7e =bQ+\_WXF< CP=2zV1:Wz68LB[BG&q)|(Ep'CFM[em9sPEn껙w]]"ߊkar^׿~g\ ɘ_L~Yo>mW ϭƐ9bدӲ(>{m]ځ^`4솘JTH[O"|^WIK`k>JԁPt\H`>,f5Bt*Ǭx{ Zmܟp_|$I?].}Y,ɧ^ PwHE~XmE-H}w'@67@Zeiz?<<,yN=ȷϟ#@iUub?3)Y= nɟq(h<ꊽVD3>1*ٟ:=1'hpvhH1Ѱ~n|F[ T| [yN}3@`nr\屡N%(~8O`t$NH.D,C\C<lpirDe(ˬc;WnWXcu, [J^:Cvz .r({hz<{{QYX ǍR_^j޶} u:et~K'jwBNbnn\ ,ɓ+o<v̦^c'@`۶xP5 BF-IBToj}ps`A(o8E]~oh8\/B{\Bg ~,EIk SpzFѲMDB>0g/QAV,k@0!'?)Kl>ɓO&>52 pp\mEW;}OЎ5*  f؁7$ϩE'|hZOM{|7)-lm`}ЍR1+u((6R H驇&QFzds%ңsϤI^1X\ l7, oa{pK*@w[{~ wFmT-<"%rPVF91S1>&ʚǬbBΦ`ڪ2|m,:wu5  g"QzD7*>w;B EU(sUJhZS-$":4"$zҼE4/ OKB:${+X$\,`nLx:oul'3^s(г3Hr5%d_b)"+jt0ʸٜQ"&{Kf0LLK%vmWbh(4,@\t +D+4r>gj'gj}@+YWdyײ,L#\Lc4`?0<'#$d;<'->^4܃ğx߽T`Mn6gIFy}w~Et!ZޓUK"'ݡ']ՒN{vm{҅9&ky4ˁi7Is}y,]lW_.68{6+h.U&-СJTtAS2wh q 4KǛfǛoLj]A͠4¤s/_uƳ d+oK'=nNަsI 4}Z`LJ5k6nkQz-`DGȏqv:5b?3ozRlm*@OPd>@ޥ[?3Wl|M5 "kE~ڇH>qPtܛ̙8ɼFE^'NgFB7RF;'lJᄣ%@iJןj)KYŐ>|0+Au&g=Qه}4 }ž-dUSHnT@p nj1GI!|vwTtҴet΍0ϛġxt8C_GKdkJ$wqaw #NCLlŞ GyBwJ<25\A1LJ'>t"47۹r#KvM$ِ8X4'Q1$*sr\γXrcw,}Poħ lND'oIoQF]֧n(G.cϟ/U—TŖDTv.d*by !Et1M#KhE24V8iYM7eڥ\^8`!/h|a.% r'P٧-:74 aYWARR_aGBl51{Ox7< 'pBrқf2q\¹aC ;\ICPι{[5xf;~9*8d=b|9"ms&GGH?'64xC@49s;D1FQH2ɚA D[;Z[?9uf\#kIl%%콼E5ci֨гP(3BB \ A+SG/ӘP$s[%k\v ˆ&r?y\r] ǾPcp9D#˝B/rI?WWEc͕i׃GzOEGJJ "[t5n^3ÓstvRaف948GUyIyƬ)0F5κj+{jM#Eɒ >Լw35 9y1G18:YJ MfxFϚZq m]U;z?ع'N-qG`n s Q/ 3jaS~3i_1w9LC>4bCYObE^Yx0+hdEu);f]]1}hٰn5*4VFcygZ9ޘvK;_b>T!_eߑa_e"vTNyNm95OK> qDggk4 2:aL0Z6䭝됍^WF*GOY?iO~d^$ 3/iULy ke^B˩£ UG{ZhAC=j9fi)p"+t"-%hd7AC%hN4::e4Ja͞=Ϟ4(s}jOCIyU-!LBvH34xdf5k7|ou}gXPo<F=)9@O0hX>Ca@0^*!\ڴǜԙG%(>_]\DUJօ~uД`C kB_s_._z7(0U18ډ;J83v@aLU'z|~O!oZZbߋsֺ/6pz^~p6ovEu E=7@f-i&SӊFT |^1sFS np;XK$&3@II\c0$pndXf`HEI"D0lVO Eƴ SAKqLŌ.!ִ".**V[@/~c?Laف *YJd/hxyԑIVD2\eJ=Jk@МMi1xshhEhsk:)qkw" NaÍktw?|؄;ޣ{Ibg|(Ft$I2_5ec҄\M $tlD483{];o;jۑT!*Nnԧ~XmXEHK4o$Sb(/BK3VOPΆF3U>W.N. 2.˪l{uiK]Z*^qZvu( }f넶D5[d02AbgZ}gVU̴՗cІ\{*H*hz+ Cmmk1m3V3\Tn.  >o6+`kEۼ~65f\e7Z #(ۏ#x+3"g$l%ـ67/oSnZ 0SN!(X;TqjO(S+7|ȑ ,ccL'CӐrmYz@sXpRe fT`0ƿ%nGY{G sD@8^s <"]-|ٖ;"qx|+I7=,#&@vI%"Pqڏ9r}HvKXa>,$jD%=$[?cD, *~JaZJ#a#Kc':P~l8`J1Ag3/W~W3v_ 7cA]uYd_$_hB}e@*P(e1'Z06_6"{{3#^ft8`-,sE2 Z8۰(b&BewA"t5=ÑA%{Wn<:( N-x ԬC\)O+yЙ>B1= ВZ5Hx)m\to`LfLdK&+/5 YFu|˞M0b_<$8ruG-Ёʒ9= e} ~~?io_m@~P7@{ݵV΋vؐÂnyly;v o7$NcS|H;ba`̯_- *sn1 8 PCoMayFg5zA_c/s;##ɴ]uvכ:Δ:kԨV`JW \F1VI&fLOw.pZO2vg| R'Qvg"=]8 SHH: oE<9zA;4βc/8Bi_ɡCq:e[Yg;7ĵ&_hw0kCcYhoή۸}q0A]/A&?WD^;wIۇVDo6xf Xl̅:~0^r;{Jܖ offrZ [;htpg%:)vc"T4Ct(hߞ].m" kC3 kԬOr mCHl1*tgckG|hFSm ߻qh2?=+5RY ItΈ92ވ/*#.ؠi oOF /t;ED7[r8.SL{ֵN-O옃ݽcEhNc֪ITOyNP*]WZ|=hJLXgP\\Q㐍UrwuN;) w\NNJH(̉L ]xMH('{42f'zj`OkԖ'zj=W5~+xז+]! +Fٕ|&MDNƉO=oiVR~rH Lk-j4G#¡1V& >vy*f~-Dq 3mv<7Թe||TH!m֨{9[\RXv e&pϨ/mh9hlڊ$=[$dԙr5sP請K6$Cb7$Z}H4Y)և_/_t&DmEw | 6?_-^xbqw{(LQMqr_ i.Z_hx}%qοuV5\^O+ )ϨY*2p#Jkp@/BuYTT&pI9-%oMnm/EI~lVny}HEڂēW$Th {.ֹGL҆kOSċH sDxqJփ#F2Dh'eL.]A WA(7~Z^%KHL$MT:|CUP PW+r$i^̠q ~ gP^,gf'΄rg8 08rQ&տTT*qOk(u夌Jlz nm4^$Q(iv^$#gg_r \D9t։׷hNB{Ad;P\Y32Dysaet\-.G[{FBv 4 cwe`i`&fDw?z_ig^o{-}QpW G)Ԥ%9P=Tݷ7jr\5KC0հnX\½!1{6G{>Or= *&O 'kf!a\\p:&F\/A@u@A ] )/-'^v| %i9Ƙʂa.\_귤DZkzUиps]|qOy}1[y3J{Cx?\" EhulFC?F#᢬wy [YwSULqMNwzV]Y.2%uWj\ts2PT2We,#tMb@+*8 uJȔrp~ѭ筿CֿFqkҀ%!zՀ~5`Ub-(vk D%sVAԤ/OP5KUyN{{/)D5߬ BX.<-ZF5a,wS7/*Xfj.l*\_}7熚NXZpw_k9PvVkYkeD^*Np}+}9IS,nS,Ue9vr6HI %U:GFS2|MWS8%:58$o7۾{tk~e*ql!q98ή-IHxM֋Z/H5o!HU<4/?4i<OPc>;<' ! s؋I4 =^ea,Ie,CX2Jt [bEc19#wk IO GH- ФLthO=U kf,?>;OT3Gia]魻z 7Qph'ʅ7c:fr%don}l ,dCmlzalWbC=p1y>ln 6No7^#j}@\6Z4FI&6G^d׿Q:;W 6"DǡIHzi@f[[m,Q;Ψ:gsnƁ$w(0e`GEGr#H.cϟ/UאVҁɶeXőtF2MK&KnG3\Va9RZRw hڭ^8!ҹ!pan!hr R@!O),XJ ov:2#(ATމ+궫N"ZG˾ƫ.z UN,(12 w*ld64v(\WZQ͖NrA.Դ\:5D=66_6px,[^]S=8v`!(nߔe] &_5Y 14*i}H>w=hD ֞.R2lboSf=IPUIJ2$0S.Ʃ]& BLҩQ*s=B X^$c6rOZ3hUdRz d})/E':ʹa&/e EѾc%KS t(&:6Yc UZ͸Z9'6 iV Xz 7v rX{ÆvȽƽ{C׸6BV\BGkbPMXlW9O ȧ-ω]6 Ps' d=iͽd4{56l{&*덽[K8~ٜK^L@zK8"ɧLmڽ^[PK(FZևuyM@e%ޱ3Q]wF3)#iց*8;VgdquBu:pa\{n,AЂ ޔ`}S|"³)^|Z^@Ӝ>ͩiO}>Mu$9xBТ#5q*0ӗߩz1|$9%L%{aR -Ҿ(TZ,w`[oׯ :LUlo {6BexyܟB}<,[ ˘z+9`/bs`8쥘]rd{OYh*-\6[@;U&A0 A;Hl *j*2!ȴ Ve|;S]xv\W@jx G.v-W]٬=qO[":l5N&WpefI*SreW2^2٤3EK4Od1 u.Am`$x"Cyόcc9!=#vCzGn n(7g^Ej)q7EH,*a & W!1mrZ7xxa@oSVz$r%|kQaS q4;M+r'ߒ,7@R~~(rҒOɂlG't Vf.g/`tHa˂>Oq$yJJh.q᱔ߧDxOA&7JXFBzb-@zAxh>7@(c%%0 ƢP(s`⃫x~s6Qo(Jx^$LlA+ :`UA8"^$rfkx[gFo- 2iHfPi;E@(oC6D+gRGkN2| Ӡ9Dc 0UCbkԠfKflD=vDgu>p 1݂RJFɍJ6juOy@`^$v)Fo? }%*[#U2-B φ+#iKg3 aޤ1[q$v}Ğ@.V8Y]&]Fn5wqׂ=7 Ϸ5p8s୮l)'ȍw6kHTLZʾvRUcjKߡgLH s9\O  +zQi].Mu&*5+Ef.u%]L=ĶZ>n9Z}=lsb.70c a R8b<A1!:K͌'l0"g=T;oũ,,Y4 4`@ A]pg,dZO=&[K }Qsz7T<=fPB;> h?4) %J_:7tw,$_Ű`xh^~K6)IVv.,\,,F=Ң">o$V I,tҳ" [+jUw~Nnk7g{H~9"܎)?Q 0A$wq@Grky|izđ}zk_FKH( K'%A4+ҴcZaWڊкn^PjĎh(*{. _&s܀Q5T(:)YQiI)3Ƒ8b]ǾMgW$ca*!g~m>3 WʂZ(0~ 3FZySdN1T^JwCX22mHlK5Rڑ>uPJ^]h 0tô d.t.CzZ<ˆEۨj8uqQ61IX[9#east` =CaXbʿH4*ʳN{ӕ}F7wi(2(0z6I9D@֊`9e`+7X}, 5X4`54XRd`u7Xn,Unwt F[6 ..6,CSB6+˛4Y)_d2U;ڄB {f0d|Dzs_D/X75W"\.ċй10VG-\,.כ.ZI*r^i.\hur;h6&T*@ɐW\DTD:/q)XfV6.סU&LsZqQWt-\x6c۸-WgÛqd'٩ڍ)jܺ!;8ċ g}P0@Wk"ɕ/KCt%X՜fۇ9o2ScBEF:,ShY#I7W"c?)YSjG$\4 لi ^׷Ph3tTجkWY)PɓX0?6ʑ 'hZx֔%0|!, :RJl3iٟHz?apR.kMV٪l wTD][lGZ@*`ny]B,*}Jҝ<@jʪK];^좫i0zKP180{0 gpJ'O7~ @vь,ׄsؒ> #`œ$WEcPf4)t$ s)|3dq 0@BMD)Pm Kh$>}(*Rhu ">*Nj *ǩ3༊ U}\{W{Ûw^Bղ F'l G,xi4X%F[)_nV 1W&R:Z-F5B Y/놝$;mJm1ǵ^G=7dp-یi4ɵ Zy! !3!'U6r 1wncyPj%ZlXN7"H;E6Z8,7$Vvɐئ XC;IшjjWti.TO À}>&+R_2fBDzX0|Lf -aA|`9*{b$Ѭw6r P}u-WTg$ k7Q8,w!N7-E7pzf {[&kH9^kS'w~_o2TK{6t#ǓiJ~U ir3V-`Cigiq ~1yks޻vMC.8]] ;q[׊a8t-9YU_{roNˬbf7Y11^[<0ۥzBY/4Ji.$RTvPna-ܕ1E/Ti .-p$ND8Cq=#~Y#D鱎xI|RxE?EmpouBΰ DP].mj/w6{(+/( I] ~otGV}7Aru/rtE!BXBaTB֨ިx6$<;lؐgÂgx6φr ٚ0sBGYؗsd ynf54jͭGJgoQPͼʭT7SeT YF{^sp# (cFf,`زipt,sbSOǰͽz+!H"shCh G U ;n/l ˈgkckj/^xsکL]}AD(|kPu`侑ֳP|d}F7wpa8(~z3mFh5i me[[o[-i[hmѶ-R ʶIKV޶_.^{ml $ O)v8%k5Òhȹ-Do&/2 6IKVl$s]_4ݲ_w@gehShmMa6 z`Zsmg{NW54cNm ciA`b-P|}5<. TG9=..0F%2o*BYN&^ޤrK2}!`6OuQ#0Z: WK@W>q>2Z k^k!})XK_Eۉ$ u;>]oL~ +LP,Z(sr^5>45UOrOn֎'־劳9\&+?f2m cl^w*"*] @~ Ɏ^VW ^ l)FOAZsG-\N^Aw*s5|njOK\!"v%yeTEIDv\w _XJAC Ċf>.0#z; Zj0 %2'/d=`q({$D[H(AP\\C  ھɏR$1 )8@V1; k'Xjgч' L y#hjW5 B鬙 8 i'ϕPz肸~s/׻>|W*J}X4FjI{ɱܧ=PLxI}r[,Iră <&,î):sTs9q9_U3KI1Ba$h|%q&9>U{I}@UQ < u*_} lGQDzNrgg_-QUdB</Xu2^Xgh~x]ussL7_QaA$ ;0Knv\L:`^# ^[ۜ9~6,֏w躼ףy1dNDE(o amMc>'7`#p\Zj94H"l^w /Fp—-sLN /w4K"/´SXa wrtЋ##.ĴC8Qs:-/}trFzp1NJnNh_Qrz#X+P^J觢9N>.vI:R.?g#%6SmuW;`QT~$Q=w)PIVM.tAo#X; 8+ _Xآ]!,!0$!kHBoH! ~K5lȯaaw_C9k=9>sS,e{ڼ˶D1ԪfX͡b@+F1`ipt97c!R v#h)xTB Mwþ[p*U=(1$1/xXґ*HBՐvV`Dnjr|G͑KnpjY;רuBLf[Rj+q7ZV*3Rb͋Px js͝*X 4.4|Բ1Rtš7<. Q>oxj) fxFz Oxo9:tiS MnTpw^x4e(znyUoN C[]ah5n@r M62r;ɟ>>9clh0 ~Lw{bF3G%`Es 0E\p`ivd&}-k/ž^!ѰDM|3D+ٶ:< a늿jz{{|3бp⫋K- 6Y,/ofۥ~pThz')ua%U'Z~-O!o:ZZxb_sֺ/6@c"Hϯ̈ f5ӊ~ɜyjGֆqpms_HvpD)9N] {hj6M)|JN!">u3#E-b DQެ([rVG/z-(bf*W B?|q@0 h/CM濏_/dal(XߋG!&V˫ byM»XPbf .05^g" ٧B>@~m}di [IP(8r} `ScE$3)s_]}[5. DI"' gmx?;6J|Dn9)S{*% WsZM n6Ԁeml 4.)YVYM5_@cbzJTD羚\T;fZbjuE-K482IδS.sWCIvaSz!ȁ :I=K>K5rf>~ekE-W8UZ~]P{yU WվW]koNߺŷ1*zθhq<媻mgYz,ŕV\ҕ3P%)@KF,qNm.8?:GiH Hs+34fw=w?VH&OܜHi4 izU4lΕOͶ))-D)=K8gs2d8\ *d[45fEˤ+P0m|* "9!9bmX4$ pgs* 0DXmB=ܰ*gueM5.4:wr\3ʙзv79aS '(-" P ˄O֖z߾dCޞ*ƴl;ÌeYo3QRL-ҜаT RڐUa!ص5$3Ɛj" fv?o#BTZ5\gg!b\HDߜ&):K%ȱ!J\&VSc M!؜L'gZޡ{&5`{zQOF\f'&lLrOd:*0e=9YV-K@xd?x/5G u8rDnef (r@qyEJ[2ǝ؆H:XٕtXE!ya,1L 0$rPQL6pWJk3e]vk=#W$N.)mrn[&e܀egfPS RBAáNװl;"Bnӆ:kx)Yg67'nz$Nh)x ( ] rC#U!OuŽ> R1^J' XbRJ,Y풬wQ8~43,8S!\oZAkrbBbӾi:,?+%6T/+eO9?O!2y]ձεyԀ@wcزUs'0 w"ב)fC@&ܩDGf$/ğTN6B\:٨ zFuLFDxrhޣm4zQҥ(j}q.HcTDv`'45*yo-{oET;dڕMQSk;^ڼ潶k$'3'9P Ms2m:dLX3'dNnrpOM<kb)Wӯ5uOnNG)^Gi)u'<(usjDOQNq)Q:;h<9 F W3Z,Mʬj e"Eas$G7o;GHLlO^8.IVP8iqѱvg9Yr^ &9$q/w\@rNKUouffcέVL0O'I8`:5U9rJ6䫋KR6DL7jor}i&71K?~xN6Smym[6Cao}&% ЫxOtz2bk^k})XK_ ~hx+<omuPJv;2I@unmϡˍ[ Te%##Dڈ2*3FiLڃTٹ\VxQ6]ApŜUYlDq Q>q5H$#$OʼnfQ{v*R$/D a//".i`%&kz&P3Q@S񱽆CFJb<֛?tEHzC٬8; }Ifh|BiH#ͱRvbB |-<@F耡 | &h$mG$mwwBGA\lC;3ԸIuI[tnda2SӦ:Ա#+1uͿ+Ez+C~F< a)T2 rp*_}[u$8^gg_ۚSlr֘Wy:Z/x􆬀2kL!2TH,4%E\-VRldP:Qu&+8Ν)J(5~;oQv Cv(X#nF o=ȍzļ[H0G"&cxr2n9A4`~ZJ\!4G~g@uLL{cu'Npgt}[o0x|xTz/q1}I,F(Q* q+yw.ط%O}X3!zê멂xZg5{$kF MN7:y1kVS.FrK׀5Sȝb-<:M G?o%5:eWOv'++nVO: b(^wq"L]#3;M by{Ð/laٵsp6±qwqEiVal)uyStrtU h?#DY7G: m>; [hG#FRߖ "u930N茐V2-jJnGޅ@t/vAcōdֵ=Y典n UpbY'm H?ʋzUT-u NY'/¿KfW%WYeu(T{ŪIcW1)+#U;zy,/UJىw_}Wmv wtޏȴ7Y6|CWn+Yg3Unܱxz3ۭ4P/8)EtQfǫk 䯽B?,6t}.2\dSO{%=Ls-+$?#ϹxfgLNfɁDEP rٌ' Gek{D. t.ʥKW ~IkvV!sWHs>,hнGQ@Wn),ι 34Cr-EUbǕAw +mA<%W)IWdnGq:T5H43{EwhyH{w+anWs 5TvY^ eDclx 6PMY*TjF;FqwIe1g+5wÓI#3a ~@Vt.cqC\?B5$}kzQ.BUZZ bitk*D h'4bq1x)vQ(pՂٶf,UlB-ވ~Xqtn^qE,H#M UE0}y+G>2m+{|#0,t^Y(:DxGXdD'u:)'g9U?4ױOw 1?ӑvZvSPuFWL9]%ITWΌS 4 R_Dm @ [)]!` 5as;cq0)⧚4:Qe#n%1l?leר,^;ǜxqw-Zٹ|绐#[DsEf y4mM. ;\th54ñ 3j߆D- 2 ృ%Q]@[a>G*P?t K`K&ѫrf;(2jS$hSfX2Ss 1@'Q?DhHv bIg ]..y\w/'%! *u$z}aR4`-{ e!'yЫof]xY#~}{RyW}TG}JːLIKLJL0Cݣ:u0 Fc=Sbq8aq zl(5\/f+N>m`"8SlO4g'OXsųy Qe bZ\!!5;I;8t`Dmr+s^wJoBx~\y 'sEmfO珣 \mrMycKCZzR]f|lj?Ղ X^ \_hI2iF+'eF4 r¨PO%e$eJʤ-)C2iIJBR42[F89u<ɷ_6boEf&VL{%=L$Ϧ. 32{ P)kA=eD!R`B N~ב,:y''#%\'o}uQh&t N|uFG;e}AE6 4svy[ͭg|M3`J]'9/~!fQ'f~S3z`zhx^5 עϝfL,_2Ti1r1:Pr_}`_vonrvL9`̣` D04 I|0zKfHާu*wjfi [R23u(>i Lʬ01$tI]n" 0W\⍶S:ў*U,]+dW=CEhEGoh-1(o]Wuu1PxoE\yd8U>eugx,`GQ@ťGP1"(xP̖vH[]ۂjD{6.PldiέuT>6(:j}clL&BS-EsQJ^y#o(>9U3#;(>o7[]eK:r9J} NbZgfZo8 U?4ٜLOqf6ޡLt`eӂ Hxcه4aY0 19bfuPT攄@Wo^NSJibBb" 395yaݛ?:5{ê9ٓ|LEV僅h{]zW/LSUKF [_jG۠/wIOݎ̐Ws*jN |v=Qk)T770'g>T,JNwC+ӍN7:ݰN86T%IKNJN:hCD/ LnV0R}mDwIJh w+zUry:d&?;9=vķ;;yn[\9QT,DŎHVX L`eJWmʿmڪ`(Q0d3-m0ѢɪS)˕+AhEGHif+L>87d{ @7R {֎Ana2>xc+{B7`˽+ӭU~ӂa- 3pBPThrb -(A O0`d1v~XVA%MUPɀը3Kk xeLsnӢ/om$2-5p"Q7 K55]Z53s &L95pٞ(\mnfu$>/q¼}+'867ඡ( ͦ5*RB8QT^`{30F다hb+ZCTCp>n2e+1;v-2E5cNT22/O[Z%-נ:SJ?N ;J~/\G6Gcksա#6T[PL9ZERcjT:?hnފNU]瑷 fhaXpa2nq/WQB"*GTQ8Q@Y%M.bFs2:@\D8\g ²`ɹ!7'Œ3m!`+e4=e4hPSFc(8Xb0sy3/A<ڬGoZUi7NY-S<x:ʱ=ϱQj]rAv_m9 wJ;b`A 䤂PǕaRz<?&K]vK}w_ySԙIj N? \GǬwë2NeM~G*9m^L|7&e/2J"bqe)WU<_gϜL3 yCҠsI,MWp٭XƂr AՍ Ug\ #˶:zغflY,xK٬`cn+l̨I8AKNclcT6Qh*[FԯQ 1AkYzP^#n~Nn?g,?$kg^pg~,@0̈́O) TeDQHD'z4BNAe:&`])OiU5mo.}'+PjakJ˅(i($!aȮa,t1ZsL띊N_! ϋ]NNo w/uN͂qr=E4F՞1@$D:mYwt:Ewu͞ZbPX6)i st\+5__ MoG6x|(͹v Q3wof1U3qvbk&42ŀO~͛qSTN+GkU?tln*(IrRjWV m-Ft׉nmw-uk_vo+Z\t"$bTiS a]ӊMi0guݯFYk܃-3 iɩdHUJGr P( 1@\AL(N(^qWixtRR231%3#S sá207aFկ޼7>6pƚ?D / 3>xv)7Ҍ%JDĸ2cs[ʺ+YhxQV[*4ބ˭j?8y=gfI\k{, T\  RڼG4bFB1ӱΰDxh_"8u9G?SwܻN\=sf_Q4ע@W]uZWeѩNzNڞ uUla|Kn|+q˾Kwt4j< 4Y ! ǜvܶbVt2Xf0dPH\y q#B qXAla qi}TI$gcĖ8nmGjÎhB2!cEf2♜JMhSjr8ʥ`ψ6SX y7%Is*Ǫ$*zbsjZQTϡ#=g؞"mF ݟ' Jk/~PsLNѷѥ.ee.+}M{ yk^FYJ'9W显lx x}FHn0D<-n &voh} m(V~hVLgf~5vy|y<3zPptOo%UB˸D!(a"G!Y_,_T`{9{/Q3V*EU6!B0赶v\f^7WM*t.>ď/,X /2A2OBfaWQY{&I7:fSuOQʥFTXc@9 ۥ1Xr46030I͗qX*,Y MyW#P|&hJKȪGYSdwσ.`e}V4IvO+xIj670-rB?%kkrL跄M)vV4ufV> % 8uh$/U~A2һ|75NUsRR.eHP9sN[1%T:Q]`Kmb\qޤ8y`NNOA?oNj? Wa /ʔ! b4rKBĮ!!#Ug榯 Ϲ;@1v f9ɓh Փp={%̔#a.3ƜﴚHU;q˂{#d͞Ͻ7X)1{a5wc9Kc !J4&X -w+UA)պ54y;U}RN!zJA0L)<>p!.;flY,ViY}jJ۫FxqT IXBBwQơJ#*TU-ԯa>/flX8fjya*Y~8*6a4sh`I R)G^/|(dy)!e|HZn Μ_tZgP_.%+Я[KInO^wkK?B]p LtDZ9o'5m.]f'ȱM7}ỗ_D:fe?PQ"@ e hXEx cߋ_/_y/o^}_][a3[ՈSdj,a?32[e@Hj&0wzHh!7AK?4ȉnv~炅'l(e9 vwn7{B;u nۥQb؂vڀjh{6m kYPz8V *"wȌ. 1L3\.IĥbE IcLxjvoVm˨,Tftcc!SncA٩$ Q0%B?N5Мui8z#T[HQ6ñ"y\HqtM58hO{N*44N@bBbu'P;Ή Z߽ӻ*ċZTiUXL٩y<;umP^Z֘vTHvC_EN[t*tNV ngn{xhoSo1#!jRD ۍg\IT:^3YRu%!="Nc7 9ڤzNrkz1\c<C6٪nCoCf,s3ѣ d0f *Dm$r-6aQ0X $p_p%zzܢQ\-JțE |p _TRwcv,W}z)pjv5񀫥QG: yUǩms0ԭ2"L݇Jn: 38+( Tes:_# ҖGiʏ1;=CR"L>ۉy?J^5]MŦ}E ;6N"c'$ZE/qu,[fMʅzMPc?X`r ^Es&C}BeM[U *ZBX9#Y|"rD)DqqN S/'?h (8%&.Ȅ~Kh6'iMݤ'ZT7?7&kmhIHOdDW9IuF]iݤ&Uz#tʡn6fCc7U.P $5jX.8kP>7D0g'ذ5ig'Ou ƴj*F;4ʇQ>Hd5z'xPс {$(& {r'2 ˜@aojRncJKrrؓؽnvz׾ ~>Q} 9A\6wQhS *_@"xj&{9WG^(ˎ-M:NWuGSVys'_o5z9'q0:"X0Lo rr/N!h=OթsW|>h*\3&l]6<<;9f$b^Zq_CE[6jH@?-0}t+6#5pVwKU'̦%`ם<}wٔ¸k]E]z#rza8$S9 >.KS{J1dpv7qSΏLrQ{k[g@7[7XKwï& ]#BMr ۧuS\sͨ@g׎~Oh8ٔTr CiW9w n0Ϋ*" z#Ϋ, T*?a/8g@ʡ~-@y`hVƫ1^-Ä3_!\d"$,B2ɘd[f4ے(-.ȋ~!Rg[:EO [9o^nqހsuz[E6mλʐMx<cs0q~q~΀s"yq//6ଞoZi5?iBV /JA~@d񾳃E$1]7oh<}F9ĸ#k q/"pcuHl©((7i7w@ly7*;d"xYoYa& I^@/5o}sJctv{=Z-sNVo7p ėb\Ϻl.smyR9㏫n- |PW5$Y[ HԮ63I?g>. u kbid,v1s4HyHF0f8 SH;N@) q-\"ϸX7dbѿ*TOTo<~8\IqP@(B)JJ=:UE?6w &JSYD;F;vTǭsl=k"L)*. zX"])lQ'rJ{uAs[3 ʦx~å!=0 E\=9&kk 얗IR 9Ҭgmȑ^6Ig_,)%͇xTy)1> 7TFV.H7E䈼;GqGv0g ?p>uI/dbyK׷"3J&fB' ߽|&g< L*we`qxdB#:)Hb~Cϸgq%Q*>p#ǂ{@C"PǤA'.S> 7__~D 1L08a_z7 Oj6UqBT5/K藆{'nf4!(ɉV'p Y#,LOh~3nCUS.!Zw_vo3a@hgzloֻ b:t fd/^%8v zLjpRX8P0An1ܘR1GYǑqǟ-#Y3Cvw%u}wtLLF厉Eol*Bۛ=Ryh5 ڣ}Wo˛WٴoNMV cG8DBqe'0 Pg8O{5Qu2\lBa 33:'1{Bխu-:-Y +̲DU83-&eZx ljq NQѢ +HLƉ&PNڂH^AC/ 0khc?w&L \[&!֫ l4>LkWJ))eԔ9֞Eѐ>U!z]uvgM4A47%Hfjf.DuZEW_ !}Cs(qkҊ>:UILyDr ns7Q3jogyjM:-6]<鑣G1OqcRJsE ZAg- gz=Vm6Q7bљI_IYw/Xn'FN  1xUi}!H+=@@7[MBQ4JH2D,?T+K8QsD9 D>L,ᴠY R_k q8 X̸v;v>Y-ۨg)]7-U3?w(Y)M2(CC,$%E+T}I.+[lâU'B9'8$/^׻ , Q!>kMFoefE4O+ 05 Ds`k CPL0Spm&f,tHT|L#/ ֶ ݙ %CỎ"o'J&'yKVKW~Y8]} NKci$ͯ KF"+=Zr苋2/kŔژ\JuK)xK44ͳn 49r^Hp?hЕU5R|7oK'wncM&FU!BsTF UPXaBv<$ۥ{v,lѴI"'$UGoPC6Ўp)s(Ʌz1 y5uO6),Ylϊ.9  n]x (ZZ0ȯID~˙15 (.^G-zTw),OZ u& I_H_HN &b$:i+i_I_Iw}yԯD_ 0 / Sxph|p8HkHi9fZ}l()PA>oNm`"SlOzg'OuM/F/<(K-Xgj.E =^xctwom?Н_wId2ɶT΂LV˜4MXW&>nCh[Nk}iE4pz j`5Yop!Ejbt:.:AvBn2Kqv7AMm#2ֱ`hX6p3CtH-cC]5z?!M,Ov*vah_v pOw1vhC U5{ش(`7^?x:f>Ev"9L:ϻ=;e`@Sg's/Ԟx$/f>+^~(?=_!]btyطO?nv"`a6}΋{ tKR; ? 0s4G8Q,d n#T3<LDQ1dø@v]:zW⥪i\Rj$.q.pmCB>/ rD>" DL&ϝs#~ .gPb{L')vh[bOBؓSs*1GyfϏhˮ)V'*-OYp78)c*#NϝRGCa VVRrJ3k&իIVO4šx_5f~@LיPtMC{ r+P`r%v_mCրM$԰,Jю*$ DNpu⤽J~<OfU[~Nw۴޶fvLk-`vvp-Ɣ@[e 1_[#K^tъ}#(%­ڝ!˴k֞`-ۂ6k8 ._[R#:G-%r`K&s&srR/X+ <:PdI˧.¥Hͣ9Y㊉PhU~Wo˛WOX[oN=(jȏH31cBO><^a $7l\ ̩?|Tm,O1k+;d|іL g@&&بZP"ڏ$&N(-" fU,4qMH|v׃X`ă;.g 7a-5Ja1qfIկ4J8$}KNFf2ϔpսzE4y5 :Nz͏t`>gs֧prH֧_|Ol}Am\ ACf:+31Q0)NF8K}LUrZr nw}L ِR"V7v'Yg|*{S XkmkNz&rfH~O~L j/k$SQ0U`Z5V SԵUSvxhszk ~*\e{SbnP*[H[BkToij77嶷$\}{(rkɟ ̆N֊eVG}];HAD{ϐUA 7w7x$#mlG87n8e;I,iRUdubb.g0 yf2}PE hs&l?l" B8N9rNPW?<^ҏxqw-z} - @2\Q=;8v3ʉ1&;]>[D-pw51cQc ۱UFǪypT8"V[n"58 K(#u 0(6NldKT}v}l}>?L^5]M>w>ދ[j"7(h(Z5ESq5cr1fg sM˗zM$Szz_d_9duf)Y M `JuwB^%Zc:+cGBlEwi-aQ`g<}hS݀٘aN>&! h)FktqJ22t'/(É^`2܂Q"b#7Ɣ咍3 uɞ L~vrzM ovv_'0NapcTXȈƁz#B$)axǐ_h&$Pit))I@ %Kj"TdJĢ 34OufߠJxM],z E ,B:2q\fi;)rSNM+њӵrsR>c58p̔'`wk/'0~P"o=8"b1/`y!q;թRFukH ̳nfTo°`vmճrT }D}v3 "jc'#ya)S .iD$&&ۓA;-.ؖ ?OqaB2“c, `BZECHwXG,jw+fehԝ?Ww(~BEC~{O,lqEoO4Q^./ۉ5F\&v|C¯$ FcS>/luj=#f8G7 H 6a'? XK߮Zʩ_U}h<Ur |!Hj&"9 SFU1ZB3_!\<y"d#č0 hn Q\ BHt $s }uJs!݌ >-BQm  8>@V)c 0̝2 C ty  ϣGy1f~JJIjY6H΋,΋VHUWo,u\j[<W[nKJZY 'Z8~ [F4ס ֽU?6o eݚdW6?6w JR|W7*;d~ k0=/%ChNp.Y~Lx.]5s⍾߯޼oo$xvkS P>һ,uzH'%~`l\/%S2՗fp[t}MY uWfeޫq"O'g5B*nUހ4KrЭ#PWs3dcIBS3r:jXovJTO LE8P6HB j Zx`}"s@5&"^QR(tJATƙtHkkU8ő6;W ΩfsCvj+2=>,1V^r1!&Ndh]9RcL:ώJ"(kL\LVM+aGv4ԗպP\k!!aٚ&#:NzQ޷_6boEfd&dˇ/a"yrfy`:rkGUtv\GC%&<UM1d d!Nr}_ G}rɠ|2ǵ ivZkIPSZo(U{lY;d-]ޫ4$oONqSr#.X:(~'\2 $b.!!316zyZсJ KĤl\>Hti.ME*hWL(Gܨr}0-ÿW^z_޼xښes4#8F,s8gI7؊UC,ʴcfvRmYfӢ|tyƣ7+d7hw[Ҹ+"G7>*fUT1V T S `=Rٴ^F<$ ZDdIp mar'̣=F` 47ӱvDSxl`2OS萰u7BSt@K\=sfQT~ZkQتج+`S]Y<s>̆s4 @>{fzn rU?4YB Oqd6YBt` eӂqp;`ͬmȆ&8:{3=V9%wKll /h\cn[s2(33#XG7zc6VT,x9\3'1  ֪9Uj^Z5D [fSqZ,ۛuRqjGGښ8Zz7$mxla-%D8ȧ@aԐ;^KT`6\uV$,=C "ګ}jLt!sPd'9 mc;<ƹ̸va.9XObI®"ȧv`<et80*F6ܥea{XlA0qAs¯~ŋl[nыsnhaXITN$Q5y܍y2luލΎݟFk,lǺڳG[WYaQBXul̊F >ܟ;T,PYDW$HRBD8;,R->E[*f:0yt5MWx/bo%;ܠhl_TNő֌욱K65-_6ɳnO_# C~Ls吥q]Xxg54Ѳ6*UTIaJ{|I?)lmWX ѬHT'$5S~HX$ EKٹM >K!?!/f꿂vZάe}V4HvO xIh%70rB?Fdk[rL跄 gF9Oufc ^9>/H#zvktѱv*NZpRpҝ  'zNp fK"`FCGxܨSK60ԁ'{60)61'ɓp8mQ2b!#vDL2TtCJ|趓@mɿm,'Qc3,m0ТǪSQ)! Ҁ??U*B6A\vQ3 *\ wjtƽ`p9/(NM;6DkNuI~cո~~1S !ǯ?&\qC~8￀) tb|TrJBխ!$0jQs Z]  UQ7uؕ2̀0kᇥN%槱[lOa莲Zb[/7`>Ņ Ot` {T$ЃeS"k !a]G)[vu4Rw2ث;^ݡ  ! W?qe>јG{o'qlo Γ,I)f'N1/\F6skթ!D1F$"u27ڄ!TDtc.}j+~UXT 3g i~0OUMĨj }ϫ(ӫl2 ? חW!4R8, ?&`qn.9F_Wo^sfw ;\ǿ5tS)s(]j̙:={QJo6[F_s?Kzy̭a&D,>-}juY<[7X 0wtid#T%q,nd﫚<;XAȅn?f|Z,֛f_[f yG+_|nVA(b˫;zޱ;NF;PT Ml=eR<ltUüi#bV:8q:kJeY~Ԫˠý9tG0dO)K}2oLwɸɔ~ I GSY֋IS/;J_jsMi˃d%R֎zY fM$8!% , r&uI/d]byK׷"3r}9w~2A_×0<{鲼Y0v<Tʡ~hʀv03vڮ1Mx}ܖ[Gm]~ݺ-`klu--BA;D[r-> iǡd݈+MPcJMpHGAX!:gcj&lj&'ݍהz=嘉Y٨nj~LE(XnW,Gۨ| v-ÿW^z_޼xZesOL3 (,o8[e4I7{؊U+afmm ^8,iъ=:3XQlX kYq s-K"T$z nYuC䱞#؏pf0<*#.CѧtBlZ#詨9Z\%i94RÊ(j>[(GUUZEYh%%,V^Z>ZlTVs&pb5.ʗs7#vZ㐒RcHVnrԪ ^ uO-0$-:"fP^lHn"YH43*@VCdz!YIVQ8q*dT\쐨eW2Y@@h8jB`8ϧxչἛ: !8g gWZπ78 mI+Jj43 b<$c 1_IUpI)<ӝq!{푴 +h[ة8msBBݙdI9?d ťI1cXn _BͪLH€820= VrTIiAř{L%΅?N4&3k!-6EH"8LNN!~>RZ@v 1}!z_ԜL#͟}#ռ ^ aG&LL2tPNUʩkxTNǶp%)mTIڍ N9> ngwch;b^U۠Wy[^!K"rn*J` Ul5 F{ +Amonx+IFN@ˠ q;3hv 01 Ѥj=4v)]7p2 :G؁ QAUQ_ҿ?, p `rx{&_[:OB3A$^?WZD0$]>[D9VCw;ΚUQ{Ш5UJw*Kw#9<(JWc)zY%ksJv 2w?EoM4$`jlocTZu/'I1g۾hn{{) ܢTdDKUvGB*(ص~h\e]2 wBWYQ5Ev]|J?/HucR]itѕs*MZoRoҝZϒ&zMosN%!aDF;S!'ꀈg[φɿN93J#hF1 mzRtr):Qc>z$>Hr)OS6VSRW@(ױScrĚԃ3FGefސʣxLw],vBc 0B:90[NBъi;q2(oڲ91c-N,6C# I)RvFnbi@煞e6?E`(] `np@;fϹ:o f5:dfGqVf| %v0/?j@`n ri/AkZYbO/W`~>Et{@N{T7ʦj*¹ι8CQӍ8S^_kG[d%mvmq *s \q`-eh o|1 pyNn1r>+~A&Y45R "^iD]A5%C9J]WEدyV58ߩH|ǒVъNU `<OA 2V/WQBp"GN8Ts?om5T$>ŋ*mJ'{l'^|1ސg -̡v.-^Lq.ݭ*w]g}h̜w/./|97PKlN5 4":-@ bB" >pux}.B6|ܐ+'sp4=A0/;+X,+~ʩ9egEKxT-!2=cQ8zfbo-ALoKcs[1  }e%oeT''Bib`A&Ѐ넕@l Y6m"NM#Xb2cpĆ xoOݦ?*?)EPHç G+t!حa #X5aW9_;\y Sām"m6:*4I" dy9!BrgN[OCC5 G-:314qe00y7bй+C%c ^ch h!IigKh۠sD𹹟0h]\/h?=W)WźuaT~ Bhz[,U z[u3󹁙s32yCf>$3WML(P HrK=+?w!l7_gjByJ(q9~G- {HG_P޶nl\x(Pî=裥ߵ׏AMfF,zW.>l}ӡtmJ.uoNpY#@\~+,Et:Sj|_]t_O9_03cvUV쩰hO쩞r絫`fFfOm +lP@\m@ T=L6mI\5[RZDX(PL<: "ܥu~&kCW1C,AwE/:ɪ5ר<`x) >d,d--}e5[ǫA3`p*2 ``l>f7|@х Fq:9/ċ9uٻ3_2zV]zH58Yh{3]m?Dh^'P yס=SAI.n=SAZ\hSg!?4;w!Yޛj #}wS?ZQLykfUj^f/ 14a~u[@-nΔ]JO󛦪7Y Y}~.t5z]}RPJk;nfHUK"E]Ihd5?-N&]&e6+ Qӈ1p|'TXp!Ʈ2OucuHW-îPlQѩ!**P¢a-Q H(AV# HE ?UHOE3/>͋h7^mǏ뛫ӁV{ɏ0^i0h\1eo39~A^䪜zWB3\b? ڱY9CAg#ű.?`۵݂Ѓ;ZP/g@Cs?وlLp:bꔎZ 8 f9 +QϜR!`eo|P۽Ug:`2_KvDɳ^I&sx1sb1WEXXƕe:i O-/)k jiL j =/ e?6"!cH%SEE80IW]-e{%{F[ 9vV=Gr%,ةg*59c94 7OII:*;|jgoή,zuޔߜҁaQڅ0Bi|B l6d}o!P`˯_|/=xFbp@3 OHwŎ,қ6+S6_n9)~fg[p?oE\[? 7F6 uмaBC]2Nt@a%-8ј/^凶w/o zpk,pYͮY礙E%ȂUa-kqHZo"Ji0U 4uqF%ڥ+a`4Ier"QTfksPZ6۬FO͊L0(-`.#3W3sJ3gbil- LyL28܏2xI\VVg֐:`O X,., W4JoF-5Ы4SvҾP[ht:Yg go;N5H]f]d,,kbdˡe:k ʒ}}m +н-ϩ؝x9jb{M$&z[AXf4uSw5cSN:fp9\ԩoˠ0/Q A+ 押g3uڬޯNVڬP9fbsgTr,P0K >cK3F.CXV᥊.u!t`K\,bl<[CI?JW~fju5Y:- i6 ^l 0}Cx"IgVlc,OnA&V`E] 4m eO5s,<+lXptwLcs?ΞLɔ90Wu6:v8^Wns8+Nn':Ś(K.%:d+T~2f8߂^wbJR^:m"M&?Ujb|A _+VC>RpۖLIq$EϞnlj)}3)+9,0/Ֆw/N>s3sL7`|pLt^1u~q)ăJީ:`+?$_5hI߼ssss+jӺmviJDؕDͮEaW] cCsݹ4]~bNqgծg^O>Ͻ0%s>PV*(>K)kqTB1,5Wg):ڼʢJN++y=k[[+izxjdNbm/ ֎wZm̳ksn4nS|nOT7u ذ[~ UثgߎTy]0Hфtf"L F+hQ$l*z/O]l2J.B_4E>L66+t>YĩL\C>e7Ja'x+1E~Y}GHCHA,5ɮKcB0gqK,d5ҦnZ"&plIX ! ~;$b(1Q~p.~psϛ˟h0k!PSve.jt8Eovf\Y0WP-X`քs7*)CωkY[&Ka-T#=p$:)1$FB1WcJ mlj-1fbKb<$Ɯ㹒]1㹙 b%Y$_sc?W`] O}E\qYeG˼A#C!QHgx#FaD#|x#s k ׋^/z=+T_*&4Zr=!&=Mz45?n/E1UEgʪ۲tQpDo]ڄkV¼>כ?k._ĵVg^F|exeZ[+p?(Kq[>M]Chk ;v,ކԙ>l'X0;r &b}jL.VH1Xc&u![MOqr/~KYH$`%XFdm-<[vu+* 67l\YNk \.8<,Kve ѓrux+ VJ*mqB !2pB7y u .k.b&>^}$7u~QW \fwbOλȇ9ONDZ`f8 JF͵Xտx]s3|QNf_th|dpIMIV"HRH axx'CĿ %1Sʺ&R, 00"1$# OUqsoUq0ETg[U@NB} prsT=eAU}]ݛ *z1\qeD䶅i&Du7fc8iFW.Riu2EniQBcp'E;!۪ I~^>}O5';ҶZ!}* N˙υ"Ves1=LH٬}[ſ[V/ͽe/^|ͭhf/YipoV~`t\͎ _8 :G[\bKsRp m^]-H4~QiMwm]`Arq>\wLhh5;sɚB>4oo窾ٷk&ܙǴx{=62L/2h8-bany+$P~+:ܓUV!!c((,Cqt%9!LZ0Js\''^ɧ+Vٛuݜg}(6T:cU5S>mΕU[pEC [</9,\Vk齁 RH9? ^˫:^rE$sWm ߳FHgݢQ\J#j\sTZnSfM&aB47yBF̎p4RMOhQ$1joS,Al&o&lxed4Jl[@?;vXxsjk 2hӤ/pB#c>t" c#ॎ/٘:P8Zڏs|!Wsg.G31vghB JÂtb@T) tΔN<kFFZsV sRcҫ χ~<k'TH) `-_ G()Ew–Jy-i (2VϚ2F<Z\p %^gBœaIoS= aL1Ph cu|aի9Ze# (B+W 5' N:e:պZOcΞ{s& q&+bV4+;.呑]oSkm;&5i*a4ڹYu4F?5G Wֱ$u@v=MM.6c{tzcW'$F 9-/Φn:TVD*u&qؖᓎ4hUrV"S+ FINҹ4 nD~g.?θ9u/u4 %x4Vdg?uM,)n5`9Ѽz/0gF~%Ă;ڝZ Rϻu۞xEP7뛨G @SWN:R5M Go]]N\ơY wDֆ}8\p\Y>6 "Hʿ Doo!e)?e(",o!*K#njf8aA¡aQ >F=b!_kx# dzC;tmƪ۲6dLYpR `+O?HЖ4& [95Swa?%Ƃ噋/\UP>zt;=k0)8_/kH٣\ܹ.}e nu+)>)ܕHR5(+IqrR.o_.jLW,S+!Z9ǙbyiϪƭp0>h }yVC@Qby+~Vא*Eja\`a!dR?+>' ;ݟk4 q 44ӥISo'Ƶ튩I+mi&4Y5w|y5% |Wa!N7Z!IWhZB@-tTcD1LtєJz`^90"mb3U/E3b_h$!dЯrӕ-!;]_e B,.ZGz桪S`{֏ipUm-F?*2ߖqlOsɟ;D8 O j k ؏߲ޛ6Ǎ$W Z}3Rҏ.5 IKuhuB]kZCv;2]>PF!+Rҽ!>}Zz9b2pĥ\D3yES1fhTCV 00CMh1BL|H T)BS?tItԢ1uсiс60bvTlbW]S."(͸,{!;srB,7-v[EX$( - 4Q$Z?J쿺BQ8Aw3%.R* doAGR2 ^2CkgLqzM^ dj^K^u} Z X%z%;aB Bhf;4K,uG&uҦA(5B{SW34:oe11 ӠjIgtO*3|}NTqȀs&Pt63 $C>pi/@ ɧ'v^|tlce-m?`6嶂Cpo~^5Y1-],NQN:=9#ct]lux] bq| Ǹ˗ D;+ȹ.BN #*W!mru_6-JbDH !M.3'_K1%?  zE.sCe8B؊# soC =+0ZF  :&y}M>{9}珏,SG0އPEԡƨ= KRu1xXz[lF|<3! JGQ#CUHJ>jO2ua-T!nH 8תl m8.7Z Kz#@HιZJ֕OlXm.Xks=YdLbeSּlbӆZ1PcFW 7ZNrvZ+3tHXu_ϢDbQ~^H ' ('B6|WAfH,l u- rٛ~ǣf~pS4̈́C'J/>/f}]n"M|iјW|ZS;֪O]X/BhRjL<= y!jX7C eD9<_\tQF7tt*ܱ ե$ B]Іtƒ m0]4{ /.o~o4( `+eaF,4jWo_mK6BH!F콫hfe{qO:Xn+W*tܛvZ *;Ҧ}6!3~<a ϰ"0.v!pฐ!C!:xBhx.hKf[x QTEyY06U֪gU[I7NOI>ػIJ6pg?EjTJl:l 7QYr1Jf aqR {u肃%c;WٻƐY[T3`]UPV¬'+@l6'eGS$0>"ƴFlN09$]W鱀c϶bRZ4 Yv&kdḘMV!F$X; *M[;_um"3@*RH=9#Ňoy~H5RaAG*ӟ$svˬ!:|ցg7 CsjދN;5r] ZjUw DKӗսW06n85?Cπ Ƃ`Kx=$7~=%`#'fLW ڡPCr#@)D.1|8'*%yXn˷H,!>nk9Xo>QۃOէnMys~u 4׿A%-ו'4>۬wkm)w!6>4cpnp/TN|FRPY?Pat}|k\+ܹp\츞(oNe(9 F"q=Nug{j@ow(3} S].n$ZŰ:<|WoOtֆ$4)Ui BLj#ޗC261i mzdJ՗4ts٘+zRaCHkB0./_{ꑿj~hG{OEzF3fm; fիSþR3۞șy B2%;5M~a ZI e7״#Es#c;}2k4qP/!g&Gx+^ kgW0(.x 7 p]ZpP,[ VKѺB!׌Ł=KTѤ *}9"HIʗ"2xXMOhжG7a!F{QԤ0 bZ^W7/.7' +=ԩc-0X(̈=:)* Ykw'jo)p%rw=wb\,v_fQr>QLӧ_;zS -[O.6+G;z]A=GbšsOuywXqVyxIEWW ٣? {uܿrP#>.3L9r@w=Oo{PD,f1f$pL@BlC%c 9U~R>l?WnN4 4#QWi ݼ .j3uTڱm##͔!S óvMOڥ6c<)]S%PTdMDV\z!T 3#8::XRd &|b͆,1Z5i0$}m+J{?a,̈́ * }uiDhlf>^Uq:Dr@=8e1_,ů% $ɝ&RBB+DD} 1"\]~4Yo "IG4/!%Py V,YBS:ɕ?_`x*{TdPd\8>3fqr7; A>݉[ApwV$='S`obuaH vo騷(6u>O&^\`՜5ije3r ݂^C0[H=Y RNX:bgƴN,u.Yyob|V1A>'|jD lMoc>;nn$Z0=hصe\Xgezbpg[Ey c#ݥa [5 >TzD.)7.D0"hs悱uuQyB%S˦)hK"C^\ W`a.OJ`;oD ͟Oq߭ T 8ęOLtȿGMb-n^o<{q_zʸ"S"uqS|_]_ ׿Ae+烻zV/r Nӧƀ.O ^21`ҝޯ\ϫ=Pd&sH?oηZM`_ ]9Gw"jb*^ݸx5 |V>zq ^CPU~xn.{Ek59ַgKkڵ[XuiSp뵡#&Ҧ&:0#Gњ0xyl"iy'3Ot!91rA&b}D\>Sh{ &-qo oUjMJ-=X%8DH:(AtZyIf)mK <)<7CߗL>d䌯e&8&&a;xdKa=''oa};{9}HX1 ǧ6RU Xǭ2eD3WvUJvF*QyaR0 @?Χ۟Oa$O̼Nr~W.JDP BձޘظmM{x|q:?䑐f>ꎽYXuO|\BOtzY@x(;ikcX &uzĞۮ*iy6O^PFtMᡫ5ŠZFp$?:Y+G^P- $zAXɲr׻PcwH5d* P )«:,1>Z1L~NĠ8vڌ@:o{QtU :"qsO^pQ339 B['lt~HCᩱOӮ m] ؞f߽݃\>C"w890\{;Wo70ÃA>']))+>`(><o?Q=zwv7}Tw(Qv)9!R9?=: ݸ ky9TYG[/pf 鷇tA_IkՂV+\۱D5KʱJΪY_W:YEp ]m񢳝Q+tR(rSbmy- gv;Kx[g6]{ҽbtK{{*źҰ],zSгyB)Z`BxB+ ŗd+tt7+xVh }V V($l!zSnׅBd)[`e",P{P7{p(OBᛲJPDI_F!8B ZR=K@gJʴOOS))ّkKIANa?q4 0QWVdy%؈u8i 8lM6q_060rC*=7 =D8tz1bSYںYW>QäOQy5]FuoJ8㍈,!W4'Zn}:8x ayGm3];ѷSC8Nc䓐 pQȰ0*1HwqfW`n ,>K?_="{f.^0h%/#,x(QMdp}!P!GFhsj羫7Y\UNM(3+%Ϲ}%۵JҘ9Ycf55f+ͱWJV݄z T*C*; U,ƙZ/B!6fijGheI4'ʮ ' x0ԀQ:C|I3d[F<ڱcT92yKөH艽f,êy׊%<)si([1TT U5`;w[('S"Tն2s_ޒ矒re=5꩖PO} w_N])3q(A||]||&@X[^95,۩ wa7s!2o?;|k4Q63:lee++[YVdYߜ-lAd b-vtoi 4*^ԗ ֔N )uFg nb1;oց*H Eգg%V{uH#, HPCn/Kg! SiL}"Y?WQ; R@_~e 2րOӀ 8;ZB_}rH8d|$쏡c)MzuMOd<A>4҈#Hj9s q^l6^fgmJ:Nii߁T;TH/7g?8gR8-Z9ZUYɶ~Ȱ2N[owthsf")!`Qaa tܓDde"w3+<-Rm"NLjar(Tx+C#K6&Un+%(֤ i%9]VXTeMjŽt%"I~ڽL)/7/O$)/̝-hKtGJ4݇WL)Q,ٕ>,!f+{mr`!|n4'fOv}>a aDRگ V7jOz9(P⹍% r2gĪD|uL ֤-FtOfrL h,f/=wMT"`Q&r,k_,/Zwyl*{6l{uN=}Em7j%&4Eئ,bnwv l˺ $x#!^Y^}Yt|n.RP>}HPqk-R2-߭!.y*qK-ZAU/q}ֻPP^Fbc/ݫuq{.4XI`[*)`ˌ¡#MCJCzWfbN$E͔`#-N<[9GA(|hfR.]4r&3,<:1& "ỏHܺ)['(b Pl};eZyLؿ~KEX\ KuNF=D4=8Ӄ2%٤I%hÏ؝݆('}SH6)'4}2|_Dݧ>s(OSs>q>ݧa'1OL';t>>UL,3xx_Y8:e<>t~ *c>B F> |WcIG==1!<.W $ij[:rB̚mM;ʲzٓZ*|(W.P×V(S[+vms ^SD 54\k:4l9F*%3sly>۩B 7t{ʢcѮc"*7b-5mdYsdz 548Ρc"x #^(jw5og-w_KWYeTVZi|5ۈ kC?Woie݇d6?2tq;xL{[Vo{I G5u7~NbLm ^S0,2 "jV ǦҪҴZUm/\)~ce5(y,].i7L-?'K(dsB7M^ht@Y "le*u @&e@i= ۠y8~CH+Z~%eI&ѹOz0^P{r| 8?~AF0驖L~Rr랧A#S44<#ih1G̻%fh_bv98mtB̄ f*8lZ*pkWCK 4P~p` F1v[R1>:ؕ_[L@{<~0J;g!Z5H,CA7`IB~ӧ^S>Ȝ_||0/;b`>@iLh')tG'm CΧ_}}5;fxs+x2c.ꘋU~=¤#O>RPRD$29֬YFP-5K,4 T pn5,{e%!C'DPԚ!n,7-]Ii~yrw ` T 8ą%+R( ,ox 9 XմheȪ񉟞Zh`-1U߫mY{=r]͜ܡ'?} vS{+zUQy ?p<.YrWfpD4~YhT$ЊCX},sACg| C,-rIT#ٜgTF)()Pz>VTg)YEW]ĠE;;J13=ӡL;#X١>04dPD6X"q=,~&]D5#]sjL ~bk܈#Y% >,̘-yme1f?5v|"ۡt<4|D55#8JW٨橈_ <yE$a4R=h> r?튛C }me|r|}twf Ӯ%B;qW½xғrCqm?fRR>3v[B},ByLنiU@0*{tV )ajљCd ##LÝ2N'=+gNAjm(<>j0IT`Bhf |-8"awg|DjvUóՃl4h;V}f^dyߙ m'LihOxL;E.a~Fl@cւ~_~]wM#wZ&gcۓTVrzdm&DəNRh&1bt('GQ"R yh$qD WEeE L@&G:+H9]o co98n(YyLtYuPL]vNe}3lo3׍ DRJ#G駂RC]V +WI8MHϋgPg](8U T7VO%LPe9e0^{8YN}6|%Zܶ`e.UMKGC^\dX?f\4ON`1pg3.hW{*{*?Lx&g5_\*C צ4[u]w jr0L֙v3\)Rf79oyQ c΅4Ԇ^bO{K v%r2<3)^vg=k3QOxk>-i3 _WǰSp7f,Z?vZFu*]FW6ѕ0F>|$, Wb.l@d"ـZ2 8_yNvf{^l]<=;3e PٔHC_:iLY.*8#BS_oa"Ego۶rKL˚Ta^ ^\~ljێKf1*R'˃ڴr -_;X9U}@&\~fFңDZTp{Xd$b\E-N#<]0 ߃^ ʨKeb*- 'xN[('v(%EYڣjWPۧ>0v0E'CؐA G]x}<]!)?< 8\͇)jŽH|͆n=\Us]1udoɕ,.ZNiLeiΐ†W,-E+I+wJ9:ݝ>T~%c8 2qC =e9ɑ2(Go 7~.]:3.LxI1iO >پ< 1l|dz{_7Ůி9DXGć$?k~O(@t-qZ!qQj+l<[,}bA`Q6`n>!SH9.S02Jd rp!r'>_ BPC9!*yLĊ`57??Hvspm )_-W9 @rt-9D3'ةLe^#cPf{$2T J9'n 1}dl,3,OO /<`* #TBd J:AC;.]6 ;!vo} +=(Nb,TETb#w쑇T; >"irtu-G/rcxv~/6r}D(e*\쾑4٤>hZƽ)-CX^#QHn'8䌡ŸQsִZ D5r(1o͋׭~57&k2%H$^& hwۊE< /{izv|Α p7Gi Z{4H_ٻF%z5,̅i,l{fj]Px%D #E"[̾E +ׅDͺD(s!)# c?,)FzKޅMؾ3#Im\,lJ7JJAp ~D$X[ ?mk!B)^lj?U 2N^/~|~?T&}@Rޑ3[ϾO#/*upĮ}ƀ=go7ˮkI1d$"lcpMz Ĵn"sl@K&RC'R]C@ZFi2D'T1{sSXp[Q e MZ>.waxyYRu#,k4YrvLܥCt1t旓 phb=,ţ")!x PzccDB} \:+v֥X,tIf̓>Mol \g%[e *]mwMZ mG0:yN"m>>exÜV*ivS&? )2Vb4CL6, lذ$k X$?b4QT;ꄛ{gß(~:n@cn:LJ[U`=uu:%t@"׬".(D=:zx/7g?88#/|ǭsNoU7\]5 Ŗ`{NOM4't $mO6eS9. NKOGcR=83 C^xL\nw GMt&~+;&caa%uazV!&GL"@Qn&ڲz)oS~Qx~װQґ% nſ yJYMv~ 45>P>Õ"Êjkճ_J2U aIU|&Luk v0ϥdC!䔜a 'G\uOɯMF3LZqs2M7: 1&S;lȘ.iRjT'5j_nB9n-NYJX2)2i_Dbf(@SN#u\SI'^RQ1@ {GɤE&B0} ++MUI Ua_UEVVAgJ}U3VچR[n/LѣC)Lx'ׂt[Fc]K kc`E)Mb`Nux3{n[jOGhƍg<}*.Il }ȑwh$W+<{B>EpUe.~0pm(yYUIxVw8;O hvϓt=gz tsm\N]Z $75df`-3kSbDێJ} -hح^9lZ`cHZG!aaB$ &+#zy=3Ӿya*< ՀH?u>ܱ)0>"DEH (+'lD s@=v6aXgƈ  c8^υ@/L3 L })Ii|ax?KR=⒗A7JR*aF.I Rv==WI0%luQvΖI' j=ұi13c!n{42I7 ss"8%7Kv$#Y؟q>^?zw U8%7$wUwCGPz^R߃C(BLx-% d gN'Jp]3+a) uxzGʲн$Fp%wZT*g}hQZ$kCϫR5 T@MHdVXjݙ` Ot^Xc@'+Qtj}H^*E2dnYΡmeɲfQ[A ѕN@t剡ؙg<fg7nl)Z;h# ,. `نʔ\ԊUAZp"fVT9@EhYD=D$#\߰YJB%JUU8UQu-uC e:A9&("ܛro垟;T}L-i@7A`lm6Q_Kė >ҫD& k Qe LtSf}ּt&>jsҗ&5l ^7Hx*CZ37n@wNu[ Bˢ]K,Y:z:;(s+eE%tΰ`"6hA26iq/^{fp)ib\VLRj߬Ga-Qh= }ѻkCLIb;fJOl*O_Ͳ;?1UhzFlg}l=z:ZͿ u+U氩߫=VvȔ1R1-b7~ZFvqNv4aT-Ǝdj EcFlV0 (KCg9ff瓎 -55ߨ#RsӍxlF }IK,HAG=π_h3:No{Tc(ӆ47q#&)ȑƍ'v CROjf9aN-Rlt YBv@T1ա] HJeEX\C0Xn:o QQZ!B[ Zl2y{TA7 0TFA;(Qqx[Mxxo"mثS?DJD*]WwT'US+!|AkP֔cq`.~?ߟox}m^U?3g%XUu1ǪV*ZuJx[sC`k?׮y[smvk.@"hPfM l1ubYw7e߹~omL;﬿#Si(4K?m,]OL00ꛮ튍oHr ;Y,=&8u8n[y` Q*׀$kg*`PIƞ]FV>L[7qherqYi2.װ= !jd(ES"|`}618l'؄`l*)!~bl e'c9iZkQg1rªVJEb3fOfP{&vƛ,uX^հ3Sc]2zcEQ % Vf`CbkzW[oq{TC*|v6.W\i"HBS6VXj\n\dB[_Wu'!(W6 Ps-4+OėVɇ-_Z=5v@KR q 1tmL}Kph EzIwM^\Ė+5osa&ju4Vlf"K8JvYqM~ؖoc?V+7^ӧU74B ?,no+GL̈́vm6KۚZ. a* mkpU~D ܫS> IO>OajE%o?Ż%㿭Y٨a*[jV <:*.1*PQ5q/mo,[jr /DY$=3ӘԐ;CG-޳#'cya'8I-_ɸ!JQ~c$d@qܹ |$_MkK;^\,l )2Xzo7;Tv[nZXnචXӧ!:@;:mN ~\|$*݌y) z'p΀¹z!"J4|躂̌>B}¿8@@AݡJԈ. ɵV$A;=H9uѺTR]3,>Z1i}ImMeybPP̅g@*^g@[ͶlČ:jze + fz鑜J#r2P8E rS~c97?/_{5pd3 0nvJOCe`XS~;]V}QCwW'sؓ?ܑ\-w%bzKqvYqbQI1 @^߮\[ TDޢ f4(=V&^[!+K z፺ķ2>L̎~^\.gC; qtL\ v|+ #۬>x2t(PN;XR\XFY:l+q6j޼bNtJkr REIe2׺UC9ʕC9YQ/ ȮhD],%d/ıMn^C1v롫9Ա A7cA lA@1f(X:MdqFrs|( 27PO$'87Zb%>FskAjԚ&U.D)]s9ß!QOzѣ}.Nv1q 5ǀǺ/V&| A2|&~^zd>cnH'"M26M;+| vyUsTEKA@9'o*+Ʋ,vt=5QĢ3&!2U`p1mA*bbc"W]*CU*dUNqv:^^6aM; 26blQ:*Y2ʥ>i/f}XE+1% A%65L+qBZG*=FeQOtQpmY!WK *>j[6Ou+Ots(= +Wi4r/kMS[*xͺس7D+j"dԡMҷBw_ 5k@lP!D{T\QXkE@( ւoB 12`W~oN#ҏ3e~pO:32#2ǭsNTxcX(lw$焩NyD=0d5[ҩ\:mTPB*iQ [! =R%np33cIjqoY],C>diÝ^\ #Uߟߟ)|2(O{">7bv"?D~8o6:ŗ7I6pNw}<#\s(ebjX91OA J[oܲ3ۦ}7U7=5٩CToȱNds,NB<n'yS} 3qBcS3|W}bg=Eug |lgD?nz@c8cHcFO\ ǘ@h /䷿O`.D'`ei5C[ >n?@RHxzV1W{XP!a?rV\@nd 4|zʗJ8UI`Щv %X@f^H8ѣC:ͪwufz{o\8 t J?$<$IRG_CeOzdn>\s&s͠F[ȰCNuP9%C͋g6۔gs^Ӊ:iŧ=ߖ Jp*^ByaՅTNX5dV6\\{<0IR3>P.Z>:[;Aңg&JYD>'q!{6 :S}#fWYl4<|2Ddl=X+)рW~lK4šn@nۖ%4Nurrd'&;1ىNLvbՉ m?- љl-lg <[Z =57``kɎ̝NMl"f1L&Z?.Z?)<9g6hA l6hZk=T[jꙨgYSgM5ufMFg퓺 Nz -x@)iH5P(C{ ;qJ8{c4rkL TK mFl>ϟd-о`O9wjI/$ύW]}CObϣ_E^&c;,ʲ(KT>==jQA3d3%Aa!obuPRH m)f֦Tʥra)`h.U S,yU neju)-˶FS-EQf( T٨zc?^V#cqo]WXhUJE{Ce^ȞjXc o*TM׵luB]#f?0~`شqm?(@@)tύaF570ه>La}øvbzgz ) m쑶6vmlcg{2[Y)onn=i064Gڶ9 a6Ai<݅q_EZB vΆN6t {?+>D.̣~v{;ޤ6uƟl8ub;7ӜفtJ[8ߧ80v&>L~[:';4+AѢnU(ုP mq̷8y,[>sϜ;UvraN.١5&7'ɹx#LV0Ɵe|4ƙ %%fVh;;|RyF3-"Vgfϵ>gRg&g]NYꭖUYn/Nl-Y6ܳ lg=Y`Fj4Mř-lqf3[[]Wٮtmܢ*CP6BC!U}O;WRVYqg݅W۪h*jiFYemFAdrǷ{=餧!"W[i>w:i;)+iiNPOyTu/;ۤ\FHC6ړ3Z^&Hia!g M{v_fR8UYʜ۴8h4o'˝00`AٵO^q_Cv".F5}G'X'4eJ8$ia8Z 3I%YH8䠓9leS抧 SVv T?U~x6[r)'b SAr'EZip`(=hW/W^SKvuB3}iVE\:k\O뾂xtαWQ$|Cm&jOeRbM[ݵ+Ϲ+jY5ۙ'>왍gq5r˪7 a {?cI{~>+"@nc +*> R^7k{,\+[TuՒcgwG]f5G>jQ}Ԥx6 X|,}Zfjt?Oe*SٟT`K;2Nb x]lfS7ͦn6ukn /[|v/F~_Fو]le#.qوF݈kods`C^`'^]s2zQG]c g;BkJoG X84)m1׵pl!vo4&'?r5k0ibRi &aɵd&۷̼]`q9 C6Г,ҿ9 ! hBJxe~مz3bԩ{*b,Hr?1p, zn WuOmme{Zk̼۬=nQ` hsuyi;[E`Sᡊp^U#Z PEvl=*X kxEy ;UoI)hߖ]4U):h/O܋ˋ^ n&_S_[VpS?D٤Q5Uw1[MbX{RL(K7)",Ʀ /={zmG6qAqBMCs"jK.]q0xx-|.9"jZ!I|1eŗfRbE!r *7Z?e|%1n&b]$0F+^s x ^]e.^¢? G"G)]*ny/ ^ 8b#sۻV<ȡґ{gWӎ+(e[cGu#ǁF %Ɔ&Yn\twET~H$羫WW/sN%jsg.%ޅ fx;XcGT͔LT³Jx/p[q?XS8>Z- `UbzLeTTqrWu7c0 XdB;!aƢJ1'IQf(9>m&D[f 5'4vQ+dՈUDtEH_%n]/qu1ӫg}eΡCL6rc^o^xTa.?ְUt*ZNպM<:wr @;;0g=o*[]yV bz[͢ˠӢD-X8)2XǘMld%(&JKżO:Rd#XlVYv0w~g4{ž,uOK/lwgP%6S@ FGl ji&L":r@͓h}-¢䄶 nµ !r>K\DW4P#HQl(1㭂!34( sn?T@1o]>kC^-Ġ" cqy^j<tw֭v~ ,ʈs)),C3xJ'fĔ#)prS=Q$3xfG>i;ݮ\լrAwZNq$MC'{P{vl#gϿ)[tQ{WY$6ϿQXSZ"K਺)GJs<&<ֆ܉X3tMj[M8ĻAG%mG̠&-sU$̚ZڤxR='b}I꣧#~'"yy_kqǎ+:|;wMčĬ˙f LLLBӽqz>VL"ʱXuSڝC܉p.wbW'ui%F"Иx.vu At`{Dms.-~cE)`OnE A6:)X ?QHf@vuH"8 wQ_;&?uOw:m3DI%'p;zDםkkˉ@j x8JO0&ixX\۳^x۝Ci̬2Ppr,Dn`W1mpC60 ha)BSXNW_qF.dZTǣ4ibF~H˓:> B/bxǨ>p}(#Zoz>C+Pprԛn_|(^|;d7&P}( =|(+>{-}(* Pԇ]DkPCQoz>ػ}x1o}N~ޘ>T8l&CU PPXדC ׇrk;2R*^$ ڰC1>Ծ2'GP`Ň|([:yc2PᰙUڇrÇR\nCA҇z7e'jNc螺zQ[iPL*=?H-zqz/o_)AUfw@+?:T.zӥV ^kJ:Un]FScFA<*١Wf7=o ݾ8S/ŷͮT'?oLfO*6u*[Q@{Q WZ(ZP\kD ׉rlm9R/*bG^ {}GqeX/"N3%ȐxSB;Nuve3Y ڣåR]oT!ʫz?0gEm\U f͌hF1`#pRL?nfXc!X:̡ 3-fHy"a*Le`Wf׀fma >hBײ~  1W&}W\OTTe پ"f'L/vx5{4ؓ 0@`[` 588>anu k|=/kTIk^ )xnHL4^ |o|->:($E" WNջ5|W7k|So!^Cn`RI=YR2gROϓ#!ϗfژǝᆩI~_?!L_Pg=JB0nؐC6s0o0pi_u#91;;xas2"u)q2"uIX7`k# }).BQ$&&&ѪaT4j8x ɑxTI:<1J6?Z@RzRҴ^K©V>in LruM&]C^QGO$SĈNF)cPŢHMC+c\!bpuVɪcŽdq(묎;AuzѩIaku::>8YuZ|::>؉TGϓVa:>Yl28l9Pu[l28 Nz­ x=i`@{ScоHMV׼ߨwnj]dnpZաĵ$}Һ/Gu_&u_к/ZeP{rWAq)hjّNԤU@<}j\D HWΧ ,6(i@džÒ>*[%wlP<& [bw\@r~ |SnY]|$!]PaRHT_y}q=\5#j2zT>TWܩ3Hd%Ѓp|nM-fM} 1vW#ֈ: +,L>P/DXW gLc?V,@+b~Eb"ߘ{ŤrMV/Skr_aN+dkxpY7 orOc$S[Y:1`ʾV8Ͳ,+e]Rwe]H ֲd *ӸsJIci@ }!H/1>29SX9,253a&OTT-]I}J}(3-J̬`3HfG`fNYL0˺˼Dd+8 ‰ri/İA-e!FjOCx=0ԫ5j*HɵtxZJMW;T 6TȪD@3 !`;fвeg#8DV4H A.Z !Ujyl gmxjlThï9,.pJw ]l|Sӳ\`jCK CQ _?IFk#SɐlfqݎL?mf>pVWxYT{" Yw_Ȝ)FCBs: еbX 2I,›ۻ5D!HWoYixGPKI˾Fb":k|Dl9n)1&WUYS!P޴gil}}pt;؏nx Xzy_n>~pAP%1A=ˏ[3N]_1U[4'ߦ՜lVVAMmUPzEt"P^ Ey?uQԃ΄]xK=,9ɻuv)}ǾwRmL6e Cbs(\;& I]gr#o0E ml4U{ )Bvm)f;aGpC8gmY5?)ٛ)vWfRPvR3 ~ BȜQ~*^Yu@M@E@cϘWH{_̣ōjޅMWCG*+ x-v`=|p"+- ]|Da+Yw>V?C0ҐIQ)0Pñd-S4+4cӚx2:oPÚo۴(Qˀ{>sᦫmJSRa' Ɂ۲t22Lot'R#ȴaH'wHth~c(Q<$u7dFFJaύP[G$wmru4'wc{7~^e:MNўH(R 3FS't*JCGx1D/ݫrzS|MFDAj @4H2NyeezN$EЌdeUH{Jն$EŀLң߿7)SdWR)d#/.}"?ղY mRr]Қ %y7 Vx2-<p 4jO)+?9 N'QL4h.Sb(!CiB#-h EI %5/B.5!x]|iNEs`2+K*,wNlQ ʘŸcg1\EDqvON7Nĩli=Gpۛb~Pl?VbN(jWcSn=Y6Q26(ui뾙iLuAKzcr; eີA8SG,'v@}.?rMNT$xp3B1л=4{vޮv\0ؐ~R5)LJD`W~S jN3[v]?=C4͎P`#M##&ۂG8^`_|zQ LҫomA e ?!ick42(lͲA lPhOe^]trfdVfMeErҝH>(D~y9 1mFcH5:MA:WS^NԤF5V@iܑ YL #CZ(K8^rx8 [kji/BGCH`X5Fw~m#FްI!e/䶆.m7rV}V8› T`N1q9La-jgѡ#̀N0h=#:轚#i=Gu':7$([m$Gvr50bGMC:y;䣸2k anI;J  ijc@$G<&vYeho9QPc/j.ȃNNÕ c:Sz{ۥ|:wTz@)Fs GEWҤe+v V@Mw/\.i'&59IT HTj,!r'}Wi7qyVdXQ 8bVv=ン)%գƊ@e%Ih~y3 CM::JO_c3d@ *JX@vjE"uXN=`@xwDL>WJRWmͮ(,y\G6)Vˋ a<H{8&b$a8Be9(8hqp`6 ]ـD(퓦I:GZ[5 ?ޓCD0mCzD  9>auނxr-1mR:j{oPpliTNE5 k)46a|Z<pluz!C^b~Wwf3lb<]Y,nj}d_$#v"U٥ch~BEnY yx"$&vBݫk8@P3`J.+R(v!&N>Æjk ճ_ˊ0 |]n~ Zyg[~`d28JȭX+@\+<GTwz=]XYF6o'"/n{m؛?J:~QW?ӏ;D=>hHns- ,Nh)z/!E=7!G6#)J QOzCcaq{.+Ri+QjZͨ DIUVMrxF\DYDO}l.4!`F@] 3;KkR,*Q>>F7RaR5];QQg6?lİN['m͐FȶF5:@$G7,P%iJ0`}ܫзP2)KR,M蠲>~+56}Ԇy#^Gc>]+2_/Ch4 @ݘvK, 0;O$yIƵOb>Yz`=_Z$=A/{_jdn 3H[kPWI&V9L;FH/F@Jb<A/n1]3gVDL2cq$=sұi_?MN3S?S х݄Ya;8$4B{{3C.o׫(F@0,Dq{#5ITWQHhx 8lXpVYoݷCf.ߟchݲth0 zi}04eثEET%|(E]SoOMdS{j):== g0У %[`ViO5gϡME'Zr඼Um+ޛA\x+mvL )%5 TcۜeX[VJ(A2&Vu0V?l8!mՐ nʔU׫_$ |2&)VmMv\x 7ˆ ҡI]i0aTfTfCc4' :mNVΛvM?EvmTCɂaA}?3``~yXiPd0`\0c{_07?`(qzPzpJCσF# 3m4c_0A%hy{PpfX4L깽.MSmLsw㙾$Nd(mRU u* Ds&̡T*vL O P#+P#kv?Fw 2t6 ^؋Ҡ1 OWUbFqHSŌW}s3]`ٍi\!f[(1#u @_7' SOQx;C?Grp&M<jtN|xTܘȑȷ)0mk[epm!yJ`nkYm5 %?{!]2HvgϾkVۈ|_Q24=2g %0/ 8;c>=TGq*#v=Hc#z^Q ,i}H;@Y>fGu 2up]EQ G-{^0\%XLAhu!)@ ==ay3*|(3]a o iraW/uvAXJ0ӏ7x=9I.Wߋ[oWɭ`s+ V[mz%|蝻 m} >Vd(RU } *͗8SޯR|}?\7w*p̂ntN8:MaT[tYPQ jpQ= .1=0Ѧi5dU 3+`ͅ, 6 0d ډòz2Eɱ%SgMY0>U(9, Xy*`z:^q2cAAlƢܠVHK[$68dcTzd`|#ỸXVuf49UY+V37@6d5lm3r ^.Ab\uu\oئ$LKnuME(97V_3?,h7R#缀>ѩ2PYZ׮0BUw;,.jhy )4OagUdb@X|jA 2I)H'Z-@%DjO# .W<FyJSx]m }v:% -Q C9Gl)]ҥY4VTgA3f|J8woڀ$un}(r-P(h3{J g#56 ̗;k @CπfL0xVܳFUS_aŤ{ff2Z 6xd_c∌$yJfsFl iCg" a(~3Khi,L=U)̢3=Ӄ4{fZqrϴ[wIvs42{[sUQ rϿ<,{=SOPԳ˝5g@3d&O@r^,xfrLdhk G @M?GwH=xdfZ4xc?bgH,Z *,:7kco+@[wIvG}d(x* Fgo2Sia>-g&Nk9 "GWa$61Xq$k'` $OQEWueb- .TS'a%ʺ;2 4:PLgU4!LH;-VS`J#)eMsiji4=Psi%~ z0AMe9GMIRWU"MnxXůֻo6AV 6d qOsm n}M ]>Ç1j*/N9W8GX"glz^\/W52# T4SkkJ\2~1W?zAC,Ƅ*Gc_d]΃[5~"U_F'9wϋ+8,eGn >o$ÊB%~ wū$&8W]3]!~O#n!6Svͧ<.,w䁙(E}CXV)y[H&l=81I|\n&bc <&&xNI.%3S` c9/W$pOY g֔̅RGR1_ + ,j~zac`?-WLJߘϻcL5hJ}U62Ec 'AK9RJ.U;l!Tf~\}f۝+뫳xu\VDolVHMu[/4~?KnDb&]O!w3ZG(-/4pSI1fBiUک)_Ga~CS@ME9Z0Tj.UZ>my\kѳ8 TTL ՔT A] ⦥M6*N4vBQaSYMn;u3 i괅%:%H,Q;7<3wEJgN͘7ug@.c>CgYQvם`9 &lpÊz;X8O NJC6*a9qr]b8RoV>=&+_=rzޏܑp:<;D\ALV]Ti0ȴ{ԛ S ٠PEb`躝=6>(d%> 鲝|_ܱ}iSǷSw2T^AT^D'zQ}DzW+TC6f֜~aY5'[ȬsX"LYSԲ[v0,bVfiUrU^Uy}Hr?^E.۰Żt8=~*2˚ig=b(Eؠ!Gaۼ"-Uhb 0P)sъ*x]M v3afR x=n ^Ot}(YG4zA%*[CpMŭfqV\k~QJ60n償y|ѓ&;Ndlj{;hI)+FN?e#/=Ëَ ے7MoM`+rXx}KA#Ev竈B7䫙~ _rQ X4@47iLƏ¾Otu8*!{7Fԟ8Α aJoa;?Xp]E$ئ_~@,Go7)ާff 9&駛4^_mE} Qϴ֏҅IU<:ϳet6IE <4?9Z5B(8!glNӊdPF%aw4[;eBO mAG56ņOBSݤ:oM~x{l((C쌙]ϵ0!(ƟBv̮fEփȑa9LViipH+9>!fij(HhHd)b -W.p튗 7ɥ`FmVEQ'lQ_+ 4N[ zxLOHٲ)bR0ЈiXt)j21rƅv\"ˈ"1{x8#nĬնf؀mހHsj 79rX[{jAO؅xBXK4tߤYG;՗5{~!8Q~v2pgG{ /8O{(?Ȱr`q-8 |S9v?LZKk҂kgmtyFMA^ F{WiXlj͇k9C|GJ. ܾՋ]j+i >Mz}OeuBLpiy/Hך ORkd0Z=C-8pU`FzaA}& 7nq.v'XVՂ2#~ߛ0ߛ)DiuN^wܩ;vQe K=|iۗ)k{T,>(]Jzn~X>K&^UEK۽`(Z* 5t3 H8mͽ^\|b2gL9fJ, ]vtцH [gw TO/@ƃJzj6k3O&PC]V%jFaocHJSc*ָ ƫu.FTV1rm>d+wo>+XoT*g 0mI(Lu51m@A[bZ0f 8mę$ZLlp-1aę? nR{5)m6){3{>{u`c:9^<ۿ]^2R\蟧WM]n:YE81vE2e\V^~aR}3R*jhԧ,S 0}J&YZ7yrϫ;:J\H}?ys4*ō˅ODxąo.F/ӡ 1&d4Os$U~7t ƹMXOߦk'z#E? ֓F!1$$TLe?ɧyzo$ӟo/:!=?\.8J)|g 5t{IgxR/T{nkpphԽIRꒃЉ#ҖXڶUt_1C=ݛ{V{fZ=LJE C=jևj+pӶBʱ4_QQm"ز=U4Xҭ?ޓz՛J;6ֺA9D dcYRٴ$j_|y qJoʦ],/WERD`ݜȚ:lw+]~E#In;+CX&r]m,5o5w$g*:v#GԈ+&zO4E-VX[#9}Lu®LǖCBb>LHȬP%^O;ѿX,,Nv扔rQinGS1z<=llgEDgc0CҦ،g;tTl?u5hFλ8bW[-Ϛ^ vuL*', oX;ek7mt8H,.]V[úZ><^? K0GBAnRfgڋύC*sBYO #ⓔu ߤ,ߎk_luEDP[r0SJyra-?2À}?O[' ɼ|}3}]};:P1kޓVX1w &HǬfE~AIAбԹ_-/0H^hOYS)lw9(,FG2c Zاl+i rlJ@x|d;t3ƂFdFZʾT{h]kI#vq5RBE)7V)!\M=Ì/_p 3FC/ ?Gvƌܬq*YTfSI9mum3ٹfy !cĭ@?9U):Upw nf/=x|.GI:(Ţ۱cK,9K,KR#)4D1+9.י9W I U:vr iX0a qTΑ)OayP5$aL,|.țau٠j?tiUod2e,¤x Cg~A6c3faX2p FORގKK_쌅! #0GԋFQA0Hkx2j SW8ZED 2=B2 iDzBnŪ_d=:_("W3 [+3@bMb֊iҟ1{sYfոY+e{oN %XLAm۔) MG-첂ĈAS9Z>-'O~yg?Bt2K.=OoWQC\5o'jdp%']$Ǹ#Oծ 69c=(яnP凹յ6 OG"|$J%"bX-r"00-݁%dB9"bOyX\?>Zr|b Y$J)݉L?\?-SKֱ ibj~D nLJM@iZmSkÞYy#ab懙oeM̑wcc&,ƔtYE Hb7?*İ~~P 62 T8S݁vieHO'@ !)Si?USVU|(ĥ=BSUVP8:Y~\ڰ?; /Q8Dz Q@t(Q@[y;G__8 Fn X>87F8<l8@'$O.wTI`l'LLF ^CJ~1%Diҋi΢zDk@B|J,}m`Pl&!wd<k|o(9h+iV~4t@OYWŢx⇟clp tZ6ɣ$eHԟNƺH)&a7|zs S7!\T%23aU_^Yx06<'_/F[rErV 'IgADb7֏ kKLXA*_;W0V_ V3rAŀ0;<|.UZxGI@w;@4 pgQ2doB %0(دNxOWxq}[)Ë)'XP}!ոARϢ?!ň.sbsZ_`Z[N褉NuTgCG MEIU80(t=(h9n.ҶdtPiڑ[8h5w udۄ%|Ro;[ 3)a㌵<{*U#:6ChS2;@fmY"2'{ٌCR]x&xytLJ<3ڣ'ٷū$Cou/FZ~OLyYezuQbfԺ(,A:vs7O5y̐jd_>eg 8I6s6([;2,%)L"Ԣڝ]bJX@H PcHARPSPHh@#l:,~j2$pHAևRP̈́)72:r7LȓF7I(jF@HYLE% kz`ry|%qqJyvP,x7!xV ๳<8x6$p=xS58T|y;[k{KYyJBt/+i$GM)qjo?okd,r&Q?|@mc5Il*FovSLiqf+OCːRo^/H&੧iv}FD RW\eH pr|=coH>KN@/;P/Wk͊$v"$79N -ۆǼ)a; JT[[Y쓘U,`4bH8̛:{GO> pM:I;UkU UWS ן8c}__xݎ^YlwԖ ΙDǀZ$Gg) pG1#^Oes,_ (ҙޥlYjLh?[|1gl603&?%!0JCg,-aPhI@;PXmtXj~ԝ%# >TC7`=h'"Ew4ykqc Rۇ?<rϏj~ya>4Vܑ vǐ"~3nyVA'JKYK3b;RN@ 8 t$tpGOw5206I5|D 'u4a}}6T_Gُi;>J P\<ɾ(^%^:o'^q():%Hݏ\(zd>͍};qM-3=Dd:c>rB H0{- Arӌb\SLW}}j[Z>ggNR$vzG6P̀?|؀ l++洳nG1LvQ BkR %qQ`3嚲o}qGn~;_ 74ƙs^)>KMGz .vzX7Zo\46A[așJf:+7əeh&Ȓݱ|ajb05\e-%ө)[eDrU{m9I' "iJZLRMMޥHp onCpsܿ|qΙl fS>}aPHN͉-(pp z$h(Ae91,2 ko89ž5k2.-D/XYl<[c[l+_kDP5-ޕp+[6)^֔ĽTk+@*n.J_AN6:JYMmVS=}H pjVvIvZ.*PUrQ0btOVq-Ÿ7_]5 ۆ%bjk eжl.; ;ȫR[e u6[}qe22󋳲CZam-( rWk1XrLb5/Kl`f@sٛ;{Lzw<_,pKjJ5T#"p^-f̂o[iZ%6󅲦򋯢*0L|1UyVW١g69kj]NyQܵ+Z@k&d%c D6AYfϛmXjl~Xz`9,%M+Pҋ){5&2 o (kE"Ϊ~1V  i>nVS X]8L/<D K5T(E46&8f|0F( l1 0F{YG| \19l^qlw~gG[E&f=U6I#w-%#dpf>\)bST4 p@5y7]E(;1n`.F>mnNqmrP qNfXMø2\$4qTw2\DG·i>`/M_Pxj S_k5?. 0QP-N֞㉛Tf*l&z7i7qA&S1b&71cMXSk1ִcY#wTw $X0\@i2%N.T (. /2<_UwT!iX8YPZ~&'7{ׇ܁8 Zɍ#'@N^ " ".ȼV΢zYi`}BbBRĠHk-KsQ,VѤ?Xu`}?%|TCTE4gLl15`|zP#/$"^Ee5WJuY';3Izt&ZHræ =MY|y5*Xf9j>-OαCK'7e: r  |Ѥ?pCIC5DrèbPYQn) 4h`Fe%>1EQvmmϝ-r{Qp^ #e 1* DF&eyFujS# \\O=P {d$ੌ[9EB!0gYPV^f)`{qLr釷l~L1R)lH9hsdBSi*|].zCm%GdS+CEe1E ~KOtc"e$ /Ep,X./h 8P.La6RYR1q0Q灥T|ɓÃ"ғU]yz9]E$ئ^~C?}I}me2~I5Dx1E<3+埿#,ǣtoR4x:<_Fnr?3TMAʿ\E4`ݯIu~Q/ |8!m {|-A Iby$gxfj>ɀ6HO!Ԙ: JdOa)W<f "=AHc2@dn-7Dj-dq/ MjEo2t]u1!BVB .z2lqz?Z{.!dt 622=e*L'=,vФv3F?XѝXeX{\K۶@&J3;6ľッ;bOCnOS`$v??x_E[Z8͐7O-="W`xw߶2E/w6r2W@ܪ@Qx(ƃZK&l|I+JbS5*'ٳlȅ801 ʆ?^frrO)iL!BW+>՞p*uŻyWO+|Q//Ϊ/;+rBi6?#$ 8xW9Pj )l)]{bUF8Q_5],wK.25W7ܾNS϶QL%Qkiqf:P 3RH >c79b̑Yl "R$I^'b{e_Gς+6otRM$*W*5w\Xsۓ5}|,#*]i*v9!S`o".-87.@~rBl?9AL.KЃX,7㝍 5X(ϗa&EzĨUT$^*ds߹fhe%^D!P"<ؗ>YV5؞idv؊'4I G{UhV[*tn/[&f6?eMl!#խͯ3qjɬ\xq'jB+[y2P# qjSwUB"Ւ# n-n"7D &|ڙC[ }.KXinBBRBv (;m(8 o%o.Z/10FvRlc$\$y>"K\VeHgWKxD#T?6z-ک%r( %s~(^k -N R_4ktS:(=>{|@fQTVnC:RIQTO3?-Aw-(7*t= oy j2?Ab570A"l^$Tbv> v\iڰ&M` =BmT::PYTNr;Zޗ8;Kȫeۡ&S~o|o*ތLŴ'jwr%=R=2?8R=cEF؎{[;P',yˠ@;䥓{oM lJfUw+RN|2V񖥥L8/M_f2ZP< u+YB6>aO8ԅ2ѳ!wIy;];J{g,a SLr#v6Jqԝ~M#RNWy0MaVo|Y 9HՏk*ѨV րxUCP%OFku qVAJOУϚpw;?%3Qfue@Ss"Q4D$-ÙɎ9v ioq&dhH? ^)F 0OdU -$L`+J^Ut,RUlvl6l7m~Tnŷޯj$sXCkHa+tRpbN@K*&rBDIrK sjra>dMS61GlRHP=}~Rg//2$MLjU|gۧ<ƴԓ/R# ԆCWA5*4*8&_d;/]M:3ld. ׿bo!$eT . W ʦV&,#4@V^ Ẁ x)p4+{7YϴKa⡻䔐Oo',xkwk}X?ٵ.%rnR eVu^&pp * k]|*Lvo?WJDNϐ-)cufu%v َ[Z(,~iCNӚ%Ό)$dI! Bgpe37Ysm6 )/JxZ|1,)+IlvF*R ,8iqNt'Pr@!cCOg ,M*&z̩%%yГ\|tgz8sџ3;xκ^;gp># ܞZ:a71C04ԚKD“L"0aLfa?g gRTNi؍yXv@#gXRN`?sd((?XSm.xc؄fclwX`;wUd߰;hqP /2OzQI/}M L++؉~ `:,2ߣG 50EUX5%osۃK3, lD`V0'l?CbϪxCA SSX0r'r-3SMsq9N2{/թޛɮF,DcygtaGTN"Ә/I 2M @ ,CШ{v}盇n/Y[sc q#ѵ^={=8ݸ8Q`+;Pqsh <*&df&[yf\ZZ!Sl䦲N%E8 Cy9z6G!Ctx;`tdce &A|tn[^;3N$lr(tv3VY7]} FȪkṷ̈̄%wtc [Ǝ_SGwfTېʑv򼛜Hizy~oΈkD=e[PpZ/L] \U@l"ȞxC BכD]D?= Őە@%q~X  +9W&e k;NpeBf 2b4ʫԪ mx0[oSئ2(MT S!<ԥCLI=&A=5L+V*?P_fNhn3縩s)&Q_ 4ugRi9_hgl9*m 7uNu7uN;Lݴk:8_ZpslzCp;`'i6 :u'h.ٳSM-}^<>s}G'(bFeu3u.mL__׍aSjyi7;M^heAn]~*<ǰx'uKd (u yLw]\Z)wVJ__ ,V( J)ӟB=OKR]ݭeZHpc5bGjfiC~|bp^O!L7i:_ןכkZyQ,zZ90{W{.IS2>V%_QGsrpV7, _*5]t)^o^YoxuZl!WUMs ^0f>- 0 gyݿqfZcAIK0駴O[Wm-2m/tϽ3)Jْf[BNA-Tr!Nl `*% ),P]B0tal|:JRn.تݘ%Z[Yp w3ݚmK_+k1EL1VmlBgα'i>oyTͲXLe}`c؋|Y'"\o֙e:{g49NĆ|ǣ<:NZaLor~.]r]?pV|IGG1ob=S0}!6 l|?9#sλE~7fKx!SEf:#/__>IeyHKD )aVz'zQjXgK9aKE*Ejǽ"==vXC~vUw4 1CSp G|okaP/bh|["1VwRgO/DYXRᯔ+aKBÚeU^t%{.l2)㩦Z\X=5W2/飖!Rˣ߷4:Y?ܽ6[h2~Iu[ ?ӲY?Rϳet6=+[j+6UW:0Kol U)d U[HYq-7R !mc,XuDmfwDᑯBq=9$4T=0oYtWI}kz!0qt9elEسua,v]{%MLKW1)cG /¾|1'`_lJe6?V#kkL6p7m|OZp1I̸4xIvs1K4p{鶧gjgnǴϥծNahϖm#fM6)VKLbQC5zBT/i`Ygjcz2=ş|tک%*]MIjg7=?r?qI/hMbS\%fh@O_پG,-?*wo<"~ڦ(:'ٷū$;oo'4JJ͎Ge$*~kel^+kZgZԚV5:¼Z2(ytW5#s匋Ȯ~ kauz[OP(ħ Tp8IR.E#'O2-$U ` M#HS`C.TsCP'OM$q b$U7ZI޻cjMo/dgnr1|;* . LNEiHW~+mݎC lT<lwG_ƛof j@/0 Wwa\@]]:g2ph$!rd\np|u_v8$rJ R=fNy68$$DɈ E9^>7'R Le@ZH)8X'9DC%i'V L:fD^X-HYGIlşLHy<$d ^;f`&L5}{ QM+@~SGWяU5 ^A)I]R<~:;+tx/޾gfFy?cH &ĚkrF٘X-5~#93 qC,U{;'_/d1kFxhj5,_s-5q%3>ɜ6MLw2M*djKM 5BY4^2fqgNl*=To1vX_ܴBP& .omB!#";P22k TO++V\y5LN_m"Bi-Uy&tnPZ)|jTDSLsT{KBmո 9U3A I1Hb7;[G;a3]ydP_UU,jLl=t%زzvsvU X*V!5 +B=_;g–-nٗMm(y?VmD>z{r<_Eqi9hRt<@w<=|q,SqWΓlUA~9hnln]n7vNa#8'GcS3;&9axD=-0SMB*<#b##Iol<Izꜟژ⏾vx۩է$%qMW jHH7Cf뺟 Mmͼa^piawl{3;j 1Ŕ0{''fYEbbШH4"CMQQj'@&XrrƲ|z/ҳO^<h=W jgYrtrR|@]kn4]. \>?XzX%U7MޭK3"ص1k[=브NDtWF{b;  SK9Cr:LND1򋃶 <{]Y+ާgtI{ \牖rcB! #6Uix%ȵR\VJs+yvs9 c2wh(ܠQȦwt3Fn ;rOOn;fɫj5-ȁئg.$bPtl>G:#smQ,FvƼjY~JEeynArznA^zJq;UW0rP]5&$1P=anl.oЧWlإJ?˰byp`~C 0i;5RÒR>8,yh-%@f2ESa5MVL(j/fNYsƶ)g&*jRyhMku\!Y[ ݳ'泎ckYDZf1_0).h2k`kJdcoyU|Du([3p,,k4&Kzz;pZ7#MN^/j7vZxJUڬzs2V‹+k `@`U`GhkC`vD]yj]%9[4)6c'x)U`M31mS~SfH1Tvp9*#Uڠc[jTv1[}u\.sߠj$߸*R "ȂRolrT}~0Tj@Szh)%Un*UR[IHiHG7;`)i'X)U M31mS~SfH1Tvp9*#Uڠc[jTv1[}u\!HKU/}xL.Er^afFϏx2hj&UR-mfLnqƬu8$igseO{-i5mٻ,\.do(̠aa\.08M䥠(q G^uy݃JsY}w!GOVM ev8軾K_I>t]QO:4>֪*~*en=1ŎY `rj]rP&:F ZFƻ gEJjb.X:4.E,ը$QRUHK|f^T[ Ӯ)']|A{3Wv!@&dxvW1XɽQ{RF1U3iT 4YV?"@lt57z/nc.yF^N"/En8 _Q.WAVOV6 k3ZEL,6V] aVXZ5+x iYz#)z4>Q̸sZZn]ۊ{^ތbϮq".޾xm~,VHIZo^$300D£\73eD.W nBe`"<}l?[Wgu̖ exp,mDSj CIC^顕i?w) !ҾW.Nȭĭف!Ĝ-*Y ɹfsZkXn[fe 6ӳllGr׮҉gZSn2ZbFT? 1O``2ϦYQO7(zCq_x⇟%?Y,NUW6:va9-Y JƋ$U|BS!ZqM2:a* h͒vg=O _AOaM<ѝ2\jŭMک =5tPyaV~ܹbG%㯩A`K 7a7Ԛ \a<RL'"vң(K@:lߘu_,S2k6ijk9A9:D$o6V4џ >H}"hCVzx Wą"A.mnԙ?jL>'[-{ydrJ]5%Kj0)#0|S`B`?9Zϩ$9Vv-n"O%_^<{*{jkoOf]_EیNwy"=D.)4|zCTPꨞ﨨1EtEڟLL-pFg= btQǫv* m$M9Ts wZ rG9j3Os(1Ptp|RS2 Qijo:9d }US=̩͜d#h]?|p6=5uZQrmj}Cת )4Y_׫B9~1'N'_|7ZCHȩ`ހ[*o> ˵@~Vɛ?:OAg?MlZ:au5>j`giE ̑r7޲/֑"s|&`9^jHr̲IwVrqЋרKVKr_%ýcjk'|o?_cK.KL*oeQo%kX}謹0-K0KV$(O5|> X6\_0 [8B* ULmoH ݤ~{oGm S{2~IUDK-?s~y&Extg~m Q_,)g 0VCǿUEayPX)˚l4˜ic Rx|U A,W aEdB6.N_,(NJ%SdnjdiTPTMg3iSkiSʹC0ۖM1s SVmlJB {|0jqDn֒drI[4 Ku0ʺ)\zYFƘE313  uÐ mZ՞ma(طmC*p.#.e%ENT(d k{܏vl 8 )\uu!V%QfHMH@݅Ӽ p0Mk$U ݃ hr6cH5k1ȕB3`:[et2w7uv)gȬF \]1Coc"fW4r[Nd?aڙN\ m:_.]aS {xK"<1}D1Syq$sf;р2ͶgQz8$B#v14?Q$Yb=y|wnD}v?Hm/Y+:޽wkFKaZ`./v;aA"Rhgj)^x}!J]41pMJ)be[4]|W1ZD ˻G_e/sGzL@r  Sxr j<,ƒ?P 3^?&~SքȰ s8-vn6vj&911=, KA"3m֥܂Z[ IȈZ#׉ ɿN 17]nk$Ն_ 7U4qG0-(gl-Q z~X=۽gd9.&IfTlbPm8Vsox'e/?:70!ܪ|N/jVwǛ0`8(y@v*sۀ/|W7oſMгNQ?ϑAr bB?p Fo!CAjߊ9K!7.g~J/2-)'l/wW_mu@/3Yj46;)x 1voϗ+ա7-"q#'Ӫs ;*3PA3Yqr;ET6.uv&ibʼn}9F7EţDh Z#~&j 3Zg`/ =SvmG`m!>rs˂a.]+ hbNCuխtgvFw+a#|n[fv@'9J~[teه=ٟ)a6 Vu6׋eࡠn1Į o忪Or T|EͩX,Ϻ5 =Y` :SJMz[fSF_Y!-dq~ZL[6&ȃ@@|Ntu?foo|6Ϊ7!Clv]Ar!VaU3 _\b/5z²vs)tSj4l \|Ύ"f˛le8~hq9KwmH͕]ѐۺ;b-Іys*i?9.=ZZ \Mfk}m,j#.'jiCLL{vCostU8֗Y`.j`< [%v-q2.ӷѴ{{%X2,peV3@re~f+WK0M,\Kͬ 0?BzFEIie^y=Abq˂g Z"ܙ2_E&x$Ǐ@.%!E;K*AXw\^g~SC 8Bȗ R>*(i`fgЀpC3LU " 6,K_CC0 f3's'KKޫd{@WfP,WD]l]hY!dLP`\2෬uF#w%t4a, ! #K, Ƥ'c5c{,fZ(Bsj&m6Z"=5;zrqzmT^bw;\^CSa?ƫ,UD!WBQՋo<0VƊNg*=)?#@0&oww~wwݫg`2 kD*҇Ǩ>*,:"Ba(k[N$iuBGkaƿg+aຄ*+B}>սvZ3::.z^p`0n\Ik*# {vXt* {7pM~aV 5)@[^K!W^y^&UMQ/}?xXu^Qu^ցxބIFmi±tȩ@5Fw$o@n3r"WAwx<:I3oVPdHu.PwӴ^I0ZzFr?'^eBSbhp&snLנKk[O|v@_s1P2%1֐k ܕ|Ӄ"Z%*p3{MD z{f8[#!^PʅI*^ŸRCh *ozl ~D0h(C1}$i =@cRXᝈMM9Tc,W9v"fS2K_3$'*Qh_=N 䚐`87*HV<ݘB--2|n076t!yIp E[BwƧN">iHr;JIeE~97_JYRJC兤Ա]+H_R5U/x"+ 5WQrS~X&]u}z}(0mv >@/eYډcG5x7|aؼc`:(3-YP S$w}p;NK\\t\=aOl1KYbc1KgscJMZ\[$[$Y$љѲ;B>/z;][5a2X?7EUJl_hT+{I%ըJ}F/&l]M̾lZX[;;_.[p)ݡ=k5d1*Xs;QH0!]U&Zq83ؤzPY)(*C`t@ҕh릷G^k7ep7B7 m6V<*;:ni*? /N|>y7M==PFn!Ǟ[ Vɹ woÇ_oy|=GɐӀq5F4a5nV2MecpF ٣05)eL>W.OO3g C o^̲uǛ,[~Ui8KgyYer_U|]Ǡ_։|lTqSu@o_KA */ܤxRx!I7Rp5+DC[#QE؃#PWJ0:_uw ATab1'G*(ҙވ;g DKOO'[tF6+(T+ KVڮPF%;]K Mi\h`U\XmFBxat>5 T ɓ/<,ڟPp2¤)FɞAڲiqح Ld&$YJICx ՆIӃItR>YgR%KC8$Ŭ&9zY]E~<^_-G&bn)9 ]1M{l2|v@BzlBمMnTXk>\n& ;PxIJy`rBKT|@imIu8k|yX=.C1Ce$[)t1$SZ:%")_MBXkLd9Qsd] ofqc_j{Vq+ѭ;vLaë;^شm8 5ػsSm*TܩSU[nUE|ZUz;׆/ݽdxzVPM Uq _NAoarv,ӏC/+PՄ!ԟm q֓c+/$<1n:zQ}BN)<*joǬS=(7BL AL뛰iaMB[TX@BQwaZTś)%%qwN+y+.JBog[#oկV<CHFT6\KJAWjx%#ļwPwWimIKxC]7φybŮuNLtX܇A歜伈r@"f 驲<=JJJy2*eR6eUc(`@<-fT7]J嶱V,JM^}q"y\kŲnO3vc,ݍ ;#!F>~m8~^6tjqH9R[T,嬉t#ιɛ]L3M1c2CICd`>Dl${'UL&AT|uW`UTI PF.0 Baa&*?>D 26W)Z?whx^g#[WčC,)#=ҡD3ehg`%!L}}6dEl%QP"h<|zCz}eHYQ&p=W@Y NTSVi|n\i c8Lȗ@L[R Zߠ2ʰP$?bjxM§?9.7*B .($d NjeC-H%bCO?eKÇbBMkrփL*gI7FLҲJѝFwNa:N,0I0)6̍x>:C8ckrݤd@adFpzd9ec7JC>vha1&G !.g|;Cr^)S -iM!jf ,x"ڈMf0Δ~`}{\ݐ6,߮6wޢ5ZKQ x ($FCya ^4toU^\\`:ɫʼnbڜ^6#_e_Ņq#s^Vc|K @kJZH:ƅҫf:ӴRHX( B,ex05yN ~ηǢqФaQ;o +C!5ҜRq;q97yߚIyt-跸>k"m8mECۧb^?f+\!}D5=[gyNI^o|1]' v Bxa`T'{ɾ "4,ᐺ]ЍE/OߣƟ_G/We!Sզj:fB:Nʋt칢t~ ![bv4Y6_Q*M鹭~%*Äq,WN]0 `Zm4xFf0=wG6HǑy)%޴mS9+xK=Ry`cZ?!%?Ajuz^'Hj&((T9 n4zƔMPnMS_F 9f?8mjbWRc. b*Hgc;uiC麩j_CjԒa:sƌy}QMbLo[6#\B9m>Z84+"^)|)Pp|S)zk nTqG ܁0s'Pt[HaMdxh#$YPfzw<`{v|pFG $h1DvZ_(? 3Hظ[OW3ztC`xlrcD ּ;Dw AM@p㡏>jV]hmV(홻-8m-v;~K6YUиꍴq2F)!82񏵎,5Mi3_s7弨\^U+VQ8kյNsd 4 }Z<UH?C"aV#u6f7K mLCgPHȉhpcVب^gc>zKݼ˿ [K1 K;`xe'ze&{(;A&>w/s< a/(|ڜ7c<16*zkjc͍$f\_!8FJNyPK*V)cVX1IME4=^A|ڊ,o&@,#V eݤlBZ3[bthyypWF4-}vW PG{BpV$ {RO=M[:nڴu@p(BI>ECJ >U,țӝ=zDs +4&6N?n*h: B5N*M>6q-TMlv8 X7;ߖf:nfDCX8xjۑlu\8tsb cg.X>/ƣL#ư]۸{UETq(F}Ҵ>[ׇ[Lzdž[kݶdWMw}om-w^\ê-XMJy0:-C0!S=b.:蔣Sx}&xb o%1ǰUSC4PÊQX멨Wun9GGbio0^(4{85G~Cœnb/BҬA4琿Ck7(c|2 ]CtL9X荣7޸^ߙˉQJaODzXbU^Ƣ^ 5iHkzϸJz=;`弫m9ezl ~ b[>MF2Fb7nJ~XX跣ߎ~YY7M-a%O?h Dk3c11 Ē4b%J jE[E; ^"^rL>RTV?ThOzIOǹ'EwwjT_IMBLLpXRmz/Dc1iQLe,XzʂpWN͸rڂ+\9iC!K CeK%vVo2nqWxpmh1ԋ^ F$ :LxƧqs,;!!-Mm)"M%i(U|Xdqˤ>oۙl'$;b@oC(0FzZ;1Nu:%(1@tbGNPkBu&~=]}PQ/cY`&[lh*C+ QmQ9~ʖf$Vbk'_bO&R|`$^6]0ChN=|fg(d)~ѷNEyjyCsȋimAk_Β_IK6) ɼEfni+3*Q$Sc*|V}mL` ,pkȠ˘M&Oׯ...I?80XJiE _n5xZ״.`a%: GVWӢeE&Hc 0|E7wB#"/仈Z"FLaG,&e!XQFvtt@䧧l~CP-b̉2߼ѧe<3S,[~ٲE@%Y~aHMt4NϞ:e.׋YQgg0 Y~&#?/.n^QYX|!2aP#Jg>10^&֙j_}F C/$&LW Z%6lcwA13hS}?_.߯bS4!hXr;f DhX&ছ;[Usi@@%u)1`NA,٬n=BW^ўl=< EL?\>d*8+?KBf}8 <=ghmr[W`k80BsY9w.{30>=tC[о85E>B¤sE+_HQAzwL FtVO-A -f< [diX䜠~Uj&zzɮ׼cNCw8p__G;Z-,$b%a.k-FJ~v#':  X@N呥c+vwbz@\8^IjRUC=H\VSw_Y~=|䫏º ?R҆;9 ZƲ+bԞʈDww@ߕ84g_hő~ܼ1%(0T٭lA0B:L$:l羚^!X?YJА?ԷGoZ;O1||>c_,|w% YM=s{HVBAPrY-b3! ZK=SzVb„P&KWn;$@$ܣˉׂGEH!"T nYc(I H1@4+1BD"`"cxhl%$**^yvܻ@*:Q_&I W!e%KM/Zll@O!DA`i6@mLSWAZsORmlI xѠI/LXzzYʫeߕؘ^G!x.q#׭v73wRgQÆǰ! 1l NN·B|ЫghqoZdJwzY#ǴyƤw[2jO)JY8 Mgf |FP'g7 ҡ6ZƳ`P}4',C|7R =nN}+C&nLdĆ 4GttF05Bx#4nKGOg_,ٌ5 ;Y{䟞׸IdFL=ldDɍ|{\!ۍQN9rH *F wU`e e37ʻXeV}ÞiXgܒ! p&E)+F'zPKwBviF%#`S +{$aQ OQҜ 2Ө||/r%4PI!gXnB !^q &*HYodzYEBȮ1`HƘаj!Q#GEU#s]hB[ǜ ɲ&mR&gМՉ>۬c.vc9kh\6T16wN&PwxDG DscjBD9oWޯ~pC_HY&Y#9KiwnbohR' r&nr>nܘU;u٘'9]N4+;"B|lwG&b#g!?gHBD  UE4PcD *\mV }v̰PT P[ā#C&@Ûl}^ u_}.~(>3{Yzz8Ϊ- >0[%kheu8{~psM5 ?;>GDt4=f`-n`6V҅I ѰG3?Hh"x$oS |ǀ:8L5 7Q=W*WtMrk>]GH@3 NHo縄Zf'ank{;[>m$ڲ}_ն4<5?|`s_$cE#G>wj9|؉֘ȎcQ9pT+?‹jqh1鄣c)Șcdb{D p~jtk;o)Vu/"cP %:Y~73$ș57G{nk3dedbOGrʐ28S(&Kףi6X4WTa;wmY;^Z3BWYMSׯc_Vr ꎿjC;m72B"pVPN4CSR= <:l߳ߦx@B~9!?|~`$wO(Ytfy9ëAf򛩎`l>DG51lv Y栗FZ^B:'j x.Q=U{^Nj}ETAbfb閗vXWwA =G`!|HpC]8!t:Z' P0NymMolF063>e~|XmX(0H&UPM DR|c/_CƲTxԣ81UvyCCub럞Vߕǩ5ZM4Cxq}=QSlF>*7A>)1ܳ39f%RsZ:??۬c.1JkMv`S~>~Z'])go%+j!-t)y=iJ_锟]]?Oz];p*%:2_uJ&V\ܳ߮SQ]h>ZpOů cf0D9,I5\hn(XFo'su\d-SiyͰ K~ס<>o?=e<6FynO:KwzmSlG ÆԒ@cl3*JH[-g'5̿+UY<M+R0o7~Mxo _,R=))5m& K]cK6{_v>{:K4*XѨq߷ɦx|8ym5깗zN㫚S\_WfLtJdv*ՂǧcTgՙ+u :^O[a:HHA*S*ʳBX9]\}OU~A [o^#>{Ǩ>*,:ʾKs^=;9%]g%@9Oul@J*s?_|GMi`~czux"exv5獴 McCbU}A`;I4w='l¼x G6F*dz9ԎχUBxzϨܼL``g,al8\; ^Vp߁) ΀s4@ ]xwME:=Ts)`2y4cܒ#d* y8/tL͞5tgvoՇ Ƴ_vo|mwX %>? _oe}xB柿3Hȳ s./=-xt^<8;c>ȅ,gg,`{\ o - ,bhEEyX ONUc2e'ťW~9K#"dy B֒A(C[v1qnH, ]6>8~ͽPK{!(zUah HI <e ćn @6"L*dRDT%|Ɂ|w & e,=wъ(Q<5wxf) fc2P>;$JDʿ e4ԁH>MDhk"iV>MFMÎ_4ϖ++%d|i!wlb3: bOzxfF?g*[f6M's<ƗzEpFE. I}(MC3~2Kˋ >?5hbg(#P0 q"KvSJ.6Du6jճG^.V?CE>::|x\̀mg?;;fi9lgf-q`@'K&~&Dt94 LB^gbü&!tBT 9hWcr>F(5[JџMѳ D8"br( WLL|\|(0o,w$rV%CL+)0Ra OhGqcQJ#F}Bp`E"0!TtNM $^#;WB;m pk4#L\Nj|r'}zµfCW`^fq\}&G@۬WA&0 ~R \7#Ѫ2 i  v=?Z3Euy#S@S4r^]v끇 W}ĔN²0LXV Φ˛lфx ر\dYSw6/kz1 + }XafyeZfA'mΞ7\vQ߹"t:UM/SتQu$&5PF'DZ[M;i8iL &BzwN߯6G+h{ɡLإ9tiwgcJzcڷmR_:&ad$Ϳ:o je3̰&ߤ8)ǼQ߸CybLnx'W؛lv|Zg7ASmx 2dXJAD"T CU&(~@-j!g sGu'ON⴯بKN:xobK*eDS:v~ioG&myM-'bI㢙A,:Fb6@4c^gQ-$*OZt5\~?NxỎV+3EPq&!YH=vӓsfp=D$2i\6 q=xу[.2-ݥ}K-=Պ:5L܃,ӏLV'` Ra/GDb75H~LK$F\c;_ECmzιx:f~8a9<ќh]oCD!-p[>'r.%y^7j|B rww@'4HY:J/ F)BeBGG̍>q|ކN3PH\rGOҸ*J5bԙޭtJ1"錌F}4o3mRt|D;$gi:WT:<:ߨl-}L/wx*P'-r -& Z|oFw:{#b㌜7C :ѹZXϣ?ܝ?oA y~tJ,zb*ANQ*u*(-i+ V[c%تb߹6\DcD}/e2?-SK:< (k:~ÞT4aM2Fw}/ФA&&mΝS˝Dtc#*[Tśi ޜ5oNysśӆCV?Nv ?pKd &{ş[Ɵˉ711pc mtb\5.9CLk041M hEukm ֻ.S.ؑ"*7o._/&1"QTPF rI\ILKjbm- [=Z8Vu2}uz/>݆t !|CM ˳ů;,h/e#AfLf2@DfY ZV❦gD*C7u$<8b7~,3>0ڐ]Z&f%)vsǝrbGȈR y]Iw(Uj8:)q;||`%;ue>t /m_H;u/>!0| @% _!+` FV„~0.8!8Ӈ";{T:jNXN߸&"`"F_7jvRB"jfBoDL;F`QfaQlI#.j\"*Y:KcEi0o2|Yo6ja L.ؘh+UldkJzNWMjTN4o -K_1{T\L f#N}䫏³)E{*"E?M N\t]u[2"zYbwwbL^Ӝည`&l(4Ja4;C*pV׃gVQ}+yO1yff˛Xoly,_y #[..Nu+ 0/56{ <<|h.\X>^3M^^_W06H^hS7 4t ߷ d7MiZq0'|c *c b) Zm&(ؔgJ:sL}ʼk+YA{%[.ЛRO~ ` V\  9-8o\RBSY _`( Z-GFQ(z\-P!\Õ @Gջ;fW=lrCD:'F*ĿS#57BOt#<_@ #P-;P-c4(B+Ym*czɧbb̓-&4쵘u_R-;Nnghyle|šb,tnnr:@.4"q5q/c\ Bn؂ep4~EqbA +Q1EU@\=h!_3 ~Qԫ>\~JtԛNNagNYS#/bs<G:NT9M:(O}=6*`cpMv#ƳC Tbi QelHݼSEx&ݻv.gW4MldzGNxG-M!fZMU˵a5{:FDu( چYYKX*  M97OzF& yؤҖ;ӐWUu?ޔxyNܛy'ZOy?H>"=ރڤQsSfjikI\{ZSaV3SmNk8"l(Pԍ|q`c@Et b45JF 7 $1M nbp# n Uz,]W:u85aWT^ClJ1 Jzttx@/{@wBI乀4@4w|Ĵh@"׀r]XD]^WWIz8W!f<:uRIc?HBmXFI20@5+肬\!Tw-qpwaZ췽gmk"Vs> U&ŒN]ˉ{'-Ҥɕ ]R{tL~F?.BZ)$[ƘKUyObU1pwjhw;0HD#N/>sakQnW}'UITcT0֍̗fjʦf*qkcl2~f`u.Om#Ga 0NRE0+_ze}[ٻN3sNM _-I(,9Us\f4+*o8<+@%?]8Qrz&C{f71rStfnH Qxdbi~0Mho0KKM8m2d z@` .XG28TZY:ՙTzY2w9@d9]%xڍ+҄KXd#   _IF']jr:yfWTC^hXe3 T Zs# D̍>M9 p?:1*9wZ zZL^n2I4WFpKfِ'hAJ}Kb"l&)S-3H]7-H bv ғNuvNh8*껮Rr{pz)9;XQ'{ݯ>|X@?Ox.ի1,^"{IYz;LK[n[ dk8bAwӎ^O\hI |bK1zQE,c$gr (lHe]ul#0/hNr#vfZ,^Fx+[[%g;^"nq7ĉϝv9:X-;VǭQݣ$8wv\xh(;ع0idlwm Z0kE4  iEbw1< .h(T(=u__ZQqe9In:vQǼQ BZ_[D GggSd̜uؑC/x^xVأ-N* r\5k䚫 wxOE/"DE&Qcf&$*uzJjԒM#@eyY]j+ RFi飻_^sD`o>|-CK!GCnAjMBUrRN܊>줌⡧IzRu@m?CPXLW9c\ quLKb9~git:u68)8:\vKtp3jf_w K=Ҫ"ŒaI|I{ ^ d7#(P:{cM*֍` Uӆ1 \P$i [^$ċ Lj-Fl1bo%DaAGWЎQ' @Q#-14S$hz/(D@P_ 6xب_OǑFo끷5 6ե:<}~|Z0@US*Uǔ9 h(Qe5HdRU>]ƩdcSqjFsvU19=Ǫ{oWof{s]bï|Ns~9pDPC=.m<%*g D3 ؁Ў!Ք- q0`/4G/jkLv@C"7IOCe_O^k ~.DCN"G)X&'vD017J!b.#VJ1 Ef:VZm cR=7&F?ѿR~vx:S ޱc!<&(G47'v16'bdP_W#LC{L螠YOqN,X# DP1o}lςF# #'0y>;ڊE7YJtFH]q}b\LU,n׊GC6"9F`Rm9,})vh#3 "16M⧧m>>pE(ҭQ+gR3&[8hr6M#׳bQJ xJiDl -[u>M0%}L{+a`JiR(nb\h٢