diff options
105 files changed, 2134 insertions, 1216 deletions
diff --git a/.github/workflows/flatpak-build.yml b/.github/workflows/flatpak-build.yml new file mode 100644 index 00000000..60041212 --- /dev/null +++ b/.github/workflows/flatpak-build.yml @@ -0,0 +1,24 @@ +name: Flatpak Build +on: + push: + branches: + - default + pull_request: + branches: + - default + +jobs: + flatpak_build: + runs-on: ubuntu-latest + container: + image: bilelmoussaoui/flatpak-github-actions:gnome-40 + options: --privileged + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v3 + with: + bundle: hexchat.flatpak + manifest-path: flatpak/io.github.Hexchat.json diff --git a/.github/workflows/msys-build.yml b/.github/workflows/msys-build.yml new file mode 100644 index 00000000..02338380 --- /dev/null +++ b/.github/workflows/msys-build.yml @@ -0,0 +1,47 @@ +name: MSYS2 Build +on: + push: + branches: + - default + pull_request: + branches: + - default + +jobs: + msys2_build: + runs-on: windows-latest + defaults: + run: + shell: msys2 {0} + + steps: + - uses: actions/checkout@v2 + + - uses: msys2/setup-msys2@v2 + with: + install: >- + mingw-w64-x86_64-gcc + mingw-w64-x86_64-pkg-config + mingw-w64-x86_64-python3-cffi + mingw-w64-x86_64-meson + mingw-w64-x86_64-gtk2 + mingw-w64-x86_64-gtk-update-icon-cache + mingw-w64-x86_64-luajit + mingw-w64-x86_64-desktop-file-utils + + - name: Configure + run: >- + meson build + -Dtext-frontend=true + -Ddbus=disabled + -Dwith-upd=false + -Dwith-perl=false + + - name: Build + run: ninja -C build + + - name: Test + run: ninja -C build test + + - name: Install + run: ninja -C build install diff --git a/.github/workflows/ubuntu-build.yml b/.github/workflows/ubuntu-build.yml index f2d3ac8e..047de23f 100644 --- a/.github/workflows/ubuntu-build.yml +++ b/.github/workflows/ubuntu-build.yml @@ -1,21 +1,26 @@ name: Ubuntu Build -on: [push, pull_request] +on: + push: + branches: + - default + pull_request: + branches: + - default + jobs: - build: - runs-on: ubuntu-18.04 + ubuntu_build: + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - with: - fetch-depth: 1 - name: Install Dependencies run: | sudo apt-get update - sudo apt-get install -y meson libcanberra-dev libdbus-glib-1-dev libglib2.0-dev libgtk2.0-dev libluajit-5.1-dev libnotify-dev libpci-dev libperl-dev libproxy-dev libssl-dev python3-dev python3-cffi mono-devel desktop-file-utils + sudo apt-get install -y meson libcanberra-dev libdbus-glib-1-dev libglib2.0-dev libgtk2.0-dev libluajit-5.1-dev libpci-dev libperl-dev libssl-dev python3-dev python3-cffi mono-devel desktop-file-utils - name: Configure - run: meson build -Dwith-text=true -Dwith-theme-manager=true + run: meson build -Dtext=true -Dtheme-manager=true -Dauto_features=enabled - name: Build run: ninja -C build diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index f8a965f9..aba1dcd1 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -1,8 +1,14 @@ name: Windows Build -on: [push, pull_request] +on: + push: + branches: + - default + pull_request: + branches: + - default jobs: - build: + windows_build: runs-on: windows-2019 strategy: matrix: @@ -17,8 +23,6 @@ jobs: steps: - uses: actions/checkout@v2 - with: - fetch-depth: 1 - name: Install Dependencies run: | @@ -27,28 +31,25 @@ jobs: Invoke-WebRequest http://files.jrsoftware.org/is/5/innosetup-5.5.9-unicode.exe -OutFile deps\innosetup-unicode.exe & deps\innosetup-unicode.exe /VERYSILENT | Out-Null - Invoke-WebRequest https://dl.hexchat.net/misc/idpsetup-1.5.1.exe -OutFile deps\idpsetup.exe + Invoke-WebRequest https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/idpsetup-1.5.1.exe -OutFile deps\idpsetup.exe & deps\idpsetup.exe /VERYSILENT - Invoke-WebRequest https://dl.hexchat.net/gtk/gtk-${{ matrix.platform }}-2018-08-29.7z -OutFile deps\gtk-${{ matrix.arch }}.7z + Invoke-WebRequest https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/gtk-${{ matrix.platform }}-2018-08-29-openssl1.1.7z -OutFile deps\gtk-${{ matrix.arch }}.7z & 7z.exe x deps\gtk-${{ matrix.arch }}.7z -oC:\gtk-build\gtk - Invoke-WebRequest https://dl.hexchat.net/gtk-win32/gendef-20111031.7z -OutFile deps\gendef.7z + Invoke-WebRequest https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/gendef-20111031.7z -OutFile deps\gendef.7z & 7z.exe x deps\gendef.7z -oC:\gtk-build - Invoke-WebRequest https://dl.hexchat.net/gtk-win32/WinSparkle-20151011.7z -OutFile deps\WinSparkle.7z + Invoke-WebRequest https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/WinSparkle-20151011.7z -OutFile deps\WinSparkle.7z & 7z.exe x deps\WinSparkle.7z -oC:\gtk-build\WinSparkle - Invoke-WebRequest https://dl.hexchat.net/misc/perl/perl-5.20.0-${{ matrix.arch }}.7z -OutFile deps\perl-${{ matrix.arch }}.7z + Invoke-WebRequest https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/perl-5.20.0-${{ matrix.arch }}.7z -OutFile deps\perl-${{ matrix.arch }}.7z & 7z.exe x deps\perl-${{ matrix.arch }}.7z -oC:\gtk-build\perl-5.20\${{ matrix.platform }} - New-Item -Path "c:\gtk-build" -Name "python-2.7" -ItemType "Directory" - New-Item -Path "c:\gtk-build" -Name "python-3.6" -ItemType "Directory" - New-Item -Path "c:\gtk-build\python-2.7" -Name "${{ matrix.platform }}" -ItemType "SymbolicLink" -Value "C:/hostedtoolcache/windows/Python/2.7.18/${{ matrix.arch }}" - New-Item -Path "c:\gtk-build\python-3.6" -Name "${{ matrix.platform }}" -ItemType "SymbolicLink" -Value "C:/hostedtoolcache/windows/Python/3.6.8/${{ matrix.arch }}" + New-Item -Path "c:\gtk-build" -Name "python-3.8" -ItemType "Directory" + New-Item -Path "c:\gtk-build\python-3.8" -Name "${{ matrix.platform }}" -ItemType "SymbolicLink" -Value "C:/hostedtoolcache/windows/Python/3.8.10/${{ matrix.arch }}" - C:/hostedtoolcache/windows/Python/3.6.8/${{ matrix.arch }}/python.exe -m pip install cffi - C:/hostedtoolcache/windows/Python/2.7.18/${{ matrix.arch }}/python.exe -m pip install -qq cffi + C:/hostedtoolcache/windows/Python/3.8.10/${{ matrix.arch }}/python.exe -m pip install cffi shell: powershell - name: Build @@ -63,12 +64,12 @@ jobs: move ..\hexchat-build .\ shell: cmd - - uses: actions/upload-artifact@v2-preview + - uses: actions/upload-artifact@v2 with: - name: Installer + name: Installer ${{ matrix.arch }} path: HexChat*.exe - - uses: actions/upload-artifact@v2-preview + - uses: actions/upload-artifact@v2 with: - name: Build + name: Build Files ${{ matrix.arch }} path: hexchat-build diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..ff138137 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "flatpak/shared-modules"] + path = flatpak/shared-modules + url = https://github.com/flathub/shared-modules.git diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..d1a988cf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +Change Log +========== + +This is a list of changes applied to the project over time. This file is +intended to provide a categorized summary of changes that's slightly easier to +follow than a git commit log. + +(Future) Initial Release +------------------------ + +Changes made since forking from hexchat. + +### Features + +- Added some error messages when plugins perform invalid operations. +- Added support for web+ URIs in general. + +### Bugfixes + +- Fixed some crashes when plugins remove hooks. + +### Removals diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..8996735c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +Contribution guidelines +======================= + +Inclusive language +------------------ + +This project has an inclusive language policy. Due to the long history of this +codebase, this policy only applies to new contributions. For everything else, +"we'll get to it when we get to it" applies. + +Inclusive language is important so as to not trivialize others' experiences. +For example: + +- **DON'T** say "abusing the protocol" + **DO** say "misusing the protocol" diff --git a/data/icons/meson.build b/data/icons/meson.build index d7926e83..710e0045 100644 --- a/data/icons/meson.build +++ b/data/icons/meson.build @@ -1,9 +1,11 @@ icondir = join_paths(get_option('datadir'), 'icons/hicolor') install_data( 'hexchat.png', + rename: 'io.github.Hexchat.png', install_dir: join_paths(icondir, '48x48/apps') ) install_data( 'hexchat.svg', + rename: 'io.github.Hexchat.svg', install_dir: join_paths(icondir, 'scalable/apps') ) diff --git a/data/meson.build b/data/meson.build index b905c314..6c6b1a9c 100644 --- a/data/meson.build +++ b/data/meson.build @@ -1,11 +1,11 @@ -if get_option('with-plugin') +if get_option('plugin') subdir('pkgconfig') endif -if get_option('with-gtk') +if get_option('gtk-frontend') subdir('icons') subdir('misc') subdir('man') -elif get_option('with-theme-manager') +elif get_option('theme-manager') subdir('misc') endif diff --git a/data/misc/io.github.Hexchat.Plugin.metainfo.xml.in b/data/misc/io.github.Hexchat.Plugin.metainfo.xml.in index b4412ce2..b32a9191 100644 --- a/data/misc/io.github.Hexchat.Plugin.metainfo.xml.in +++ b/data/misc/io.github.Hexchat.Plugin.metainfo.xml.in @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <component type="addon"> <id>io.github.Hexchat.Plugin.@NAME@</id> - <extends>io.github.Hexchat.desktop</extends> + <extends>io.github.Hexchat</extends> <name>@NAME@ Plugin</name> <summary>@SUMMARY@</summary> <url type="homepage">https://hexchat.github.io/</url> diff --git a/data/misc/io.github.Hexchat.ThemeManager.desktop.in b/data/misc/io.github.Hexchat.ThemeManager.desktop.in index 53cac289..705f24ba 100644 --- a/data/misc/io.github.Hexchat.ThemeManager.desktop.in +++ b/data/misc/io.github.Hexchat.ThemeManager.desktop.in @@ -1,5 +1,6 @@ [Desktop Entry] Name=HexChat Theme Manager +Comment=A simple theme manager for HexChat Exec=thememan %f Icon=hexchat Terminal=false diff --git a/data/misc/io.github.Hexchat.appdata.xml.in b/data/misc/io.github.Hexchat.appdata.xml.in index 9b0bac72..0d67a0b6 100644 --- a/data/misc/io.github.Hexchat.appdata.xml.in +++ b/data/misc/io.github.Hexchat.appdata.xml.in @@ -1,7 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> -<component type="desktop"> - <id>io.github.Hexchat.desktop</id> +<component type="desktop-application"> + <id>io.github.Hexchat</id> <name>HexChat</name> + <launchable type="desktop-id">io.github.Hexchat.desktop</launchable> <developer_name>HexChat</developer_name> <metadata_license>CC0-1.0</metadata_license> <project_license>GPL-2.0+</project_license> @@ -26,6 +27,47 @@ <id>hexchat.desktop</id> </provides> <releases> + <release date="2024-02-07" version="2.16.2"> + <description> + <p>This is a minor release with small improvements and fixes:</p> + <ul> + <li>Add support for SCRAM SASL mechanisms</li> + <li>Add option to hide nick from window title</li> + <li>Change SERVER command to default to TLS, adding an "-insecure" argument</li> + <li>Increase max server password length to 1024</li> + <li>Fix detecting some URLS causing a crash</li> + </ul> + </description> + </release> + <release date="2022-02-12" version="2.16.1"> + <description> + <p>This is a minor release with mostly bug-fixes:</p> + <ul> + <li>Add `-NOOVERRIDE` flag to the `GUI COLOR` command</li> + <li>Add `-q` (quiet) flag to the `EXECWRITE` command</li> + <li>Rename installed icon to match app-id (Fixes notification icon)</li> + <li>Fix escaping already escaped URLs when opening them</li> + <li>Fix Python scripts not being opened as UTF-8</li> + <li>Fix `TIMER` command supporting decimals regardless of locale</li> + </ul> + </description> + </release> + <release date="2021-10-01" version="2.16.0"> + <description> + <p>This is a feature release:</p> + <ul> + <li>Add support for IRCv3 SETNAME, invite-notify, account-tag, standard replies, and UTF8ONLY</li> + <li>Add support for strikethrough formatting</li> + <li>Fix text clipping issues by respecting font line height</li> + <li>Fix URLs not being escaped when opened</li> + <li>Fix possible hang when showing notifications</li> + <li>Print ChanServ notices in the front tab by default</li> + <li>Update network list</li> + <li>python: Rewrite plugin improving memory usage and compatibility</li> + <li>fishlim: Add support for CBC and other improvements</li> + </ul> + </description> + </release> <release date="2019-12-20" version="2.14.3"> <description> <p>This is a bug-fix release:</p> diff --git a/data/misc/io.github.Hexchat.desktop.in.in b/data/misc/io.github.Hexchat.desktop.in.in index 7bf6a9a8..5e00ce8f 100644 --- a/data/misc/io.github.Hexchat.desktop.in.in +++ b/data/misc/io.github.Hexchat.desktop.in.in @@ -4,7 +4,7 @@ GenericName=IRC Client Comment=Chat with other people online Keywords=IM;Chat; Exec=@exec_command@ -Icon=hexchat +Icon=io.github.Hexchat Terminal=false Type=Application Categories=GTK;Network;IRCClient; diff --git a/data/misc/meson.build b/data/misc/meson.build index f7f1c27f..2abf3075 100644 --- a/data/misc/meson.build +++ b/data/misc/meson.build @@ -2,8 +2,8 @@ appdir = join_paths(get_option('datadir'), 'applications') metainfodir = join_paths(get_option('datadir'), 'metainfo') desktop_utils = find_program('desktop-file-validate', required: false) -if get_option('with-gtk') - if get_option('with-appdata') +if get_option('gtk-frontend') + if get_option('install-appdata') hexchat_appdata = i18n.merge_file( input: 'io.github.Hexchat.appdata.xml.in', output: 'io.github.Hexchat.appdata.xml', @@ -21,7 +21,7 @@ if get_option('with-gtk') endif desktop_conf = configuration_data() - if get_option('with-dbus') + if dbus_glib_dep.found() desktop_conf.set('exec_command', 'hexchat --existing %U') else desktop_conf.set('exec_command', 'hexchat %U') @@ -49,7 +49,7 @@ if get_option('with-gtk') endif endif -if get_option('with-theme-manager') +if get_option('theme-manager') htm_desktop = i18n.merge_file( input: 'io.github.Hexchat.ThemeManager.desktop.in', output: 'io.github.Hexchat.ThemeManager.desktop', @@ -70,7 +70,7 @@ if get_option('with-theme-manager') ) endif -if get_option('with-plugin') and get_option('with-appdata') +if get_option('plugin') plugin_metainfo = [] # FIXME: These should all get translated somewhere @@ -124,4 +124,4 @@ if get_option('with-plugin') and get_option('with-appdata') install_dir: get_option('install-plugin-metainfo') ? metainfodir : '', ) endforeach -endif \ No newline at end of file +endif diff --git a/flatpak/Load-plugins-from-Flatpak-extensions.patch b/flatpak/Load-plugins-from-Flatpak-extensions.patch new file mode 100644 index 00000000..e93e864c --- /dev/null +++ b/flatpak/Load-plugins-from-Flatpak-extensions.patch @@ -0,0 +1,25 @@ +From 918503d57c6740d20be68a6717158673a2a8b25f Mon Sep 17 00:00:00 2001 +From: Patrick Griffis <tingping@tingping.se> +Date: Sat, 17 Mar 2018 05:57:49 -0400 +Subject: [PATCH] Support loading Flatpak extensions + +--- + src/common/plugin.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/common/plugin.c b/src/common/plugin.c +index 3ad3c558..6addf962 100644 +--- a/src/common/plugin.c ++++ b/src/common/plugin.c +@@ -450,6 +450,8 @@ plugin_auto_load (session *sess) + lib_dir = plugin_get_libdir (); + sub_dir = g_build_filename (get_xdir (), "addons", NULL); + ++ for_files ("/app/extensions/lib/hexchat/plugins", "*.so", plugin_auto_load_cb); ++ + #ifdef WIN32 + /* a long list of bundled plugins that should be loaded automatically, + * user plugins should go to <config>, leave Program Files alone! */ +-- +2.14.3 + diff --git a/flatpak/io.github.Hexchat.json b/flatpak/io.github.Hexchat.json new file mode 100644 index 00000000..94cfb6c8 --- /dev/null +++ b/flatpak/io.github.Hexchat.json @@ -0,0 +1,78 @@ +{ + "app-id": "io.github.Hexchat", + "branch": "stable", + "runtime": "org.gnome.Platform", + "runtime-version": "40", + "sdk": "org.gnome.Sdk", + "command": "hexchat", + "finish-args": [ + "--share=ipc", + "--socket=x11", + "--share=network", + "--socket=pulseaudio", + "--filesystem=xdg-download", + + "--talk-name=org.freedesktop.Notifications", + + "--talk-name=org.mpris.MediaPlayer2.*" + ], + "add-extensions": { + "io.github.Hexchat.Plugin": { + "version": "20.08", + "directory": "extensions", + "add-ld-path": "lib", + "merge-dirs": "lib/hexchat/plugins", + "subdirectories": true, + "no-autodownload": true, + "autodelete": true + } + }, + "modules": [ + "shared-modules/gtk2/gtk2.json", + "shared-modules/gtk2/gtk2-common-themes.json", + "shared-modules/dbus-glib/dbus-glib.json", + "shared-modules/lua5.3/lua-5.3.5.json", + "shared-modules/libcanberra/libcanberra.json", + "python3-cffi.json", + { + "name": "lgi", + "buildsystem": "meson", + "sources": [ + { + "type": "git", + "url": "https://github.com/pavouk/lgi.git", + "commit": "95418635aa8151a516d43166227ea2b9d4c4403f" + } + ] + }, + { + "name": "hexchat", + "buildsystem": "meson", + "config-opts": [ + "--buildtype=release", + "-Ddbus-service-use-appid=true", + "-Dwith-perl=false", + "-Dwith-lua=lua" + ], + "build-options": { + "cflags": "-Wno-error=missing-include-dirs" + }, + "cleanup": [ + "/share/man" + ], + "post-install": [ + "install -d /app/extensions" + ], + "sources": [ + { + "type": "dir", + "path": ".." + }, + { + "type": "patch", + "path": "Load-plugins-from-Flatpak-extensions.patch" + } + ] + } + ] +} diff --git a/flatpak/python3-cffi.json b/flatpak/python3-cffi.json new file mode 100644 index 00000000..171a384a --- /dev/null +++ b/flatpak/python3-cffi.json @@ -0,0 +1,19 @@ +{ + "name": "python3-cffi", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"cffi\" --no-build-isolation" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/0f/86/e19659527668d70be91d0369aeaa055b4eb396b0f387a4f92293a20035bd/pycparser-2.20.tar.gz", + "sha256": "2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/a8/20/025f59f929bbcaa579704f443a438135918484fffaacfaddba776b374563/cffi-1.14.5.tar.gz", + "sha256": "fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" + } + ] +} \ No newline at end of file diff --git a/flatpak/shared-modules b/flatpak/shared-modules new file mode 160000 +Subproject 2c2f8fef2e926ff03158c0b3341526cce1b405a diff --git a/meson.build b/meson.build index 9b33574b..2f8fb3f2 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('hexchat', 'c', - version: '2.14.3', - meson_version: '>= 0.40.0', + version: '2.16.2', + meson_version: '>= 0.47.0', default_options: [ 'c_std=gnu89', 'buildtype=debugoptimized', @@ -13,14 +13,19 @@ gnome = import('gnome') cc = meson.get_compiler('c') -libgio_dep = dependency('gio-2.0', version: '>= 2.34.0') +libgio_dep = dependency('gio-2.0', version: '>= 2.36.0') libgmodule_dep = dependency('gmodule-2.0') + +libcanberra_dep = dependency('libcanberra', version: '>= 0.22', + required: get_option('libcanberra')) +dbus_glib_dep = dependency('dbus-glib-1', required: get_option('dbus')) + global_deps = [] if cc.get_id() == 'msvc' - libssl_dep = cc.find_library('libeay32') + libssl_dep = cc.find_library('libssl') else libssl_dep = dependency('openssl', version: '>= 0.9.8', - required: get_option('with-ssl')) + required: get_option('tls')) endif config_h = configuration_data() @@ -29,21 +34,21 @@ config_h.set_quoted('PACKAGE_NAME', meson.project_name()) config_h.set_quoted('GETTEXT_PACKAGE', 'hexchat') config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('datadir'), 'locale')) +config_h.set_quoted('G_LOG_DOMAIN', 'hexchat') config_h.set10('ENABLE_NLS', true) # Optional features -config_h.set('USE_OPENSSL', get_option('with-ssl')) -config_h.set('USE_LIBPROXY', get_option('with-libproxy')) -config_h.set('USE_LIBCANBERRA', get_option('with-libcanberra')) -config_h.set('USE_DBUS', get_option('with-dbus')) -config_h.set('USE_PLUGIN', get_option('with-plugin')) +config_h.set('USE_OPENSSL', libssl_dep.found()) +config_h.set('USE_LIBCANBERRA', libcanberra_dep.found()) +config_h.set('USE_DBUS', dbus_glib_dep.found()) +config_h.set('USE_PLUGIN', get_option('plugin')) config_h.set('G_DISABLE_SINGLE_INCLUDES', true) config_h.set('GTK_DISABLE_DEPRECATED', true) config_h.set('GTK_DISABLE_SINGLE_INCLUDES', true) config_h.set('GDK_PIXBUF_DISABLE_SINGLE_INCLUDES', true) -config_h.set('GLIB_VERSION_MAX_ALLOWED', 'GLIB_VERSION_2_34') -config_h.set('GLIB_VERSION_MIN_REQUIRED', 'GLIB_VERSION_2_34') +config_h.set('GLIB_VERSION_MAX_ALLOWED', 'GLIB_VERSION_2_36') +config_h.set('GLIB_VERSION_MIN_REQUIRED', 'GLIB_VERSION_2_36') # Detected features config_h.set('HAVE_MEMRCHR', cc.has_function('memrchr')) @@ -153,7 +158,7 @@ endforeach add_project_link_arguments(global_ldflags, language: 'c') subdir('src') -if get_option('with-plugin') +if get_option('plugin') subdir('plugins') endif if cc.get_id() != 'msvc' @@ -161,6 +166,32 @@ if cc.get_id() != 'msvc' subdir('po') # FIXME: build xgettext meson.add_install_script('meson_post_install.py', - '@0@'.format(get_option('with-theme-manager')) + '@0@'.format(get_option('theme-manager')) ) endif + +if meson.version().version_compare('>= 0.53.0') + summary({ + 'prefix': get_option('prefix'), + 'bindir': get_option('bindir'), + 'libdir': get_option('libdir'), + 'datadir': get_option('datadir'), + }, section: 'Directories') + + summary({ + 'TLS (openssl)': libssl_dep.found(), + 'Plugin Support': get_option('plugin'), + 'DBus Support': dbus_glib_dep.found(), + 'libcanberra': libcanberra_dep.found(), + }, section: 'Features') + + summary({ + 'Lua': get_option('with-lua'), + 'Python': get_option('with-python'), + 'Perl': get_option('with-perl'), + 'Perl Legacy API': get_option('with-perl-legacy-api'), + 'FiSH': get_option('with-fishlim'), + 'Sysinfo': get_option('with-sysinfo'), + 'DCC Checksum': get_option('with-checksum'), + }, section: 'Plugins') +endif diff --git a/meson_options.txt b/meson_options.txt index 100a5ee7..c2ca54a2 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,36 +1,38 @@ -option('with-gtk', type: 'boolean', +# Applications +option('gtk-frontend', type: 'boolean', description: 'Main graphical interface' ) -option('with-text', type: 'boolean', value: false, +option('text-frontend', type: 'boolean', value: false, description: 'Text interface (not generally useful)' ) -option('with-ssl', type: 'boolean', +option('theme-manager', type: 'boolean', value: false, + description: 'Utility to help manage themes, requires mono/.net' +) + +# Features +option('tls', type: 'feature', value: 'enabled', description: 'Support for TLS connections, requires openssl' ) -option('with-plugin', type: 'boolean', +option('plugin', type: 'boolean', description: 'Support for loadable plugins' ) -option('with-dbus', type: 'boolean', +option('dbus', type: 'feature', value: 'auto', description: 'Support used for single-instance and scripting interface, Unix only' ) -option('with-libproxy', type: 'boolean', - description: 'Support for getting proxy information, Unix only' -) -option('with-libnotify', type: 'boolean', - description: 'Support for freedesktop notifications, Unix only' -) -option('with-libcanberra', type: 'boolean', +option('libcanberra', type: 'feature', value: 'auto', description: 'Support for sound alerts, Unix only' ) -option('with-theme-manager', type: 'boolean', value: false, - description: 'Utility to help manage themes, requires mono/.net' -) + +# Install options option('dbus-service-use-appid', type: 'boolean', value: false, description: 'Rename dbus service to match app-id, required for Flatpak' ) -option('with-appdata', type: 'boolean', +option('install-appdata', type: 'boolean', description: 'Install appdata files for app stores' ) +option('install-plugin-metainfo', type: 'boolean', value: false, + description: 'Installs metainfo files for enabled plugins, useful when distros create split packages' +) # Plugins option('with-checksum', type: 'boolean', @@ -60,9 +62,6 @@ option('with-upd', type: 'boolean', option('with-winamp', type: 'boolean', description: 'Winamp plugin, Windows only' ) -option('install-plugin-metainfo', type: 'boolean', value: false, - description: 'Installs metainfo files for enabled plugins, useful when distros create split packages' -) option('with-perl-legacy-api', type: 'boolean', value: false, description: 'Enables the legacy IRC perl module for compatibility with old scripts' ) diff --git a/plugins/checksum/checksum.c b/plugins/checksum/checksum.c index 6aa64b64..4db14c93 100644 --- a/plugins/checksum/checksum.c +++ b/plugins/checksum/checksum.c @@ -22,216 +22,162 @@ #include "config.h" -#include <stdlib.h> -#include <glib.h> -#include <glib/gstdio.h> #include <gio/gio.h> #include "hexchat-plugin.h" -#define BUFSIZE 32768 -#define DEFAULT_LIMIT 256 /* default size is 256 MiB */ -#define SHA256_DIGEST_LENGTH 32 -#define SHA256_BUFFER_LENGTH 65 - static hexchat_plugin *ph; /* plugin handle */ static char name[] = "Checksum"; static char desc[] = "Calculate checksum for DCC file transfers"; -static char version[] = "3.1"; - -static void -set_limit (char *size) -{ - int limit = atoi (size); - - if (limit > 0 && limit < INT_MAX) - { - if (hexchat_pluginpref_set_int (ph, "limit", limit)) - hexchat_printf (ph, "Checksum: File size limit has successfully been set to: %d MiB\n", limit); - else - hexchat_printf (ph, "Checksum: File access error while saving!\n"); - } - else - { - hexchat_printf (ph, "Checksum: Invalid input!\n"); - } -} - -static int -get_limit (void) -{ - int size = hexchat_pluginpref_get_int (ph, "limit"); - - if (size <= 0 || size >= INT_MAX) - return DEFAULT_LIMIT; - else - return size; -} - -static gboolean -check_limit (GFile *file) -{ - GFileInfo *file_info; - goffset file_size; - - file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, - NULL, NULL); - - if (!file_info) - return FALSE; +static char version[] = "4.0"; - file_size = g_file_info_get_size (file_info); - g_object_unref (file_info); - if (file_size > get_limit () * 1048576ll) - return FALSE; +typedef struct { + gboolean send_message; + char *servername; + char *channel; +} ChecksumCallbackInfo; - return TRUE; -} -static gboolean -sha256_from_stream (GFileInputStream *file_stream, char out_buf[]) +static void +print_sha256_result (ChecksumCallbackInfo *info, const char *checksum, const char *filename, GError *error) { - GChecksum *checksum; - gssize bytes_read; - guint8 digest[SHA256_DIGEST_LENGTH]; - gsize digest_len = sizeof(digest); - guchar buffer[BUFSIZE]; - gsize i; - - checksum = g_checksum_new (G_CHECKSUM_SHA256); - - while ((bytes_read = g_input_stream_read (G_INPUT_STREAM (file_stream), buffer, sizeof (buffer), NULL, NULL))) - { - if (bytes_read == -1) - { - g_checksum_free (checksum); - return FALSE; + // So then we get the next best available channel, since we always want to print at least somewhere, it's fine + hexchat_context *ctx = hexchat_find_context(ph, info->servername, info->channel); + if (!ctx) { + // before we print a private message to the wrong channel, we exit early + if (info->send_message) { + return; } - g_checksum_update (checksum, buffer, bytes_read); + // if the context isn't found the first time, we search in the server + ctx = hexchat_find_context(ph, info->servername, NULL); + if (!ctx) { + // The second time we exit early, since printing in another server isn't desireable + return; + } } - g_checksum_get_digest (checksum, digest, &digest_len); - g_checksum_free (checksum); + hexchat_set_context(ph, ctx); - for (i = 0; i < SHA256_DIGEST_LENGTH; i++) - { - /* out_buf will be exactly SHA256_BUFFER_LENGTH including null */ - g_sprintf (out_buf + (i * 2), "%02x", digest[i]); + if (error) { + hexchat_printf (ph, "Failed to create checksum for %s: %s\n", filename, error->message); + } else if (info->send_message) { + hexchat_commandf (ph, "quote PRIVMSG %s :SHA-256 checksum for %s (remote): %s", hexchat_get_info (ph, "channel"), filename, checksum); + } else { + hexchat_printf (ph, "SHA-256 checksum for %s (local): %s\n", filename, checksum); } +} - return TRUE; +static void +file_sha256_complete (GFile *file, GAsyncResult *result, gpointer user_data) +{ + ChecksumCallbackInfo * callback_info = user_data; + GError *error = NULL; + char *sha256 = NULL; + const char *filename = g_task_get_task_data (G_TASK (result)); + + sha256 = g_task_propagate_pointer (G_TASK (result), &error); + print_sha256_result (callback_info, sha256, filename, error); + + g_free(callback_info->servername); + g_free(callback_info->channel); + g_free(callback_info); + g_free (sha256); + g_clear_error (&error); } -static gboolean -sha256_from_file (char *filename, char out_buf[]) +static void +thread_sha256_file (GTask *task, GFile *file, gpointer task_data, GCancellable *cancellable) { - GFileInputStream *file_stream; - char *filename_fs; - GFile *file; - - filename_fs = g_filename_from_utf8 (filename, -1, NULL, NULL, NULL); - if (!filename_fs) - { - hexchat_printf (ph, "Checksum: Invalid filename (%s)\n", filename); - return FALSE; - } - - file = g_file_new_for_path (filename_fs); - g_free (filename_fs); - if (!file) - { - hexchat_printf (ph, "Checksum: Failed to open %s\n", filename); - return FALSE; + GChecksum *checksum; + GFileInputStream *istream; + guint8 buffer[32768]; + GError *error = NULL; + gssize ret; + + istream = g_file_read (file, cancellable, &error); + if (error) { + g_task_return_error (task, error); + return; } - if (!check_limit (file)) - { - hexchat_printf (ph, "Checksum: %s is larger than size limit. You can increase it with /CHECKSUM SET.\n", filename); - g_object_unref (file); - return FALSE; - } + checksum = g_checksum_new (G_CHECKSUM_SHA256); - file_stream = g_file_read (file, NULL, NULL); - if (!file_stream) - { - hexchat_printf (ph, "Checksum: Failed to read file %s\n", filename); - g_object_unref (file); - return FALSE; - } + while ((ret = g_input_stream_read (G_INPUT_STREAM (istream), buffer, sizeof(buffer), cancellable, &error)) > 0) + g_checksum_update (checksum, buffer, ret); - if (!sha256_from_stream (file_stream, out_buf)) - { - hexchat_printf (ph, "Checksum: Failed to generate checksum for %s\n", filename); - g_object_unref (file_stream); - g_object_unref (file); - return FALSE; + if (error) { + g_checksum_free (checksum); + g_task_return_error (task, error); + return; } - g_object_unref (file_stream); - g_object_unref (file); - return TRUE; + g_task_return_pointer (task, g_strdup (g_checksum_get_string (checksum)), g_free); + g_checksum_free (checksum); } static int dccrecv_cb (char *word[], void *userdata) { + GTask *task; + char *filename_fs; + GFile *file; const char *dcc_completed_dir; - char *filename, checksum[SHA256_BUFFER_LENGTH]; - - /* Print in the privmsg tab of the sender */ - hexchat_set_context (ph, hexchat_find_context (ph, NULL, word[3])); + char *filename; if (hexchat_get_prefs (ph, "dcc_completed_dir", &dcc_completed_dir, NULL) == 1 && dcc_completed_dir[0] != '\0') filename = g_build_filename (dcc_completed_dir, word[1], NULL); else filename = g_strdup (word[2]); - if (sha256_from_file (filename, checksum)) - { - hexchat_printf (ph, "SHA-256 checksum for %s (local): %s\n", word[1], checksum); + filename_fs = g_filename_from_utf8 (filename, -1, NULL, NULL, NULL); + if (!filename_fs) { + hexchat_printf (ph, "Checksum: Invalid filename (%s)\n", filename); + g_free (filename); + return HEXCHAT_EAT_NONE; } - g_free (filename); - return HEXCHAT_EAT_NONE; -} + ChecksumCallbackInfo *callback_data = g_new (ChecksumCallbackInfo, 1); + callback_data->servername = g_strdup(hexchat_get_info(ph, "server")); + callback_data->channel = g_strdup(hexchat_get_info(ph, "channel")); + callback_data->send_message = FALSE; -static int -dccoffer_cb (char *word[], void *userdata) -{ - char checksum[SHA256_BUFFER_LENGTH]; - /* Print in the privmsg tab of the receiver */ - hexchat_set_context (ph, hexchat_find_context (ph, NULL, word[3])); + file = g_file_new_for_path (filename_fs); + task = g_task_new (file, NULL, (GAsyncReadyCallback) file_sha256_complete, (gpointer)callback_data); + g_task_set_task_data (task, filename, g_free); + g_task_run_in_thread (task, (GTaskThreadFunc) thread_sha256_file); - if (sha256_from_file (word[3], checksum)) - { - hexchat_commandf (ph, "quote PRIVMSG %s :SHA-256 checksum for %s (remote): %s", word[2], word[1], checksum); - } + g_free (filename_fs); + g_object_unref (file); + g_object_unref (task); return HEXCHAT_EAT_NONE; } static int -checksum (char *word[], char *word_eol[], void *userdata) +dccoffer_cb (char *word[], void *userdata) { - if (!g_ascii_strcasecmp ("GET", word[2])) - { - hexchat_printf (ph, "File size limit for checksums: %d MiB", get_limit ()); - } - else if (!g_ascii_strcasecmp ("SET", word[2])) - { - set_limit (word[3]); - } - else - { - hexchat_printf (ph, "Usage: /CHECKSUM GET|SET\n"); - hexchat_printf (ph, " GET - print the maximum file size (in MiB) to be hashed\n"); - hexchat_printf (ph, " SET <filesize> - set the maximum file size (in MiB) to be hashed\n"); - } + GFile *file; + GTask *task; + char *filename; + + ChecksumCallbackInfo *callback_data = g_new (ChecksumCallbackInfo, 1); + callback_data->servername = g_strdup(hexchat_get_info(ph, "server")); + callback_data->channel = g_strdup(hexchat_get_info(ph, "channel")); + callback_data->send_message = TRUE; + + filename = g_strdup (word[3]); + file = g_file_new_for_path (filename); + task = g_task_new (file, NULL, (GAsyncReadyCallback) file_sha256_complete, (gpointer)callback_data); + g_task_set_task_data (task, filename, g_free); + g_task_run_in_thread (task, (GTaskThreadFunc) thread_sha256_file); + + g_object_unref (file); + g_object_unref (task); - return HEXCHAT_EAT_ALL; + return HEXCHAT_EAT_NONE; } int @@ -243,13 +189,6 @@ hexchat_plugin_init (hexchat_plugin *plugin_handle, char **plugin_name, char **p *plugin_desc = desc; *plugin_version = version; - /* this is required for the very first run */ - if (hexchat_pluginpref_get_int (ph, "limit") == -1) - { - hexchat_pluginpref_set_int (ph, "limit", DEFAULT_LIMIT); - } - - hexchat_hook_command (ph, "CHECKSUM", HEXCHAT_PRI_NORM, checksum, "Usage: /CHECKSUM GET|SET", NULL); hexchat_hook_print (ph, "DCC RECV Complete", HEXCHAT_PRI_NORM, dccrecv_cb, NULL); hexchat_hook_print (ph, "DCC Offer", HEXCHAT_PRI_NORM, dccoffer_cb, NULL); diff --git a/plugins/checksum/meson.build b/plugins/checksum/meson.build index 25835457..e3008f75 100644 --- a/plugins/checksum/meson.build +++ b/plugins/checksum/meson.build @@ -3,4 +3,5 @@ shared_module('checksum', 'checksum.c', install: true, install_dir: plugindir, name_prefix: '', + vs_module_defs: 'checksum.def', ) diff --git a/plugins/exec/meson.build b/plugins/exec/meson.build index 3f9e8a32..782814da 100644 --- a/plugins/exec/meson.build +++ b/plugins/exec/meson.build @@ -1,5 +1,6 @@ shared_module('exec', 'exec.c', dependencies: hexchat_plugin_dep, install: true, - install_dir: plugindir + install_dir: plugindir, + vs_module_defs: 'exec.def', ) diff --git a/plugins/fishlim/fish.c b/plugins/fishlim/fish.c index c2c2b9da..0b24ed48 100644 --- a/plugins/fishlim/fish.c +++ b/plugins/fishlim/fish.c @@ -87,6 +87,54 @@ static const signed char fish_unbase64[256] = { dest |= (uint8_t)*((source)++); \ } while (0); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include <openssl/provider.h> +static OSSL_PROVIDER *legacy_provider; +static OSSL_PROVIDER *default_provider; +static OSSL_LIB_CTX *ossl_ctx; +#endif + +int fish_init(void) +{ +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + ossl_ctx = OSSL_LIB_CTX_new(); + if (!ossl_ctx) + return 0; + + legacy_provider = OSSL_PROVIDER_load(ossl_ctx, "legacy"); + if (!legacy_provider) { + fish_deinit(); + return 0; + } + + default_provider = OSSL_PROVIDER_load(ossl_ctx, "default"); + if (!default_provider) { + fish_deinit(); + return 0; + } +#endif + return 1; +} + +void fish_deinit(void) +{ +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (legacy_provider) { + OSSL_PROVIDER_unload(legacy_provider); + legacy_provider = NULL; + } + + if (default_provider) { + OSSL_PROVIDER_unload(default_provider); + default_provider = NULL; + } + + if (ossl_ctx) { + OSSL_LIB_CTX_free(ossl_ctx); + ossl_ctx = NULL; + } +#endif +} /** * Encode ECB FiSH Base64 @@ -97,7 +145,8 @@ static const signed char fish_unbase64[256] = { */ char *fish_base64_encode(const char *message, size_t message_len) { BF_LONG left = 0, right = 0; - int i, j; + int i; + size_t j; char *encoded = NULL; char *end = NULL; char *msg = NULL; @@ -228,9 +277,19 @@ char *fish_cipher(const char *plaintext, size_t plaintext_len, const char *key, plaintext_len -= 8; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + cipher = EVP_CIPHER_fetch(ossl_ctx, "BF-CBC", NULL); +#else cipher = (EVP_CIPHER *) EVP_bf_cbc(); +#endif + } else if (mode == EVP_CIPH_ECB_MODE) { + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + cipher = EVP_CIPHER_fetch(ossl_ctx, "BF-ECB", NULL); +#else cipher = (EVP_CIPHER *) EVP_bf_ecb(); +#endif } /* Zero Padding */ diff --git a/plugins/fishlim/fish.h b/plugins/fishlim/fish.h index 6a2e911c..75829061 100644 --- a/plugins/fishlim/fish.h +++ b/plugins/fishlim/fish.h @@ -35,6 +35,8 @@ enum fish_mode { FISH_CBC_MODE = 0x2 }; +int fish_init(void); +void fish_deinit(void); char *fish_base64_encode(const char *message, size_t message_len); char *fish_base64_decode(const char *message, size_t *final_len); char *fish_encrypt(const char *key, size_t keylen, const char *message, size_t message_len, enum fish_mode mode); diff --git a/plugins/fishlim/fishlim.vcxproj b/plugins/fishlim/fishlim.vcxproj index 579c2436..3661e1e6 100644 --- a/plugins/fishlim/fishlim.vcxproj +++ b/plugins/fishlim/fishlim.vcxproj @@ -29,7 +29,7 @@ </PropertyGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ClCompile> - <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;FISHLIM_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;FISHLIM_EXPORTS;HAVE_DH_SET0_PQG;HAVE_DH_GET0_KEY;HAVE_DH_SET0_KEY;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories>$(DepsRoot)\include;$(Glib);..\..\src\common;$(HexChatLib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> @@ -40,7 +40,7 @@ </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ClCompile> - <PreprocessorDefinitions>WIN32;_WIN64;_AMD64_;NDEBUG;_WINDOWS;_USRDLL;FISHLIM_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>WIN32;_WIN64;_AMD64_;NDEBUG;_WINDOWS;_USRDLL;FISHLIM_EXPORTS;HAVE_DH_SET0_PQG;HAVE_DH_GET0_KEY;HAVE_DH_SET0_KEY;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories>$(DepsRoot)\include;$(Glib);..\..\src\common;$(HexChatLib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> diff --git a/plugins/fishlim/meson.build b/plugins/fishlim/meson.build index 65ccc9ea..232c9a33 100644 --- a/plugins/fishlim/meson.build +++ b/plugins/fishlim/meson.build @@ -19,4 +19,5 @@ shared_module('fishlim', fishlim_sources, install: true, install_dir: plugindir, name_prefix: '', + vs_module_defs: 'fishlim.def', ) diff --git a/plugins/fishlim/plugin_hexchat.c b/plugins/fishlim/plugin_hexchat.c index 83286e28..a8b127f2 100644 --- a/plugins/fishlim/plugin_hexchat.c +++ b/plugins/fishlim/plugin_hexchat.c @@ -418,8 +418,6 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) { g_assert(hexchat_set_context(ph, query_ctx) == 1); dh_message++; /* : prefix */ - if (*dh_message == '+' || *dh_message == '-') - dh_message++; /* identify-msg */ if (g_strcmp0 (word[6], "CBC") == 0) mode = FISH_CBC_MODE; @@ -817,6 +815,9 @@ int hexchat_plugin_init(hexchat_plugin *plugin_handle, hexchat_hook_server_attrs(ph, "TOPIC", HEXCHAT_PRI_NORM, handle_incoming, NULL); hexchat_hook_server_attrs(ph, "332", HEXCHAT_PRI_NORM, handle_incoming, NULL); + if (!fish_init()) + return 0; + if (!dh1080_init()) return 0; @@ -830,6 +831,7 @@ int hexchat_plugin_init(hexchat_plugin *plugin_handle, int hexchat_plugin_deinit(void) { g_clear_pointer(&pending_exchanges, g_hash_table_destroy); dh1080_deinit(); + fish_deinit(); hexchat_printf(ph, "%s plugin unloaded\n", plugin_name); return 1; diff --git a/plugins/fishlim/tests/meson.build b/plugins/fishlim/tests/meson.build index 3b75018e..1a0394a3 100644 --- a/plugins/fishlim/tests/meson.build +++ b/plugins/fishlim/tests/meson.build @@ -1,17 +1,16 @@ -subdir('old_version') - fishlim_test_sources = [ 'tests.c', - 'fake/keystore.c', + 'mock-keystore.c', '../fish.c', '../utils.c', ] fishlim_tests = executable('fishlim_tests', fishlim_test_sources, - dependencies: [libgio_dep, libssl_dep], - link_with : fishlim_old_lib + dependencies: [libgio_dep, libssl_dep, hexchat_plugin_dep], + include_directories: include_directories('..'), ) test('Fishlim Tests', fishlim_tests, - timeout: 90 + protocol: 'tap', + timeout: 600, ) diff --git a/plugins/fishlim/tests/fake/keystore.c b/plugins/fishlim/tests/mock-keystore.c index 854f38dc..57a98847 100644 --- a/plugins/fishlim/tests/fake/keystore.c +++ b/plugins/fishlim/tests/mock-keystore.c @@ -1,5 +1,4 @@ /* - Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se> Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,26 +21,31 @@ */ -#include "../../fish.h" - +#include "fish.h" /** * Extracts a key from the key store file. */ -char *keystore_get_key(const char *nick, enum fish_mode *mode) { +char * +keystore_get_key(const char *nick, enum fish_mode *mode) +{ return NULL; } /** * Sets a key in the key store file. */ -gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) { +gboolean +keystore_store_key(const char *nick, const char *key, enum fish_mode mode) +{ return TRUE; } /** * Deletes a nick from the key store. */ -gboolean keystore_delete_nick(const char *nick) { +gboolean +keystore_delete_nick(const char *nick) +{ return TRUE; } diff --git a/plugins/fishlim/tests/old_version/fish.c b/plugins/fishlim/tests/old_version/fish.c deleted file mode 100644 index 99601398..00000000 --- a/plugins/fishlim/tests/old_version/fish.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - - Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se> - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ - -#ifdef __APPLE__ -#define __AVAILABILITYMACROS__ -#define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER -#endif - -#include <stdlib.h> -#include <string.h> -#include <openssl/blowfish.h> - -#include "fish.h" - -#define IB 64 -static const char fish_base64[64] = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; -static const signed char fish_unbase64[256] = { - IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB, - IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB, -/* ! " # $ % & ' ( ) * + , - . / */ - IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB, 0, 1, -/* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ - 2, 3, 4, 5, 6, 7, 8, 9, 10,11,IB,IB,IB,IB,IB,IB, -/* @ A B C D E F G H I J K L M N O */ - IB,38,39,40,41,42,43,44, 45,46,47,48,49,50,51,52, -/* P Q R S T U V W X Y Z [ \ ] ^ _*/ - 53,54,55,56,57,58,59,60, 61,62,63,IB,IB,IB,IB,IB, -/* ` a b c d e f g h i j k l m n o */ - IB,12,13,14,15,16,17,18, 19,20,21,22,23,24,25,26, -/* p q r s t u v w x y z { | } ~ <del> */ - 27,28,29,30,31,32,33,34, 35,36,37,IB,IB,IB,IB,IB, -}; - -#define GET_BYTES(dest, source) do { \ - *((dest)++) = ((source) >> 24) & 0xFF; \ - *((dest)++) = ((source) >> 16) & 0xFF; \ - *((dest)++) = ((source) >> 8) & 0xFF; \ - *((dest)++) = (source) & 0xFF; \ -} while (0); - - -char *__old_fish_encrypt(const char *key, size_t keylen, const char *message) { - BF_KEY bfkey; - size_t messagelen; - size_t i; - int j; - char *encrypted; - char *end; - unsigned char bit; - unsigned char word; - unsigned char d; - BF_set_key(&bfkey, keylen, (const unsigned char*)key); - - messagelen = strlen(message); - if (messagelen == 0) return NULL; - encrypted = g_malloc(((messagelen - 1) / 8) * 12 + 12 + 1); /* each 8-byte block becomes 12 bytes */ - end = encrypted; - - while (*message) { - /* Read 8 bytes (a Blowfish block) */ - BF_LONG binary[2] = { 0, 0 }; - unsigned char c; - for (i = 0; i < 8; i++) { - c = message[i]; - binary[i >> 2] |= c << 8*(3 - (i&3)); - if (c == '\0') break; - } - message += 8; - - /* Encrypt block */ - BF_encrypt(binary, &bfkey); - - /* Emit FiSH-BASE64 */ - bit = 0; - word = 1; - for (j = 0; j < 12; j++) { - d = fish_base64[(binary[word] >> bit) & 63]; - *(end++) = d; - bit += 6; - if (j == 5) { - bit = 0; - word = 0; - } - } - - /* Stop if a null terminator was found */ - if (c == '\0') break; - } - *end = '\0'; - return encrypted; -} - - -char *__old_fish_decrypt(const char *key, size_t keylen, const char *data) { - BF_KEY bfkey; - size_t i; - char *decrypted; - char *end; - unsigned char bit; - unsigned char word; - unsigned char d; - BF_set_key(&bfkey, keylen, (const unsigned char*)key); - - decrypted = g_malloc(strlen(data) + 1); - end = decrypted; - - while (*data) { - /* Convert from FiSH-BASE64 */ - BF_LONG binary[2] = { 0, 0 }; - bit = 0; - word = 1; - for (i = 0; i < 12; i++) { - d = fish_unbase64[(const unsigned char)*(data++)]; - if (d == IB) goto decrypt_end; - binary[word] |= (unsigned long)d << bit; - bit += 6; - if (i == 5) { - bit = 0; - word = 0; - } - } - - /* Decrypt block */ - BF_decrypt(binary, &bfkey); - - /* Copy to buffer */ - GET_BYTES(end, binary[0]); - GET_BYTES(end, binary[1]); - } - - decrypt_end: - *end = '\0'; - return decrypted; -} \ No newline at end of file diff --git a/plugins/fishlim/tests/old_version/fish.h b/plugins/fishlim/tests/old_version/fish.h deleted file mode 100644 index 7037782b..00000000 --- a/plugins/fishlim/tests/old_version/fish.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - - Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se> - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - -*/ - -#ifndef FISH_OLD_H -#define FISH_OLD_H - -#include <stddef.h> - -#include <glib.h> - -char *__old_fish_encrypt(const char *key, size_t keylen, const char *message); -char *__old_fish_decrypt(const char *key, size_t keylen, const char *data); - -#endif - - diff --git a/plugins/fishlim/tests/old_version/meson.build b/plugins/fishlim/tests/old_version/meson.build deleted file mode 100644 index 54647d71..00000000 --- a/plugins/fishlim/tests/old_version/meson.build +++ /dev/null @@ -1,4 +0,0 @@ -fishlim_old_lib = shared_library('fishlim_old_lib', - ['fish.c'], - dependencies: [libgio_dep, libssl_dep], -) diff --git a/plugins/fishlim/tests/tests.c b/plugins/fishlim/tests/tests.c index bb841c5e..553816d3 100644 --- a/plugins/fishlim/tests/tests.c +++ b/plugins/fishlim/tests/tests.c @@ -1,5 +1,4 @@ /* - Copyright (c) 2020 <bakasura@protonmail.ch> Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,24 +21,22 @@ */ -#ifndef PLUGIN_HEXCHAT_FISHLIM_TEST_H -#define PLUGIN_HEXCHAT_FISHLIM_TEST_H - -// Libs +#include <string.h> #include <glib.h> -// Project Libs -#include "../fish.h" -#include "../utils.h" -#include "old_version/fish.h" + +#include "fish.h" +#include "utils.h" /** * Auxiliary function: Generate a random string * @param out Preallocated string to fill * @param len Size of bytes to fill */ -void random_string(char *out, size_t len) { +static void +random_string(char *out, size_t len) +{ GRand *rand = NULL; - int i = 0; + size_t i = 0; rand = g_rand_new(); for (i = 0; i < len; ++i) { @@ -51,13 +48,14 @@ void random_string(char *out, size_t len) { g_rand_free(rand); } - /** - * Check encrypt and decrypt in ECB mode and compare with old implementation + * Check encrypt and decrypt in ECB mode */ -void __ecb(void) { - char *bo64 = NULL, *b64 = NULL; - char *deo = NULL, *de = NULL; +static void +test_ecb(void) +{ + char *b64 = NULL; + char *de = NULL; int key_len, message_len = 0; char key[57]; char message[1000]; @@ -71,36 +69,20 @@ void __ecb(void) { random_string(message, message_len); /* Encrypt */ - bo64 = __old_fish_encrypt(key, key_len, message); - g_assert_nonnull(bo64); b64 = fish_encrypt(key, key_len, message, message_len, FISH_ECB_MODE); g_assert_nonnull(b64); - g_assert_cmpuint(g_strcmp0(b64, bo64), == , 0); /* Decrypt */ /* Linear */ - deo = __old_fish_decrypt(key, key_len, bo64); de = fish_decrypt_str(key, key_len, b64, FISH_ECB_MODE); - g_assert_nonnull(deo); - g_assert_nonnull(de); - g_assert_cmpuint(g_strcmp0(de, message), == , 0); - g_assert_cmpuint(g_strcmp0(deo, message), == , 0); - g_assert_cmpuint(g_strcmp0(de, deo), == , 0); - g_free(deo); + g_assert_cmpstr (de, ==, message); g_free(de); + /* Mixed */ - deo = __old_fish_decrypt(key, key_len, b64); - de = fish_decrypt_str(key, key_len, bo64, FISH_ECB_MODE); - g_assert_nonnull(deo); - g_assert_nonnull(de); - g_assert_cmpuint(g_strcmp0(de, message), == , 0); - g_assert_cmpuint(g_strcmp0(deo, message), == , 0); - g_assert_cmpuint(g_strcmp0(de, deo), == , 0); - g_free(deo); + de = fish_decrypt_str(key, key_len, b64, FISH_ECB_MODE); + g_assert_cmpstr (de, ==, message); g_free(de); - /* Free */ - g_free(bo64); g_free(b64); } } @@ -109,7 +91,9 @@ void __ecb(void) { /** * Check encrypt and decrypt in CBC mode */ -void __cbc(void) { +static void +test_cbc(void) +{ char *b64 = NULL; char *de = NULL; int key_len, message_len = 0; @@ -131,11 +115,9 @@ void __cbc(void) { /* Decrypt */ /* Linear */ de = fish_decrypt_str(key, key_len, b64, FISH_CBC_MODE); - g_assert_nonnull(de); - g_assert_cmpuint(g_strcmp0(de, message), == , 0); + g_assert_cmpstr (de, ==, message); g_free(de); - /* Free */ g_free(b64); } } @@ -144,48 +126,49 @@ void __cbc(void) { /** * Check the calculation of final length from an encoded string in Base64 */ -void __base64_len(void) { +static void +test_base64_len (void) +{ char *b64 = NULL; - int i, message_len = 0; char message[1000]; + int message_end = sizeof (message) - 1; - for (i = 0; i < 10; ++i) { + random_string(message, message_end); - for (message_len = 1; message_len < 1000; ++message_len) { - random_string(message, message_len); - b64 = g_base64_encode((const unsigned char *) message, message_len); - g_assert_nonnull(b64); - g_assert_cmpuint(strlen(b64), == , base64_len(message_len)); - g_free(b64); - } + for (; message_end >= 0; --message_end) { + message[message_end] = '\0'; /* Truncate instead of generating new strings */ + b64 = g_base64_encode((const unsigned char *) message, message_end); + g_assert_nonnull(b64); + g_assert_cmpuint(strlen(b64), == , base64_len(message_end)); + g_free(b64); } } /** * Check the calculation of final length from an encoded string in BlowcryptBase64 */ -void __base64_fish_len(void) { +static void +test_base64_fish_len (void) +{ char *b64 = NULL; - int i, message_len = 0; + int message_len = 0; char message[1000]; - for (i = 0; i < 10; ++i) { - - for (message_len = 1; message_len < 1000; ++message_len) { - random_string(message, message_len); - b64 = fish_base64_encode(message, message_len); - g_assert_nonnull(b64); - g_assert_cmpuint(strlen(b64), == , base64_fish_len(message_len)); - g_free(b64); - } + for (message_len = 1; message_len < 1000; ++message_len) { + random_string(message, message_len); + b64 = fish_base64_encode(message, message_len); + g_assert_nonnull(b64); + g_assert_cmpuint(strlen(b64), == , base64_fish_len(message_len)); + g_free(b64); } } - /** * Check the calculation of final length from an encrypted string in ECB mode */ -void __base64_ecb_len(void) { +static void +test_base64_ecb_len(void) +{ char *b64 = NULL; int key_len, message_len = 0; char key[57]; @@ -209,7 +192,9 @@ void __base64_ecb_len(void) { /** * Check the calculation of final length from an encrypted string in CBC mode */ -void __base64_cbc_len(void) { +static void +test_base64_cbc_len(void) +{ char *b64 = NULL; int key_len, message_len = 0; char key[57]; @@ -233,7 +218,9 @@ void __base64_cbc_len(void) { /** * Check the calculation of length limit for a plaintext in each encryption mode */ -void __max_text_command_len(void) { +static void +test_max_text_command_len(void) +{ int max_encoded_len, plaintext_len; enum fish_mode mode; @@ -248,50 +235,51 @@ void __max_text_command_len(void) { /** * Check the calculation of length limit for a plaintext in each encryption mode */ -void __foreach_utf8_data_chunks(void) { +static void +test_foreach_utf8_data_chunks(void) +{ GRand *rand = NULL; GString *chunks = NULL; - int tests, max_chunks_len, chunks_len; + int max_chunks_len, chunks_len; char ascii_message[1001]; char *data_chunk = NULL; rand = g_rand_new(); + max_chunks_len = g_rand_int_range(rand, 2, 301); + random_string(ascii_message, 1000); - for (tests = 0; tests < 1000; ++tests) { - - max_chunks_len = g_rand_int_range(rand, 2, 301); - random_string(ascii_message, 1000); + data_chunk = ascii_message; - data_chunk = ascii_message; + chunks = g_string_new(NULL); - chunks = g_string_new(NULL); - - while (foreach_utf8_data_chunks(data_chunk, max_chunks_len, &chunks_len)) { - g_string_append(chunks, g_strndup(data_chunk, chunks_len)); - /* Next chunk */ - data_chunk += chunks_len; - } - /* Check data loss */ - g_assert_cmpstr(chunks->str, == , ascii_message); - g_string_free(chunks, TRUE); + while (foreach_utf8_data_chunks(data_chunk, max_chunks_len, &chunks_len)) { + g_string_append(chunks, g_strndup(data_chunk, chunks_len)); + /* Next chunk */ + data_chunk += chunks_len; } -} + /* Check data loss */ + g_assert_cmpstr(chunks->str, == , ascii_message); + g_string_free(chunks, TRUE); + g_rand_free (rand); +} -int main(int argc, char *argv[]) { +int +main(int argc, char *argv[]) { g_test_init(&argc, &argv, NULL); - g_test_add_func("/fishlim/__ecb", __ecb); - g_test_add_func("/fishlim/__cbc", __ecb); - g_test_add_func("/fishlim/__base64_len", __base64_len); - g_test_add_func("/fishlim/__base64_fish_len", __base64_fish_len); - g_test_add_func("/fishlim/__base64_ecb_len", __base64_ecb_len); - g_test_add_func("/fishlim/__base64_cbc_len", __base64_cbc_len); - g_test_add_func("/fishlim/__max_text_command_len", __max_text_command_len); - g_test_add_func("/fishlim/__foreach_utf8_data_chunks", __foreach_utf8_data_chunks); - - return g_test_run(); + g_test_add_func("/fishlim/ecb", test_ecb); + g_test_add_func("/fishlim/cbc", test_cbc); + g_test_add_func("/fishlim/base64_len", test_base64_len); + g_test_add_func("/fishlim/base64_fish_len", test_base64_fish_len); + g_test_add_func("/fishlim/base64_ecb_len", test_base64_ecb_len); + g_test_add_func("/fishlim/base64_cbc_len", test_base64_cbc_len); + g_test_add_func("/fishlim/max_text_command_len", test_max_text_command_len); + g_test_add_func("/fishlim/foreach_utf8_data_chunks", test_foreach_utf8_data_chunks); + + fish_init(); + int ret = g_test_run(); + fish_deinit(); + return ret; } - -#endif //PLUGIN_HEXCHAT_FISHLIM_TEST_H \ No newline at end of file diff --git a/plugins/fishlim/utils.c b/plugins/fishlim/utils.c index 4052995a..17ed4c2e 100644 --- a/plugins/fishlim/utils.c +++ b/plugins/fishlim/utils.c @@ -22,6 +22,8 @@ */ +#include <string.h> + #include "utils.h" #include "fish.h" diff --git a/plugins/lua/lua.c b/plugins/lua/lua.c index d1370eaf..f8f051f4 100644 --- a/plugins/lua/lua.c +++ b/plugins/lua/lua.c @@ -957,16 +957,21 @@ static int api_hexchat_pluginprefs_meta_pairs(lua_State *L) hexchat_plugin *h; if(!script->name) + return luaL_error(L, "cannot use hexchat.pluginprefs before registering with hexchat.register"); dest = lua_newuserdata(L, 4096); + h = script->handle; if(!hexchat_pluginpref_list(h, dest)) strcpy(dest, ""); lua_pushlightuserdata(L, dest); lua_pushlightuserdata(L, dest); lua_pushcclosure(L, api_hexchat_pluginprefs_meta_pairs_closure, 2); - return 1; + lua_insert(L, -2); // Return the userdata (second return value from pairs), + // even though it's not used by the closure (first return + // value from pairs), so that Lua knows not to GC it. + return 2; } static int api_attrs_meta_index(lua_State *L) @@ -1764,4 +1769,3 @@ G_MODULE_EXPORT int hexchat_plugin_deinit(hexchat_plugin *plugin_handle) g_clear_pointer(&expand_buffer, g_free); return 1; } - diff --git a/plugins/perl/meson.build b/plugins/perl/meson.build index 06ffd54b..ebcf35bb 100644 --- a/plugins/perl/meson.build +++ b/plugins/perl/meson.build @@ -88,4 +88,5 @@ shared_module('perl', install_dir: plugindir, install_rpath: perl_rpath, name_prefix: '', + vs_module_defs: 'perl.def', ) diff --git a/plugins/python/meson.build b/plugins/python/meson.build index 5fd7ec2f..1e9b41ad 100644 --- a/plugins/python/meson.build +++ b/plugins/python/meson.build @@ -28,4 +28,5 @@ shared_module('python', python3_source, install: true, install_dir: plugindir, name_prefix: '', + vs_module_defs: 'python.def' ) diff --git a/plugins/python/python.py b/plugins/python/python.py index 30694802..9eca7d6e 100644 --- a/plugins/python/python.py +++ b/plugins/python/python.py @@ -64,6 +64,10 @@ class Stdout: else: self.buffer += string + def flush(self): + lib.hexchat_print(lib.ph, bytes(self.buffer)) + self.buffer = bytearray() + def isatty(self): return False @@ -146,8 +150,8 @@ class Plugin: def loadfile(self, filename): try: self.filename = filename - with open(filename) as f: - data = f.read() + with open(filename, 'rb') as f: + data = f.read().decode('utf-8') compiled = compile_file(data, filename) exec(compiled, self.globals) @@ -198,7 +202,7 @@ else: # There can be empty entries between non-empty ones so find the actual last value def wordlist_len(words): - for i in range(31, 1, -1): + for i in range(31, 0, -1): if ffi.string(words[i]): return i @@ -284,10 +288,18 @@ def _on_server_attrs_hook(word, word_eol, attrs, userdata): @ffi.def_extern() def _on_timer_hook(userdata): hook = ffi.from_handle(userdata) - if hook.callback(hook.userdata) is True: + if hook.callback(hook.userdata) == True: return 1 - hook.is_unload = True # Don't unhook + try: + # Avoid calling hexchat_unhook twice if unnecessary + hook.is_unload = True + except ReferenceError: + # hook is a weak reference, it might have been destroyed by the callback + # in which case it has already been removed from hook.plugin.hooks and + # we wouldn't be able to test it with h == hook anyway. + return 0 + for h in hook.plugin.hooks: if h == hook: hook.plugin.hooks.remove(h) diff --git a/plugins/python/python2.vcxproj b/plugins/python/python2.vcxproj deleted file mode 100644 index 42895ce4..00000000 --- a/plugins/python/python2.vcxproj +++ /dev/null @@ -1,65 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <PropertyGroup Label="Configuration"> - <PlatformToolset>v142</PlatformToolset> - <ConfigurationType>DynamicLibrary</ConfigurationType> - </PropertyGroup> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Release|Win32"> - <Configuration>Release</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|x64"> - <Configuration>Release</Configuration> - <Platform>x64</Platform> - </ProjectConfiguration> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{19C52A0A-A790-409E-A28A-9745FF990F5C}</ProjectGuid> - <Keyword>Win32Proj</Keyword> - <RootNamespace>python2</RootNamespace> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - <Import Project="..\..\win32\hexchat.props" /> - <PropertyGroup> - <TargetName>$(Python2Output)</TargetName> - <OutDir>$(HexChatRel)plugins\</OutDir> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <ClCompile> - <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;PYTHON_EXPORTS;$(OwnFlags);%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories>$(Glib);$(Python2Path)\include;..\..\src\common;$(HexChatLib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - </ClCompile> - <Link> - <ModuleDefinitionFile>python.def</ModuleDefinitionFile> - <AdditionalDependencies>"$(Python2Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies> - <AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python2Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> - </Link> - <PreBuildEvent> - <Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command> - </PreBuildEvent> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> - <ClCompile> - <PreprocessorDefinitions>WIN32;_WIN64;_AMD64_;NDEBUG;_WINDOWS;_USRDLL;PYTHON_EXPORTS;$(OwnFlags);%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories>$(Glib);$(Python2Path)\include;..\..\src\common;$(HexChatLib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - </ClCompile> - <Link> - <ModuleDefinitionFile>python.def</ModuleDefinitionFile> - <AdditionalDependencies>"$(Python2Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies> - <AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python2Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> - </Link> - <PreBuildEvent> - <Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command> - </PreBuildEvent> - </ItemDefinitionGroup> - <ItemGroup> - <None Include="python.def" /> - </ItemGroup> - <ItemGroup> - <ClCompile Include="$(IntDir)python.c" /> - </ItemGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> -</Project> diff --git a/plugins/python/python2.vcxproj.filters b/plugins/python/python2.vcxproj.filters deleted file mode 100644 index d56e53b6..00000000 --- a/plugins/python/python2.vcxproj.filters +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup> - <Filter Include="Source Files"> - <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> - <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> - </Filter> - <Filter Include="Resource Files"> - <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> - <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> - </Filter> - </ItemGroup> - <ItemGroup> - <None Include="python.def"> - <Filter>Resource Files</Filter> - </None> - </ItemGroup> - <ItemGroup> - <ClCompile Include="python.c"> - <Filter>Source Files</Filter> - </ClCompile> - </ItemGroup> -</Project> \ No newline at end of file diff --git a/plugins/sysinfo/meson.build b/plugins/sysinfo/meson.build index 7e2cdb6c..08f08c2c 100644 --- a/plugins/sysinfo/meson.build +++ b/plugins/sysinfo/meson.build @@ -57,4 +57,5 @@ shared_module('sysinfo', sysinfo_sources, install: true, install_dir: plugindir, name_prefix: '', + vs_module_defs: 'sysinfo.def', ) diff --git a/plugins/upd/meson.build b/plugins/upd/meson.build index 7ab9d830..68217b31 100644 --- a/plugins/upd/meson.build +++ b/plugins/upd/meson.build @@ -5,4 +5,5 @@ shared_module('upd', 'upd.c', install: true, install_dir: plugindir, name_prefix: '', + vs_module_defs: 'upd.def', ) diff --git a/plugins/upd/upd.c b/plugins/upd/upd.c index c9011c04..2a938596 100644 --- a/plugins/upd/upd.c +++ b/plugins/upd/upd.c @@ -24,7 +24,7 @@ #include "hexchat-plugin.h" -#define APPCAST_URL "https://dl.hexchat.net/appcast.xml" +#define APPCAST_URL "https://hexchat.github.io/appcast.xml" static hexchat_plugin *ph; /* plugin handle */ static char name[] = "Update Checker"; diff --git a/plugins/winamp/meson.build b/plugins/winamp/meson.build index b07e7071..8d298651 100644 --- a/plugins/winamp/meson.build +++ b/plugins/winamp/meson.build @@ -3,4 +3,5 @@ shared_module('winamp', 'winamp.c', install: true, install_dir: plugindir, name_prefix: '', + vs_module_defs: 'winamp.def', ) diff --git a/po/de.po b/po/de.po index 6ea54e8b..d0e152ef 100644 --- a/po/de.po +++ b/po/de.po @@ -367,6 +367,14 @@ msgstr "Aufgelöst zu:" msgid "Looking up %s..." msgstr "Schlage %s nach …" +#: src/common/inbound.c:1992 +msgid "Could not create SCRAM session with digest %s" +msgstr "Es konnte keine SCRAM-Sitzung mit der Hashfunktion %s erstellt werden" + +#: src/common/inbound.c:2024 +msgid "SASL SCRAM authentication failed: %s" +msgstr "SASL SCRAM Authentifizierung fehlgeschlagen: %s" + #: src/common/notify.c:559 #, c-format msgid " %-20s online\n" diff --git a/po/en_GB.po b/po/en_GB.po index 4acd694a..99775c0e 100644 --- a/po/en_GB.po +++ b/po/en_GB.po @@ -346,6 +346,14 @@ msgstr "Resolved to:" msgid "Looking up %s..." msgstr "Looking up %s..." +#: src/common/inbound.c:1992 +msgid "Could not create SCRAM session with digest %s" +msgstr "Could not create SCRAM session with digest %s" + +#: src/common/inbound.c:2024 +msgid "SASL SCRAM authentication failed: %s" +msgstr "SASL SCRAM authentication failed: %s" + #: src/common/notify.c:559 #, c-format msgid " %-20s online\n" diff --git a/po/it.po b/po/it.po index 966c6dcf..76f04720 100644 --- a/po/it.po +++ b/po/it.po @@ -343,6 +343,14 @@ msgstr "Risolto a:" msgid "Looking up %s..." msgstr "Ricerca di %s..." +#: src/common/inbound.c:1992 +msgid "Could not create SCRAM session with digest %s" +msgstr "Impossibile creare una sessione SCRAM con la funzione hash %s" + +#: src/common/inbound.c:2024 +msgid "SASL SCRAM authentication failed: %s" +msgstr "SASL SCRAM autenticazione fallita: %s" + #: src/common/notify.c:559 #, c-format msgid " %-20s online\n" diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c index fdee9f2c..f0799de9 100644 --- a/src/common/cfgfiles.c +++ b/src/common/cfgfiles.c @@ -468,6 +468,7 @@ const struct prefs vars[] = {"gui_win_fullscreen", P_OFFINT (hex_gui_win_fullscreen), TYPE_INT}, {"gui_win_left", P_OFFINT (hex_gui_win_left), TYPE_INT}, {"gui_win_modes", P_OFFINT (hex_gui_win_modes), TYPE_BOOL}, + {"gui_win_nick", P_OFFINT (hex_gui_win_nick), TYPE_BOOL}, {"gui_win_save", P_OFFINT (hex_gui_win_save), TYPE_BOOL}, {"gui_win_state", P_OFFINT (hex_gui_win_state), TYPE_INT}, {"gui_win_swap", P_OFFINT (hex_gui_win_swap), TYPE_BOOL}, @@ -772,6 +773,7 @@ load_default_config(void) prefs.hex_gui_ulist_count = 1; prefs.hex_gui_ulist_icons = 1; prefs.hex_gui_ulist_style = 1; + prefs.hex_gui_win_nick = 1; prefs.hex_gui_win_save = 1; prefs.hex_input_filter_beep = 1; prefs.hex_input_flash_hilight = 1; diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index bc191f43..c91d8cbb 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -36,6 +36,7 @@ <ClInclude Include="server.h" /> <ClInclude Include="servlist.h" /> <ClInclude Include="ssl.h" /> + <ClInclude Include="scram.h" /> <ClInclude Include="sysinfo\sysinfo.h" /> <ClInclude Include="text.h" /> <ClInclude Include="$(HexChatLib)textenums.h" /> @@ -69,6 +70,7 @@ <ClCompile Include="server.c" /> <ClCompile Include="servlist.c" /> <ClCompile Include="ssl.c" /> + <ClCompile Include="scram.c" /> <ClCompile Include="sysinfo\win32\backend.c" /> <ClCompile Include="text.c" /> <ClCompile Include="tree.c" /> diff --git a/src/common/ctcp.c b/src/common/ctcp.c index a8e1ea8d..f9c05440 100644 --- a/src/common/ctcp.c +++ b/src/common/ctcp.c @@ -94,9 +94,6 @@ ctcp_handle (session *sess, char *to, char *nick, char *ip, char outbuf[1024]; int ctcp_offset = 2; - if (serv->have_idmsg && (word[4][1] == '+' || word[4][1] == '-') ) - ctcp_offset = 3; - /* consider DCC to be different from other CTCPs */ if (!g_ascii_strncasecmp (msg, "DCC", 3)) { @@ -129,7 +126,7 @@ ctcp_handle (session *sess, char *to, char *nick, char *ip, if (ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset)) goto generic; - inbound_action (sess, to, nick, ip, msg + 7, FALSE, id, tags_data); + inbound_action (sess, to, nick, ip, msg + 7, FALSE, tags_data->identified, tags_data); return; } diff --git a/src/common/dbus/dbus-client.c b/src/common/dbus/dbus-client.c index 8b40dd24..e70a49a9 100644 --- a/src/common/dbus/dbus-client.c +++ b/src/common/dbus/dbus-client.c @@ -67,7 +67,7 @@ hexchat_remote (void) gboolean hexchat_running; GError *error = NULL; char *command = NULL; - int i; + guint i; /* if there is nothing to do, return now. */ if (!arg_existing || !(arg_url || arg_urls || arg_command)) { diff --git a/src/common/dbus/meson.build b/src/common/dbus/meson.build index 69066be0..856bbe55 100644 --- a/src/common/dbus/meson.build +++ b/src/common/dbus/meson.build @@ -1,5 +1,5 @@ dbus_deps = [ - dependency('dbus-glib-1') + dbus_glib_dep ] dbus_sources = [ diff --git a/src/common/fe.h b/src/common/fe.h index 6614055b..b8a6279e 100644 --- a/src/common/fe.h +++ b/src/common/fe.h @@ -69,7 +69,16 @@ int fe_input_add (int sok, int flags, void *func, void *data); void fe_input_remove (int tag); void fe_idle_add (void *func, void *data); void fe_set_topic (struct session *sess, char *topic, char *stripped_topic); -void fe_set_tab_color (struct session *sess, int col); +typedef enum +{ + FE_COLOR_NONE = 0, + FE_COLOR_NEW_DATA = 1, + FE_COLOR_NEW_MSG = 2, + FE_COLOR_NEW_HILIGHT = 3, + FE_COLOR_FLAG_NOOVERRIDE = 8, +} tabcolor; +#define FE_COLOR_ALLFLAGS (FE_COLOR_FLAG_NOOVERRIDE) +void fe_set_tab_color (struct session *sess, tabcolor col); void fe_flash_window (struct session *sess); void fe_update_mode_buttons (struct session *sess, char mode, char sign); void fe_update_channel_key (struct session *sess); @@ -132,6 +141,7 @@ void fe_get_int (char *prompt, int def, void *callback, void *ud); #define FRF_NOASKOVERWRITE 32 /* don't ask to overwrite existing files */ #define FRF_EXTENSIONS 64 /* specify file extensions to be displayed */ #define FRF_MIMETYPES 128 /* specify file mimetypes to be displayed */ +#define FRF_MODAL 256 /* dialog should be modal to parent */ void fe_get_file (const char *title, char *initial, void (*callback) (void *userdata, char *file), void *userdata, int flags); diff --git a/src/common/hexchat.c b/src/common/hexchat.c index 9be2e56d..f74fe489 100644 --- a/src/common/hexchat.c +++ b/src/common/hexchat.c @@ -57,10 +57,6 @@ #include <glib-object.h> /* for g_type_init() */ #endif -#ifdef USE_LIBPROXY -#include <proxy.h> -#endif - GSList *popup_list = 0; GSList *button_list = 0; GSList *dlgbutton_list = 0; @@ -111,10 +107,6 @@ struct session *current_tab; struct session *current_sess = 0; struct hexchatprefs prefs; -#ifdef USE_LIBPROXY -pxProxyFactory *libproxy_factory; -#endif - /* * Update the priority queue of the "interesting sessions" * (sess_list_by_lastact). @@ -560,7 +552,7 @@ new_ircwindow (server *serv, char *name, int type, int focus) if (user && user->hostname) set_topic (sess, user->hostname, user->hostname); } - plugin_emit_dummy_print (sess, "Open Context"); + plugin_emit_dummy_print (sess, "Open Context", -1); return sess; } @@ -637,7 +629,7 @@ session_free (session *killsess) GSList *list; int oldidx; - plugin_emit_dummy_print (killsess, "Close Context"); + plugin_emit_dummy_print (killsess, "Close Context", 0); if (current_tab == killsess) current_tab = NULL; @@ -1102,10 +1094,6 @@ main (int argc, char *argv[]) hexchat_remote (); #endif -#ifdef USE_LIBPROXY - libproxy_factory = px_proxy_factory_new (); -#endif - #ifdef WIN32 coinit_result = CoInitializeEx (NULL, COINIT_APARTMENTTHREADED); if (SUCCEEDED (coinit_result)) @@ -1148,10 +1136,6 @@ main (int argc, char *argv[]) } #endif -#ifdef USE_LIBPROXY - px_proxy_factory_free (libproxy_factory); -#endif - #ifdef WIN32 WSACleanup (); #endif diff --git a/src/common/hexchat.h b/src/common/hexchat.h index d8effa1f..5ead96d1 100644 --- a/src/common/hexchat.h +++ b/src/common/hexchat.h @@ -41,6 +41,7 @@ #ifdef USE_OPENSSL #include <openssl/ssl.h> /* SSL_() */ +#include "scram.h" #endif #ifdef __EMX__ /* for o/s 2 */ @@ -150,6 +151,7 @@ struct hexchatprefs unsigned int hex_gui_ulist_style; unsigned int hex_gui_usermenu; unsigned int hex_gui_win_modes; + unsigned int hex_gui_win_nick; unsigned int hex_gui_win_save; unsigned int hex_gui_win_swap; unsigned int hex_gui_win_ucount; @@ -429,6 +431,9 @@ typedef struct session /* SASL Mechanisms */ #define MECH_PLAIN 0 #define MECH_EXTERNAL 1 +#define MECH_SCRAM_SHA_1 2 +#define MECH_SCRAM_SHA_256 3 +#define MECH_SCRAM_SHA_512 4 typedef struct server { @@ -501,9 +506,9 @@ typedef struct server int joindelay_tag; /* waiting before we send JOIN */ char hostname[128]; /* real ip number */ char servername[128]; /* what the server says is its name */ - char password[86]; + char password[1024]; char nick[NICKLEN]; - char linebuf[2048]; /* RFC says 512 chars including \r\n */ + char linebuf[8704]; /* RFC says 512 chars including \r\n, IRCv3 message tags add 8191, plus the NUL byte */ char *last_away_reason; int pos; /* current position in linebuf */ int nickcount; @@ -567,7 +572,7 @@ typedef struct server unsigned int have_awaynotify:1; unsigned int have_uhnames:1; unsigned int have_whox:1; /* have undernet's WHOX features */ - unsigned int have_idmsg:1; /* freenode's IDENTIFY-MSG */ + unsigned int have_idmsg:1; /* cap solanum.chat/identify-msg */ unsigned int have_accnotify:1; /* cap account-notify */ unsigned int have_extjoin:1; /* cap extended-join */ unsigned int have_account_tag:1; /* cap account-tag */ @@ -584,6 +589,7 @@ typedef struct server #ifdef USE_OPENSSL unsigned int use_ssl:1; /* is server SSL capable? */ unsigned int accept_invalid_cert:1;/* ignore result of server's cert. verify */ + scram_session *scram_session; /* session for SASL SCRAM authentication */ #endif } server; diff --git a/src/common/inbound.c b/src/common/inbound.c index 7175b2ae..fdee2ecc 100644 --- a/src/common/inbound.c +++ b/src/common/inbound.c @@ -107,7 +107,8 @@ find_session_from_nick (char *nick, server *serv) if (serv->front_session) { - if (userlist_find (serv->front_session, nick)) + // If we are here for ChanServ, then it is usually a reply for the user + if (!g_ascii_strcasecmp(nick, "ChanServ") || userlist_find (serv->front_session, nick)) return serv->front_session; } @@ -189,7 +190,7 @@ inbound_privmsg (server *serv, char *from, char *ip, char *text, int id, if (ip && ip[0]) set_topic (sess, ip, ip); - inbound_chanmsg (serv, NULL, NULL, from, text, FALSE, id, tags_data); + inbound_chanmsg (serv, NULL, NULL, from, text, FALSE, tags_data->identified, tags_data); return; } @@ -1473,10 +1474,17 @@ inbound_user_info (session *sess, char *chan, char *user, char *host, for (list = sess_list; list; list = list->next) { sess = list->data; - if (sess->type == SESS_CHANNEL && sess->server == serv) + if (sess->server != serv) + continue; + + if (sess->type == SESS_CHANNEL) { userlist_add_hostname (sess, nick, uhost, realname, servname, account, away); } + else if (sess->type == SESS_DIALOG && uhost && !serv->p_cmp (sess->channel, nick)) + { + set_topic (sess, uhost, uhost); + } } } @@ -1640,7 +1648,10 @@ inbound_identified (server *serv) /* 'MODE +e MYSELF' on freenode */ static const char *sasl_mechanisms[] = { "PLAIN", - "EXTERNAL" + "EXTERNAL", + "SCRAM-SHA-1", + "SCRAM-SHA-256", + "SCRAM-SHA-512" }; static void @@ -1655,7 +1666,7 @@ inbound_toggle_caps (server *serv, const char *extensions_str, gboolean enable) { const char *extension = extensions[i]; - if (!strcmp (extension, "identify-msg")) + if (!strcmp (extension, "solanum.chat/identify-msg")) serv->have_idmsg = enable; else if (!strcmp (extension, "multi-prefix")) serv->have_namesx = enable; @@ -1681,6 +1692,12 @@ inbound_toggle_caps (server *serv, const char *extensions_str, gboolean enable) #ifdef USE_OPENSSL if (serv->loginmethod == LOGIN_SASLEXTERNAL) serv->sasl_mech = MECH_EXTERNAL; + else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_1) + serv->sasl_mech = MECH_SCRAM_SHA_1; + else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_256) + serv->sasl_mech = MECH_SCRAM_SHA_256; + else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_512) + serv->sasl_mech = MECH_SCRAM_SHA_512; #endif /* Mechanism either defaulted to PLAIN or server gave us list */ tcp_sendf (serv, "AUTHENTICATE %s\r\n", sasl_mechanisms[serv->sasl_mech]); @@ -1712,8 +1729,6 @@ inbound_cap_del (server *serv, char *nick, char *extensions, } static const char * const supported_caps[] = { - "identify-msg", - /* IRCv3.1 */ "multi-prefix", "away-notify", @@ -1729,6 +1744,7 @@ static const char * const supported_caps[] = { "setname", "invite-notify", "account-tag", + "extended-monitor", /* ZNC */ "znc.in/server-time-iso", @@ -1736,6 +1752,9 @@ static const char * const supported_caps[] = { /* Twitch */ "twitch.tv/membership", + + /* Solanum */ + "solanum.chat/identify-msg", }; static int @@ -1756,6 +1775,30 @@ get_supported_mech (server *serv, const char *list) break; } } + else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_1) + { + if (!strcmp(mechs[i], "SCRAM-SHA-1")) + { + ret = MECH_SCRAM_SHA_1; + break; + } + } + else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_256) + { + if (!strcmp(mechs[i], "SCRAM-SHA-256")) + { + ret = MECH_SCRAM_SHA_256; + break; + } + } + else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_512) + { + if (!strcmp(mechs[i], "SCRAM-SHA-512")) + { + ret = MECH_SCRAM_SHA_512; + break; + } + } else #endif if (!strcmp (mechs[i], "PLAIN")) @@ -1811,7 +1854,11 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str, /* if the SASL password is set AND auth mode is set to SASL, request SASL auth */ if (!g_strcmp0 (extension, "sasl") && - ((serv->loginmethod == LOGIN_SASL && strlen (serv->password) != 0) + (((serv->loginmethod == LOGIN_SASL + || serv->loginmethod == LOGIN_SASL_SCRAM_SHA_1 + || serv->loginmethod == LOGIN_SASL_SCRAM_SHA_256 + || serv->loginmethod == LOGIN_SASL_SCRAM_SHA_512) + && strlen (serv->password) != 0) || serv->loginmethod == LOGIN_SASLEXTERNAL)) { if (value) @@ -1891,11 +1938,103 @@ inbound_cap_list (server *serv, char *nick, char *extensions, NULL, NULL, 0, tags_data->timestamp); } +static void +plain_authenticate (server *serv, char *user, char *password) +{ + char *pass = encode_sasl_pass_plain (user, password); + + if (pass == NULL) + { + /* something went wrong abort */ + tcp_sendf (serv, "AUTHENTICATE *\r\n"); + return; + } + + /* long SASL passwords must be split into 400-byte chunks + https://ircv3.net/specs/extensions/sasl-3.1#the-authenticate-command */ + size_t pass_len = strlen (pass); + if (pass_len <= 400) + tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass); + else + { + size_t sent = 0; + while (sent < pass_len) + { + char *pass_chunk = g_strndup (pass + sent, 400); + tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass_chunk); + sent += 400; + g_free (pass_chunk); + } + } + if (pass_len % 400 == 0) + tcp_sendf (serv, "AUTHENTICATE +\r\n"); +} + +#ifdef USE_OPENSSL +/* + * Sends AUTHENTICATE messages to log in via SCRAM. + */ +static void +scram_authenticate (server *serv, const char *data, const char *digest, + const char *user, const char *password) +{ + char *encoded, *decoded, *output; + scram_status status; + size_t output_len; + gsize decoded_len; + + if (serv->scram_session == NULL) + { + serv->scram_session = scram_session_create (digest, user, password); + + if (serv->scram_session == NULL) + { + PrintTextf (serv->server_session, _("Could not create SCRAM session with digest %s"), digest); + g_warning ("Could not create SCRAM session with digest %s", digest); + tcp_sendf (serv, "AUTHENTICATE *\r\n"); + return; + } + } + + decoded = g_base64_decode (data, &decoded_len); + status = scram_process (serv->scram_session, decoded, &output, &output_len); + g_free (decoded); + + if (status == SCRAM_IN_PROGRESS) + { + // Authentication is still in progress + encoded = g_base64_encode ((guchar *) output, output_len); + tcp_sendf (serv, "AUTHENTICATE %s\r\n", encoded); + g_free (encoded); + g_free (output); + } + else if (status == SCRAM_SUCCESS) + { + // Authentication succeeded + tcp_sendf (serv, "AUTHENTICATE +\r\n"); + g_clear_pointer (&serv->scram_session, scram_session_free); + } + else if (status == SCRAM_ERROR) + { + // Authentication failed + tcp_sendf (serv, "AUTHENTICATE *\r\n"); + + if (serv->scram_session->error != NULL) + { + PrintTextf (serv->server_session, _("SASL SCRAM authentication failed: %s"), serv->scram_session->error); + g_info ("SASL SCRAM authentication failed: %s", serv->scram_session->error); + } + + g_clear_pointer (&serv->scram_session, scram_session_free); + } +} +#endif + void inbound_sasl_authenticate (server *serv, char *data) { ircnet *net = (ircnet*)serv->network; - char *user, *pass = NULL; + char *user; const char *mech = sasl_mechanisms[serv->sasl_mech]; /* Got a list of supported mechanisms from outdated inspircd @@ -1911,26 +2050,24 @@ inbound_sasl_authenticate (server *serv, char *data) switch (serv->sasl_mech) { case MECH_PLAIN: - pass = encode_sasl_pass_plain (user, serv->password); + plain_authenticate(serv, user, serv->password); break; #ifdef USE_OPENSSL case MECH_EXTERNAL: - pass = g_strdup ("+"); + tcp_sendf (serv, "AUTHENTICATE +\r\n"); break; -#endif - } - - if (pass == NULL) - { - /* something went wrong abort */ - tcp_sendf (serv, "AUTHENTICATE *\r\n"); + case MECH_SCRAM_SHA_1: + scram_authenticate(serv, data, "SHA1", user, serv->password); + return; + case MECH_SCRAM_SHA_256: + scram_authenticate(serv, data, "SHA256", user, serv->password); return; + case MECH_SCRAM_SHA_512: + scram_authenticate(serv, data, "SHA512", user, serv->password); + return; +#endif } - tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass); - g_free (pass); - - EMIT_SIGNAL_TIMESTAMP (XP_TE_SASLAUTH, serv->server_session, user, (char*)mech, NULL, NULL, 0, 0); } @@ -1938,6 +2075,9 @@ inbound_sasl_authenticate (server *serv, char *data) void inbound_sasl_error (server *serv) { +#ifdef USE_OPENSSL + g_clear_pointer (&serv->scram_session, scram_session_free); +#endif /* Just abort, not much we can do */ tcp_sendf (serv, "AUTHENTICATE *\r\n"); } diff --git a/src/common/meson.build b/src/common/meson.build index 492227b2..35db2c27 100644 --- a/src/common/meson.build +++ b/src/common/meson.build @@ -15,6 +15,7 @@ common_sources = [ 'plugin-identd.c', 'plugin-timer.c', 'proto-irc.c', + 'scram.c', 'server.c', 'servlist.c', 'text.c', @@ -28,6 +29,7 @@ common_sysinfo_deps = [] common_deps = [ libgio_dep, + libcanberra_dep, ] + global_deps common_includes = [ @@ -46,7 +48,6 @@ if host_machine.system() == 'windows' ] common_sysinfo_deps += [ cc.find_library('wbemuuid'), # sysinfo - cc.find_library('wbemcore'), ] common_sources += 'sysinfo/win32/backend.c' @@ -72,26 +73,18 @@ textevents = custom_target('textevents', # SIGACTION # HAVE_GTK_MAC -if get_option('with-ssl') +if libssl_dep.found() common_sources += 'ssl.c' common_deps += libssl_dep endif -if get_option('with-libproxy') - common_deps += dependency('libproxy-1.0') -endif - -if get_option('with-libcanberra') - common_deps += dependency('libcanberra', version: '>= 0.22') -endif - -if get_option('with-dbus') +if dbus_glib_dep.found() subdir('dbus') common_deps += hexchat_dbus_dep common_includes += include_directories('dbus') endif -if get_option('with-plugin') +if get_option('plugin') common_deps += libgmodule_dep install_headers('hexchat-plugin.h') endif diff --git a/src/common/modes.c b/src/common/modes.c index 3c0ac8ab..1ff309bd 100644 --- a/src/common/modes.c +++ b/src/common/modes.c @@ -67,8 +67,8 @@ send_channel_modes (session *sess, char *tbuf, char *word[], int wpos, int usable_modes, orig_len, len, wlen, i, max; server *serv = sess->server; - /* sanity check. IRC RFC says three per line. */ - if (serv->modes_per_line < 3) + /* sanity check. IRC RFC says three per line but some servers may support less. */ + if (serv->modes_per_line < 1) serv->modes_per_line = 3; if (modes_per_line < 1) modes_per_line = serv->modes_per_line; @@ -680,7 +680,7 @@ handle_mode (server * serv, char *word[], char *word_eol[], int len; size_t arg; size_t i, num_args; - int num_modes; + size_t num_modes; size_t offset = 3; int all_modes_have_args = FALSE; int using_front_tab = FALSE; @@ -785,6 +785,64 @@ handle_mode (server * serv, char *word[], char *word_eol[], mode_print_grouped (sess, nick, &mr, tags_data); } +static char +hex_to_chr(char chr) +{ + return g_ascii_isdigit (chr) ? chr - '0' : g_ascii_tolower (chr) - 'a' + 10; +} + +static void +parse_005_token (const char *token, char **name, char **value, gboolean *adding) +{ + char *toksplit, *valuecurr; + size_t idx; + + if (token[0] == '-') + { + *adding = FALSE; + token++; + } else + { + *adding = TRUE; + } + + toksplit = strchr (token, '='); + if (toksplit && *toksplit++) + { + /* The token has a value; parse any escape codes. */ + *name = g_strndup (token, toksplit - token - 1); + *value = g_malloc (strlen (toksplit) + 1); + valuecurr = *value; + + while (*toksplit) + { + if (toksplit[0] == '\\') + { + /** If it's a malformed escape then just skip it. */ + if (toksplit[1] == 'x' && g_ascii_isxdigit (toksplit[2]) && g_ascii_isxdigit (toksplit[3])) + *valuecurr++ = hex_to_chr (toksplit[2]) << 4 | hex_to_chr (toksplit[3]); + + for (idx = 0; idx < 4; ++idx) + { + /* We need to do this to avoid jumping past the end of the array. */ + if (*toksplit) + toksplit++; + } + } else + { + /** Non-escape characters can be copied as is. */ + *valuecurr++ = *toksplit++; + } + } + *valuecurr++ = 0; + } else + { + /* The token has no value; store a dummy value instead. */ + *name = g_strdup (token); + *value = g_strdup (""); + } +} + /* handle the 005 numeric */ void @@ -792,84 +850,99 @@ inbound_005 (server * serv, char *word[], const message_tags_data *tags_data) { int w; char *pre; + char *tokname, *tokvalue; + gboolean tokadding; w = 4; /* start at the 4th word */ while (w < PDIWORDS && *word[w]) { - if (strncmp (word[w], "MODES=", 6) == 0) + if (word[w][0] == ':') + break; // :are supported by this server + + parse_005_token(word[w], &tokname, &tokvalue, &tokadding); + if (g_strcmp0 (tokname, "MODES") == 0) { - serv->modes_per_line = atoi (word[w] + 6); - } else if (strncmp (word[w], "CHANTYPES=", 10) == 0) + serv->modes_per_line = atoi (tokvalue); + } else if (g_strcmp0 (tokname, "CHANTYPES") == 0) { g_free (serv->chantypes); - serv->chantypes = g_strdup (word[w] + 10); - } else if (strncmp (word[w], "CHANMODES=", 10) == 0) + serv->chantypes = g_strdup (tokvalue); + } else if (g_strcmp0 (tokname, "CHANMODES") == 0) { g_free (serv->chanmodes); - serv->chanmodes = g_strdup (word[w] + 10); - } else if (strncmp (word[w], "PREFIX=", 7) == 0) + serv->chanmodes = g_strdup (tokvalue); + } else if (g_strcmp0 (tokname, "PREFIX") == 0) { - pre = strchr (word[w] + 7, ')'); + pre = strchr (tokvalue, ')'); if (pre) { pre[0] = 0; /* NULL out the ')' */ g_free (serv->nick_prefixes); g_free (serv->nick_modes); serv->nick_prefixes = g_strdup (pre + 1); - serv->nick_modes = g_strdup (word[w] + 8); + serv->nick_modes = g_strdup (tokvalue + 1); } else { /* bad! some ircds don't give us the modes. */ /* in this case, we use it only to strip /NAMES */ serv->bad_prefix = TRUE; g_free (serv->bad_nick_prefixes); - serv->bad_nick_prefixes = g_strdup (word[w] + 7); + serv->bad_nick_prefixes = g_strdup (tokvalue); } - } else if (strncmp (word[w], "WATCH=", 6) == 0) + } else if (g_strcmp0 (tokname, "WATCH") == 0) { - serv->supports_watch = TRUE; - } else if (strncmp (word[w], "MONITOR=", 8) == 0) + serv->supports_watch = tokadding; + } else if (g_strcmp0 (tokname, "MONITOR") == 0) { - serv->supports_monitor = TRUE; - } else if (strncmp (word[w], "NETWORK=", 8) == 0) + serv->supports_monitor = tokadding; + } else if (g_strcmp0 (tokname, "NETWORK") == 0) { - if (serv->server_session->type == SESS_SERVER) + if (serv->server_session->type == SESS_SERVER && strlen (tokvalue)) { - safe_strcpy (serv->server_session->channel, word[w] + 8, CHANLEN); + safe_strcpy (serv->server_session->channel, tokvalue, CHANLEN); fe_set_channel (serv->server_session); } - } else if (strncmp (word[w], "CASEMAPPING=", 12) == 0) + } else if (g_strcmp0 (tokname, "CASEMAPPING") == 0) { - if (strcmp (word[w] + 12, "ascii") == 0) /* bahamut */ + if (g_strcmp0 (tokvalue, "ascii") == 0) serv->p_cmp = (void *)g_ascii_strcasecmp; - } else if (strncmp (word[w], "CHARSET=", 8) == 0) + } else if (g_strcmp0 (tokname, "CHARSET") == 0) { - if (g_ascii_strncasecmp (word[w] + 8, "UTF-8", 5) == 0) + if (g_ascii_strcasecmp (tokvalue, "UTF-8") == 0) { server_set_encoding (serv, "UTF-8"); } - } else if (strcmp (word[w], "NAMESX") == 0) + } else if (g_strcmp0 (tokname, "UTF8ONLY") == 0) { - /* 12345678901234567 */ - tcp_send_len (serv, "PROTOCTL NAMESX\r\n", 17); - } else if (strcmp (word[w], "WHOX") == 0) + server_set_encoding (serv, "UTF-8"); + } else if (g_strcmp0 (tokname, "NAMESX") == 0) + { + if (tokadding && !serv->have_namesx) + { + /* only use protoctl if the server doesn't have the equivalent cap */ + tcp_send_len (serv, "PROTOCTL NAMESX\r\n", 17); + serv->have_namesx = TRUE; + } + } else if (g_strcmp0 (tokname, "WHOX") == 0) { - serv->have_whox = TRUE; - } else if (strcmp (word[w], "EXCEPTS") == 0) + serv->have_whox = tokadding; + } else if (g_strcmp0 (tokname, "EXCEPTS") == 0) { - serv->have_except = TRUE; - } else if (strcmp (word[w], "INVEX") == 0) + serv->have_except = tokadding; + } else if (g_strcmp0 (tokname, "INVEX") == 0) { /* supports mode letter +I, default channel invite */ - serv->have_invite = TRUE; - } else if (strncmp (word[w], "ELIST=", 6) == 0) + serv->have_invite = tokadding; + } else if (g_strcmp0 (tokname, "ELIST") == 0) { /* supports LIST >< min/max user counts? */ - if (strchr (word[w] + 6, 'U') || strchr (word[w] + 6, 'u')) + if (strchr (tokvalue, 'U') || strchr (tokvalue, 'u')) serv->use_listargs = TRUE; } + g_free (tokname); + g_free (tokvalue); w++; } } diff --git a/src/common/notify.c b/src/common/notify.c index b5316c36..ef52889b 100644 --- a/src/common/notify.c +++ b/src/common/notify.c @@ -123,7 +123,11 @@ notify_save (void) { int fh; struct notify *notify; - GSList *list = notify_list; + // while reading the notify.conf file, elements are added by prepending to the + // list. reverse the list before writing to disk to keep the original + // order of the list + GSList *list = g_slist_copy(notify_list); + list = g_slist_reverse(list); fh = hexchat_open_file ("notify.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); if (fh != -1) @@ -142,6 +146,7 @@ notify_save (void) } close (fh); } + g_slist_free(list); } void diff --git a/src/common/outbound.c b/src/common/outbound.c index 614aad38..b8153502 100644 --- a/src/common/outbound.c +++ b/src/common/outbound.c @@ -468,7 +468,7 @@ create_mask (session * sess, char *mask, char *mode, char *typestr, int deop) type = prefs.hex_irc_ban_type; buf[0] = 0; - if (inet_addr (fullhost) != -1) /* "fullhost" is really a IP number */ + if (inet_addr (fullhost) != (guint32) -1) /* "fullhost" is really a IP number */ { lastdot = strrchr (fullhost, '.'); if (!lastdot) @@ -1579,9 +1579,26 @@ cmd_execw (struct session *sess, char *tbuf, char *word[], char *word_eol[]) EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0); return FALSE; } - len = strlen(word_eol[2]); - temp = g_strconcat (word_eol[2], "\n", NULL); - PrintText(sess, temp); + if (strcmp (word[2], "--") == 0) + { + len = strlen(word_eol[3]); + temp = g_strconcat (word_eol[3], "\n", NULL); + PrintText(sess, temp); + } + else + { + if (strcmp (word[2], "-q") == 0) + { + len = strlen(word_eol[3]); + temp = g_strconcat (word_eol[3], "\n", NULL); + } + else + { + len = strlen(word_eol[2]); + temp = g_strconcat (word_eol[2], "\n", NULL); + PrintText(sess, temp); + } + } write(sess->running_exec->myfd, temp, len + 1); g_free(temp); @@ -2152,7 +2169,6 @@ cmd_gui (struct session *sess, char *tbuf, char *word[], char *word_eol[]) { case 0x058b836e: fe_ctrl_gui (sess, 8, 0); break; /* APPLY */ case 0xac1eee45: fe_ctrl_gui (sess, 7, 2); break; /* ATTACH */ - case 0x05a72f63: fe_ctrl_gui (sess, 4, atoi (word[3])); break; /* COLOR */ case 0xb06a1793: fe_ctrl_gui (sess, 7, 1); break; /* DETACH */ case 0x05cfeff0: fe_ctrl_gui (sess, 3, 0); break; /* FLASH */ case 0x05d154d8: fe_ctrl_gui (sess, 2, 0); break; /* FOCUS */ @@ -2166,6 +2182,12 @@ cmd_gui (struct session *sess, char *tbuf, char *word[], char *word_eol[]) else return FALSE; break; + case 0x05a72f63: /* COLOR */ + if (!g_ascii_strcasecmp (word[4], "-NOOVERRIDE")) + fe_ctrl_gui (sess, 4, FE_COLOR_FLAG_NOOVERRIDE | atoi (word[3])); + else + fe_ctrl_gui (sess, 4, atoi (word[3])); + break; default: return FALSE; } @@ -3225,16 +3247,28 @@ cmd_reconnect (struct session *sess, char *tbuf, char *word[], char *word_eol[]) else if (*word[2]) { int offset = 0; -#ifdef USE_OPENSSL - int use_ssl = FALSE; - if (strcmp (word[2], "-ssl") == 0) +#ifdef USE_OPENSSL + int use_ssl = TRUE; + int use_ssl_noverify = FALSE; + if (g_strcmp0 (word[2], "-ssl") == 0) { use_ssl = TRUE; + use_ssl_noverify = FALSE; + offset++; /* args move up by 1 word */ + } else if (g_strcmp0 (word[2], "-ssl-noverify") == 0) + { + use_ssl = TRUE; + use_ssl_noverify = TRUE; + offset++; /* args move up by 1 word */ + } else if (g_strcmp0 (word[2], "-insecure") == 0) + { + use_ssl = FALSE; + use_ssl_noverify = FALSE; offset++; /* args move up by 1 word */ } serv->use_ssl = use_ssl; - serv->accept_invalid_cert = TRUE; + serv->accept_invalid_cert = use_ssl_noverify; #endif if (*word[4+offset]) @@ -3421,18 +3455,34 @@ cmd_server (struct session *sess, char *tbuf, char *word[], char *word_eol[]) char *pass = NULL; char *channel = NULL; char *key = NULL; +#ifdef USE_OPENSSL + int use_ssl = TRUE; + int use_ssl_noverify = FALSE; +#else int use_ssl = FALSE; +#endif int is_url = TRUE; server *serv = sess->server; ircnet *net = NULL; #ifdef USE_OPENSSL /* BitchX uses -ssl, mIRC uses -e, let's support both */ - if (strcmp (word[2], "-ssl") == 0 || strcmp (word[2], "-e") == 0) + if (g_strcmp0 (word[2], "-ssl") == 0 || g_strcmp0 (word[2], "-e") == 0) { use_ssl = TRUE; offset++; /* args move up by 1 word */ } + else if (g_strcmp0 (word[2], "-ssl-noverify") == 0) + { + use_ssl = TRUE; + use_ssl_noverify = TRUE; + offset++; /* args move up by 1 word */ + } + else if (g_strcmp0 (word[2], "-insecure") == 0) + { + use_ssl = FALSE; + offset++; /* args move up by 1 word */ + } #endif if (!parse_irc_url (word[2 + offset], &server_name, &port, &channel, &key, &use_ssl)) @@ -3473,6 +3523,13 @@ cmd_server (struct session *sess, char *tbuf, char *word[], char *word_eol[]) use_ssl = TRUE; #endif } + else if (port[0] == '-') + { + port++; +#ifdef USE_OPENSSL + use_ssl = FALSE; +#endif + } if (*pass) { @@ -3497,7 +3554,7 @@ cmd_server (struct session *sess, char *tbuf, char *word[], char *word_eol[]) #ifdef USE_OPENSSL serv->use_ssl = use_ssl; - serv->accept_invalid_cert = TRUE; + serv->accept_invalid_cert = use_ssl_noverify; #endif /* try to connect by Network name */ @@ -3528,7 +3585,7 @@ cmd_servchan (struct session *sess, char *tbuf, char *word[], int offset = 0; #ifdef USE_OPENSSL - if (strcmp (word[2], "-ssl") == 0) + if (g_strcmp0 (word[2], "-ssl") == 0 || g_strcmp0 (word[2], "-ssl-noverify") == 0 || g_strcmp0 (word[2], "-insecure") == 0) offset++; #endif @@ -3863,34 +3920,6 @@ cmd_wallchop (struct session *sess, char *tbuf, char *word[], } static int -cmd_wallchan (struct session *sess, char *tbuf, char *word[], - char *word_eol[]) -{ - GSList *list; - - if (*word_eol[2]) - { - list = sess_list; - while (list) - { - sess = list->data; - if (sess->type == SESS_CHANNEL) - { - message_tags_data no_tags = MESSAGE_TAGS_DATA_INIT; - - inbound_chanmsg (sess->server, NULL, sess->channel, - sess->server->nick, word_eol[2], TRUE, FALSE, - &no_tags); - sess->server->p_message (sess->server, sess->channel, word_eol[2]); - } - list = list->next; - } - return TRUE; - } - return FALSE; -} - -static int cmd_hop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) { int i = 2; @@ -3930,7 +3959,7 @@ cmd_voice (struct session *sess, char *tbuf, char *word[], char *word_eol[]) const struct commands xc_cmds[] = { {"ADDBUTTON", cmd_addbutton, 0, 0, 1, N_("ADDBUTTON <name> <action>, adds a button under the user-list")}, - {"ADDSERVER", cmd_addserver, 0, 0, 1, N_("ADDSERVER <NewNetwork> <newserver/6667>, adds a new network with a new server to the network list")}, + {"ADDSERVER", cmd_addserver, 0, 0, 1, N_("ADDSERVER <NewNetwork> <hostname/port>, adds a new network with a new server to the network list")}, {"ALLCHAN", cmd_allchannels, 0, 0, 1, N_("ALLCHAN <cmd>, sends a command to all channels you're in")}, {"ALLCHANL", cmd_allchannelslocal, 0, 0, 1, @@ -3986,7 +4015,7 @@ const struct commands xc_cmds[] = { N_("EXECKILL [-9], kills a running exec in the current session. If -9 is given the process is SIGKILL'ed")}, #ifndef __EMX__ {"EXECSTOP", cmd_execs, 0, 0, 1, N_("EXECSTOP, sends the process SIGSTOP")}, - {"EXECWRITE", cmd_execw, 0, 0, 1, N_("EXECWRITE, sends data to the processes stdin")}, + {"EXECWRITE", cmd_execw, 0, 0, 1, N_("EXECWRITE [-q|--], sends data to the processes stdin; use -q flag to quiet/suppress output at text box; use -- flag to stop interpreting arguments as flags, needed if -q itself would occur as data")}, #endif #endif #if 0 @@ -4001,8 +4030,9 @@ const struct commands xc_cmds[] = { {"GETINT", cmd_getint, 0, 0, 1, "GETINT <default> <command> <prompt>"}, {"GETSTR", cmd_getstr, 0, 0, 1, "GETSTR <default> <command> <prompt>"}, {"GHOST", cmd_ghost, 1, 0, 1, N_("GHOST <nick> [password], Kills a ghosted nickname")}, - {"GUI", cmd_gui, 0, 0, 1, "GUI [APPLY|ATTACH|DETACH|SHOW|HIDE|FOCUS|FLASH|ICONIFY|COLOR <n>]\n" - " GUI [MSGBOX <text>|MENU TOGGLE]"}, + {"GUI", cmd_gui, 0, 0, 1, "GUI [APPLY|ATTACH|DETACH|SHOW|HIDE|FOCUS|FLASH|ICONIFY]\n" + " GUI [MSGBOX <text>|MENU TOGGLE]\n" + " GUI COLOR <n> [-NOOVERRIDE]"}, {"HELP", cmd_help, 0, 0, 1, 0}, {"HOP", cmd_hop, 1, 1, 1, N_("HOP <nick>, gives chanhalf-op status to the nick (needs chanop)")}, @@ -4077,7 +4107,7 @@ const struct commands xc_cmds[] = { N_("QUOTE <text>, sends the text in raw form to the server")}, #ifdef USE_OPENSSL {"RECONNECT", cmd_reconnect, 0, 0, 1, - N_("RECONNECT [-ssl] [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")}, + N_("RECONNECT [-ssl|-ssl-noverify] [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")}, #else {"RECONNECT", cmd_reconnect, 0, 0, 1, N_("RECONNECT [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")}, @@ -4089,14 +4119,14 @@ const struct commands xc_cmds[] = { {"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")}, #ifdef USE_OPENSSL {"SERVCHAN", cmd_servchan, 0, 0, 1, - N_("SERVCHAN [-ssl] <host> <port> <channel>, connects and joins a channel")}, + N_("SERVCHAN [-insecure|-ssl|-ssl-noverify] <host> <port> <channel>, connects and joins a channel using ssl unless otherwise specified")}, #else {"SERVCHAN", cmd_servchan, 0, 0, 1, N_("SERVCHAN <host> <port> <channel>, connects and joins a channel")}, #endif #ifdef USE_OPENSSL {"SERVER", cmd_server, 0, 0, 1, - N_("SERVER [-ssl] <host> [<port>] [<password>], connects to a server, the default port is 6667 for normal connections, and 6697 for ssl connections")}, + N_("SERVER [-insecure|-ssl|-ssl-noverify] <host> [<port>] [<password>], connects to a server using ssl unless otherwise specified, the default port is 6697 for ssl connections and 6667 for insecure connections")}, #else {"SERVER", cmd_server, 0, 0, 1, N_("SERVER <host> [<port>] [<password>], connects to a server, the default port is 6667")}, @@ -4127,8 +4157,6 @@ const struct commands xc_cmds[] = { {"USERLIST", cmd_userlist, 1, 1, 1, 0}, {"VOICE", cmd_voice, 1, 1, 1, N_("VOICE <nick>, gives voice status to someone (needs chanop)")}, - {"WALLCHAN", cmd_wallchan, 1, 1, 1, - N_("WALLCHAN <message>, writes the message to all channels")}, {"WALLCHOP", cmd_wallchop, 1, 1, 1, N_("WALLCHOP <message>, sends the message to all chanops on the current channel")}, {0, 0, 0, 0, 0, 0} @@ -4420,6 +4448,9 @@ check_special_chars (char *cmd, int do_ascii) /* check for %X */ case 'I': buf[i] = '\035'; break; + case 'S': + buf[i] = '\036'; + break; case 'C': buf[i] = '\003'; break; diff --git a/src/common/plugin-timer.c b/src/common/plugin-timer.c index d0c82c28..17f11ce3 100644 --- a/src/common/plugin-timer.c +++ b/src/common/plugin-timer.c @@ -198,7 +198,7 @@ timer_cb (char *word[], char *word_eol[], void *userdata) offset += 2; } - timeout = atof (word[2 + offset]); + timeout = g_ascii_strtod (word[2 + offset], NULL); command = word_eol[3 + offset]; if (timeout < 0.1 || timeout * 1000 > INT_MAX || !command[0]) diff --git a/src/common/plugin.c b/src/common/plugin.c index d3f3b7ca..f4691d73 100644 --- a/src/common/plugin.c +++ b/src/common/plugin.c @@ -491,7 +491,6 @@ plugin_auto_load (session *sess) for_files (lib_dir, "hcfishlim.dll", plugin_auto_load_cb); for_files(lib_dir, "hclua.dll", plugin_auto_load_cb); for_files (lib_dir, "hcperl.dll", plugin_auto_load_cb); - for_files (lib_dir, "hcpython2.dll", plugin_auto_load_cb); for_files (lib_dir, "hcpython3.dll", plugin_auto_load_cb); for_files (lib_dir, "hcupd.dll", plugin_auto_load_cb); for_files (lib_dir, "hcwinamp.dll", plugin_auto_load_cb); @@ -572,7 +571,7 @@ plugin_hook_find (GSList *list, int type, char *name) static int plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[], - hexchat_event_attrs *attrs, int type) + hexchat_event_attrs *attrs, int type, int mask) { /* fix segfault https://github.com/hexchat/hexchat/issues/2265 */ static int depth = 0; @@ -612,6 +611,11 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[], break; } + if ((ret & mask) != ret) { + g_critical("plugin tried to eat cleanup hooks"); + } + ret &= mask; + if ((ret & HEXCHAT_EAT_HEXCHAT) && (ret & HEXCHAT_EAT_PLUGIN)) { eat = 1; @@ -652,7 +656,7 @@ xit: int plugin_emit_command (session *sess, char *name, char *word[], char *word_eol[]) { - return plugin_hook_run (sess, name, word, word_eol, NULL, HOOK_COMMAND); + return plugin_hook_run (sess, name, word, word_eol, NULL, HOOK_COMMAND, -1); } hexchat_event_attrs * @@ -678,7 +682,7 @@ plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[], attrs.server_time_utc = server_time; return plugin_hook_run (sess, name, word, word_eol, &attrs, - HOOK_SERVER | HOOK_SERVER_ATTRS); + HOOK_SERVER | HOOK_SERVER_ATTRS, -1); } /* see if any plugins are interested in this print event */ @@ -691,7 +695,7 @@ plugin_emit_print (session *sess, char *word[], time_t server_time) attrs.server_time_utc = server_time; return plugin_hook_run (sess, word[0], word, NULL, &attrs, - HOOK_PRINT | HOOK_PRINT_ATTRS); + HOOK_PRINT | HOOK_PRINT_ATTRS, -1); } /* used by plugin_emit_dummy_print to fix some UB */ @@ -707,7 +711,7 @@ check_and_invalidate(void *plug_, void *killsess_) } int -plugin_emit_dummy_print (session *sess, char *name) +plugin_emit_dummy_print (session *sess, char *name, int mask) { char *word[PDIWORDS]; int i; @@ -716,7 +720,7 @@ plugin_emit_dummy_print (session *sess, char *name) for (i = 1; i < PDIWORDS; i++) word[i] = "\000"; - i = plugin_hook_run (sess, name, word, NULL, NULL, HOOK_PRINT); + i = plugin_hook_run (sess, name, word, NULL, NULL, HOOK_PRINT, mask); /* shoehorned fix for Undefined Behaviour */ /* see https://stackoverflow.com/q/52628773/3691554 */ @@ -758,7 +762,7 @@ plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, gu for (i = 5; i < PDIWORDS; i++) word[i] = "\000"; - return plugin_hook_run (sess, word[0], word, NULL, NULL, HOOK_PRINT); + return plugin_hook_run (sess, word[0], word, NULL, NULL, HOOK_PRINT, -1); } static int @@ -1149,12 +1153,16 @@ hexchat_get_context (hexchat_plugin *ph) int hexchat_set_context (hexchat_plugin *ph, hexchat_context *context) { - if (is_session (context)) + if (context == NULL) { + return 0; + } + if (!is_session (context)) { - ph->context = context; - return 1; + g_critical("plugin tried to set an invalid context"); + return 0; } - return 0; + ph->context = context; + return 1; } hexchat_context * diff --git a/src/common/plugin.h b/src/common/plugin.h index fb7da831..051d1f5a 100644 --- a/src/common/plugin.h +++ b/src/common/plugin.h @@ -174,7 +174,7 @@ int plugin_emit_command (session *sess, char *name, char *word[], char *word_eol int plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[], time_t server_time); int plugin_emit_print (session *sess, char *word[], time_t server_time); -int plugin_emit_dummy_print (session *sess, char *name); +int plugin_emit_dummy_print (session *sess, char *name, int mask); int plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, gunichar key); GList* plugin_command_list(GList *tmp_list); int plugin_show_help (session *sess, char *cmd); diff --git a/src/common/proto-irc.c b/src/common/proto-irc.c index c8e44b62..5b8e02c4 100644 --- a/src/common/proto-irc.c +++ b/src/common/proto-irc.c @@ -460,6 +460,18 @@ channel_date (session *sess, char *chan, char *timestr, tags_data->timestamp); } +static int +trailing_index(char *word_eol[]) +{ + int param_index; + for (param_index = 3; param_index < PDIWORDS; ++param_index) + { + if (word_eol[param_index][0] == ':') + break; + } + return param_index; +} + static void process_numeric (session * sess, int n, char *word[], char *word_eol[], char *text, @@ -491,22 +503,6 @@ process_numeric (session * sess, int n, goto def; - case 4: /* check the ircd type */ - serv->use_listargs = FALSE; - serv->modes_per_line = 3; /* default to IRC RFC */ - if (strncmp (word[5], "bahamut", 7) == 0) /* DALNet */ - { - serv->use_listargs = TRUE; /* use the /list args */ - } else if (strncmp (word[5], "u2.10.", 6) == 0) /* Undernet */ - { - serv->use_listargs = TRUE; /* use the /list args */ - serv->modes_per_line = 6; /* allow 6 modes per line */ - } else if (strncmp (word[5], "glx2", 4) == 0) - { - serv->use_listargs = TRUE; /* use the /list args */ - } - goto def; - case 5: inbound_005 (serv, word, tags_data); goto def; @@ -631,7 +627,7 @@ process_numeric (session * sess, int n, case 320: /* :is an identified user */ if (!serv->skip_next_whois) EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS_ID, whois_sess, word[4], - word_eol[5] + 1, NULL, NULL, 0, + word_eol[5][0] == ':' ? word_eol[5] + 1 : word_eol[5], NULL, NULL, 0, tags_data->timestamp); break; @@ -801,7 +797,7 @@ process_numeric (session * sess, int n, break; case 346: /* +I-list entry */ - if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], 346, + if (!inbound_banlist (sess, atol (STRIP_COLON (word, word_eol, 7)), word[4], word[5], word[6], 346, tags_data)) goto def; break; @@ -812,7 +808,7 @@ process_numeric (session * sess, int n, break; case 348: /* +e-list entry */ - if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], 348, + if (!inbound_banlist (sess, atol (STRIP_COLON (word, word_eol, 7)), word[4], word[5], word[6], 348, tags_data)) goto def; break; @@ -837,7 +833,7 @@ process_numeric (session * sess, int n, break; case 367: /* banlist entry */ - if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], 367, + if (!inbound_banlist (sess, atol (STRIP_COLON (word, word_eol, 7)), word[4], word[5], word[6], 367, tags_data)) goto def; break; @@ -924,6 +920,14 @@ process_numeric (session * sess, int n, notify_set_online (serv, word[4], tags_data); break; + case 524: // ERR_HELPNOTFOUND + case 704: // RPL_HELPSTART + case 705: // RPL_HELPTXT + case 706: // RPL_ENDOFHELP + EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, sess, STRIP_COLON(word, word_eol, 5), NULL, NULL, NULL, + 0, tags_data->timestamp); + break; + case 728: /* +q-list entry */ /* NOTE: FREENODE returns these results inconsistent with e.g. +b */ /* Who else has imlemented MODE_QUIET, I wonder? */ @@ -1139,6 +1143,39 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[], (word_eol[3][0] == ':') ? word_eol[3] + 1 : NULL, tags_data); return; + + case WORDL('F','A','I','L'): + text = STRIP_COLON(word, word_eol, trailing_index(word_eol)); + if (g_strcmp0(word[3], "*") == 0) + { + EMIT_SIGNAL_TIMESTAMP (XP_TE_FAIL, sess, word[4], text, NULL, NULL, NULL, tags_data->timestamp); + } else + { + EMIT_SIGNAL_TIMESTAMP (XP_TE_FAILCMD, sess, word[3], word[4], text, NULL, NULL, tags_data->timestamp); + } + return; + + case WORDL('W','A','R','N'): + text = STRIP_COLON(word, word_eol, trailing_index(word_eol)); + if (g_strcmp0(word[3], "*") == 0) + { + EMIT_SIGNAL_TIMESTAMP (XP_TE_WARN, sess, word[4], text, NULL, NULL, NULL, tags_data->timestamp); + } else + { + EMIT_SIGNAL_TIMESTAMP (XP_TE_WARNCMD, sess, word[3], word[4], text, NULL, NULL, tags_data->timestamp); + } + return; + + case WORDL('N','O','T','E'): + text = STRIP_COLON(word, word_eol, trailing_index(word_eol)); + if (g_strcmp0(word[3], "*") == 0) + { + EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTE, sess, word[4], text, NULL, NULL, NULL, tags_data->timestamp); + } else + { + EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTECMD, sess, word[3], word[4], text, NULL, NULL, tags_data->timestamp); + } + return; } goto garbage; @@ -1189,8 +1226,6 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[], case WORDL('N','O','T','I'): { - int id = FALSE; /* identified */ - text = word_eol[4]; if (*text == ':') { @@ -1219,18 +1254,8 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[], } #endif - if (serv->have_idmsg) - { - if (*text == '+') - { - id = TRUE; - text++; - } else if (*text == '-') - text++; - } - if (!ignore_check (word[1], IG_NOTI)) - inbound_notice (serv, word[3], nick, text, ip, id, tags_data); + inbound_notice (serv, word[3], nick, text, ip, tags_data->identified, tags_data); } return; @@ -1238,7 +1263,6 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[], { char *to = word[3]; int len; - int id = FALSE; /* identified */ if (*to) { /* Handle limited channel messages, for now no special event */ @@ -1249,15 +1273,7 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[], text = word_eol[4]; if (*text == ':') text++; - if (serv->have_idmsg) - { - if (*text == '+') - { - id = TRUE; - text++; - } else if (*text == '-') - text++; - } + len = strlen (text); if (text[0] == 1) /* ctcp */ { @@ -1289,7 +1305,7 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[], } } - ctcp_handle (sess, to, nick, ip, text, word, word_eol, id, + ctcp_handle (sess, to, nick, ip, text, word, word_eol, tags_data->identified, tags_data); /* Note word will be invalid beyond this scope */ @@ -1300,13 +1316,13 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[], { if (ignore_check (word[1], IG_CHAN)) return; - inbound_chanmsg (serv, NULL, to, nick, text, FALSE, id, + inbound_chanmsg (serv, NULL, to, nick, text, FALSE, tags_data->identified, tags_data); } else { if (ignore_check (word[1], IG_PRIV)) return; - inbound_privmsg (serv, nick, ip, text, id, tags_data); + inbound_privmsg (serv, nick, ip, text, tags_data->identified, tags_data); } } } @@ -1537,6 +1553,9 @@ handle_message_tags (server *serv, const char *tags_str, if (serv->have_account_tag && !strcmp (key, "account")) tags_data->account = g_strdup (value); + if (serv->have_idmsg && strcmp (key, "solanum.chat/identified")) + tags_data->identified = TRUE; + if (serv->have_server_time && !strcmp (key, "time")) handle_message_tag_time (value, tags_data); } diff --git a/src/common/proto-irc.h b/src/common/proto-irc.h index 0f72c644..6f52f1bc 100644 --- a/src/common/proto-irc.h +++ b/src/common/proto-irc.h @@ -26,6 +26,7 @@ #define MESSAGE_TAGS_DATA_INIT \ { \ NULL, /* account name */ \ + FALSE, /* identified to nick */ \ (time_t)0, /* timestamp */ \ } @@ -38,6 +39,7 @@ typedef struct { char *account; + gboolean identified; time_t timestamp; } message_tags_data; diff --git a/src/common/scram.c b/src/common/scram.c new file mode 100644 index 00000000..b39199de --- /dev/null +++ b/src/common/scram.c @@ -0,0 +1,333 @@ +/* HexChat + * Copyright (C) 2023 Patrick Okraku + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "hexchat.h" + +#ifdef USE_OPENSSL + +#include "scram.h" +#include <openssl/hmac.h> +#include <openssl/rand.h> + +#define NONCE_LENGTH 18 +#define CLIENT_KEY "Client Key" +#define SERVER_KEY "Server Key" + +// EVP_MD_CTX_create() and EVP_MD_CTX_destroy() were renamed in OpenSSL 1.1.0 +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) +#define EVP_MD_CTX_new(ctx) EVP_MD_CTX_create(ctx) +#define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx) +#endif + +scram_session +*scram_session_create (const char *digest, const char *username, const char *password) +{ + scram_session *session; + const EVP_MD *md; +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) + OpenSSL_add_all_algorithms (); +#endif + md = EVP_get_digestbyname (digest); + + if (md == NULL) + { + // Unknown message digest + return NULL; + } + + session = g_new0 (scram_session, 1); + session->digest = md; + session->digest_size = EVP_MD_size (md); + session->username = g_strdup (username); + session->password = g_strdup (password); + return session; +} + +void +scram_session_free (scram_session *session) +{ + if (session == NULL) + { + return; + } + + g_free (session->username); + g_free (session->password); + g_free (session->client_nonce_b64); + g_free (session->client_first_message_bare); + g_free (session->salted_password); + g_free (session->auth_message); + g_free (session->error); + + g_free (session); +} + +static int +create_nonce (void *buffer, size_t length) +{ + return RAND_bytes (buffer, length); +} + +static int +create_SHA (scram_session *session, const unsigned char *input, size_t input_len, + unsigned char *output, unsigned int *output_len) +{ + EVP_MD_CTX *md_ctx = EVP_MD_CTX_new (); + + if (!EVP_DigestInit_ex (md_ctx, session->digest, NULL)) + { + session->error = g_strdup ("Message digest initialization failed"); + EVP_MD_CTX_free (md_ctx); + return SCRAM_ERROR; + } + + if (!EVP_DigestUpdate (md_ctx, input, input_len)) + { + session->error = g_strdup ("Message digest update failed"); + EVP_MD_CTX_free (md_ctx); + return SCRAM_ERROR; + } + + if (!EVP_DigestFinal_ex (md_ctx, output, output_len)) + { + session->error = g_strdup ("Message digest finalization failed"); + EVP_MD_CTX_free (md_ctx); + return SCRAM_ERROR; + } + + EVP_MD_CTX_free (md_ctx); + return SCRAM_IN_PROGRESS; +} + +static scram_status +process_client_first (scram_session *session, char **output, size_t *output_len) +{ + char nonce[NONCE_LENGTH]; + + if (!create_nonce (nonce, NONCE_LENGTH)) + { + session->error = g_strdup ("Could not create client nonce"); + return SCRAM_ERROR; + } + + session->client_nonce_b64 = g_base64_encode ((guchar *) nonce, NONCE_LENGTH); + *output = g_strdup_printf ("n,,n=%s,r=%s", session->username, session->client_nonce_b64); + *output_len = strlen (*output); + session->client_first_message_bare = g_strdup (*output + 3); + session->step++; + return SCRAM_IN_PROGRESS; +} + +static scram_status +process_server_first (scram_session *session, const char *data, char **output, + size_t *output_len) +{ + char **params, *client_final_message_without_proof, *salt, *server_nonce_b64, + *client_proof_b64; + unsigned char *client_key, stored_key[EVP_MAX_MD_SIZE], *client_signature, *client_proof; + unsigned int i, param_count, iteration_count, client_key_len, stored_key_len; + gsize salt_len = 0; + size_t client_nonce_len; + + params = g_strsplit (data, ",", -1); + param_count = g_strv_length (params); + + if (param_count < 3) + { + session->error = g_strdup_printf ("Invalid server-first-message: %s", data); + g_strfreev (params); + return SCRAM_ERROR; + } + + server_nonce_b64 = NULL; + salt = NULL; + iteration_count = 0; + + for (i = 0; i < param_count; i++) + { + if (!strncmp (params[i], "r=", 2)) + { + g_free (server_nonce_b64); + server_nonce_b64 = g_strdup (params[i] + 2); + } + else if (!strncmp (params[i], "s=", 2)) + { + g_free (salt); + salt = g_strdup (params[i] + 2); + } + else if (!strncmp (params[i], "i=", 2)) + { + iteration_count = strtoul (params[i] + 2, NULL, 10); + } + } + + g_strfreev (params); + + if (server_nonce_b64 == NULL || *server_nonce_b64 == '\0' || salt == NULL || + *salt == '\0' || iteration_count == 0) + { + session->error = g_strdup_printf ("Invalid server-first-message: %s", data); + g_free (server_nonce_b64); + g_free (salt); + return SCRAM_ERROR; + } + + client_nonce_len = strlen (session->client_nonce_b64); + + // The server can append his nonce to the client's nonce + if (strlen (server_nonce_b64) < client_nonce_len || + strncmp (server_nonce_b64, session->client_nonce_b64, client_nonce_len)) + { + session->error = g_strdup_printf ("Invalid server nonce: %s", server_nonce_b64); + return SCRAM_ERROR; + } + + g_base64_decode_inplace ((gchar *) salt, &salt_len); + + // SaltedPassword := Hi(Normalize(password), salt, i) + session->salted_password = g_malloc (session->digest_size); + + PKCS5_PBKDF2_HMAC (session->password, strlen (session->password), (unsigned char *) salt, + salt_len, iteration_count, session->digest, session->digest_size, + session->salted_password); + + // AuthMessage := client-first-message-bare + "," + + // server-first-message + "," + + // client-final-message-without-proof + client_final_message_without_proof = g_strdup_printf ("c=biws,r=%s", server_nonce_b64); + + session->auth_message = g_strdup_printf ("%s,%s,%s", session->client_first_message_bare, + data, client_final_message_without_proof); + + // ClientKey := HMAC(SaltedPassword, "Client Key") + client_key = g_malloc0 (session->digest_size); + + HMAC (session->digest, session->salted_password, session->digest_size, + (unsigned char *) CLIENT_KEY, strlen (CLIENT_KEY), client_key, &client_key_len); + + // StoredKey := H(ClientKey) + if (!create_SHA (session, client_key, session->digest_size, stored_key, &stored_key_len)) + { + g_free (client_final_message_without_proof); + g_free (server_nonce_b64); + g_free (salt); + g_free (client_key); + return SCRAM_ERROR; + } + + // ClientSignature := HMAC(StoredKey, AuthMessage) + client_signature = g_malloc0 (session->digest_size); + HMAC (session->digest, stored_key, stored_key_len, (unsigned char *) session->auth_message, + strlen ((char *) session->auth_message), client_signature, NULL); + + // ClientProof := ClientKey XOR ClientSignature + client_proof = g_malloc0 (client_key_len); + + for (i = 0; i < client_key_len; i++) + { + client_proof[i] = client_key[i] ^ client_signature[i]; + } + + client_proof_b64 = g_base64_encode ((guchar *) client_proof, client_key_len); + + *output = g_strdup_printf ("%s,p=%s", client_final_message_without_proof, client_proof_b64); + *output_len = strlen (*output); + + g_free (server_nonce_b64); + g_free (salt); + g_free (client_final_message_without_proof); + g_free (client_key); + g_free (client_signature); + g_free (client_proof); + g_free (client_proof_b64); + + session->step++; + return SCRAM_IN_PROGRESS; +} + +static scram_status +process_server_final (scram_session *session, const char *data) +{ + char *verifier; + unsigned char *server_key, *server_signature; + unsigned int server_key_len = 0, server_signature_len = 0; + gsize verifier_len = 0; + + if (strlen (data) < 3 || (data[0] != 'v' && data[1] != '=')) + { + return SCRAM_ERROR; + } + + verifier = g_strdup (data + 2); + g_base64_decode_inplace (verifier, &verifier_len); + + // ServerKey := HMAC(SaltedPassword, "Server Key") + server_key = g_malloc0 (session->digest_size); + HMAC (session->digest, session->salted_password, session->digest_size, + (unsigned char *) SERVER_KEY, strlen (SERVER_KEY), server_key, &server_key_len); + + // ServerSignature := HMAC(ServerKey, AuthMessage) + server_signature = g_malloc0 (session->digest_size); + HMAC (session->digest, server_key, session->digest_size, + (unsigned char *) session->auth_message, strlen ((char *) session->auth_message), + server_signature, &server_signature_len); + + if (verifier_len == server_signature_len && + memcmp (verifier, server_signature, verifier_len) == 0) + { + g_free (verifier); + g_free (server_key); + g_free (server_signature); + return SCRAM_SUCCESS; + } + else + { + g_free (verifier); + g_free (server_key); + g_free (server_signature); + return SCRAM_ERROR; + } +} + +scram_status +scram_process (scram_session *session, const char *input, char **output, size_t *output_len) +{ + scram_status status; + + switch (session->step) + { + case 0: + status = process_client_first (session, output, output_len); + break; + case 1: + status = process_server_first (session, input, output, output_len); + break; + case 2: + status = process_server_final (session, input); + break; + default: + *output = NULL; + *output_len = 0; + status = SCRAM_ERROR; + break; + } + + return status; +} + +#endif \ No newline at end of file diff --git a/src/common/scram.h b/src/common/scram.h new file mode 100644 index 00000000..ffe22037 --- /dev/null +++ b/src/common/scram.h @@ -0,0 +1,51 @@ +/* HexChat + * Copyright (C) 2023 Patrick Okraku + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#ifndef HEXCHAT_SCRAM_H +#define HEXCHAT_SCRAM_H + +#include "config.h" +#ifdef USE_OPENSSL +#include <openssl/evp.h> + +typedef struct +{ + const EVP_MD *digest; + size_t digest_size; + char *username; + char *password; + char *client_nonce_b64; + char *client_first_message_bare; + unsigned char *salted_password; + char *auth_message; + char *error; + int step; +} scram_session; + +typedef enum +{ + SCRAM_ERROR = 0, + SCRAM_IN_PROGRESS, + SCRAM_SUCCESS +} scram_status; + +scram_session *scram_session_create (const char *digset, const char *username, const char *password); +void scram_session_free (scram_session *session); +scram_status scram_process (scram_session *session, const char *input, char **output, size_t *output_len); + +#endif +#endif \ No newline at end of file diff --git a/src/common/server.c b/src/common/server.c index 5c645eb5..c78ce900 100644 --- a/src/common/server.c +++ b/src/common/server.c @@ -61,10 +61,6 @@ #include "ssl.h" #endif -#ifdef USE_LIBPROXY -#include <proxy.h> -#endif - #ifdef USE_OPENSSL /* local variables */ static struct session *g_sess = NULL; @@ -78,9 +74,15 @@ static void server_disconnect (session * sess, int sendquit, int err); static int server_cleanup (server * serv); static void server_connect (server *serv, char *hostname, int port, int no_login); -#ifdef USE_LIBPROXY -extern pxProxyFactory *libproxy_factory; -#endif +static void +write_error (char *message, GError **error) +{ + if (error == NULL || *error == NULL) { + return; + } + g_printerr ("%s: %s\n", message, (*error)->message); + g_clear_error (error); +} /* actually send to the socket. This might do a character translation or send via SSL. server/dcc both use this function. */ @@ -360,7 +362,7 @@ server_read (GIOChannel *source, GIOCondition condition, server *serv) serv->linebuf[serv->pos] = lbuf[i]; if (serv->pos >= (sizeof (serv->linebuf) - 1)) fprintf (stderr, - "*** HEXCHAT WARNING: Buffer overflow - shit server!\n"); + "*** HEXCHAT WARNING: Buffer overflow - non-compliant server!\n"); else serv->pos++; } @@ -770,7 +772,7 @@ server_connect_success (server *serv) /* it'll be a memory leak, if connection isn't terminated by server_cleanup() */ - if ((err = _SSL_set_verify (serv->ctx, ssl_cb_verify, NULL))) + if ((err = _SSL_set_verify (serv->ctx, ssl_cb_verify))) { EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, err, NULL, NULL, NULL, 0); @@ -1392,14 +1394,16 @@ server_child (server * serv) if (!serv->dont_use_proxy) /* blocked in serverlist? */ { -#ifdef USE_LIBPROXY if (prefs.hex_net_proxy_type == 5) { char **proxy_list; char *url, *proxy; + GProxyResolver *resolver; + GError *error = NULL; + resolver = g_proxy_resolver_get_default (); url = g_strdup_printf ("irc://%s:%d", hostname, port); - proxy_list = px_proxy_factory_get_proxies (libproxy_factory, url); + proxy_list = g_proxy_resolver_lookup (resolver, url, NULL, &error); if (proxy_list) { /* can use only one */ @@ -1412,6 +1416,8 @@ server_child (server * serv) proxy_type = 3; else if (!strncmp (proxy, "socks", 5)) proxy_type = 2; + } else { + write_error ("Failed to lookup proxy", &error); } if (proxy_type) { @@ -1426,7 +1432,7 @@ server_child (server * serv) g_strfreev (proxy_list); g_free (url); } -#endif + if (prefs.hex_net_proxy_host[0] && prefs.hex_net_proxy_type > 0 && prefs.hex_net_proxy_use != 2) /* proxy is NOT dcc-only */ @@ -1553,7 +1559,7 @@ server_connect (server *serv, char *hostname, int port, int no_login) if (!hostname[0]) return; - if (port < 0) + if (port < 1 || port > 65535) { /* use default port for this server type */ port = 6667; @@ -1561,8 +1567,8 @@ server_connect (server *serv, char *hostname, int port, int no_login) if (serv->use_ssl) port = 6697; #endif + g_debug ("Attempted to connect to invalid port, assuming default port %d", port); } - port &= 0xffff; /* wrap around */ if (serv->connected || serv->connecting || serv->recondelay_tag) server_disconnect (sess, TRUE, -1); @@ -1759,11 +1765,14 @@ server_set_defaults (server *serv) g_free (serv->chanmodes); g_free (serv->nick_prefixes); g_free (serv->nick_modes); - +#ifdef USE_OPENSSL + g_clear_pointer (&serv->scram_session, scram_session_free); +#endif serv->chantypes = g_strdup ("#&!+"); serv->chanmodes = g_strdup ("beI,k,l"); serv->nick_prefixes = g_strdup ("@%+"); serv->nick_modes = g_strdup ("ohv"); + serv->modes_per_line = 3; /* https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.3.1 */ serv->sasl_mech = MECH_PLAIN; if (!serv->encoding) @@ -1772,6 +1781,7 @@ server_set_defaults (server *serv) serv->nickcount = 1; serv->end_of_motd = FALSE; serv->sent_capend = FALSE; + serv->use_listargs = FALSE; serv->is_away = FALSE; serv->supports_watch = FALSE; serv->supports_monitor = FALSE; @@ -1929,6 +1939,8 @@ server_free (server *serv) #ifdef USE_OPENSSL if (serv->ctx) _SSL_context_free (serv->ctx); + + g_clear_pointer (&serv->scram_session, scram_session_free); #endif fe_server_callback (serv); diff --git a/src/common/servlist.c b/src/common/servlist.c index 79a5694b..1f29bb32 100644 --- a/src/common/servlist.c +++ b/src/common/servlist.c @@ -54,12 +54,6 @@ static const struct defaultserver def[] = /* Invalid hostname in cert */ {0, "irc.2600.net"}, - {"AccessIRC", 0}, - /* Self signed */ - {0, "irc.accessirc.net"}, - - {"ACN", 0, 0, 0, LOGIN_SASL, 0, TRUE}, - {0, "global.acn.gr"}, {"AfterNET", 0, 0, 0, LOGIN_SASL, 0, TRUE}, {0, "irc.afternet.org"}, @@ -67,15 +61,11 @@ static const struct defaultserver def[] = {"Aitvaras", 0}, #ifdef USE_OPENSSL {0, "irc.data.lt/+6668"}, - {0, "irc.omnitel.net/+6668"}, - {0, "irc.ktu.lt/+6668"}, - {0, "irc.kis.lt/+6668"}, + {0, "irc.omicron.lt/+6668"}, {0, "irc.vub.lt/+6668"}, #endif {0, "irc.data.lt"}, - {0, "irc.omnitel.net"}, - {0, "irc.ktu.lt"}, - {0, "irc.kis.lt"}, + {0, "irc.omicron.lt"}, {0, "irc.vub.lt"}, {"Anthrochat", 0, 0, 0, 0, 0, TRUE}, @@ -90,10 +80,6 @@ static const struct defaultserver def[] = {"AzzurraNet", 0}, {0, "irc.azzurra.org"}, - {"BetaChat", 0, 0, 0, LOGIN_SASL}, - {0, "irc.betachat.net"}, - {"BuddyIM", 0, 0, 0, LOGIN_SASL, 0, TRUE}, - {0, "irc.buddy.im"}, {"Canternet", 0, 0, 0, LOGIN_SASL, 0, TRUE}, {0, "irc.canternet.org"}, @@ -103,19 +89,16 @@ static const struct defaultserver def[] = {"ChatJunkies", 0}, {0, "irc.chatjunkies.org"}, - {"ChatNet", 0}, - {0, "irc.chatnet.org"}, + {"chatpat", 0, 0, "CP1251", LOGIN_CUSTOM, "MSG NS IDENTIFY %p"}, + {0, "irc.unibg.net"}, + {0, "irc.chatpat.bg"}, {"ChatSpike", 0, 0, 0, LOGIN_SASL}, {0, "irc.chatspike.net"}, - {"ChattingAway", 0}, - {0, "irc.chattingaway.com"}, - - {"Criten", 0}, - /* Self signed */ - {0, "irc.criten.net"}, - + {"DaIRC", 0}, + {0, "irc.dairc.net"}, + {"DALnet", 0, 0, 0, LOGIN_NICKSERV}, /* Self signed */ {0, "us.dal.net"}, @@ -132,19 +115,20 @@ static const struct defaultserver def[] = {"Dark-Tou-Net", 0}, {0, "irc.d-t-net.de"}, - - {"DeltaAnime", 0}, - {0, "irc.deltaanime.net"}, + + {"DigitalIRC", 0, 0, 0, LOGIN_SASL, 0, TRUE}, + {0, "irc.digitalirc.org"}, + +#ifdef USE_OPENSSL + {"DosersNET", 0, 0, 0, LOGIN_SASL, 0, TRUE}, + {0, "irc.dosers.net/+6697"}, +#endif {"EFnet", 0}, {0, "irc.choopa.net"}, - {0, "irc.paraphysics.net"}, {0, "efnet.port80.se"}, {0, "irc.underworld.no"}, - {0, "irc.inet.tele.dk"}, - - {"ElectroCode", 0, 0, 0, LOGIN_SASL, 0, TRUE}, - {0, "irc.electrocode.net"}, + {0, "efnet.deic.eu"}, {"EnterTheGame", 0}, {0, "irc.enterthegame.com"}, @@ -166,20 +150,8 @@ static const struct defaultserver def[] = /* Self signed */ {0, "irc.fdfnet.net"}, - {"freenode", 0, 0, 0, LOGIN_SASL, 0, TRUE}, - {0, "chat.freenode.net"}, - /* irc. points to chat. but many users and urls still reference it */ - {0, "irc.freenode.net"}, - - {"GalaxyNet", 0}, - {0, "irc.galaxynet.org"}, - {"GameSurge", 0}, {0, "irc.gamesurge.net"}, - - {"GeeksIRC", 0, 0, 0, LOGIN_SASL}, - /* Self signed */ - {0, "irc.geeksirc.net"}, {"GeekShed", 0, 0, 0, 0, 0, TRUE}, {0, "irc.geekshed.net"}, @@ -207,38 +179,31 @@ static const struct defaultserver def[] = {"Hashmark", 0}, {0, "irc.hashmark.net"}, - {"IdleMonkeys", 0}, - {0, "irc.idlemonkeys.net"}, + {"ICQ-Chat", 0, 0, 0, LOGIN_SASL, 0, TRUE}, + {0, "irc.icq-chat.com"}, - {"IndirectIRC", 0, 0, 0, LOGIN_SASL}, - /* Self signed */ - {0, "irc.indirectirc.com"}, - {"Interlinked", 0, 0, 0, LOGIN_SASL, 0, TRUE}, {0, "irc.interlinked.me"}, + {"Irc-Nerds", 0, 0, 0, LOGIN_SASL, 0, TRUE}, + {0, "irc.irc-nerds.net"}, + {"IRC4Fun", 0, 0, 0, LOGIN_SASL, 0, TRUE}, {0, "irc.irc4fun.net"}, - {"IRCHighWay", 0, 0, 0, 0, 0, TRUE}, - {0, "irc.irchighway.net"}, - {"IRCNet", 0}, {0, "open.ircnet.net"}, - {"Irctoo.net", 0}, + {"IRCtoo", 0}, {0, "irc.irctoo.net"}, - {"iZ-smart.net", 0, 0, "CP1252"}, - {0, "irc.iz-smart.net"}, - - {"KBFail", 0}, + {"Keyboard-Failure", 0}, /* SSL is self-signed */ {0, "irc.kbfail.net"}, - {"Krstarica", 0}, - {0, "irc.krstarica.com"}, - + {"Libera.Chat", 0, 0, 0, LOGIN_SASL, 0, TRUE}, + {0, "irc.libera.chat"}, + #ifdef USE_OPENSSL {"LibertaCasa", 0, 0, 0, LOGIN_SASL, 0, TRUE}, {0, "irc.liberta.casa"}, @@ -248,9 +213,6 @@ static const struct defaultserver def[] = /* Self signed */ {0, "irc.librairc.net"}, - {"Libera.Chat", 0, 0, 0, LOGIN_SASL, 0, TRUE}, - {0, "irc.libera.chat"}, - #ifdef USE_OPENSSL {"LinkNet", 0}, {0, "irc.link-net.org/+7000"}, @@ -262,10 +224,6 @@ static const struct defaultserver def[] = {"MIXXnet", 0}, {0, "irc.mixxnet.net"}, - {"ObsidianIRC", 0}, - /* Self signed */ - {0, "irc.obsidianirc.net"}, - {"Oceanius", 0, 0, 0, LOGIN_SASL}, /* Self signed */ {0, "irc.oceanius.com"}, @@ -276,16 +234,16 @@ static const struct defaultserver def[] = {"OtherNet", 0}, {0, "irc.othernet.org"}, - {"OzNet", 0}, + {"OzOrg", 0}, {0, "irc.oz.org"}, - {"PIRC.PL", 0, 0, 0, 0, 0, TRUE}, + {"PIK", 0}, + {0, "irc.krstarica.com"}, + + {"pirc.pl", 0, 0, 0, 0, 0, TRUE}, {0, "irc.pirc.pl"}, - - {"PonyChat", 0, 0, 0, LOGIN_SASL, 0, TRUE}, - {0, "irc.ponychat.net"}, - {"PTNet.org", 0}, + {"PTNet", 0}, {0, "irc.ptnet.org"}, {0, "uevora.ptnet.org"}, {0, "claranet.ptnet.org"}, @@ -306,12 +264,6 @@ static const struct defaultserver def[] = {0, "irc.ru"}, {0, "irc.lucky.net"}, - {"SceneNet", 0}, - {0, "irc.scene.org"}, - - {"SeilEn.de", 0, 0, "CP1252"}, - {0, "irc.seilen.de"}, - {"Serenity-IRC", 0}, {0, "irc.serenity-irc.net"}, @@ -328,10 +280,6 @@ static const struct defaultserver def[] = {"Sohbet.Net", 0, 0, "CP1254"}, {0, "irc.sohbet.net"}, - {"SolidIRC", 0}, - /* Self signed */ - {0, "irc.solidirc.com"}, - {"SorceryNet", 0, 0, 0, LOGIN_SASL}, /* Self signed */ {0, "irc.sorcery.net"}, @@ -339,9 +287,6 @@ static const struct defaultserver def[] = {"SpotChat", 0, 0, 0, LOGIN_SASL, 0, TRUE}, {0, "irc.spotchat.org"}, - {"StarChat", 0}, - {0, "irc.starchat.net"}, - {"Station51", 0}, /* Self signed */ {0, "irc.station51.net"}, @@ -360,9 +305,18 @@ static const struct defaultserver def[] = {"Techtronix", 0, 0, 0, LOGIN_SASL, 0, TRUE}, {0, "irc.techtronix.net"}, + {"TechNet", 0, 0, 0, LOGIN_SASL, 0, TRUE}, + {0, "irc.technet.chat"}, + {"tilde.chat", 0, 0, 0, LOGIN_SASL, 0, TRUE}, {0, "irc.tilde.chat"}, + {"TURLINet", 0, 0, 0, 0, 0, TRUE}, + /* all servers use UTF-8 and valid certs */ + {0, "irc.servx.org"}, + {0, "i.valware.uk"}, + + #ifdef USE_OPENSSL {"TripSit", 0, 0, 0, LOGIN_SASL, 0, TRUE}, {0, "irc.tripsit.me"}, @@ -370,27 +324,12 @@ static const struct defaultserver def[] = {0, "coconut.tripsit.me"}, {0, "innsbruck.tripsit.me"}, #endif - - {"TURLINet", 0, 0, 0, 0, 0, TRUE}, - /* Other servers use CP1251 and invalid certs */ - {0, "irc.servx.ru"}, {"UnderNet", 0, 0, 0, LOGIN_CUSTOM, "MSG x@channels.undernet.org login %u %p"}, - {0, "us.undernet.org"}, - - {"UniBG", 0, 0, "CP1251", LOGIN_CUSTOM, "MSG NS IDENTIFY %p"}, - {0, "irc.lirex.com"}, - {0, "irc.naturella.com"}, - {0, "irc.techno-link.com"}, - - {"Worldnet", 0}, - {0, "irc.worldnet.net"}, + {0, "irc.undernet.org"}, {"Xertion", 0, 0, 0, LOGIN_SASL, 0, TRUE}, {0, "irc.xertion.org"}, - - {"DeltaPool", 0, 0, 0, LOGIN_SASL, 0, TRUE}, - {0, "irc.deltapool.net"}, {0,0} }; @@ -947,6 +886,9 @@ servlist_net_add (char *name, char *comment, int prepend) net = g_new0 (ircnet, 1); net->name = g_strdup (name); net->flags = FLAG_CYCLE | FLAG_USE_GLOBAL | FLAG_USE_PROXY; +#ifdef USE_OPENSSL + net->flags |= FLAG_USE_SSL; +#endif if (prepend) network_list = g_slist_prepend (network_list, net); diff --git a/src/common/servlist.h b/src/common/servlist.h index ec885fef..c3d158b2 100644 --- a/src/common/servlist.h +++ b/src/common/servlist.h @@ -79,6 +79,9 @@ extern GSList *network_list; #define LOGIN_CHALLENGEAUTH 8 #define LOGIN_CUSTOM 9 #define LOGIN_SASLEXTERNAL 10 +#define LOGIN_SASL_SCRAM_SHA_1 11 +#define LOGIN_SASL_SCRAM_SHA_256 12 +#define LOGIN_SASL_SCRAM_SHA_512 13 #define CHALLENGEAUTH_ALGO "HMAC-SHA-256" #define CHALLENGEAUTH_NICK "Q@CServe.quakenet.org" diff --git a/src/common/ssl.c b/src/common/ssl.c index 0eb78bd7..e7f7e0a8 100644 --- a/src/common/ssl.c +++ b/src/common/ssl.c @@ -321,23 +321,22 @@ _SSL_socket (SSL_CTX *ctx, int sd) char * -_SSL_set_verify (SSL_CTX *ctx, void *verify_callback, char *cacert) +_SSL_set_verify (SSL_CTX *ctx, void *verify_callback) { - if (!SSL_CTX_set_default_verify_paths (ctx)) +#ifdef DEFAULT_CERT_FILE + if (!SSL_CTX_load_verify_locations (ctx, DEFAULT_CERT_FILE, NULL)) { - __SSL_fill_err_buf ("SSL_CTX_set_default_verify_paths"); + __SSL_fill_err_buf ("SSL_CTX_load_verify_locations"); return (err_buf); } -/* - if (cacert) +#else + if (!SSL_CTX_set_default_verify_paths (ctx)) { - if (!SSL_CTX_load_verify_locations (ctx, cacert, NULL)) - { - __SSL_fill_err_buf ("SSL_CTX_load_verify_locations"); - return (err_buf); - } + __SSL_fill_err_buf ("SSL_CTX_set_default_verify_paths"); + return (err_buf); } -*/ +#endif + SSL_CTX_set_verify (ctx, SSL_VERIFY_PEER, verify_callback); return (NULL); diff --git a/src/common/ssl.h b/src/common/ssl.h index e722f831..bea2f440 100644 --- a/src/common/ssl.h +++ b/src/common/ssl.h @@ -45,7 +45,7 @@ SSL_CTX *_SSL_context_init (void (*info_cb_func)); #define _SSL_context_free(a) SSL_CTX_free(a); SSL *_SSL_socket (SSL_CTX *ctx, int sd); -char *_SSL_set_verify (SSL_CTX *ctx, void *(verify_callback), char *cacert); +char *_SSL_set_verify (SSL_CTX *ctx, void *(verify_callback)); /* int SSL_connect(SSL *); int SSL_accept(SSL *); diff --git a/src/common/sysinfo/win32/backend.c b/src/common/sysinfo/win32/backend.c index 1d88b139..e2ae83ed 100644 --- a/src/common/sysinfo/win32/backend.c +++ b/src/common/sysinfo/win32/backend.c @@ -84,7 +84,8 @@ sysinfo_get_cpu_arch (void) GetNativeSystemInfo (&si); - if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 || + si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64) { return cpu_arch = 64; } @@ -106,7 +107,8 @@ sysinfo_get_build_arch (void) GetSystemInfo (&si); - if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 || + si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64) { return build_arch = 64; } @@ -354,6 +356,8 @@ static char *read_cpu_info (IWbemClassObject *object) VariantClear (&max_clock_speed_variant); + g_strchomp (name_utf8); + if (cpu_freq_mhz > 1000) { result = g_strdup_printf ("%s (%.2fGHz)", name_utf8, cpu_freq_mhz / 1000.f); diff --git a/src/common/text.c b/src/common/text.c index b0a90e03..a77700fa 100644 --- a/src/common/text.c +++ b/src/common/text.c @@ -1512,6 +1512,17 @@ static char * const pevt_discon_help[] = { N_("Error"), }; +static char * const pevt_stdrpl_help[] = { + N_("Error Code"), + N_("Error Message"), +}; + +static char * const pevt_stdrplcmd_help[] = { + N_("Command"), + N_("Error Code"), + N_("Error Message"), +}; + #include "textevents.h" static void diff --git a/src/common/textevents.in b/src/common/textevents.in index 14bd4b06..19b0d497 100644 --- a/src/common/textevents.in +++ b/src/common/textevents.in @@ -436,6 +436,18 @@ pevt_discon_help %C20*%O$tDisconnected (%C20$1%O) 1 +Fail +XP_TE_FAIL +pevt_stdrpl_help +%C20*%O$t$2%O +2 + +Fail Command +XP_TE_FAILCMD +pevt_stdrplcmd_help +%C20*%O$t$1: $3%O +3 + Found IP XP_TE_FOUNDIP pevt_foundip_help @@ -574,6 +586,18 @@ pevt_generic_none_help %C23*%O$tNo process is currently running 0 +Note +XP_TE_NOTE +pevt_stdrpl_help +%C22*%O$t$2%O +2 + +Note Command +XP_TE_NOTECMD +pevt_stdrplcmd_help +%C22*%O$t$1: $3%O +3 + Notice XP_TE_NOTICE pevt_notice_help @@ -802,6 +826,18 @@ pevt_usersonchan_help %C22*%O$tUsers on %C22$1%C: %C24$2%O 2 +Warn +XP_TE_WARN +pevt_stdrpl_help +%C23*%O$t$2%O +2 + +Warn Command +XP_TE_WARNCMD +pevt_stdrplcmd_help +%C23*%O$t$1: $3%O +3 + WhoIs Authenticated XP_TE_WHOIS_AUTH pevt_whoisauth_help diff --git a/src/common/url.c b/src/common/url.c index 6a1d09e8..162c5706 100644 --- a/src/common/url.c +++ b/src/common/url.c @@ -331,7 +331,7 @@ url_check_line (char *buf) GRegex *re(void); GMatchInfo *gmi; char *po = buf; - int i; + size_t i; /* Skip over message prefix */ if (*po == ':') @@ -536,6 +536,7 @@ struct { "lastfm", "/", URI_PATH }, { "xfire", "", URI_PATH }, { "ts3server", "", URI_PATH }, + { "web+*", "", URI_AUTHORITY | URI_OPT_USERINFO | URI_PATH }, { NULL, "", 0} }; @@ -569,7 +570,21 @@ re_url (void) g_string_append (grist_gstr, "|"); g_string_append (grist_gstr, "("); - g_string_append_printf (grist_gstr, "%s:", uri[i].scheme); + + if (!strcmp(uri[i].scheme, "web+*")) + { + g_string_append(grist_gstr, "web\\+[a-z]+"); + } + else + { + char *scheme_escaped = g_regex_escape_string (uri[i].scheme, strlen(uri[i].scheme)); + + g_string_append_printf (grist_gstr, "%s", scheme_escaped); + + g_free (scheme_escaped); + } + + g_string_append(grist_gstr, ":"); if (uri[i].flags & URI_AUTHORITY) g_string_append (grist_gstr, "//"); diff --git a/src/common/util.c b/src/common/util.c index 5b5fb23f..bd920cae 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -329,6 +329,7 @@ strip_color2 (const char *src, int len, char *dst, int flags) case '\026': /*ATTR_REVERSE: */ case '\002': /*ATTR_BOLD: */ case '\037': /*ATTR_UNDERLINE: */ + case '\036': /*ATTR_STRIKETHROUGH: */ case '\035': /*ATTR_ITALICS: */ if (!(flags & STRIP_ATTRIB)) goto pass_char; break; @@ -987,7 +988,7 @@ void country_search (char *pattern, void *ud, void (*print)(void *, char *, ...)) { const domain_t *dom; - int i; + size_t i; for (i = 0; i < sizeof (domain) / sizeof (domain_t); i++) { @@ -1374,11 +1375,16 @@ str_sha256hash (char *string) int i; unsigned char hash[SHA256_DIGEST_LENGTH]; char buf[SHA256_DIGEST_LENGTH * 2 + 1]; /* 64 digit hash + '\0' */ + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + SHA256 (string, strlen (string), hash); +#else SHA256_CTX sha256; SHA256_Init (&sha256); SHA256_Update (&sha256, string, strlen (string)); SHA256_Final (hash, &sha256); +#endif for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { diff --git a/src/fe-gtk/chanlist.c b/src/fe-gtk/chanlist.c index aeddc417..abf62843 100644 --- a/src/fe-gtk/chanlist.c +++ b/src/fe-gtk/chanlist.c @@ -512,7 +512,7 @@ chanlist_save (GtkWidget * wid, server *serv) GtkTreeModel *model = GET_MODEL (serv); if (gtk_tree_model_get_iter_first (model, &iter)) - gtkutil_file_req (_("Select an output filename"), chanlist_filereq_done, + gtkutil_file_req (NULL, _("Select an output filename"), chanlist_filereq_done, serv, NULL, NULL, FRF_WRITE); } diff --git a/src/fe-gtk/dccgui.c b/src/fe-gtk/dccgui.c index 5b8dbac9..728698e3 100644 --- a/src/fe-gtk/dccgui.c +++ b/src/fe-gtk/dccgui.c @@ -146,7 +146,7 @@ fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive) mdc->maxcps = maxcps; mdc->passive = passive; - gtkutil_file_req (tbuf, dcc_send_filereq_file, mdc, prefs.hex_dcc_dir, NULL, FRF_MULTIPLE|FRF_FILTERISINITIAL); + gtkutil_file_req (NULL, tbuf, dcc_send_filereq_file, mdc, prefs.hex_dcc_dir, NULL, FRF_MULTIPLE|FRF_FILTERISINITIAL); g_free (tbuf); } diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c index ee3e847c..125ab577 100644 --- a/src/fe-gtk/fe-gtk.c +++ b/src/fe-gtk/fe-gtk.c @@ -664,13 +664,13 @@ fe_print_text (struct session *sess, char *text, time_t stamp, return; if (sess == current_tab) - fe_set_tab_color (sess, 0); + fe_set_tab_color (sess, FE_COLOR_NONE); else if (sess->tab_state & TAB_STATE_NEW_HILIGHT) - fe_set_tab_color (sess, 3); + fe_set_tab_color (sess, FE_COLOR_NEW_HILIGHT); else if (sess->tab_state & TAB_STATE_NEW_MSG) - fe_set_tab_color (sess, 2); + fe_set_tab_color (sess, FE_COLOR_NEW_MSG); else - fe_set_tab_color (sess, 1); + fe_set_tab_color (sess, FE_COLOR_NEW_DATA); } void @@ -903,7 +903,7 @@ fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *) if (dcc->file) { char *filepath = g_build_filename (prefs.hex_dcc_dir, dcc->file, NULL); - gtkutil_file_req (message, dcc_saveas_cb, ud, filepath, NULL, + gtkutil_file_req (NULL, message, dcc_saveas_cb, ud, filepath, NULL, FRF_WRITE|FRF_NOASKOVERWRITE|FRF_FILTERISINITIAL); g_free (filepath); } @@ -1054,6 +1054,45 @@ osx_show_uri (const char *url) #endif +static inline char * +escape_uri (const char *uri) +{ + return g_uri_escape_string(uri, G_URI_RESERVED_CHARS_GENERIC_DELIMITERS G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, FALSE); +} + +static inline gboolean +uri_contains_forbidden_characters (const char *uri) +{ + while (*uri) + { + if (!g_ascii_isalnum (*uri) && !strchr ("-._~:/?#[]@!$&'()*+,;=", *uri)) + return TRUE; + uri++; + } + + return FALSE; +} + +static char * +maybe_escape_uri (const char *uri) +{ + /* The only way to know if a string has already been escaped or not + * is by fulling parsing each segement but we can try some more simple heuristics. */ + + /* If we find characters that should clearly be escaped. */ + if (uri_contains_forbidden_characters (uri)) + return escape_uri (uri); + + /* If it fails to be unescaped then it was not escaped. */ + char *unescaped = g_uri_unescape_string (uri, NULL); + if (!unescaped) + return escape_uri (uri); + g_free (unescaped); + + /* At this point it is probably safe to pass through as-is. */ + return g_strdup (uri); +} + static void fe_open_url_inner (const char *url) { @@ -1071,7 +1110,10 @@ fe_open_url_inner (const char *url) #elif defined(__APPLE__) osx_show_uri (url); #else - gtk_show_uri (NULL, url, GDK_CURRENT_TIME, NULL); + char *escaped_url = maybe_escape_uri (url); + g_debug ("Opening URL \"%s\" (%s)", escaped_url, url); + gtk_show_uri (NULL, escaped_url, GDK_CURRENT_TIME, NULL); + g_free (escaped_url); #endif } @@ -1173,7 +1215,7 @@ fe_get_file (const char *title, char *initial, { /* OK: Call callback once per file, then once more with file=NULL. */ /* CANCEL: Call callback once with file=NULL. */ - gtkutil_file_req (title, callback, userdata, initial, NULL, flags | FRF_FILTERISINITIAL); + gtkutil_file_req (NULL, title, callback, userdata, initial, NULL, flags | FRF_FILTERISINITIAL); } void diff --git a/src/fe-gtk/fkeys.c b/src/fe-gtk/fkeys.c index dc4b41bc..6dd16e35 100644 --- a/src/fe-gtk/fkeys.c +++ b/src/fe-gtk/fkeys.c @@ -894,7 +894,7 @@ key_save_kbs (void) #define STRIP_WHITESPACE \ while (buf[0] == ' ' || buf[0] == '\t') \ buf++; \ - len = strlen (buf); \ + len = strlen (buf); \ while (buf[len] == ' ' || buf[len] == '\t') \ { \ buf[len] = 0; \ diff --git a/src/fe-gtk/gtkutil.c b/src/fe-gtk/gtkutil.c index 674ad4fc..98a971f9 100644 --- a/src/fe-gtk/gtkutil.c +++ b/src/fe-gtk/gtkutil.c @@ -190,7 +190,7 @@ gtkutil_file_req_response (GtkWidget *dialog, gint res, struct file_req *freq) } void -gtkutil_file_req (const char *title, void *callback, void *userdata, char *filter, char *extensions, +gtkutil_file_req (GtkWindow *parent, const char *title, void *callback, void *userdata, char *filter, char *extensions, int flags) { struct file_req *freq; @@ -269,6 +269,16 @@ gtkutil_file_req (const char *title, void *callback, void *userdata, char *filte G_CALLBACK (gtkutil_file_req_response), freq); g_signal_connect (G_OBJECT (dialog), "destroy", G_CALLBACK (gtkutil_file_req_destroy), (gpointer) freq); + + if (parent) + gtk_window_set_transient_for (GTK_WINDOW (dialog), parent); + + if (flags & FRF_MODAL) + { + g_assert (parent); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + } + gtk_widget_show (dialog); } diff --git a/src/fe-gtk/gtkutil.h b/src/fe-gtk/gtkutil.h index 0aa36439..c6e380e9 100644 --- a/src/fe-gtk/gtkutil.h +++ b/src/fe-gtk/gtkutil.h @@ -25,7 +25,7 @@ typedef void (*filereqcallback) (void *, char *file); -void gtkutil_file_req (const char *title, void *callback, void *userdata, char *filter, char *extensions, int flags); +void gtkutil_file_req (GtkWindow *parent, const char *title, void *callback, void *userdata, char *filter, char *extensions, int flags); void gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad); void gtkutil_destroy_on_esc (GtkWidget *win); GtkWidget *gtkutil_button (GtkWidget *box, char *stock, char *tip, void *callback, diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 4e5baaa0..a3e633bc 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -175,20 +175,26 @@ fe_flash_window (session *sess) /* set a tab plain, red, light-red, or blue */ void -fe_set_tab_color (struct session *sess, int col) +fe_set_tab_color (struct session *sess, tabcolor col) { struct session *server_sess = sess->server->server_session; + int col_noflags = (col & ~FE_COLOR_ALLFLAGS); + int col_shouldoverride = !(col & FE_COLOR_FLAG_NOOVERRIDE); + if (sess->res->tab && sess->gui->is_tab && (col == 0 || sess != current_tab)) { - switch (col) + switch (col_noflags) { case 0: /* no particular color (theme default) */ sess->tab_state = TAB_STATE_NONE; chan_set_color (sess->res->tab, plain_list); break; case 1: /* new data has been displayed (dark red) */ - sess->tab_state = TAB_STATE_NEW_DATA; - chan_set_color (sess->res->tab, newdata_list); + if (col_shouldoverride || !((sess->tab_state & TAB_STATE_NEW_MSG) + || (sess->tab_state & TAB_STATE_NEW_HILIGHT))) { + sess->tab_state = TAB_STATE_NEW_DATA; + chan_set_color (sess->res->tab, newdata_list); + } if (chan_is_collapsed (sess->res->tab) && !((server_sess->tab_state & TAB_STATE_NEW_MSG) @@ -201,8 +207,10 @@ fe_set_tab_color (struct session *sess, int col) break; case 2: /* new message arrived in channel (light red) */ - sess->tab_state = TAB_STATE_NEW_MSG; - chan_set_color (sess->res->tab, newmsg_list); + if (col_shouldoverride || !(sess->tab_state & TAB_STATE_NEW_HILIGHT)) { + sess->tab_state = TAB_STATE_NEW_MSG; + chan_set_color (sess->res->tab, newmsg_list); + } if (chan_is_collapsed (sess->res->tab) && !(server_sess->tab_state & TAB_STATE_NEW_HILIGHT) @@ -391,27 +399,22 @@ fe_set_title (session *sess) _(DISPLAY_NAME)); break; case SESS_SERVER: - g_snprintf (tbuf, sizeof (tbuf), "%s @ %s - %s", - sess->server->nick, server_get_network (sess->server, TRUE), + g_snprintf (tbuf, sizeof (tbuf), "%s%s%s - %s", + prefs.hex_gui_win_nick ? sess->server->nick : "", + prefs.hex_gui_win_nick ? " @ " : "", server_get_network (sess->server, TRUE), _(DISPLAY_NAME)); break; case SESS_CHANNEL: /* don't display keys in the titlebar */ - if (prefs.hex_gui_win_modes) - { - g_snprintf (tbuf, sizeof (tbuf), - "%s @ %s / %s (%s) - %s", - sess->server->nick, server_get_network (sess->server, TRUE), - sess->channel, sess->current_modes ? sess->current_modes : "", - _(DISPLAY_NAME)); - } - else - { g_snprintf (tbuf, sizeof (tbuf), - "%s @ %s / %s - %s", - sess->server->nick, server_get_network (sess->server, TRUE), - sess->channel, _(DISPLAY_NAME)); - } + "%s%s%s / %s%s%s%s - %s", + prefs.hex_gui_win_nick ? sess->server->nick : "", + prefs.hex_gui_win_nick ? " @ " : "", + server_get_network (sess->server, TRUE), sess->channel, + prefs.hex_gui_win_modes && sess->current_modes ? " (" : "", + prefs.hex_gui_win_modes && sess->current_modes ? sess->current_modes : "", + prefs.hex_gui_win_modes && sess->current_modes ? ")" : "", + _(DISPLAY_NAME)); if (prefs.hex_gui_win_ucount) { g_snprintf (tbuf + strlen (tbuf), 9, " (%d)", sess->total); @@ -419,8 +422,9 @@ fe_set_title (session *sess) break; case SESS_NOTICES: case SESS_SNOTICES: - g_snprintf (tbuf, sizeof (tbuf), "%s @ %s (notices) - %s", - sess->server->nick, server_get_network (sess->server, TRUE), + g_snprintf (tbuf, sizeof (tbuf), "%s%s%s (notices) - %s", + prefs.hex_gui_win_nick ? sess->server->nick : "", + prefs.hex_gui_win_nick ? " @ " : "", server_get_network (sess->server, TRUE), _(DISPLAY_NAME)); break; default: @@ -540,7 +544,7 @@ mg_focus (session *sess) /* when called via mg_changui_new, is_tab might be true, but sess->res->tab is still NULL. */ if (sess->res->tab) - fe_set_tab_color (sess, 0); + fe_set_tab_color (sess, FE_COLOR_NONE); } static int @@ -955,7 +959,7 @@ mg_populate (session *sess) mg_set_topic_tip (sess); - plugin_emit_dummy_print (sess, "Focus Tab"); + plugin_emit_dummy_print (sess, "Focus Tab", -1); } void @@ -1394,6 +1398,8 @@ mg_color_insert (GtkWidget *item, gpointer userdata) text = "\037"; break; case 102: text = "\035"; break; + case 103: + text = "\036"; break; default: text = "\017"; break; } @@ -1447,7 +1453,8 @@ mg_create_color_menu (GtkWidget *menu, session *sess) mg_markup_item (submenu, _("<b>Bold</b>"), 100); mg_markup_item (submenu, _("<u>Underline</u>"), 101); mg_markup_item (submenu, _("<i>Italic</i>"), 102); - mg_markup_item (submenu, _("Normal"), 103); + mg_markup_item (submenu, _("<s>Strikethrough</s>"), 103); + mg_markup_item (submenu, _("Normal"), 999); subsubmenu = mg_submenu (submenu, _("Colors 0-7")); @@ -3070,7 +3077,7 @@ mg_tabwin_focus_cb (GtkWindow * win, GdkEventFocus *event, gpointer userdata) if (current_sess) { gtk_xtext_check_marker_visibility (GTK_XTEXT (current_sess->gui->xtext)); - plugin_emit_dummy_print (current_sess, "Focus Window"); + plugin_emit_dummy_print (current_sess, "Focus Window", -1); } unflash_window (GTK_WIDGET (win)); return FALSE; @@ -3084,7 +3091,7 @@ mg_topwin_focus_cb (GtkWindow * win, GdkEventFocus *event, session *sess) sess->server->server_session = sess; gtk_xtext_check_marker_visibility(GTK_XTEXT (current_sess->gui->xtext)); unflash_window (GTK_WIDGET (win)); - plugin_emit_dummy_print (sess, "Focus Window"); + plugin_emit_dummy_print (sess, "Focus Window", -1); return FALSE; } diff --git a/src/fe-gtk/menu.c b/src/fe-gtk/menu.c index 233715e5..76bc3906 100644 --- a/src/fe-gtk/menu.c +++ b/src/fe-gtk/menu.c @@ -1362,7 +1362,7 @@ savebuffer_req_done (session *sess, char *file) static void menu_savebuffer (GtkWidget * wid, gpointer none) { - gtkutil_file_req (_("Select an output filename"), savebuffer_req_done, + gtkutil_file_req (NULL, _("Select an output filename"), savebuffer_req_done, current_sess, NULL, NULL, FRF_WRITE); } diff --git a/src/fe-gtk/meson.build b/src/fe-gtk/meson.build index 3dfc7427..d07514db 100644 --- a/src/fe-gtk/meson.build +++ b/src/fe-gtk/meson.build @@ -43,11 +43,7 @@ hexchat_gtk_cflags = [] hexchat_gtk_ldflags = [] -if get_option('with-libnotify') - hexchat_gtk_sources += 'notifications/notification-libnotify.c' - hexchat_gtk_deps += dependency('libnotify') -elif false # TODO HAVE_GTK_MAC -elif host_machine.system() == 'windows' +if host_machine.system() == 'windows' hexchat_gtk_sources += 'notifications/notification-windows.c' # TODO: mingw doesn't have these headers or libs @@ -57,7 +53,7 @@ elif host_machine.system() == 'windows' #) else - hexchat_gtk_sources += 'notifications/notification-dummy.c' + hexchat_gtk_sources += 'notifications/notification-freedesktop.c' endif iso_codes = dependency('iso-codes', required: false) @@ -69,7 +65,7 @@ if iso_codes.found() join_paths(iso_codes_prefix, 'share/locale')) endif -if get_option('with-plugin') +if get_option('plugin') hexchat_gtk_sources += 'plugingui.c' endif diff --git a/src/fe-gtk/notifications/notification-freedesktop.c b/src/fe-gtk/notifications/notification-freedesktop.c new file mode 100644 index 00000000..a23284e5 --- /dev/null +++ b/src/fe-gtk/notifications/notification-freedesktop.c @@ -0,0 +1,148 @@ +/* HexChat + * Copyright (C) 2021 Patrick Griffis. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "config.h" + +#include <string.h> +#include <gio/gio.h> + +static GDBusProxy *fdo_notifications; +static gboolean strip_markup; + +static void +on_notify_ready (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data) +{ + GError *error = NULL; + guint32 notification_id; + GVariant *response = g_dbus_proxy_call_finish (proxy, res, &error); + if (error) + { + g_info ("Failed to send notification: %s", error->message); + g_error_free (error); + return; + } + + g_variant_get (response, "(u)", ¬ification_id); + g_info ("Notification sent. ID=%u", notification_id); + + g_variant_unref (response); +} + +void +notification_backend_show (const char *title, const char *text) +{ + GVariantBuilder params; + + g_assert (fdo_notifications); + + if (strip_markup) + text = g_markup_escape_text (text, -1); + + g_variant_builder_init (¶ms, G_VARIANT_TYPE ("(susssasa{sv}i)")); + g_variant_builder_add (¶ms, "s", "hexchat"); /* App name */ + g_variant_builder_add (¶ms, "u", 0); /* ID, 0 means don't replace */ + g_variant_builder_add (¶ms, "s", "io.github.Hexchat"); /* App icon */ + g_variant_builder_add (¶ms, "s", title); + g_variant_builder_add (¶ms, "s", text); + g_variant_builder_add (¶ms, "as", NULL); /* Actions */ + + /* Hints */ + g_variant_builder_open (¶ms, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_open (¶ms, G_VARIANT_TYPE ("{sv}")); + g_variant_builder_add (¶ms, "s", "desktop-entry"); + g_variant_builder_add (¶ms, "v", g_variant_new_string ("io.github.Hexchat")); + g_variant_builder_close (¶ms); + g_variant_builder_close (¶ms); + + g_variant_builder_add (¶ms, "i", -1); /* Expiration */ + + g_dbus_proxy_call (fdo_notifications, + "Notify", + g_variant_builder_end (¶ms), + G_DBUS_CALL_FLAGS_NONE, + 1000, + NULL, + (GAsyncReadyCallback)on_notify_ready, + NULL); + + if (strip_markup) + g_free ((char*)text); +} + +int +notification_backend_init (const char **error) +{ + GError *err = NULL; + GVariant *response; + char **capabilities; + guint i; + + fdo_notifications = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + NULL, + &err); + + if (err) + goto return_error; + + response = g_dbus_proxy_call_sync (fdo_notifications, + "GetCapabilities", + NULL, + G_DBUS_CALL_FLAGS_NONE, + 30, + NULL, + &err); + + if (err) + { + g_clear_object (&fdo_notifications); + goto return_error; + } + + g_variant_get (response, "(^a&s)", &capabilities); + for (i = 0; capabilities[i]; i++) + { + if (strcmp (capabilities[i], "body-markup") == 0) + strip_markup = TRUE; + } + + g_free (capabilities); + g_variant_unref (response); + return 1; + +return_error: + *error = g_strdup (err->message); + g_error_free (err); + return 0; +} + +void +notification_backend_deinit (void) +{ + g_clear_object (&fdo_notifications); +} + +int +notification_backend_supported (void) +{ + return fdo_notifications != NULL; +} diff --git a/src/fe-gtk/notifications/notification-libnotify.c b/src/fe-gtk/notifications/notification-libnotify.c deleted file mode 100644 index ee417396..00000000 --- a/src/fe-gtk/notifications/notification-libnotify.c +++ /dev/null @@ -1,81 +0,0 @@ -/* HexChat - * Copyright (C) 2015 Patrick Griffis. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - */ - -#include "config.h" -#include <glib.h> -#include <libnotify/notify.h> - -#ifndef NOTIFY_CHECK_VERSION -#define NOTIFY_CHECK_VERSION(x,y,z) 0 -#endif - -static gboolean strip_markup = FALSE; - -void -notification_backend_show (const char *title, const char *text) -{ - NotifyNotification *notification; - - if (strip_markup) - text = g_markup_escape_text (text, -1); - -#if NOTIFY_CHECK_VERSION(0,7,0) - notification = notify_notification_new (title, text, "hexchat"); -#else - notification = notify_notification_new (title, text, "hexchat", NULL); -#endif -#if NOTIFY_CHECK_VERSION(0,6,0) - notify_notification_set_hint (notification, "desktop-entry", g_variant_new_string ("io.github.Hexchat")); -#else - notify_notification_set_hint_string (notification, "desktop-entry", "io.github.Hexchat"); -#endif - - notify_notification_show (notification, NULL); - - g_object_unref (notification); - if (strip_markup) - g_free ((char*)text); -} - -int -notification_backend_init (const char **error) -{ - GList* server_caps; - - if (!notify_init (PACKAGE_NAME)) - return 0; - - server_caps = notify_get_server_caps (); - if (g_list_find_custom (server_caps, "body-markup", (GCompareFunc)g_strcmp0)) - strip_markup = TRUE; - g_list_free_full (server_caps, g_free); - - return 1; -} - -void -notification_backend_deinit (void) -{ - notify_uninit (); -} - -int -notification_backend_supported (void) -{ - return notify_is_initted (); -} diff --git a/src/fe-gtk/plugin-notification.c b/src/fe-gtk/plugin-notification.c index 29478d7a..fc71d22b 100644 --- a/src/fe-gtk/plugin-notification.c +++ b/src/fe-gtk/plugin-notification.c @@ -218,7 +218,7 @@ notification_plugin_init (hexchat_plugin *plugin_handle, char **plugin_name, cha if (!notification_backend_init (&error)) { if (error) - hexchat_printf(plugin_handle, "Failed loading notification plugin: %s\n", error); + g_debug("Failed loading notification plugin: %s\n", error); return 0; } @@ -246,7 +246,7 @@ notification_plugin_init (hexchat_plugin *plugin_handle, char **plugin_name, cha int -notification_plugin_deinit (void) +notification_plugin_deinit (void *unused_param) { notification_backend_deinit (); return 1; diff --git a/src/fe-gtk/plugingui.c b/src/fe-gtk/plugingui.c index 83bb745f..c40ac304 100644 --- a/src/fe-gtk/plugingui.c +++ b/src/fe-gtk/plugingui.c @@ -161,7 +161,7 @@ plugingui_load (void) { char *sub_dir = g_build_filename (get_xdir(), "addons", NULL); - gtkutil_file_req (_("Select a Plugin or Script to load"), plugingui_load_cb, current_sess, + gtkutil_file_req (NULL, _("Select a Plugin or Script to load"), plugingui_load_cb, current_sess, sub_dir, "*."PLUGIN_SUFFIX";*.lua;*.pl;*.py;*.tcl;*.js", FRF_FILTERISINITIAL|FRF_EXTENSIONS); g_free (sub_dir); diff --git a/src/fe-gtk/rawlog.c b/src/fe-gtk/rawlog.c index 52a77267..666059c6 100644 --- a/src/fe-gtk/rawlog.c +++ b/src/fe-gtk/rawlog.c @@ -77,7 +77,7 @@ rawlog_clearbutton (GtkWidget * wid, server *serv) static int rawlog_savebutton (GtkWidget * wid, server *serv) { - gtkutil_file_req (_("Save As..."), rawlog_save, serv, NULL, NULL, FRF_WRITE); + gtkutil_file_req (NULL, _("Save As..."), rawlog_save, serv, NULL, NULL, FRF_WRITE); return FALSE; } diff --git a/src/fe-gtk/servlistgui.c b/src/fe-gtk/servlistgui.c index b22330ac..0e5e108b 100644 --- a/src/fe-gtk/servlistgui.c +++ b/src/fe-gtk/servlistgui.c @@ -39,6 +39,12 @@ #define SERVLIST_X_PADDING 4 /* horizontal paddig in the network editor */ #define SERVLIST_Y_PADDING 0 /* vertical padding in the network editor */ +#ifdef USE_OPENSSL +# define DEFAULT_SERVER "newserver/6697" +#else +# define DEFAULT_SERVER "newserver/6667" +#endif + /* servlistgui.c globals */ static GtkWidget *serverlist_win = NULL; static GtkWidget *networks_tree; /* network TreeView */ @@ -122,6 +128,9 @@ static int login_types_conf[] = LOGIN_SASL, #ifdef USE_OPENSSL LOGIN_SASLEXTERNAL, + LOGIN_SASL_SCRAM_SHA_1, + LOGIN_SASL_SCRAM_SHA_256, + LOGIN_SASL_SCRAM_SHA_512, #endif LOGIN_PASS, LOGIN_MSG_NICKSERV, @@ -140,9 +149,12 @@ static int login_types_conf[] = static const char *login_types[]= { "Default", - "SASL (username + password)", + "SASL PLAIN (username + password)", #ifdef USE_OPENSSL "SASL EXTERNAL (cert)", + "SASL SCRAM-SHA-1", + "SASL SCRAM-SHA-256", + "SASL SCRAM-SHA-512", #endif "Server password (/PASS password)", "NickServ (/MSG NickServ + password)", @@ -299,7 +311,7 @@ servlist_networks_populate_ (GtkWidget *treeview, GSList *netlist, gboolean favo if (!netlist) { net = servlist_net_add (_("New Network"), "", FALSE); - servlist_server_add (net, "newserver/6667"); + servlist_server_add (net, DEFAULT_SERVER); netlist = network_list; } store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); @@ -434,10 +446,10 @@ servlist_addserver (void) return; store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (edit_trees[SERVER_TREE]))); - servlist_server_add (selected_net, "newserver/6667"); + servlist_server_add (selected_net, DEFAULT_SERVER); gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, "newserver/6667", 1, TRUE, -1); + gtk_list_store_set (store, &iter, 0, DEFAULT_SERVER, 1, TRUE, -1); /* select this server */ servlist_select_and_show (GTK_TREE_VIEW (edit_trees[SERVER_TREE]), &iter, store); @@ -498,7 +510,7 @@ servlist_addnet_cb (GtkWidget *item, GtkTreeView *treeview) net = servlist_net_add (_("New Network"), "", TRUE); net->encoding = g_strdup (IRC_DEFAULT_CHARSET); - servlist_server_add (net, "newserver/6667"); + servlist_server_add (net, DEFAULT_SERVER); store = (GtkListStore *)gtk_tree_view_get_model (treeview); gtk_list_store_prepend (store, &iter); diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 3d003eef..0e1dfde3 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -48,6 +48,7 @@ GtkStyle *create_input_style (GtkStyle *); #define LABEL_INDENT 12 +static GtkWidget *setup_window = NULL; static int last_selected_page = 0; static int last_selected_row = 0; /* sound row */ static gboolean color_change; @@ -175,6 +176,7 @@ static const setting appearance_settings[] = {ST_HEADER, N_("Title Bar"),0,0,0}, {ST_TOGGLE, N_("Show channel modes"), P_OFFINTNL(hex_gui_win_modes),0,0,0}, {ST_TOGGLR, N_("Show number of users"), P_OFFINTNL(hex_gui_win_ucount),0,0,0}, + {ST_TOGGLE, N_("Show nickname"), P_OFFINTNL(hex_gui_win_nick),0,0,0}, {ST_END, 0, 0, 0, 0, 0} }; @@ -614,9 +616,7 @@ static const char *const proxytypes[] = N_("SOCKS4"), N_("SOCKS5"), N_("HTTP"), -#ifdef USE_LIBPROXY N_("Auto"), -#endif NULL }; @@ -1107,8 +1107,8 @@ setup_browsefile_cb (GtkWidget *button, GtkWidget *entry) filter = "image/*"; filter_type = FRF_MIMETYPES; #endif - gtkutil_file_req (_("Select an Image File"), setup_filereq_cb, - entry, NULL, filter, filter_type|FRF_RECENTLYUSED); + gtkutil_file_req (GTK_WINDOW (setup_window), _("Select an Image File"), setup_filereq_cb, + entry, NULL, filter, filter_type|FRF_RECENTLYUSED|FRF_MODAL); } static void @@ -1143,7 +1143,7 @@ setup_fontsel_cancel (GtkWidget *button, GtkFontSelectionDialog *dialog) static void setup_browsefolder_cb (GtkWidget *button, GtkEntry *entry) { - gtkutil_file_req (_("Select Download Folder"), setup_filereq_cb, entry, (char*)gtk_entry_get_text (entry), NULL, FRF_CHOOSEFOLDER); + gtkutil_file_req (GTK_WINDOW (setup_window), _("Select Download Folder"), setup_filereq_cb, entry, (char*)gtk_entry_get_text (entry), NULL, FRF_CHOOSEFOLDER|FRF_MODAL); } static void @@ -1156,6 +1156,9 @@ setup_browsefont_cb (GtkWidget *button, GtkWidget *entry) dialog = (GtkFontSelectionDialog *) gtk_font_selection_dialog_new (_("Select font")); font_dialog = (GtkWidget *)dialog; /* global var */ + gtk_window_set_transient_for (GTK_WINDOW (font_dialog), GTK_WINDOW (setup_window)); + gtk_window_set_modal (GTK_WINDOW (font_dialog), TRUE); + sel = (GtkFontSelection *) gtk_font_selection_dialog_get_font_selection (dialog); if (gtk_entry_get_text (GTK_ENTRY (entry))[0]) @@ -1459,6 +1462,8 @@ setup_color_cb (GtkWidget *button, gpointer userdata) g_object_set_data (G_OBJECT (ok_button), "b", button); gtk_widget_set_sensitive (help_button, FALSE); gtk_color_selection_set_current_color (GTK_COLOR_SELECTION (gtk_color_selection_dialog_get_color_selection (cdialog)), color); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (setup_window)); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); gtk_widget_show (dialog); g_object_unref (cancel_button); @@ -1713,8 +1718,8 @@ setup_snd_browse_cb (GtkWidget *button, GtkEntry *entry) filter_type = FRF_MIMETYPES; #endif - gtkutil_file_req (_("Select a sound file"), setup_snd_filereq_cb, entry, - sounds_dir, filter, FRF_FILTERISINITIAL|filter_type); + gtkutil_file_req (GTK_WINDOW (setup_window), _("Select a sound file"), setup_snd_filereq_cb, entry, + sounds_dir, filter, FRF_MODAL|FRF_FILTERISINITIAL|filter_type); g_free (sounds_dir); } @@ -2338,8 +2343,6 @@ setup_close_cb (GtkWidget *win, GtkWidget **swin) void setup_open (void) { - static GtkWidget *setup_window = NULL; - if (setup_window) { gtk_window_present (GTK_WINDOW (setup_window)); diff --git a/src/fe-gtk/sexy-spell-entry.c b/src/fe-gtk/sexy-spell-entry.c index dce19b82..a3042783 100644 --- a/src/fe-gtk/sexy-spell-entry.c +++ b/src/fe-gtk/sexy-spell-entry.c @@ -390,6 +390,17 @@ insert_italic (SexySpellEntry *entry, guint start, gboolean toggle) } static void +insert_strikethrough (SexySpellEntry *entry, guint start, gboolean toggle) +{ + PangoAttribute *sattr; + + sattr = pango_attr_strikethrough_new (!toggle); + sattr->start_index = start; + sattr->end_index = PANGO_ATTR_INDEX_TO_TEXT_END; + pango_attr_list_change (entry->priv->attr_list, sattr); +} + +static void insert_color (SexySpellEntry *entry, guint start, int fgcolor, int bgcolor) { PangoAttribute *fgattr; @@ -429,6 +440,7 @@ insert_reset (SexySpellEntry *entry, guint start) insert_bold (entry, start, TRUE); insert_underline (entry, start, TRUE); insert_italic (entry, start, TRUE); + insert_strikethrough (entry, start, TRUE); insert_color (entry, start, -1, -1); } @@ -918,6 +930,7 @@ check_attributes (SexySpellEntry *entry, const char *text, int len) gboolean bold = FALSE; gboolean italic = FALSE; gboolean underline = FALSE; + gboolean strikethrough = FALSE; int parsing_color = 0; char fg_color[3]; char bg_color[3]; @@ -942,6 +955,12 @@ check_attributes (SexySpellEntry *entry, const char *text, int len) italic = !italic; goto check_color; + case ATTR_STRIKETHROUGH: + insert_hiddenchar (entry, i, i + 1); + insert_strikethrough (entry, i, strikethrough); + strikethrough = !strikethrough; + goto check_color; + case ATTR_UNDERLINE: insert_hiddenchar (entry, i, i + 1); insert_underline (entry, i, underline); @@ -954,6 +973,7 @@ check_attributes (SexySpellEntry *entry, const char *text, int len) bold = FALSE; italic = FALSE; underline = FALSE; + strikethrough = FALSE; goto check_color; case ATTR_HIDDEN: @@ -1235,7 +1255,7 @@ void sexy_spell_entry_activate_default_languages(SexySpellEntry *entry) { GSList *enchant_langs; - char *lang, *langs; + char *lang, **i, **langs; if (!have_enchant) return; @@ -1245,21 +1265,21 @@ sexy_spell_entry_activate_default_languages(SexySpellEntry *entry) enchant_langs = sexy_spell_entry_get_languages(entry); - langs = g_strdup (prefs.hex_text_spell_langs); + langs = g_strsplit_set (prefs.hex_text_spell_langs, ", \t", 0); - lang = strtok (langs, ","); - while (lang != NULL) + for (i = langs; *i; i++) { + lang = *i; + if (enchant_has_lang (lang, enchant_langs)) { sexy_spell_entry_activate_language_internal (entry, lang, NULL); } - lang = strtok (NULL, ","); } g_slist_foreach(enchant_langs, (GFunc) g_free, NULL); g_slist_free(enchant_langs); - g_free (langs); + g_strfreev (langs); /* If we don't have any languages activated, use "en" */ if (entry->priv->dict_list == NULL) diff --git a/src/fe-gtk/textgui.c b/src/fe-gtk/textgui.c index b0f2f392..b5eaf893 100644 --- a/src/fe-gtk/textgui.c +++ b/src/fe-gtk/textgui.c @@ -282,7 +282,7 @@ pevent_save_cb (GtkWidget * wid, void *data) { if (data) { - gtkutil_file_req (_("Print Texts File"), pevent_save_req_cb, NULL, + gtkutil_file_req (NULL, _("Print Texts File"), pevent_save_req_cb, NULL, NULL, NULL, FRF_WRITE); return; } @@ -304,7 +304,7 @@ pevent_load_req_cb (void *arg1, char *file) static void pevent_load_cb (GtkWidget * wid, void *data) { - gtkutil_file_req (_("Print Texts File"), pevent_load_req_cb, NULL, NULL, NULL, 0); + gtkutil_file_req (NULL, _("Print Texts File"), pevent_load_req_cb, NULL, NULL, NULL, 0); } static void diff --git a/src/fe-gtk/urlgrab.c b/src/fe-gtk/urlgrab.c index fd8d8d91..fc2f4b5a 100644 --- a/src/fe-gtk/urlgrab.c +++ b/src/fe-gtk/urlgrab.c @@ -145,7 +145,7 @@ url_save_callback (void *arg1, char *file) static void url_button_save (void) { - gtkutil_file_req (_("Select an output filename"), + gtkutil_file_req (NULL, _("Select an output filename"), url_save_callback, NULL, NULL, NULL, FRF_WRITE); } diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index 418bb4da..be978f22 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -170,7 +170,8 @@ xtext_pango_attr (PangoAttribute *attr) static void xtext_pango_init (GtkXText *xtext) { - int i, j; + size_t i; + int j; char buf[2] = "\000"; if (attr_lists[0]) @@ -433,6 +434,7 @@ gtk_xtext_init (GtkXText * xtext) xtext->nc = 0; xtext->pixel_offset = 0; xtext->underline = FALSE; + xtext->strikethrough = FALSE; xtext->hidden = FALSE; xtext->font = NULL; xtext->layout = NULL; @@ -946,7 +948,7 @@ gtk_xtext_find_char (GtkXText * xtext, int x, int y, int *off, int *out_of_bound textentry *ent; int line; int subline; - int outofbounds; + int outofbounds = FALSE; /* Adjust y value for negative rounding, double to int */ if (y < 0) @@ -2451,6 +2453,7 @@ gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf, case ATTR_REVERSE: case ATTR_BOLD: case ATTR_UNDERLINE: + case ATTR_STRIKETHROUGH: case ATTR_ITALICS: xtext_do_chunk (&c); if (*text == ATTR_RESET) @@ -2627,6 +2630,13 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, g_object_unref (pix); } + if (xtext->strikethrough) + { + /* pango_attr_strikethrough_new does not render in the custom widget so we need to reinvent the wheel */ + y = dest_y + (xtext->fontsize / 2); + gdk_draw_line (xtext->draw_buf, gc, dest_x, y, dest_x + str_width - 1, y); + } + if (xtext->underline) { dounder: @@ -2651,6 +2661,7 @@ gtk_xtext_reset (GtkXText * xtext, int mark, int attribs) if (attribs) { xtext->underline = FALSE; + xtext->strikethrough = FALSE; xtext->hidden = FALSE; } if (!mark) @@ -2961,6 +2972,12 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, pstr += j + 1; j = 0; break; + case ATTR_STRIKETHROUGH: + RENDER_FLUSH; + xtext->strikethrough = !xtext->strikethrough; + pstr += j + 1; + j = 0; + break; case ATTR_ITALICS: RENDER_FLUSH; *emphasis ^= EMPH_ITAL; @@ -3191,6 +3208,7 @@ find_next_wrap (GtkXText * xtext, textentry * ent, unsigned char *str, case ATTR_REVERSE: case ATTR_BOLD: case ATTR_UNDERLINE: + case ATTR_STRIKETHROUGH: case ATTR_ITALICS: if (*str == ATTR_RESET) emphasis = 0; diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h index 12d6f563..18d769fb 100644 --- a/src/fe-gtk/xtext.h +++ b/src/fe-gtk/xtext.h @@ -29,16 +29,17 @@ #define GTK_IS_XTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_XTEXT)) #define GTK_XTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_XTEXT, GtkXTextClass)) -#define ATTR_BOLD '\002' -#define ATTR_COLOR '\003' -#define ATTR_BLINK '\006' -#define ATTR_BEEP '\007' -#define ATTR_HIDDEN '\010' -#define ATTR_ITALICS2 '\011' -#define ATTR_RESET '\017' -#define ATTR_REVERSE '\026' -#define ATTR_ITALICS '\035' -#define ATTR_UNDERLINE '\037' +#define ATTR_BOLD '\002' +#define ATTR_COLOR '\003' +#define ATTR_BLINK '\006' +#define ATTR_BEEP '\007' +#define ATTR_HIDDEN '\010' +#define ATTR_ITALICS2 '\011' +#define ATTR_RESET '\017' +#define ATTR_REVERSE '\026' +#define ATTR_ITALICS '\035' +#define ATTR_STRIKETHROUGH '\036' +#define ATTR_UNDERLINE '\037' /* these match palette.h */ #define XTEXT_MIRC_COLS 32 @@ -207,6 +208,7 @@ struct _GtkXText /* current text states */ unsigned int underline:1; + unsigned int strikethrough:1; unsigned int hidden:1; /* text parsing states */ diff --git a/src/fe-text/fe-text.c b/src/fe-text/fe-text.c index 1d411ddf..3673a81f 100644 --- a/src/fe-text/fe-text.c +++ b/src/fe-text/fe-text.c @@ -623,7 +623,7 @@ fe_cleanup (void) { } void -fe_set_tab_color (struct session *sess, int col) +fe_set_tab_color (struct session *sess, tabcolor col) { } void diff --git a/src/meson.build b/src/meson.build index ff2c8871..23453ec1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,13 +1,13 @@ subdir('common') -if get_option('with-gtk') +if get_option('gtk-frontend') subdir('fe-gtk') endif -if get_option('with-text') +if get_option('text-frontend') subdir('fe-text') endif -if get_option('with-theme-manager') +if get_option('theme-manager') subdir('htm') endif diff --git a/win32/copy/copy.vcxproj b/win32/copy/copy.vcxproj index b26d7e28..cb3ea1cf 100644 --- a/win32/copy/copy.vcxproj +++ b/win32/copy/copy.vcxproj @@ -40,7 +40,8 @@ <None Include="$(DepsRoot)\bin\gthread-2.0-0.dll" /> <None Include="$(DepsRoot)\bin\gtk-win32-2.0.dll" /> <None Include="$(DepsRoot)\bin\iconv.dll" /> - <None Include="$(DepsRoot)\bin\libeay32.dll" /> + <None Include="$(DepsRoot)\bin\libcrypto*.dll" /> + <None Include="$(DepsRoot)\bin\libssl*.dll" /> <None Include="$(DepsRoot)\bin\libenchant.dll" /> <None Include="$(DepsRoot)\bin\ffi-7.dll" /> <None Include="$(DepsRoot)\bin\intl.dll" /> @@ -50,7 +51,6 @@ <None Include="$(DepsRoot)\bin\pangocairo-1.0-0.dll" /> <None Include="$(DepsRoot)\bin\pangoft2-1.0-0.dll" /> <None Include="$(DepsRoot)\bin\pangowin32-1.0-0.dll" /> - <None Include="$(DepsRoot)\bin\ssleay32.dll" /> <None Include="$(DepsRoot)\bin\zlib1.dll" /> <None Include="$(WinSparklePath)\WinSparkle.dll" /> <None Include="$(HexChatBin)thememan.exe" /> @@ -65,7 +65,6 @@ <LuaShare Include="$(DepsRoot)\share\lua\**\**\*.lua" /> <Typelib Include="$(DepsRoot)\lib\girepository-1.0\*.typelib" /> <None Include="$(Python3Path)\Lib\site-packages\_cffi_backend.*.pyd" /> - <None Include="$(Python2Path)\Lib\site-packages\_cffi_backend.pyd" /> <Engines Include="$(DepsRoot)\lib\gtk-2.0\i686-pc-vs14\engines\**\*" /> diff --git a/win32/hexchat.props b/win32/hexchat.props index f40c794a..d6c2bf1a 100644 --- a/win32/hexchat.props +++ b/win32/hexchat.props @@ -7,33 +7,29 @@ <YourDepsPath>c:\gtk-build\gtk</YourDepsPath> <YourGendefPath>c:\gtk-build\gendef</YourGendefPath> <YourPerlPath>c:\gtk-build\perl-5.20</YourPerlPath> - <YourPython2Path>c:\gtk-build\python-2.7</YourPython2Path> - <YourPython3Path>c:\gtk-build\python-3.6</YourPython3Path> + <YourPython3Path>c:\gtk-build\python-3.8</YourPython3Path> <YourWinSparklePath>c:\gtk-build\WinSparkle</YourWinSparklePath> <!-- YOU SHOULDN'T TOUCH ANYTHING BELOW --> <!-- G_DISABLE_DEPRECATED is unfeasible due to g_completion_* --> <!-- must be buildable with GSEAL_ENABLE in the future, xtext, setup, and chanview-tabs stand in the way --> - <OwnFlags>GTK_DISABLE_DEPRECATED;GDK_PIXBUF_DISABLE_DEPRECATED;G_DISABLE_SINGLE_INCLUDES;GDK_PIXBUF_DISABLE_SINGLE_INCLUDES;GTK_DISABLE_SINGLE_INCLUDES;HAVE_STRTOULL;strtoull=_strtoui64;strcasecmp=stricmp;strncasecmp=strnicmp;__inline__=__inline</OwnFlags> + <OwnFlags>GTK_DISABLE_DEPRECATED;GDK_PIXBUF_DISABLE_DEPRECATED;G_DISABLE_SINGLE_INCLUDES;GDK_PIXBUF_DISABLE_SINGLE_INCLUDES;GTK_DISABLE_SINGLE_INCLUDES;HAVE_X509_GET_SIGNATURE_NID;HAVE_SSL_CTX_GET_SSL_METHOD;DEFAULT_CERT_FILE="cert.pem";HAVE_STRTOULL;strtoull=_strtoui64;strcasecmp=stricmp;strncasecmp=strnicmp;__inline__=__inline</OwnFlags> <!-- FIXME: Add ability to use debug builds --> <DepsRoot>$(YourDepsPath)\$(PlatformName)\release</DepsRoot> <GendefPath>$(YourGendefPath)</GendefPath> <WinSparklePath>$(YourWinSparklePath)\$(PlatformName)</WinSparklePath> <PerlPath>$(YourPerlPath)\$(PlatformName)</PerlPath> <PerlLib>perl520</PerlLib> - <Python2Path>$(YourPython2Path)\$(PlatformName)</Python2Path> - <Python2Lib>python27</Python2Lib> - <Python2Output>hcpython2</Python2Output> <Python3Path>$(YourPython3Path)\$(PlatformName)</Python3Path> - <Python3Lib>python36</Python3Lib> + <Python3Lib>python38</Python3Lib> <Python3Output>hcpython3</Python3Output> <LuaInclude>$(DepsRoot)\include\luajit-2.1</LuaInclude> <LuaOutput>hclua</LuaOutput> <LuaLib>lua51</LuaLib> <Glib>$(DepsRoot)\include\glib-2.0;$(DepsRoot)\lib\glib-2.0\include;$(DepsRoot)\include\libxml2</Glib> <Gtk>$(DepsRoot)\include\gtk-2.0;$(DepsRoot)\lib\gtk-2.0\include;$(DepsRoot)\include\atk-1.0;$(DepsRoot)\include\cairo;$(DepsRoot)\include\pango-1.0;$(DepsRoot)\include\gdk-pixbuf-2.0</Gtk> - <DepLibs>gtk-win32-2.0.lib;gdk-win32-2.0.lib;atk-1.0.lib;gio-2.0.lib;gdk_pixbuf-2.0.lib;pangowin32-1.0.lib;pangocairo-1.0.lib;pango-1.0.lib;cairo.lib;gobject-2.0.lib;gmodule-2.0.lib;glib-2.0.lib;intl.lib;libxml2.lib;libeay32.lib;ssleay32.lib;wininet.lib;winmm.lib;ws2_32.lib</DepLibs> + <DepLibs>gtk-win32-2.0.lib;gdk-win32-2.0.lib;atk-1.0.lib;gio-2.0.lib;gdk_pixbuf-2.0.lib;pangowin32-1.0.lib;pangocairo-1.0.lib;pango-1.0.lib;cairo.lib;gobject-2.0.lib;gmodule-2.0.lib;glib-2.0.lib;intl.lib;libxml2.lib;libcrypto.lib;libssl.lib;ssleay32.lib;wininet.lib;winmm.lib;ws2_32.lib</DepLibs> <DataDir>$(SolutionDir)..\data\\</DataDir> <HexChatBuild>$(SolutionDir)..\..\hexchat-build</HexChatBuild> <HexChatBin>$(HexChatBuild)\$(PlatformName)\bin\</HexChatBin> diff --git a/win32/hexchat.sln b/win32/hexchat.sln index 8759c59b..57476b02 100644 --- a/win32/hexchat.sln +++ b/win32/hexchat.sln @@ -21,11 +21,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{5611 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripting", "scripting", "{D237DA6B-BD5F-46C0-8BEA-50E9A1340240}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "python2", "..\plugins\python\python2.vcxproj", "{19C52A0A-A790-409E-A28A-9745FF990F5C}" - ProjectSection(ProjectDependencies) = postProject - {87554B59-006C-4D94-9714-897B27067BA3} = {87554B59-006C-4D94-9714-897B27067BA3} - EndProjectSection -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "checksum", "..\plugins\checksum\checksum.vcxproj", "{5EF7F47D-D09C-43C4-BF64-B28B11A0FF91}" ProjectSection(ProjectDependencies) = postProject {87554B59-006C-4D94-9714-897B27067BA3} = {87554B59-006C-4D94-9714-897B27067BA3} @@ -72,7 +67,6 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "installer", "installer\installer.vcxproj", "{5A0F4962-E670-4DA2-9E45-52CC47F26E2F}" ProjectSection(ProjectDependencies) = postProject {C2321A03-0BA7-45B3-8740-ABD82B36B0BF} = {C2321A03-0BA7-45B3-8740-ABD82B36B0BF} - {19C52A0A-A790-409E-A28A-9745FF990F5C} = {19C52A0A-A790-409E-A28A-9745FF990F5C} {BF0EBC16-68AD-4CD1-864C-5B56836EBE2A} = {BF0EBC16-68AD-4CD1-864C-5B56836EBE2A} {17E4BE39-76F7-4A06-AD21-EFD0C5091F76} = {17E4BE39-76F7-4A06-AD21-EFD0C5091F76} {4C0F3940-2EEE-4646-82F7-6CE75B9A72F4} = {4C0F3940-2EEE-4646-82F7-6CE75B9A72F4} @@ -134,10 +128,6 @@ Global {E93E1255-95D1-4B08-8FDF-B53CC6A21280}.Release|Win32.Build.0 = Release|Win32 {E93E1255-95D1-4B08-8FDF-B53CC6A21280}.Release|x64.ActiveCfg = Release|x64 {E93E1255-95D1-4B08-8FDF-B53CC6A21280}.Release|x64.Build.0 = Release|x64 - {19C52A0A-A790-409E-A28A-9745FF990F5C}.Release|Win32.ActiveCfg = Release|Win32 - {19C52A0A-A790-409E-A28A-9745FF990F5C}.Release|Win32.Build.0 = Release|Win32 - {19C52A0A-A790-409E-A28A-9745FF990F5C}.Release|x64.ActiveCfg = Release|x64 - {19C52A0A-A790-409E-A28A-9745FF990F5C}.Release|x64.Build.0 = Release|x64 {5EF7F47D-D09C-43C4-BF64-B28B11A0FF91}.Release|Win32.ActiveCfg = Release|Win32 {5EF7F47D-D09C-43C4-BF64-B28B11A0FF91}.Release|Win32.Build.0 = Release|Win32 {5EF7F47D-D09C-43C4-BF64-B28B11A0FF91}.Release|x64.ActiveCfg = Release|x64 @@ -206,7 +196,6 @@ Global {87554B59-006C-4D94-9714-897B27067BA3} = {AAACEB12-9475-410E-AF5A-FDFF907E9043} {E4BDB4C8-2335-415A-ACEE-BA88B19BFE82} = {AAACEB12-9475-410E-AF5A-FDFF907E9043} {E93E1255-95D1-4B08-8FDF-B53CC6A21280} = {AAACEB12-9475-410E-AF5A-FDFF907E9043} - {19C52A0A-A790-409E-A28A-9745FF990F5C} = {D237DA6B-BD5F-46C0-8BEA-50E9A1340240} {5EF7F47D-D09C-43C4-BF64-B28B11A0FF91} = {561126F4-FA18-45FC-A2BF-8F858F161D6D} {17E4BE39-76F7-4A06-AD21-EFD0C5091F76} = {561126F4-FA18-45FC-A2BF-8F858F161D6D} {3C4F42FC-292A-420B-B63D-C03DFBDD8E4E} = {561126F4-FA18-45FC-A2BF-8F858F161D6D} diff --git a/win32/installer/hexchat.iss.tt b/win32/installer/hexchat.iss.tt index be985384..8337258f 100644 --- a/win32/installer/hexchat.iss.tt +++ b/win32/installer/hexchat.iss.tt @@ -25,7 +25,7 @@ DefaultDirName={pf64}\HexChat DefaultDirName={pf32}\HexChat #endif DefaultGroupName=HexChat -DisableProgramGroupPage=yes +AllowNoIcons=yes SolidCompression=yes Compression=lzma2/ultra64 SourceDir=..\rel @@ -60,6 +60,9 @@ Name: "custom"; Description: "Custom Installation"; Flags: iscustom Name: "libs"; Description: "HexChat"; Types: normal minimal custom; Flags: fixed Name: "xctext"; Description: "HexChat-Text"; Types: custom; Flags: disablenouninstallwarning Name: "xtm"; Description: "HexChat Theme Manager"; Types: normal custom; Flags: disablenouninstallwarning +Name: "icons"; Description: "Create Shortcuts"; Types: custom; Flags: disablenouninstallwarning +Name: "icons\desktopicon"; Description: "Create Desktop Shortcut"; Types: custom; Flags: disablenouninstallwarning +Name: "icons\quicklaunchicon"; Description: "Create Quick Launch Shortcut"; Types: custom; Flags: disablenouninstallwarning Name: "translations"; Description: "Translations"; Types: normal custom; Flags: disablenouninstallwarning Name: "spell"; Description: "Spelling Dictionaries"; Types: custom; Flags: disablenouninstallwarning Name: "plugins"; Description: "Plugins"; Types: custom; Flags: disablenouninstallwarning @@ -72,9 +75,7 @@ Name: "plugins\winamp"; Description: "Winamp"; Types: custom; Flags: disablenoun Name: "langs"; Description: "Language Interfaces"; Types: custom; Flags: disablenouninstallwarning Name: "langs\lua"; Description: "Lua"; Types: normal custom; Flags: disablenouninstallwarning Name: "langs\perl"; Description: "Perl (requires Perl 5.20)"; Types: custom; Flags: disablenouninstallwarning -Name: "langs\python"; Description: "Python Interface"; Types: custom; Flags: disablenouninstallwarning -Name: "langs\python\python2"; Description: "Python (requires Python 2.7)"; Types: custom; Flags: disablenouninstallwarning exclusive -Name: "langs\python\python3"; Description: "Python (requires Python 3.6)"; Types: custom; Flags: disablenouninstallwarning exclusive +Name: "langs\python"; Description: "Python (requires Python 3.8)"; Types: custom; Flags: disablenouninstallwarning [Tasks] Name: portable; Description: "Yes"; GroupDescription: "Portable Mode: Stores configuration files within install directory for portable drives."; Flags: unchecked @@ -138,7 +139,13 @@ Source: "gspawn-win32-helper-console.exe"; DestDir: "{app}"; Flags: ignoreversio Source: "gthread-2.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs Source: "gtk-win32-2.0.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs Source: "iconv.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs -Source: "libeay32.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs +#if APPARCH == "x64" +Source: "libcrypto-1_1-x64.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs +Source: "libssl-1_1-x64.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs +#else +Source: "libcrypto-1_1.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs +Source: "libssl-1_1.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs +#endif Source: "libenchant.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs Source: "ffi-7.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs Source: "intl.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs @@ -148,7 +155,6 @@ Source: "pango-1.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: l Source: "pangocairo-1.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs Source: "pangoft2-1.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs Source: "pangowin32-1.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs -Source: "ssleay32.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs Source: "zlib1.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs Source: "plugins\hcnotifications-winrt.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: libs @@ -180,25 +186,24 @@ Source: "plugins\hcperl.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Co Source: "python\*.py"; DestDir: "{app}\python"; Flags: ignoreversion; Components: langs\python -Source: "plugins\hcpython2.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\python\python2 -Source: "_cffi_backend.pyd"; DestDir: "{app}"; Flags: ignoreversion; Components: langs\python\python2 - -Source: "plugins\hcpython3.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\python\python3 -Source: "_cffi_backend.cp3*.pyd"; DestDir: "{app}"; Flags: ignoreversion; Components: langs\python\python3 +Source: "plugins\hcpython3.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\python +Source: "_cffi_backend.cp3*.pyd"; DestDir: "{app}"; Flags: ignoreversion; Components: langs\python Source: "hexchat.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: libs Source: "hexchat-text.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: xctext Source: "thememan.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: xtm [Icons] -Name: "{group}\HexChat"; Filename: "{app}\hexchat.exe"; AppUserModelID: "HexChat.Desktop.Notify"; Tasks: not portable -Name: "{group}\HexChat Safe Mode"; Filename: "{app}\hexchat.exe"; Parameters: "--no-auto --no-plugins"; Tasks: not portable -Name: "{group}\HexChat ChangeLog"; Filename: "{app}\changelog.url"; IconFilename: "{sys}\shell32.dll"; IconIndex: 165; Tasks: not portable -Name: "{group}\HexChat ReadMe"; Filename: "{app}\readme.url"; IconFilename: "{sys}\shell32.dll"; IconIndex: 23; Tasks: not portable -Name: "{group}\HexChat Config Folder"; Filename: "%APPDATA%\HexChat\"; Tasks: not portable -Name: "{group}\HexChat-Text"; Filename: "{app}\hexchat-text.exe"; Components: xctext; Tasks: not portable -Name: "{group}\HexChat Theme Manager"; Filename: "{app}\thememan.exe"; Components: xtm; Tasks: not portable -Name: "{group}\Uninstall HexChat"; Filename: "{uninstallexe}"; Tasks: not portable +Name: "{group}\HexChat"; Filename: "{app}\hexchat.exe"; AppUserModelID: "HexChat.Desktop.Notify"; Tasks: not portable; Check: not WizardNoIcons +Name: "{group}\HexChat Safe Mode"; Filename: "{app}\hexchat.exe"; Parameters: "--no-auto --no-plugins"; Tasks: not portable; Check: not WizardNoIcons +Name: "{group}\HexChat ChangeLog"; Filename: "{app}\changelog.url"; IconFilename: "{sys}\shell32.dll"; IconIndex: 165; Tasks: not portable; Check: not WizardNoIcons +Name: "{group}\HexChat ReadMe"; Filename: "{app}\readme.url"; IconFilename: "{sys}\shell32.dll"; IconIndex: 23; Tasks: not portable; Check: not WizardNoIcons +Name: "{group}\HexChat Config Folder"; Filename: "%APPDATA%\HexChat\"; Tasks: not portable; Check: not WizardNoIcons +Name: "{group}\HexChat-Text"; Filename: "{app}\hexchat-text.exe"; Components: xctext; Tasks: not portable; Check: not WizardNoIcons +Name: "{group}\HexChat Theme Manager"; Filename: "{app}\thememan.exe"; Components: xtm; Tasks: not portable; Check: not WizardNoIcons +Name: "{group}\Uninstall HexChat"; Filename: "{uninstallexe}"; Tasks: not portable; Check: not WizardNoIcons +Name: "{commondesktop}\HexChat"; Filename: "{app}\hexchat.exe"; AppUserModelID: "HexChat.Desktop.Notify"; Components: icons\desktopicon; Tasks: not portable +Name: "{commonappdata}\Microsoft\Internet Explorer\Quick Launch\HexChat"; Filename: "{app}\hexchat.exe"; Components: icons\quicklaunchicon; Tasks: not portable [Messages] BeveledLabel= {#APPNAM} @@ -290,20 +295,18 @@ begin begin #if APPARCH == "x64" - REDIST := 'https://dl.hexchat.net/misc/vcredist_2015_x64.exe'; - REDIST_2013 := 'https://dl.hexchat.net/misc/vcredist_2013_x64.exe'; - PERL := 'https://dl.hexchat.net/misc/perl/Perl%205.20.0%20x64.msi'; - PY2 := 'https://www.python.org/ftp/python/2.7.14/python-2.7.14.amd64.msi'; - PY3 := 'https://www.python.org/ftp/python/3.6.4/python-3.6.4-amd64.exe'; + REDIST := 'https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/vcredist_2015_x64.exe'; + REDIST_2013 := 'https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/vcredist_2013_x64.exe'; + PERL := 'https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/Perl.5.20.0.x64.msi'; + PY3 := 'https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe'; #else - REDIST := 'https://dl.hexchat.net/misc/vcredist_2015_x86.exe'; - REDIST_2013 := 'https://dl.hexchat.net/misc/vcredist_2013_x86.exe'; - PERL := 'https://dl.hexchat.net/misc/perl/Perl%205.20.0%20x86.msi'; - PY2 := 'https://www.python.org/ftp/python/2.7.14/python-2.7.14.msi'; - PY3 := 'https://www.python.org/ftp/python/3.6.4/python-3.6.4.exe'; + REDIST := 'https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/vcredist_2015_x86.exe'; + REDIST_2013 := 'https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/vcredist_2013_x86.exe'; + PERL := 'https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/Perl.5.20.0.x86.msi'; + PY3 := 'https://www.python.org/ftp/python/3.8.10/python-3.8.10.exe'; #endif - DOTNET := 'https://dl.hexchat.net/misc/dotnet_40.exe'; - SPELL := 'https://dl.hexchat.net/hexchat/HexChat%20Spelling%20Dictionaries%20r2.exe'; + DOTNET := 'https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/dotnet_40.exe'; + SPELL := 'https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/HexChat.Spelling.Dictionaries.r2.exe'; if not CheckVCInstall() then idpAddFile(REDIST, ExpandConstant('{tmp}\vcredist.exe')); @@ -324,10 +327,7 @@ begin idpAddFile(PERL, ExpandConstant('{tmp}\perl.msi')) end; - if IsComponentSelected('langs\python\python2') and not CheckDLL('python27.dll') then - idpAddFile(PY2, ExpandConstant('{tmp}\python.msi')); - - if IsComponentSelected('langs\python\python3') and not CheckDLL('python36.dll') then + if IsComponentSelected('langs\python\python3') and not CheckDLL('python38.dll') then idpAddFile(PY3, ExpandConstant('{tmp}\python.exe')); end; end; |