mirror of
https://github.com/weechat/weechat.git
synced 2026-06-12 14:14:48 +02:00
Compare commits
30 Commits
main
...
eb8c8641ea
| Author | SHA1 | Date | |
|---|---|---|---|
| eb8c8641ea | |||
| bd6455d07f | |||
| 8d3180fa78 | |||
| 519ab4e7bb | |||
| 3c36fd7412 | |||
| b62c97dbe3 | |||
| f91f92b48f | |||
| 30529057c8 | |||
| a69f356182 | |||
| 1438255a87 | |||
| d15ce789a0 | |||
| 377b6da43d | |||
| 8b1b06a407 | |||
| c09c1bf2fc | |||
| c83afb9a06 | |||
| ed9535a43f | |||
| 2148829ebe | |||
| 1ca2a00255 | |||
| c737373d17 | |||
| 30230498b2 | |||
| 35699ea802 | |||
| 5e4c165dad | |||
| 23fb6bfe88 | |||
| ec03437f9e | |||
| 564ad2d5cd | |||
| f935aa3f9f | |||
| 76a7d5d3bd | |||
| f0f77e1bd9 | |||
| 8c0a3b4d81 | |||
| a4a06f255a |
@@ -36,7 +36,7 @@ env:
|
||||
libcurl4-gnutls-dev
|
||||
libgcrypt20-dev
|
||||
libgnutls28-dev
|
||||
liblua5.3-dev
|
||||
liblua5.4-dev
|
||||
libncurses-dev
|
||||
libperl-dev
|
||||
libphp-embed
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get --yes --no-install-recommends install ${{ env.CHECK_DEPS_UBUNTU }}
|
||||
pipx install msgcheck ruff
|
||||
cargo install --version 0.0.8 poexam
|
||||
cargo install --version 0.0.10 poexam
|
||||
|
||||
- name: Check gettext files (msgcheck)
|
||||
run: msgcheck po/*.po
|
||||
@@ -280,8 +280,10 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
dnf install -y epel-release
|
||||
dnf install -y epel-release dnf-plugins-core
|
||||
dnf config-manager --set-enabled crb
|
||||
# pin a working ruby stream (ruby:4.0 has broken module metadata on Rocky 9.8)
|
||||
dnf module enable -y ruby:3.3
|
||||
dnf install -y ${{ env.WEECHAT_DEPS_ROCKYLINUX }}
|
||||
|
||||
- name: Build and run tests
|
||||
|
||||
@@ -9,7 +9,14 @@ select = [
|
||||
ignore = [
|
||||
"brackets",
|
||||
"double-quotes",
|
||||
"double-words",
|
||||
"header",
|
||||
"html-tags",
|
||||
"paths",
|
||||
"punc-space-str",
|
||||
"unchanged",
|
||||
"unicode-ctrl",
|
||||
"urls",
|
||||
]
|
||||
path_words = "."
|
||||
langs = [
|
||||
|
||||
@@ -6,6 +6,29 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# WeeChat ChangeLog
|
||||
|
||||
## Version 4.9.2 (2026-06-07)
|
||||
|
||||
### Fixed
|
||||
|
||||
- api: fix infinite loop in function string_replace when the search string is empty
|
||||
- irc: limit size of data received from the server to prevent memory exhaustion
|
||||
- irc: fix out-of-bounds read on incoming DCC command with a quoted filename ending the message ([#2322](https://github.com/weechat/weechat/issues/2322))
|
||||
- relay: limit size of received websocket frame and HTTP body to prevent memory exhaustion
|
||||
- relay: limit size of partial message received while reading an HTTP request to prevent memory exhaustion
|
||||
- relay: fix out-of-bounds read in dump of data ([#2324](https://github.com/weechat/weechat/issues/2324))
|
||||
- xfer: replace directory separator in remote nick by underscore in download filename to prevent writing the file outside the download directory ([#2321](https://github.com/weechat/weechat/issues/2321))
|
||||
- xfer: fix out-of-bounds read when receiving empty line in DCC chat ([#2323](https://github.com/weechat/weechat/issues/2323))
|
||||
|
||||
## Version 4.9.1 (2026-05-31)
|
||||
|
||||
### Fixed
|
||||
|
||||
- core: fix option weechat.look.color_real_white not applied when color is "white" on 16+ colors terminals ([#1742](https://github.com/weechat/weechat/issues/1742))
|
||||
- irc: fix tag in message with list of names when joining a channel
|
||||
- relay: limit size of decompressed websocket frame with permessage-deflate to prevent memory exhaustion ([GHSA-v2v4-45wm-5cr3](https://github.com/weechat/weechat/security/advisories/GHSA-v2v4-45wm-5cr3))
|
||||
- relay: fix timing attack on password authentication ([GHSA-vhv8-g2r9-cwcc](https://github.com/weechat/weechat/security/advisories/GHSA-vhv8-g2r9-cwcc))
|
||||
- api, relay: fix timing attack on TOTP validation ([GHSA-vhv8-g2r9-cwcc](https://github.com/weechat/weechat/security/advisories/GHSA-vhv8-g2r9-cwcc))
|
||||
|
||||
## Version 4.9.0 (2026-03-29)
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -28,6 +28,7 @@ project(weechat C)
|
||||
# CMake options
|
||||
set(CMAKE_VERBOSE_MAKEFILE OFF)
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}")
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
set(CMAKE_SKIP_RPATH ON)
|
||||
|
||||
# compiler options
|
||||
|
||||
@@ -12,7 +12,7 @@ Build-Depends:
|
||||
libperl-dev,
|
||||
python3-dev,
|
||||
libaspell-dev,
|
||||
liblua5.3-dev,
|
||||
liblua5.4-dev,
|
||||
tcl8.6-dev,
|
||||
guile-3.0-dev,
|
||||
php-dev, libphp-embed, libargon2-dev, libsodium-dev,
|
||||
|
||||
@@ -12,7 +12,7 @@ Build-Depends:
|
||||
libperl-dev,
|
||||
python3-dev,
|
||||
libaspell-dev,
|
||||
liblua5.3-dev,
|
||||
liblua5.4-dev,
|
||||
tcl8.6-dev,
|
||||
guile-3.0-dev,
|
||||
php-dev, libphp-embed, libargon2-dev, libsodium-dev,
|
||||
|
||||
+10
-4
@@ -46,6 +46,9 @@
|
||||
#include <netinet/in.h>
|
||||
#include <inttypes.h>
|
||||
#define BE_INT64 htonll
|
||||
#elif defined(__HAIKU__)
|
||||
#include <ByteOrder.h>
|
||||
#define BE_INT64 B_HOST_TO_BENDIAN_INT64
|
||||
#else
|
||||
#define BE_INT64 htobe64
|
||||
#endif
|
||||
@@ -657,15 +660,18 @@ weecrypto_totp_validate (const char *secret_base32, time_t totp_time,
|
||||
|
||||
otp_ok = 0;
|
||||
|
||||
/*
|
||||
* Compare in constant time and never break early: a non-constant
|
||||
* compare and an early exit on match would let an observer measure
|
||||
* how many digits of the expected OTP they got right and which
|
||||
* time-window offset matched.
|
||||
*/
|
||||
for (i = moving_factor - window; i <= moving_factor + window; i++)
|
||||
{
|
||||
rc = weecrypto_totp_generate_internal (secret, length_secret,
|
||||
i, digits, str_otp);
|
||||
if (rc && (strcmp (str_otp, otp) == 0))
|
||||
{
|
||||
if (rc && (string_memcmp_constant_time (str_otp, otp, digits) == 0))
|
||||
otp_ok = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free (secret);
|
||||
|
||||
@@ -922,6 +922,43 @@ string_strcmp_ignore_chars (const char *string1, const char *string2,
|
||||
string_charcasecmp (string1, string2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare two memory areas of the same size in constant time.
|
||||
*
|
||||
* Use to compare secrets (e.g. password hashes, MACs) without leaking
|
||||
* information through the comparison's running time. The loop always
|
||||
* walks the full "size" bytes and uses only bitwise operations on the
|
||||
* data, so the execution time depends on "size" alone, not on the
|
||||
* position of the first differing byte.
|
||||
*
|
||||
* If either pointer is NULL, the areas are considered different (the
|
||||
* NULL check itself is not constant time but does not look at any
|
||||
* secret content).
|
||||
*
|
||||
* Return:
|
||||
* 0: areas are equal
|
||||
* 1: areas differ
|
||||
*/
|
||||
|
||||
int
|
||||
string_memcmp_constant_time (const void *area1, const void *area2, size_t size)
|
||||
{
|
||||
const unsigned char *p1, *p2;
|
||||
unsigned char diff;
|
||||
size_t i;
|
||||
|
||||
if (!area1 || !area2)
|
||||
return (area1 == area2) ? 0 : 1;
|
||||
|
||||
p1 = (const unsigned char *)area1;
|
||||
p2 = (const unsigned char *)area2;
|
||||
diff = 0;
|
||||
for (i = 0; i < size; i++)
|
||||
diff |= p1[i] ^ p2[i];
|
||||
|
||||
return (diff == 0) ? 0 : 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Search for a string in another string (locale and case independent).
|
||||
*
|
||||
@@ -1928,6 +1965,9 @@ string_replace (const char *string, const char *search, const char *replace)
|
||||
if (!string || !search || !replace)
|
||||
return NULL;
|
||||
|
||||
if (!search[0])
|
||||
return strdup (string);
|
||||
|
||||
length1 = strlen (search);
|
||||
length2 = strlen (replace);
|
||||
|
||||
|
||||
@@ -69,6 +69,8 @@ extern int string_strcmp_ignore_chars (const char *string1,
|
||||
const char *string2,
|
||||
const char *chars_ignored,
|
||||
int case_sensitive);
|
||||
extern int string_memcmp_constant_time (const void *area1, const void *area2,
|
||||
size_t size);
|
||||
extern const char *string_strcasestr (const char *string, const char *search);
|
||||
extern int string_match (const char *string, const char *mask,
|
||||
int case_sensitive);
|
||||
|
||||
@@ -125,7 +125,9 @@ struct t_url_constant url_auth[] =
|
||||
URL_DEF_CONST(AUTH, NTLM),
|
||||
URL_DEF_CONST(AUTH, ANY),
|
||||
URL_DEF_CONST(AUTH, ANYSAFE),
|
||||
#if LIBCURL_VERSION_NUM < 0x081500 /* < 8.21.0 */
|
||||
URL_DEF_CONST(AUTH, DIGEST_IE),
|
||||
#endif
|
||||
URL_DEF_CONST(AUTH, ONLY),
|
||||
#if LIBCURL_VERSION_NUM < 0x080800 /* < 8.8.0 */
|
||||
URL_DEF_CONST(AUTH, NTLM_WB),
|
||||
|
||||
@@ -372,7 +372,8 @@ gui_window_set_weechat_color (WINDOW *window, int num_color)
|
||||
* if not real white, we use default terminal foreground instead of
|
||||
* white if bold attribute is set
|
||||
*/
|
||||
if ((fg == COLOR_WHITE) && (gui_color[num_color]->attributes & A_BOLD)
|
||||
if (((fg == COLOR_WHITE + 8)
|
||||
|| ((fg == COLOR_WHITE) && (gui_color[num_color]->attributes & A_BOLD)))
|
||||
&& !CONFIG_BOOLEAN(config_look_color_real_white))
|
||||
{
|
||||
fg = -1;
|
||||
@@ -442,7 +443,8 @@ gui_window_set_custom_color_fg (WINDOW *window, int fg)
|
||||
* if not real white, we use default terminal foreground instead of
|
||||
* white if bold attribute is set
|
||||
*/
|
||||
if ((fg == COLOR_WHITE) && (attributes & A_BOLD)
|
||||
if (((fg == COLOR_WHITE + 8)
|
||||
|| ((fg == COLOR_WHITE) && (attributes & A_BOLD)))
|
||||
&& !CONFIG_BOOLEAN(config_look_color_real_white))
|
||||
{
|
||||
fg = -1;
|
||||
@@ -535,7 +537,8 @@ gui_window_set_custom_color_fg_bg (WINDOW *window, int fg, int bg,
|
||||
* if not real white, we use default terminal foreground instead of
|
||||
* white if bold attribute is set
|
||||
*/
|
||||
if ((fg == COLOR_WHITE) && (attributes & A_BOLD)
|
||||
if (((fg == COLOR_WHITE + 8)
|
||||
|| ((fg == COLOR_WHITE) && (attributes & A_BOLD)))
|
||||
&& !CONFIG_BOOLEAN(config_look_color_real_white))
|
||||
{
|
||||
fg = -1;
|
||||
|
||||
@@ -857,7 +857,7 @@ irc_ctcp_recv_dcc (struct t_irc_protocol_ctxt *ctxt, const char *arguments)
|
||||
* double-quote
|
||||
*/
|
||||
pos = strrchr (pos_file, '"');
|
||||
if (!pos || (pos == pos_file))
|
||||
if (!pos || (pos == pos_file) || !pos[1])
|
||||
{
|
||||
weechat_printf (
|
||||
ctxt->server->buffer,
|
||||
@@ -1032,7 +1032,7 @@ irc_ctcp_recv_dcc (struct t_irc_protocol_ctxt *ctxt, const char *arguments)
|
||||
* double-quote
|
||||
*/
|
||||
pos = strrchr (pos_file, '"');
|
||||
if (!pos || (pos == pos_file))
|
||||
if (!pos || (pos == pos_file) || !pos[1])
|
||||
{
|
||||
weechat_printf (
|
||||
ctxt->server->buffer,
|
||||
@@ -1176,7 +1176,7 @@ irc_ctcp_recv_dcc (struct t_irc_protocol_ctxt *ctxt, const char *arguments)
|
||||
* double-quote
|
||||
*/
|
||||
pos = strrchr (pos_file, '"');
|
||||
if (!pos || (pos == pos_file))
|
||||
if (!pos || (pos == pos_file) || !pos[1])
|
||||
{
|
||||
weechat_printf (
|
||||
ctxt->server->buffer,
|
||||
|
||||
@@ -208,7 +208,8 @@ irc_protocol_tags_add_cb (void *data,
|
||||
*/
|
||||
|
||||
const char *
|
||||
irc_protocol_tags (struct t_irc_protocol_ctxt *ctxt, const char *extra_tags)
|
||||
irc_protocol_tags_cmd (struct t_irc_protocol_ctxt *ctxt, const char *command,
|
||||
const char *extra_tags)
|
||||
{
|
||||
static char string[4096];
|
||||
const char *ptr_tag_batch, *ptr_nick, *ptr_address;
|
||||
@@ -222,7 +223,7 @@ irc_protocol_tags (struct t_irc_protocol_ctxt *ctxt, const char *extra_tags)
|
||||
ptr_nick = NULL;
|
||||
ptr_address = NULL;
|
||||
|
||||
is_numeric = irc_protocol_is_numeric_command (ctxt->command);
|
||||
is_numeric = irc_protocol_is_numeric_command (command);
|
||||
has_irc_tags = (ctxt->tags
|
||||
&& weechat_hashtable_get_integer (ctxt->tags,
|
||||
"items_count") > 0);
|
||||
@@ -287,9 +288,9 @@ irc_protocol_tags (struct t_irc_protocol_ctxt *ctxt, const char *extra_tags)
|
||||
}
|
||||
}
|
||||
|
||||
if (ctxt->command && ctxt->command[0])
|
||||
if (command && command[0])
|
||||
{
|
||||
log_level = irc_protocol_log_level_for_command (ctxt->command);
|
||||
log_level = irc_protocol_log_level_for_command (command);
|
||||
if (log_level > 0)
|
||||
{
|
||||
snprintf (str_log_level, sizeof (str_log_level),
|
||||
@@ -299,8 +300,8 @@ irc_protocol_tags (struct t_irc_protocol_ctxt *ctxt, const char *extra_tags)
|
||||
|
||||
snprintf (string, sizeof (string),
|
||||
"%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
|
||||
(ctxt->command && ctxt->command[0]) ? "irc_" : "",
|
||||
(ctxt->command && ctxt->command[0]) ? ctxt->command : "",
|
||||
(command && command[0]) ? "irc_" : "",
|
||||
(command && command[0]) ? command : "",
|
||||
(is_numeric) ? "," : "",
|
||||
(is_numeric) ? "irc_numeric" : "",
|
||||
(str_irc_tags && (*str_irc_tags)[0]) ? "," : "",
|
||||
@@ -322,6 +323,16 @@ irc_protocol_tags (struct t_irc_protocol_ctxt *ctxt, const char *extra_tags)
|
||||
return (string[0] == ',') ? string + 1 : string;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build tags list with IRC command and optional tags and nick.
|
||||
*/
|
||||
|
||||
const char *
|
||||
irc_protocol_tags (struct t_irc_protocol_ctxt *ctxt, const char *extra_tags)
|
||||
{
|
||||
return irc_protocol_tags_cmd (ctxt, ctxt->command, extra_tags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Build a string with nick and optional address.
|
||||
*
|
||||
@@ -4153,16 +4164,25 @@ IRC_PROTOCOL_CALLBACK(005)
|
||||
if (ctxt->server->isupport)
|
||||
{
|
||||
length_isupport = strlen (ctxt->server->isupport);
|
||||
isupport2 = realloc (ctxt->server->isupport,
|
||||
length_isupport + /* existing */
|
||||
1 + /* space */
|
||||
length + /* new */
|
||||
1);
|
||||
if (isupport2)
|
||||
/*
|
||||
* limit the size of the accumulated ISUPPORT data: once the
|
||||
* maximum is reached, ignore the extra data (protection against a
|
||||
* server flooding "005" messages, which would consume all the
|
||||
* memory)
|
||||
*/
|
||||
if (length_isupport + 1 + length < IRC_SERVER_ISUPPORT_MAX_LENGTH)
|
||||
{
|
||||
ctxt->server->isupport = isupport2;
|
||||
strcat (ctxt->server->isupport, " ");
|
||||
strcat (ctxt->server->isupport, str_info);
|
||||
isupport2 = realloc (ctxt->server->isupport,
|
||||
length_isupport + /* existing */
|
||||
1 + /* space */
|
||||
length + /* new */
|
||||
1);
|
||||
if (isupport2)
|
||||
{
|
||||
ctxt->server->isupport = isupport2;
|
||||
strcat (ctxt->server->isupport, " ");
|
||||
strcat (ctxt->server->isupport, str_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -6659,7 +6679,7 @@ IRC_PROTOCOL_CALLBACK(366)
|
||||
ctxt->server, NULL, ctxt->command, "names", ptr_channel->buffer),
|
||||
ctxt->date,
|
||||
ctxt->date_usec,
|
||||
irc_protocol_tags (ctxt, NULL),
|
||||
irc_protocol_tags_cmd (ctxt, "353", NULL),
|
||||
_("%sNicks %s%s%s%s: %s[%s%s]"),
|
||||
weechat_prefix ("network"),
|
||||
IRC_COLOR_CHAT_CHANNEL,
|
||||
|
||||
@@ -3409,6 +3409,14 @@ irc_server_msgq_add_unterminated (struct t_irc_server *server,
|
||||
|
||||
if (server->unterminated_message)
|
||||
{
|
||||
/*
|
||||
* limit the size of the unterminated message: once the maximum is
|
||||
* reached, ignore the extra data (protection against a server sending
|
||||
* a very long line without end-of-line, which would consume all the
|
||||
* memory)
|
||||
*/
|
||||
if (strlen (server->unterminated_message) >= IRC_SERVER_RECV_MSG_MAX_LENGTH)
|
||||
return;
|
||||
unterminated_message2 =
|
||||
realloc (server->unterminated_message,
|
||||
(strlen (server->unterminated_message) +
|
||||
|
||||
@@ -144,6 +144,15 @@ enum t_irc_server_option
|
||||
#define IRC_SERVER_MULTILINE_DEFAULT_MAX_BYTES 4096
|
||||
#define IRC_SERVER_MULTILINE_DEFAULT_MAX_LINES 24
|
||||
|
||||
/*
|
||||
* maximum length of an unterminated message (a received line without
|
||||
* end-of-line) and of the accumulated "005" (ISUPPORT) data; these limits
|
||||
* protect against a server sending a huge amount of data without end-of-line
|
||||
* (or a flood of "005" messages), which would consume all the memory
|
||||
*/
|
||||
#define IRC_SERVER_RECV_MSG_MAX_LENGTH (64 * 1024)
|
||||
#define IRC_SERVER_ISUPPORT_MAX_LENGTH (64 * 1024)
|
||||
|
||||
/* casemapping (string comparisons for nicks/channels) */
|
||||
enum t_irc_server_casemapping
|
||||
{
|
||||
|
||||
@@ -624,6 +624,7 @@ plugin_load (const char *filename, int init_plugin, int argc, char **argv)
|
||||
new_plugin->strncasecmp = &string_strncasecmp;
|
||||
new_plugin->strncasecmp_range = &string_strncasecmp_range;
|
||||
new_plugin->strcmp_ignore_chars = &string_strcmp_ignore_chars;
|
||||
new_plugin->string_memcmp_constant_time = &string_memcmp_constant_time;
|
||||
new_plugin->strcasestr = &string_strcasestr;
|
||||
new_plugin->strlen_screen = &gui_chat_strlen_screen;
|
||||
new_plugin->string_match = &string_match;
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "../../weechat-plugin.h"
|
||||
#include "../relay.h"
|
||||
#include "relay-irc.h"
|
||||
#include "../relay-auth.h"
|
||||
#include "../relay-buffer.h"
|
||||
#include "../relay-client.h"
|
||||
#include "../relay-config.h"
|
||||
@@ -1701,7 +1702,7 @@ relay_irc_recv (struct t_relay_client *client, const char *data)
|
||||
NULL, NULL, NULL);
|
||||
if (password)
|
||||
{
|
||||
if (strcmp (password, pos_password) == 0)
|
||||
if (relay_auth_password_equals (password, pos_password))
|
||||
{
|
||||
RELAY_IRC_DATA(client, password_ok) = 1;
|
||||
weechat_hook_signal_send ("relay_client_auth_ok",
|
||||
|
||||
@@ -102,6 +102,66 @@ relay_auth_generate_nonce (int size)
|
||||
return nonce_hexa;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare two passwords for equality in constant time.
|
||||
*
|
||||
* HMAC both sides with a fresh random key, then compare the fixed-size
|
||||
* MACs. This hides both the per-byte comparison and the password length
|
||||
* from a timing-side-channel observer.
|
||||
*
|
||||
* Both messages are prefixed with a zero byte so empty passwords still
|
||||
* produce a valid HMAC (the underlying crypto API rejects zero-length
|
||||
* messages); the prefix is identical on both sides so equal inputs
|
||||
* still yield equal MACs.
|
||||
*
|
||||
* Return:
|
||||
* 1: passwords are equal
|
||||
* 0: passwords are not equal (or an error occurred)
|
||||
*/
|
||||
|
||||
int
|
||||
relay_auth_password_equals (const char *password1, const char *password2)
|
||||
{
|
||||
unsigned char key[32];
|
||||
char hmac1[64], hmac2[64];
|
||||
char *buf1, *buf2;
|
||||
int buf1_size, buf2_size, hmac1_size, hmac2_size, rc;
|
||||
|
||||
if (!password1 || !password2)
|
||||
return 0;
|
||||
|
||||
rc = 0;
|
||||
buf1_size = strlen (password1) + 1;
|
||||
buf2_size = strlen (password2) + 1;
|
||||
buf1 = malloc (buf1_size);
|
||||
buf2 = malloc (buf2_size);
|
||||
if (buf1 && buf2)
|
||||
{
|
||||
buf1[0] = 0;
|
||||
memcpy (buf1 + 1, password1, buf1_size - 1);
|
||||
buf2[0] = 0;
|
||||
memcpy (buf2 + 1, password2, buf2_size - 1);
|
||||
gcry_create_nonce (key, sizeof (key));
|
||||
if (weechat_crypto_hmac (key, sizeof (key),
|
||||
buf1, buf1_size,
|
||||
"sha256",
|
||||
hmac1, &hmac1_size)
|
||||
&& weechat_crypto_hmac (key, sizeof (key),
|
||||
buf2, buf2_size,
|
||||
"sha256",
|
||||
hmac2, &hmac2_size)
|
||||
&& (hmac1_size == hmac2_size)
|
||||
&& (weechat_string_memcmp_constant_time (
|
||||
hmac1, hmac2, hmac1_size) == 0))
|
||||
{
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
free (buf1);
|
||||
free (buf2);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if password received as plain text is valid.
|
||||
*
|
||||
@@ -127,7 +187,7 @@ relay_auth_check_password_plain (struct t_relay_client *client,
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (strcmp (password, relay_password) == 0) ? 0 : -2;
|
||||
return relay_auth_password_equals (password, relay_password) ? 0 : -2;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -347,8 +407,9 @@ relay_auth_check_hash_sha (const char *hash_algo,
|
||||
const char *hash_sha,
|
||||
const char *relay_password)
|
||||
{
|
||||
char *salt_password, hash[512 / 8], hash_hexa[((512 / 8) * 2) + 1];
|
||||
int rc, length, hash_size;
|
||||
char *salt_password, *hash_sha_upper, hash[512 / 8];
|
||||
char hash_hexa[((512 / 8) * 2) + 1];
|
||||
int rc, length, hash_size, hash_hexa_len;
|
||||
|
||||
rc = 0;
|
||||
|
||||
@@ -366,10 +427,23 @@ relay_auth_check_hash_sha (const char *hash_algo,
|
||||
hash_algo,
|
||||
hash, &hash_size))
|
||||
{
|
||||
weechat_string_base_encode ("16", hash, hash_size,
|
||||
hash_hexa);
|
||||
if (weechat_strcasecmp (hash_hexa, hash_sha) == 0)
|
||||
hash_hexa_len = weechat_string_base_encode ("16", hash, hash_size,
|
||||
hash_hexa);
|
||||
/*
|
||||
* Compare in constant time to defeat timing attacks: the
|
||||
* client-supplied hash is normalized to uppercase to match
|
||||
* the output of base16 encoding, then compared byte-for-byte
|
||||
* with no early exit.
|
||||
*/
|
||||
hash_sha_upper = weechat_string_toupper (hash_sha);
|
||||
if (hash_sha_upper
|
||||
&& ((int)strlen (hash_sha_upper) == hash_hexa_len)
|
||||
&& (weechat_string_memcmp_constant_time (
|
||||
hash_hexa, hash_sha_upper, hash_hexa_len) == 0))
|
||||
{
|
||||
rc = 1;
|
||||
}
|
||||
free (hash_sha_upper);
|
||||
}
|
||||
free (salt_password);
|
||||
}
|
||||
@@ -393,8 +467,8 @@ relay_auth_check_hash_pbkdf2 (const char *hash_pbkdf2_algo,
|
||||
const char *hash_pbkdf2,
|
||||
const char *relay_password)
|
||||
{
|
||||
char hash[512 / 8], hash_hexa[((512 / 8) * 2) + 1];
|
||||
int rc, hash_size;
|
||||
char *hash_pbkdf2_upper, hash[512 / 8], hash_hexa[((512 / 8) * 2) + 1];
|
||||
int rc, hash_size, hash_hexa_len;
|
||||
|
||||
rc = 0;
|
||||
|
||||
@@ -407,9 +481,18 @@ relay_auth_check_hash_pbkdf2 (const char *hash_pbkdf2_algo,
|
||||
iterations,
|
||||
hash, &hash_size))
|
||||
{
|
||||
weechat_string_base_encode ("16", hash, hash_size, hash_hexa);
|
||||
if (weechat_strcasecmp (hash_hexa, hash_pbkdf2) == 0)
|
||||
hash_hexa_len = weechat_string_base_encode ("16", hash, hash_size,
|
||||
hash_hexa);
|
||||
/* see relay_auth_check_hash_sha for rationale */
|
||||
hash_pbkdf2_upper = weechat_string_toupper (hash_pbkdf2);
|
||||
if (hash_pbkdf2_upper
|
||||
&& ((int)strlen (hash_pbkdf2_upper) == hash_hexa_len)
|
||||
&& (weechat_string_memcmp_constant_time (
|
||||
hash_hexa, hash_pbkdf2_upper, hash_hexa_len) == 0))
|
||||
{
|
||||
rc = 1;
|
||||
}
|
||||
free (hash_pbkdf2_upper);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ extern char *relay_auth_password_hash_algo_name[];
|
||||
|
||||
extern int relay_auth_password_hash_algo_search (const char *name);
|
||||
extern char *relay_auth_generate_nonce (int size);
|
||||
extern int relay_auth_password_equals (const char *password1,
|
||||
const char *password2);
|
||||
extern int relay_auth_check_password_plain (struct t_relay_client *client,
|
||||
const char *password,
|
||||
const char *relay_password);
|
||||
|
||||
@@ -513,6 +513,19 @@ relay_http_add_to_body (struct t_relay_http_request *request,
|
||||
if (!partial_message || !*partial_message)
|
||||
return;
|
||||
|
||||
/*
|
||||
* reject the body if its announced length is too big: this prevents a
|
||||
* client from forcing an unbounded allocation by announcing a huge
|
||||
* "Content-Length"
|
||||
*/
|
||||
if (request->content_length > RELAY_HTTP_BODY_MAX_LENGTH)
|
||||
{
|
||||
free (*partial_message);
|
||||
*partial_message = NULL;
|
||||
request->status = RELAY_HTTP_END;
|
||||
return;
|
||||
}
|
||||
|
||||
num_bytes_missing = request->content_length
|
||||
- request->body_size;
|
||||
if (num_bytes_missing <= 0)
|
||||
@@ -993,6 +1006,14 @@ relay_http_recv (struct t_relay_client *client, const char *data, int size)
|
||||
|
||||
if (client->partial_message)
|
||||
{
|
||||
/*
|
||||
* limit the size of the partial message: once the maximum is reached,
|
||||
* ignore the extra data (protection against a client sending a huge
|
||||
* amount of data without any end-of-line and dribbling it, which would
|
||||
* consume all the memory)
|
||||
*/
|
||||
if (strlen (client->partial_message) >= RELAY_HTTP_PARTIAL_MESSAGE_MAX_LENGTH)
|
||||
return;
|
||||
new_partial = realloc (client->partial_message,
|
||||
strlen (client->partial_message) +
|
||||
strlen (data) + 1);
|
||||
@@ -1695,7 +1716,7 @@ relay_http_print_log_request (struct t_relay_http_request *request)
|
||||
weechat_log_printf (" path_items. . . . . . . : %p", request->path_items);
|
||||
if (request->path_items)
|
||||
{
|
||||
for (i = 0; request->path_items[0]; i++)
|
||||
for (i = 0; request->path_items[i]; i++)
|
||||
{
|
||||
weechat_log_printf (" '%s'", request->path_items[i]);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,22 @@ enum t_relay_client_http_status
|
||||
#define RELAY_HTTP_ERROR_METHOD_NOT_ALLOWED "Method Not Allowed"
|
||||
#define RELAY_HTTP_ERROR_OUT_OF_MEMORY "Out of memory"
|
||||
|
||||
/*
|
||||
* maximum length of an HTTP request body: used as an upper bound on the
|
||||
* "Content-Length" accepted from a client, to prevent a client from forcing
|
||||
* an unbounded allocation by announcing a huge body
|
||||
*/
|
||||
#define RELAY_HTTP_BODY_MAX_LENGTH (8 * 1024 * 1024)
|
||||
|
||||
/*
|
||||
* maximum length of the partial message accumulated while reading an HTTP
|
||||
* request: once this limit is reached, the extra data is ignored; this
|
||||
* protects against a client sending a huge amount of data without any
|
||||
* end-of-line (an unterminated method or header line), which would consume
|
||||
* all the memory
|
||||
*/
|
||||
#define RELAY_HTTP_PARTIAL_MESSAGE_MAX_LENGTH (8 * 1024 * 1024)
|
||||
|
||||
struct t_relay_http_request
|
||||
{
|
||||
enum t_relay_client_http_status status; /* HTTP status */
|
||||
|
||||
@@ -532,7 +532,7 @@ relay_websocket_inflate (const void *data, size_t size, z_stream *strm,
|
||||
int rc;
|
||||
unsigned char append_bytes[4] = { 0x00, 0x00, 0xFF, 0xFF };
|
||||
Bytef *data2, *dest, *dest2;
|
||||
uLongf size2, dest_size;
|
||||
uLongf size2, dest_size, new_size;
|
||||
|
||||
if (!data || (size == 0) || !strm || !size_decompressed)
|
||||
return NULL;
|
||||
@@ -549,8 +549,13 @@ relay_websocket_inflate (const void *data, size_t size, z_stream *strm,
|
||||
memcpy (data2, data, size);
|
||||
memcpy (data2 + size, append_bytes, sizeof (append_bytes));
|
||||
|
||||
/* estimate the decompressed size, by default 10 * size */
|
||||
dest_size = 10 * size2;
|
||||
/*
|
||||
* estimate the decompressed size, by default 10 * size, capped to the
|
||||
* maximum allowed size (also prevents an integer overflow on the
|
||||
* multiplication)
|
||||
*/
|
||||
dest_size = (size2 > WEBSOCKET_INFLATE_MAX_SIZE / 10) ?
|
||||
WEBSOCKET_INFLATE_MAX_SIZE : 10 * size2;
|
||||
dest = malloc (dest_size);
|
||||
if (!dest)
|
||||
goto error;
|
||||
@@ -579,8 +584,19 @@ relay_websocket_inflate (const void *data, size_t size, z_stream *strm,
|
||||
&& (strm->avail_in > 0)))
|
||||
{
|
||||
/* output buffer is not large enough */
|
||||
strm->avail_out += dest_size;
|
||||
dest_size *= 2;
|
||||
if (dest_size >= WEBSOCKET_INFLATE_MAX_SIZE)
|
||||
{
|
||||
/*
|
||||
* decompressed data is too large: reject the frame
|
||||
* (protection against a "deflate bomb")
|
||||
*/
|
||||
goto error;
|
||||
}
|
||||
/* double the buffer, capped to the maximum allowed size */
|
||||
new_size = (dest_size > WEBSOCKET_INFLATE_MAX_SIZE / 2) ?
|
||||
WEBSOCKET_INFLATE_MAX_SIZE : dest_size * 2;
|
||||
strm->avail_out += (uInt)(new_size - dest_size);
|
||||
dest_size = new_size;
|
||||
dest2 = realloc (dest, dest_size);
|
||||
if (!dest2)
|
||||
goto error;
|
||||
@@ -687,6 +703,14 @@ relay_websocket_decode_frame (const unsigned char *buffer,
|
||||
index_buffer += length_frame_size;
|
||||
}
|
||||
|
||||
/*
|
||||
* reject the frame if its announced length is too big: this prevents
|
||||
* a client from forcing an unbounded allocation (and unbounded
|
||||
* accumulation of partial frames) by announcing a huge frame
|
||||
*/
|
||||
if (length_frame > WEBSOCKET_FRAME_MAX_LENGTH)
|
||||
return 0;
|
||||
|
||||
if (masked_frame)
|
||||
{
|
||||
/* read mask (4 bytes) */
|
||||
|
||||
@@ -41,6 +41,21 @@
|
||||
|
||||
#define WEBSOCKET_SUB_PROTOCOL_API_WEECHAT "api.weechat"
|
||||
|
||||
/*
|
||||
* maximum length of a websocket frame received from a client (or a remote
|
||||
* WeeChat): used as an upper bound on the announced frame payload length, to
|
||||
* prevent a client from forcing an unbounded allocation by announcing a huge
|
||||
* frame and dribbling its payload
|
||||
*/
|
||||
#define WEBSOCKET_FRAME_MAX_LENGTH (8 * 1024 * 1024)
|
||||
|
||||
/*
|
||||
* maximum size of a decompressed websocket frame (with "permessage-deflate"):
|
||||
* used as an upper bound when inflating, to prevent a small compressed frame
|
||||
* from decompressing to an unbounded amount of data ("deflate bomb")
|
||||
*/
|
||||
#define WEBSOCKET_INFLATE_MAX_SIZE (8 * 1024 * 1024)
|
||||
|
||||
struct t_relay_client;
|
||||
struct t_relay_http_request;
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ struct t_weelist_item;
|
||||
* please change the date with current one; for a second change at same
|
||||
* date, increment the 01, otherwise please keep 01.
|
||||
*/
|
||||
#define WEECHAT_PLUGIN_API_VERSION "20251112-01"
|
||||
#define WEECHAT_PLUGIN_API_VERSION "20260530-01"
|
||||
|
||||
/* macros for defining plugin infos */
|
||||
#define WEECHAT_PLUGIN_NAME(__name) \
|
||||
@@ -348,6 +348,8 @@ struct t_weechat_plugin
|
||||
int max, int range);
|
||||
int (*strcmp_ignore_chars) (const char *string1, const char *string2,
|
||||
const char *chars_ignored, int case_sensitive);
|
||||
int (*string_memcmp_constant_time) (const void *area1, const void *area2,
|
||||
size_t size);
|
||||
const char *(*strcasestr) (const char *string, const char *search);
|
||||
int (*strlen_screen) (const char *string);
|
||||
int (*string_match) (const char *string, const char *mask,
|
||||
@@ -1348,6 +1350,9 @@ extern int weechat_plugin_end (struct t_weechat_plugin *plugin);
|
||||
(weechat_plugin->strcmp_ignore_chars)(__string1, __string2, \
|
||||
__chars_ignored, \
|
||||
__case_sensitive)
|
||||
#define weechat_string_memcmp_constant_time(__area1, __area2, __size) \
|
||||
(weechat_plugin->string_memcmp_constant_time)(__area1, __area2, \
|
||||
__size)
|
||||
#define weechat_strcasestr(__string, __search) \
|
||||
(weechat_plugin->strcasestr)(__string, __search)
|
||||
#define weechat_strlen_screen(__string) \
|
||||
|
||||
@@ -162,7 +162,7 @@ xfer_chat_recv_cb (const void *pointer, void *data, int fd)
|
||||
{
|
||||
ctcp_action = 0;
|
||||
length = strlen (ptr_buf);
|
||||
if (ptr_buf[length - 1] == '\r')
|
||||
if ((length > 0) && (ptr_buf[length - 1] == '\r'))
|
||||
{
|
||||
ptr_buf[length - 1] = '\0';
|
||||
length--;
|
||||
|
||||
@@ -251,7 +251,7 @@ xfer_file_find_suffix (struct t_xfer *xfer)
|
||||
void
|
||||
xfer_file_find_filename (struct t_xfer *xfer)
|
||||
{
|
||||
char *dir_separator, *path;
|
||||
char *dir_separator, *path, *nick;
|
||||
struct t_hashtable *options;
|
||||
|
||||
if (!XFER_IS_FILE(xfer->type))
|
||||
@@ -287,12 +287,20 @@ xfer_file_find_filename (struct t_xfer *xfer)
|
||||
{
|
||||
strcat (xfer->local_filename, dir_separator);
|
||||
}
|
||||
free (dir_separator);
|
||||
if (weechat_config_boolean (xfer_config_file_use_nick_in_filename))
|
||||
{
|
||||
strcat (xfer->local_filename, xfer->remote_nick);
|
||||
/*
|
||||
* the remote nick comes from the server and can contain a directory
|
||||
* separator: replace it so the nick cannot make the file be written
|
||||
* outside the download directory
|
||||
*/
|
||||
nick = (dir_separator) ?
|
||||
weechat_string_replace (xfer->remote_nick, dir_separator, "_") : NULL;
|
||||
strcat (xfer->local_filename, (nick) ? nick : xfer->remote_nick);
|
||||
free (nick);
|
||||
strcat (xfer->local_filename, ".");
|
||||
}
|
||||
free (dir_separator);
|
||||
strcat (xfer->local_filename, xfer->filename);
|
||||
|
||||
free (path);
|
||||
|
||||
@@ -800,6 +800,38 @@ TEST(CoreString, StringComparison)
|
||||
LONGS_EQUAL(-2, string_strcmp_ignore_chars ("è", "ê", "", 1));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* string_memcmp_constant_time
|
||||
*/
|
||||
|
||||
TEST(CoreString, MemcmpConstantTime)
|
||||
{
|
||||
/* NULL handling */
|
||||
LONGS_EQUAL(0, string_memcmp_constant_time (NULL, NULL, 0));
|
||||
LONGS_EQUAL(0, string_memcmp_constant_time (NULL, NULL, 4));
|
||||
LONGS_EQUAL(1, string_memcmp_constant_time (NULL, "abcd", 4));
|
||||
LONGS_EQUAL(1, string_memcmp_constant_time ("abcd", NULL, 4));
|
||||
|
||||
/* zero-size compare always equal */
|
||||
LONGS_EQUAL(0, string_memcmp_constant_time ("", "", 0));
|
||||
LONGS_EQUAL(0, string_memcmp_constant_time ("abc", "xyz", 0));
|
||||
|
||||
/* equal areas */
|
||||
LONGS_EQUAL(0, string_memcmp_constant_time ("abcd", "abcd", 4));
|
||||
LONGS_EQUAL(0, string_memcmp_constant_time ("\x00\x01\x02\xff",
|
||||
"\x00\x01\x02\xff", 4));
|
||||
|
||||
/* differing areas (first / middle / last byte) */
|
||||
LONGS_EQUAL(1, string_memcmp_constant_time ("Xbcd", "abcd", 4));
|
||||
LONGS_EQUAL(1, string_memcmp_constant_time ("aXcd", "abcd", 4));
|
||||
LONGS_EQUAL(1, string_memcmp_constant_time ("abcX", "abcd", 4));
|
||||
LONGS_EQUAL(1, string_memcmp_constant_time ("abcd", "abce", 4));
|
||||
|
||||
/* only compares "size" bytes, ignores trailing content */
|
||||
LONGS_EQUAL(0, string_memcmp_constant_time ("abcd", "abcz", 3));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* string_strcasestr
|
||||
@@ -1422,6 +1454,8 @@ TEST(CoreString, Replace)
|
||||
WEE_TEST_STR(NULL, string_replace ("string", NULL, "replace"));
|
||||
WEE_TEST_STR(NULL, string_replace (NULL, "search", "replace"));
|
||||
|
||||
WEE_TEST_STR("test abc def", string_replace("test abc def", "", "xxx"));
|
||||
|
||||
WEE_TEST_STR("test abc def", string_replace("test abc def", "xyz", "xxx"));
|
||||
WEE_TEST_STR("test xxx def", string_replace("test abc def", "abc", "xxx"));
|
||||
WEE_TEST_STR("xxx test xxx def xxx",
|
||||
|
||||
@@ -305,7 +305,7 @@ TEST_GROUP(IrcProtocolWithServer)
|
||||
|
||||
void server_recv (const char *command)
|
||||
{
|
||||
char str_command[4096];
|
||||
char str_command[8192];
|
||||
|
||||
record_start ();
|
||||
arraylist_clear (sent_messages);
|
||||
@@ -3866,6 +3866,44 @@ TEST(IrcProtocolWithServer, 005_full)
|
||||
STRCMP_EQUAL(IRC_MSG_005 " " IRC_MSG_005, ptr_server->isupport);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* irc_protocol_cb_005 (accumulated ISUPPORT is bounded)
|
||||
*/
|
||||
|
||||
TEST(IrcProtocolWithServer, 005_limit)
|
||||
{
|
||||
char str_msg[4096], str_value[3500];
|
||||
size_t length1, length2;
|
||||
int i;
|
||||
|
||||
SRV_INIT;
|
||||
|
||||
memset (str_value, 'X', sizeof (str_value) - 1);
|
||||
str_value[sizeof (str_value) - 1] = '\0';
|
||||
snprintf (str_msg, sizeof (str_msg),
|
||||
":server 005 alice TEST=%s :are supported", str_value);
|
||||
|
||||
/* flood the server with "005" messages */
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
server_recv (str_msg);
|
||||
}
|
||||
CHECK(ptr_server->isupport);
|
||||
length1 = strlen (ptr_server->isupport);
|
||||
|
||||
/* the accumulated ISUPPORT data must be bounded */
|
||||
CHECK(length1 <= IRC_SERVER_ISUPPORT_MAX_LENGTH + sizeof (str_value));
|
||||
|
||||
/* receiving more "005" messages must not grow it any further */
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
server_recv (str_msg);
|
||||
}
|
||||
length2 = strlen (ptr_server->isupport);
|
||||
LONGS_EQUAL(length1, length2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* irc_protocol_cb_005 (infos from server, multiple messages)
|
||||
|
||||
@@ -65,6 +65,49 @@ TEST(IrcServer, Valid)
|
||||
irc_server_free (server);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* irc_server_msgq_add_unterminated (via irc_server_msgq_add_buffer)
|
||||
*
|
||||
* Check that data received without any end-of-line does not grow the
|
||||
* unterminated message buffer without limit.
|
||||
*/
|
||||
|
||||
TEST(IrcServer, MsgqAddBufferLimit)
|
||||
{
|
||||
struct t_irc_server *server;
|
||||
char chunk[4097];
|
||||
int i;
|
||||
size_t length1, length2;
|
||||
|
||||
server = irc_server_alloc ("server_msgq");
|
||||
CHECK(server);
|
||||
|
||||
memset (chunk, 'a', sizeof (chunk) - 1);
|
||||
chunk[sizeof (chunk) - 1] = '\0';
|
||||
|
||||
/* feed a lot of data with no end-of-line */
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
irc_server_msgq_add_buffer (server, chunk);
|
||||
}
|
||||
CHECK(server->unterminated_message);
|
||||
length1 = strlen (server->unterminated_message);
|
||||
|
||||
/* the buffer must be bounded (not ~400 KB) */
|
||||
CHECK(length1 <= IRC_SERVER_RECV_MSG_MAX_LENGTH + sizeof (chunk));
|
||||
|
||||
/* feeding more data must not grow the buffer any further */
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
irc_server_msgq_add_buffer (server, chunk);
|
||||
}
|
||||
length2 = strlen (server->unterminated_message);
|
||||
LONGS_EQUAL(length1, length2);
|
||||
|
||||
irc_server_free (server);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* irc_server_search
|
||||
|
||||
@@ -109,6 +109,31 @@ TEST(RelayAuth, GenerateNonce)
|
||||
free (nonce);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* relay_auth_password_equals
|
||||
*/
|
||||
|
||||
TEST(RelayAuth, PasswordEquals)
|
||||
{
|
||||
/* invalid arguments */
|
||||
LONGS_EQUAL(0, relay_auth_password_equals (NULL, NULL));
|
||||
LONGS_EQUAL(0, relay_auth_password_equals ("abcd", NULL));
|
||||
LONGS_EQUAL(0, relay_auth_password_equals (NULL, "abcd"));
|
||||
|
||||
/* different passwords */
|
||||
LONGS_EQUAL(0, relay_auth_password_equals ("test", "password"));
|
||||
LONGS_EQUAL(0, relay_auth_password_equals ("Password", "password"));
|
||||
LONGS_EQUAL(0, relay_auth_password_equals ("", "password"));
|
||||
LONGS_EQUAL(0, relay_auth_password_equals ("password", ""));
|
||||
|
||||
/* equal passwords */
|
||||
LONGS_EQUAL(1, relay_auth_password_equals ("", ""));
|
||||
LONGS_EQUAL(1, relay_auth_password_equals ("password", "password"));
|
||||
LONGS_EQUAL(1, relay_auth_password_equals ("a really long password with spaces",
|
||||
"a really long password with spaces"));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* relay_auth_check_password_plain
|
||||
|
||||
@@ -41,6 +41,7 @@ extern "C"
|
||||
#include "src/plugins/relay/relay-client.h"
|
||||
#include "src/plugins/relay/relay-config.h"
|
||||
#include "src/plugins/relay/relay-http.h"
|
||||
#include "src/plugins/relay/relay-server.h"
|
||||
#include "src/plugins/relay/relay-websocket.h"
|
||||
#include "src/plugins/weechat-plugin.h"
|
||||
|
||||
@@ -161,6 +162,35 @@ TEST(RelayHttp, RequestAllocReinitFree)
|
||||
relay_http_request_free (request);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* relay_http_add_to_body (body too large is rejected)
|
||||
*/
|
||||
|
||||
TEST(RelayHttp, AddToBodyLimit)
|
||||
{
|
||||
struct t_relay_http_request *request;
|
||||
char *partial;
|
||||
|
||||
request = relay_http_request_alloc ();
|
||||
CHECK(request);
|
||||
|
||||
/* announce a body larger than the maximum allowed */
|
||||
request->status = RELAY_HTTP_BODY;
|
||||
request->content_length = RELAY_HTTP_BODY_MAX_LENGTH + 1;
|
||||
partial = strdup ("some body data");
|
||||
|
||||
relay_http_add_to_body (request, &partial);
|
||||
|
||||
/* the body must be rejected: nothing allocated, request ended */
|
||||
POINTERS_EQUAL(NULL, request->body);
|
||||
LONGS_EQUAL(0, request->body_size);
|
||||
POINTERS_EQUAL(NULL, partial);
|
||||
LONGS_EQUAL(RELAY_HTTP_END, request->status);
|
||||
|
||||
relay_http_request_free (request);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* relay_http_url_decode
|
||||
@@ -992,6 +1022,69 @@ TEST(RelayHttp, Recv)
|
||||
/* TODO: write tests */
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* relay_http_recv (partial message accumulated is bounded)
|
||||
*
|
||||
* Check that data received without any end-of-line does not grow the partial
|
||||
* message buffer without limit.
|
||||
*/
|
||||
|
||||
TEST(RelayHttp, RecvLimit)
|
||||
{
|
||||
struct t_relay_server *server;
|
||||
struct t_relay_client *client;
|
||||
char *chunk;
|
||||
int chunk_size, i;
|
||||
size_t length1, length2;
|
||||
|
||||
/* disable auto-open of relay buffer (it would pollute other tests) */
|
||||
config_file_option_set (relay_config_look_auto_open_buffer, "off", 1);
|
||||
|
||||
server = relay_server_new ("weechat", RELAY_PROTOCOL_WEECHAT, NULL,
|
||||
9000,
|
||||
NULL, /* path */
|
||||
1, /* ipv4 */
|
||||
0, /* ipv6 */
|
||||
0, /* tls */
|
||||
0); /* unix_socket */
|
||||
CHECK(server);
|
||||
client = relay_client_new (-1, "test", server);
|
||||
CHECK(client);
|
||||
|
||||
chunk_size = 1024 * 1024;
|
||||
chunk = (char *)malloc (chunk_size + 1);
|
||||
CHECK(chunk);
|
||||
memset (chunk, 'a', chunk_size);
|
||||
chunk[chunk_size] = '\0';
|
||||
|
||||
/* feed more than the maximum, with no end-of-line (16 MB) */
|
||||
for (i = 0; i < 16; i++)
|
||||
{
|
||||
relay_http_recv (client, chunk, chunk_size);
|
||||
}
|
||||
CHECK(client->partial_message);
|
||||
length1 = strlen (client->partial_message);
|
||||
|
||||
/* the partial message must be bounded (not ~16 MB) */
|
||||
CHECK(length1 <= RELAY_HTTP_PARTIAL_MESSAGE_MAX_LENGTH + (size_t)chunk_size);
|
||||
|
||||
/* feeding more data must not grow it any further */
|
||||
for (i = 0; i < 16; i++)
|
||||
{
|
||||
relay_http_recv (client, chunk, chunk_size);
|
||||
}
|
||||
length2 = strlen (client->partial_message);
|
||||
LONGS_EQUAL(length1, length2);
|
||||
|
||||
free (chunk);
|
||||
relay_client_free (client);
|
||||
relay_server_free (server);
|
||||
|
||||
/* restore auto-open of relay buffer */
|
||||
config_file_option_reset (relay_config_look_auto_open_buffer, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* relay_http_compress
|
||||
|
||||
@@ -466,6 +466,41 @@ TEST(RelayWebsocket, Inflate)
|
||||
free (payload_comp);
|
||||
|
||||
relay_websocket_deflate_free (ws_deflate);
|
||||
|
||||
/*
|
||||
* protection against "deflate bomb": a small compressed frame that
|
||||
* decompresses to more than WEBSOCKET_INFLATE_MAX_SIZE must be rejected
|
||||
* (relay_websocket_inflate returns NULL)
|
||||
*/
|
||||
ws_deflate = relay_websocket_deflate_alloc ();
|
||||
CHECK(ws_deflate);
|
||||
ws_deflate->window_bits_deflate = 15;
|
||||
ws_deflate->window_bits_inflate = 15;
|
||||
ws_deflate->strm_deflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_deflate));
|
||||
CHECK(ws_deflate->strm_deflate);
|
||||
LONGS_EQUAL(1, relay_websocket_deflate_init_stream_deflate (ws_deflate));
|
||||
ws_deflate->strm_inflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_inflate));
|
||||
CHECK(ws_deflate->strm_inflate);
|
||||
LONGS_EQUAL(1, relay_websocket_deflate_init_stream_inflate (ws_deflate));
|
||||
|
||||
/* highly compressible payload that decompresses past the maximum size */
|
||||
size_t bomb_size = WEBSOCKET_INFLATE_MAX_SIZE + (1024 * 1024);
|
||||
char *bomb = (char *)calloc (1, bomb_size);
|
||||
CHECK(bomb);
|
||||
|
||||
payload_comp = (char *)relay_websocket_deflate (bomb, bomb_size,
|
||||
ws_deflate->strm_deflate, &size_comp);
|
||||
CHECK(payload_comp);
|
||||
CHECK(size_comp < bomb_size);
|
||||
|
||||
payload_decomp = (char *)relay_websocket_inflate (payload_comp, size_comp,
|
||||
ws_deflate->strm_inflate, &size_decomp);
|
||||
POINTERS_EQUAL(NULL, payload_decomp);
|
||||
|
||||
free (payload_comp);
|
||||
free (bomb);
|
||||
|
||||
relay_websocket_deflate_free (ws_deflate);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -475,7 +510,44 @@ TEST(RelayWebsocket, Inflate)
|
||||
|
||||
TEST(RelayWebsocket, DecodeFrame)
|
||||
{
|
||||
/* TODO: write tests */
|
||||
struct t_relay_websocket_frame *frames;
|
||||
char *partial_ws_frame;
|
||||
int num_frames, partial_ws_frame_size;
|
||||
/* small unmasked binary frame with payload "hello" */
|
||||
unsigned char frame_ok[7] = { 0x82, 0x05, 'h', 'e', 'l', 'l', 'o' };
|
||||
/* masked frame announcing a 1 GB payload (64-bit length field) */
|
||||
unsigned char frame_too_big[10] = {
|
||||
0x82, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
/* a valid small frame is decoded */
|
||||
frames = NULL;
|
||||
num_frames = 0;
|
||||
partial_ws_frame = NULL;
|
||||
partial_ws_frame_size = 0;
|
||||
LONGS_EQUAL(1, relay_websocket_decode_frame (
|
||||
frame_ok, sizeof (frame_ok), 0, NULL,
|
||||
&frames, &num_frames, &partial_ws_frame,
|
||||
&partial_ws_frame_size));
|
||||
LONGS_EQUAL(1, num_frames);
|
||||
CHECK(frames);
|
||||
LONGS_EQUAL(5, frames[0].payload_size);
|
||||
MEMCMP_EQUAL("hello", frames[0].payload, 5);
|
||||
free (frames[0].payload);
|
||||
free (frames);
|
||||
free (partial_ws_frame);
|
||||
|
||||
/* a frame announcing an oversized payload is rejected (return 0) */
|
||||
frames = NULL;
|
||||
num_frames = 0;
|
||||
partial_ws_frame = NULL;
|
||||
partial_ws_frame_size = 0;
|
||||
LONGS_EQUAL(0, relay_websocket_decode_frame (
|
||||
frame_too_big, sizeof (frame_too_big), 1, NULL,
|
||||
&frames, &num_frames, &partial_ws_frame,
|
||||
&partial_ws_frame_size));
|
||||
free (frames);
|
||||
free (partial_ws_frame);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "src/core/core-config-file.h"
|
||||
#include "src/plugins/xfer/xfer.h"
|
||||
#include "src/plugins/xfer/xfer-config.h"
|
||||
#include "src/plugins/xfer/xfer-file.h"
|
||||
}
|
||||
|
||||
@@ -32,6 +37,42 @@ TEST_GROUP(XferFile)
|
||||
{
|
||||
};
|
||||
|
||||
/*
|
||||
* Build a "file recv" xfer with the given remote nick (and a fixed filename),
|
||||
* call xfer_file_find_filename and return a copy of the basename of the local
|
||||
* filename (the part after the last directory separator).
|
||||
*
|
||||
* Note: result must be freed after use.
|
||||
*/
|
||||
|
||||
static char *
|
||||
test_find_filename_basename (const char *remote_nick)
|
||||
{
|
||||
struct t_xfer xfer;
|
||||
char *pos, *result;
|
||||
|
||||
memset (&xfer, 0, sizeof (xfer));
|
||||
xfer.type = XFER_TYPE_FILE_RECV_ACTIVE;
|
||||
xfer.remote_nick = strdup (remote_nick);
|
||||
xfer.filename = strdup ("test.txt");
|
||||
|
||||
xfer_file_find_filename (&xfer);
|
||||
|
||||
result = NULL;
|
||||
if (xfer.local_filename)
|
||||
{
|
||||
pos = strrchr (xfer.local_filename, DIR_SEPARATOR_CHAR);
|
||||
result = strdup ((pos) ? pos + 1 : xfer.local_filename);
|
||||
}
|
||||
|
||||
free (xfer.remote_nick);
|
||||
free (xfer.filename);
|
||||
free (xfer.local_filename);
|
||||
free (xfer.temp_local_filename);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test functions:
|
||||
* xfer_file_search_crc32
|
||||
@@ -91,7 +132,30 @@ TEST(XferFile, FindSuffix)
|
||||
|
||||
TEST(XferFile, FindFilename)
|
||||
{
|
||||
/* TODO: write tests */
|
||||
char *basename;
|
||||
|
||||
config_file_option_set (xfer_config_file_download_path, "/tmp/weechat_test_xfer", 1);
|
||||
|
||||
/* remote nick without directory separator: used as-is */
|
||||
basename = test_find_filename_basename ("alice");
|
||||
STRCMP_EQUAL("alice.test.txt", basename);
|
||||
free (basename);
|
||||
|
||||
/*
|
||||
* remote nick with a directory separator: the separator is replaced by
|
||||
* "_" so the nick cannot make the file be written outside the download
|
||||
* directory
|
||||
*/
|
||||
basename = test_find_filename_basename ("../foo");
|
||||
STRCMP_EQUAL(".._foo.test.txt", basename);
|
||||
free (basename);
|
||||
|
||||
/* all directory separators in the nick are replaced */
|
||||
basename = test_find_filename_basename ("a/b/c");
|
||||
STRCMP_EQUAL("a_b_c.test.txt", basename);
|
||||
free (basename);
|
||||
|
||||
config_file_option_unset (xfer_config_file_download_path);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@
|
||||
# devel-number the devel version as hex number ("0x04010000" for "4.1.0-dev")
|
||||
#
|
||||
|
||||
weechat_stable="4.9.0"
|
||||
weechat_devel="4.9.0"
|
||||
weechat_stable="4.9.2"
|
||||
weechat_devel="4.9.3-dev"
|
||||
|
||||
stable_major=$(echo "${weechat_stable}" | cut -d"." -f1)
|
||||
stable_minor=$(echo "${weechat_stable}" | cut -d"." -f2)
|
||||
|
||||
Reference in New Issue
Block a user