diff options
Diffstat (limited to 'src/plugins/relay')
-rw-r--r-- | src/plugins/relay/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/plugins/relay/api/relay-api-msg.c | 2 | ||||
-rw-r--r-- | src/plugins/relay/api/remote/relay-remote-network.c | 1089 | ||||
-rw-r--r-- | src/plugins/relay/api/remote/relay-remote-network.h | 26 | ||||
-rw-r--r-- | src/plugins/relay/irc/relay-irc.c | 2 | ||||
-rw-r--r-- | src/plugins/relay/relay-client.c | 105 | ||||
-rw-r--r-- | src/plugins/relay/relay-client.h | 15 | ||||
-rw-r--r-- | src/plugins/relay/relay-command.c | 24 | ||||
-rw-r--r-- | src/plugins/relay/relay-http.c | 14 | ||||
-rw-r--r-- | src/plugins/relay/relay-raw.c | 244 | ||||
-rw-r--r-- | src/plugins/relay/relay-raw.h | 11 | ||||
-rw-r--r-- | src/plugins/relay/relay-remote.c | 225 | ||||
-rw-r--r-- | src/plugins/relay/relay-remote.h | 12 | ||||
-rw-r--r-- | src/plugins/relay/relay-websocket.c | 155 | ||||
-rw-r--r-- | src/plugins/relay/relay-websocket.h | 18 | ||||
-rw-r--r-- | src/plugins/relay/relay.c | 2 | ||||
-rw-r--r-- | src/plugins/relay/relay.h | 13 | ||||
-rw-r--r-- | src/plugins/relay/weechat/relay-weechat-msg.c | 6 |
18 files changed, 1688 insertions, 277 deletions
diff --git a/src/plugins/relay/CMakeLists.txt b/src/plugins/relay/CMakeLists.txt index 724191947..280b155ec 100644 --- a/src/plugins/relay/CMakeLists.txt +++ b/src/plugins/relay/CMakeLists.txt @@ -49,6 +49,8 @@ if(ENABLE_CJSON) api/relay-api.c api/relay-api.h api/relay-api-msg.c api/relay-api-msg.h api/relay-api-protocol.c api/relay-api-protocol.h + # API relay remote + api/remote/relay-remote-network.c api/remote/relay-remote-network.h ) endif() diff --git a/src/plugins/relay/api/relay-api-msg.c b/src/plugins/relay/api/relay-api-msg.c index 1fdf3afe6..cb87d7045 100644 --- a/src/plugins/relay/api/relay-api-msg.c +++ b/src/plugins/relay/api/relay-api-msg.c @@ -172,7 +172,7 @@ relay_api_msg_send_json_internal (struct t_relay_client *client, string = cJSON_PrintUnformatted (json); num_bytes = relay_client_send ( client, - RELAY_CLIENT_MSG_STANDARD, + RELAY_MSG_STANDARD, string, (string) ? strlen (string) : 0, NULL); /* raw_message */ diff --git a/src/plugins/relay/api/remote/relay-remote-network.c b/src/plugins/relay/api/remote/relay-remote-network.c new file mode 100644 index 000000000..f5a11c1f1 --- /dev/null +++ b/src/plugins/relay/api/remote/relay-remote-network.c @@ -0,0 +1,1089 @@ +/* + * relay-remote-network.c - network functions for relay remote + * + * Copyright (C) 2024 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat 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 3 of the License, or + * (at your option) any later version. + * + * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <gcrypt.h> +#include <gnutls/gnutls.h> +#include <cjson/cJSON.h> + +#include "../../../weechat-plugin.h" +#include "../../relay.h" +#include "../../relay-auth.h" +#include "../../relay-http.h" +#include "../../relay-raw.h" +#include "../../relay-remote.h" +#include "../../relay-websocket.h" +#include "../relay-api.h" + + +/* + * Gets URL to an API resource. + * + * For example if remote URL is "https://localhost:9000" and the resource is + * "handshake", it returns: "https://localhost:9000/api/handshake". + * + * Note: result must be free after use. + */ + +char * +relay_remote_network_get_url_resource (struct t_relay_remote *remote, + const char *resource) +{ + const char *ptr_url; + char url[4096]; + + if (!remote || !resource || !resource[0]) + return NULL; + + ptr_url = weechat_config_string (remote->options[RELAY_REMOTE_OPTION_URL]); + if (!ptr_url || !ptr_url[0]) + return NULL; + + snprintf (url, sizeof (url), + "%s%sapi/%s", + ptr_url, + (ptr_url[strlen (ptr_url) - 1] == '/') ? "" : "/", + resource); + + return strdup (url); +} + +/* + * Close connection with remote. + */ + +void +relay_remote_network_close_connection (struct t_relay_remote *remote) +{ + if (!remote) + return; + if (remote->hook_fd) + { + weechat_unhook (remote->hook_fd); + remote->hook_fd = NULL; + } + if (remote->hook_connect) + { + weechat_unhook (remote->hook_connect); + remote->hook_connect = NULL; + } + if (remote->sock != -1) + { +#ifdef _WIN32 + closesocket (remote->sock); +#else + close (remote->sock); +#endif /* _WIN32 */ + remote->sock = -1; + } + relay_websocket_deflate_free (remote->ws_deflate); + remote->ws_deflate = NULL; +} + +/* + * Disconnects from remote. + */ + +void +relay_remote_network_disconnect (struct t_relay_remote *remote) +{ + if (!remote) + return; + + relay_remote_network_close_connection (remote); + relay_remote_set_status (remote, RELAY_STATUS_DISCONNECTED); + weechat_printf (NULL, "remote[%s]: disconnected", remote->name); +} + +/* + * Checks if authentication via websocket handshake was successful. + * + * Returns: + * 1: authentication successful + * 0: authentication has failed + */ + +int +relay_remote_network_check_auth (struct t_relay_remote *remote, + const char *buffer) +{ + struct t_relay_http_response *http_resp; + cJSON *json_body, *json_error; + const char *msg_error, *msg_resp_error, *ptr_ws_accept; + char *key, hash[160 / 8], sec_websocket_accept[128]; + int length, accept_ok, hash_size; + + http_resp = NULL; + msg_error = NULL; + msg_resp_error = NULL; + accept_ok = 0; + + http_resp = relay_http_parse_response (buffer); + if (!http_resp) + { + msg_error = _("invalid response from remote"); + goto error; + } + + if (http_resp->body) + { + json_body = cJSON_Parse (http_resp->body); + if (json_body) + { + json_error = cJSON_GetObjectItem (json_body, "error"); + if (json_error && cJSON_IsString (json_error)) + msg_resp_error = cJSON_GetStringValue (json_error); + } + } + + if ((http_resp->return_code != 101) + || (weechat_strcasecmp (http_resp->message, "Switching Protocols") != 0)) + { + if (http_resp->return_code == 401) + msg_error = _("authentication failed with remote"); + else + msg_error = _("invalid response from remote"); + goto error; + } + + if (remote->websocket_key) + { + ptr_ws_accept = weechat_hashtable_get (http_resp->headers, + "sec-websocket-accept"); + if (ptr_ws_accept) + { + length = strlen (remote->websocket_key) + strlen (WEBSOCKET_GUID) + 1; + key = malloc (length); + if (key) + { + snprintf (key, length, + "%s%s", remote->websocket_key, WEBSOCKET_GUID); + if (weechat_crypto_hash (key, strlen (key), "sha1", + hash, &hash_size)) + { + if (weechat_string_base_encode ("64", hash, hash_size, + sec_websocket_accept) > 0) + { + if (strcmp (ptr_ws_accept, sec_websocket_accept) == 0) + accept_ok = 1; + } + } + free (key); + } + } + } + + relay_websocket_parse_extensions ( + weechat_hashtable_get (http_resp->headers, "sec-websocket-extensions"), + remote->ws_deflate); + + if (!accept_ok) + { + msg_error = _("invalid websocket response (handshake error)"); + goto error; + } + + relay_http_response_free (http_resp); + + return 1; + +error: + weechat_printf ( + NULL, + _("%sremote[%s]: error: %s%s%s%s"), + weechat_prefix ("error"), + remote->name, + msg_error, + (msg_resp_error) ? " (" : "", + (msg_resp_error) ? msg_resp_error : "", + (msg_resp_error) ? ")" : ""); + if (http_resp) + relay_http_response_free (http_resp); + return 0; +} + +/* + * Sends data to the remote. + * + * Returns the number of bytes sent to the remote. + */ + +int +relay_remote_network_send_data (struct t_relay_remote *remote, + const char *data, int data_size) +{ + if (remote->tls) + { + return (remote->sock >= 0) ? + gnutls_record_send (remote->gnutls_sess, data, data_size) : + data_size; + } + else + { + return (remote->sock >= 0) ? + send (remote->sock, data, data_size, 0) : + data_size; + } + +} + +/* + * Sends data to the remote. + * If the remote is connected, encapsulate data in a websocket frame. + * + * Returns the number of bytes sent to the remote. + */ + +int +relay_remote_network_send (struct t_relay_remote *remote, + enum t_relay_msg_type msg_type, + const char *data, int data_size) +{ + const char *ptr_data; + char *websocket_frame; + unsigned long long length_frame; + int opcode, flags, num_sent; + + ptr_data = data; + websocket_frame = NULL; + + if (remote->status == RELAY_STATUS_CONNECTED) + { + /* encapsulate data in a websocket frame */ + switch (msg_type) + { + case RELAY_MSG_PING: + opcode = WEBSOCKET_FRAME_OPCODE_PING; + break; + case RELAY_MSG_PONG: + opcode = WEBSOCKET_FRAME_OPCODE_PONG; + break; + case RELAY_MSG_CLOSE: + opcode = WEBSOCKET_FRAME_OPCODE_CLOSE; + break; + default: + opcode = WEBSOCKET_FRAME_OPCODE_TEXT; + break; + } + websocket_frame = relay_websocket_encode_frame ( + remote->ws_deflate, opcode, 1, data, data_size, &length_frame); + if (websocket_frame) + { + ptr_data = websocket_frame; + data_size = length_frame; + } + } + + num_sent = relay_remote_network_send_data (remote, ptr_data, data_size); + + if (websocket_frame) + free (websocket_frame); + + if (num_sent >= 0) + { + flags = RELAY_RAW_FLAG_SEND; + if ((msg_type == RELAY_MSG_PING) + || (msg_type == RELAY_MSG_PONG) + || (msg_type == RELAY_MSG_CLOSE)) + { + flags |= RELAY_RAW_FLAG_BINARY; + } + relay_raw_print_remote (remote, msg_type, flags, data, data_size); + } + + return num_sent; +} + +/* + * Reads text buffer from a remote. + */ + +void +relay_remote_network_recv_text (struct t_relay_remote *remote, + const char *buffer, int buffer_size) +{ + char request[1024]; + + relay_raw_print_remote (remote, RELAY_MSG_STANDARD, + RELAY_RAW_FLAG_RECV, + buffer, buffer_size); + + if (remote->status == RELAY_STATUS_AUTHENTICATING) + { + if (!relay_remote_network_check_auth (remote, buffer)) + { + relay_remote_network_disconnect (remote); + return; + } + relay_remote_set_status (remote, RELAY_STATUS_CONNECTED); + snprintf (request, sizeof (request), + "{\"request\": \"GET /api/buffers\"}"); + relay_remote_network_send (remote, RELAY_MSG_STANDARD, + request, strlen (request)); + } +} + +/* + * Reads websocket frames. + */ + +void +relay_remote_network_read_websocket_frames (struct t_relay_remote *remote, + struct t_relay_websocket_frame *frames, + int num_frames) +{ + int i; + + if (!frames || (num_frames <= 0)) + return; + + for (i = 0; i < num_frames; i++) + { + if (frames[i].payload_size == 0) + { + /* + * When decoded length is 0, assume remote sent a PONG frame. + * + * RFC 6455 Section 5.5.3: + * + * "A Pong frame MAY be sent unsolicited. This serves as a + * unidirectional heartbeat. A response to an unsolicited + * Pong frame is not expected." + */ + continue; + } + switch (frames[i].opcode) + { + case RELAY_MSG_PING: + /* print message in raw buffer */ + relay_raw_print_remote (remote, RELAY_MSG_PING, + RELAY_RAW_FLAG_RECV | RELAY_RAW_FLAG_BINARY, + frames[i].payload, + frames[i].payload_size); + /* answer with a PONG */ + relay_remote_network_send (remote, + RELAY_MSG_PONG, + frames[i].payload, + frames[i].payload_size); + break; + case RELAY_MSG_CLOSE: + /* print message in raw buffer */ + relay_raw_print_remote (remote, RELAY_MSG_CLOSE, + RELAY_RAW_FLAG_RECV | RELAY_RAW_FLAG_BINARY, + frames[i].payload, + frames[i].payload_size); + /* answer with a CLOSE */ + relay_remote_network_send (remote, + RELAY_MSG_CLOSE, + frames[i].payload, + frames[i].payload_size); + /* close the connection */ + relay_remote_network_disconnect (remote); + /* ignore any other message after the close */ + return; + default: + if (frames[i].payload) + { + relay_remote_network_recv_text (remote, + frames[i].payload, + frames[i].payload_size); + } + break; + } + } +} + +/* + * Reads a buffer of bytes from a remote. + */ + +void +relay_remote_network_recv_buffer (struct t_relay_remote *remote, + const char *buffer, int buffer_size) +{ + struct t_relay_websocket_frame *frames; + char *partial_ws_frame; + int rc, i, num_frames, partial_ws_frame_size; + + /* if authenticating is in progress, check if it was successful */ + if (remote->status == RELAY_STATUS_AUTHENTICATING) + { + relay_remote_network_recv_text (remote, buffer, buffer_size); + } + else if (remote->status == RELAY_STATUS_CONNECTED) + { + partial_ws_frame = NULL; + partial_ws_frame_size = 0; + rc = relay_websocket_decode_frame ( + (const unsigned char *)buffer, + buffer_size, + 0, /* expect_masked_frame */ + remote->ws_deflate, + &frames, + &num_frames, + &partial_ws_frame, + &partial_ws_frame_size); + if (!rc) + { + /* fatal error when decoding frame: close connection */ + for (i = 0; i < num_frames; i++) + { + if (frames[i].payload) + free (frames[i].payload); + } + free (frames); + weechat_printf ( + NULL, + _("%sremote[%s]: error decoding websocket frame"), + weechat_prefix ("error"), + remote->name); + relay_remote_network_disconnect (remote); + return; + } + relay_remote_network_read_websocket_frames (remote, frames, num_frames); + } +} + +/* + * Callback for fd hook. + */ + +int +relay_remote_network_recv_cb (const void *pointer, void *data, int fd) +{ + struct t_relay_remote *remote; + static char buffer[4096 + 2]; + int num_read, end_recv; + + /* make C compiler happy */ + (void) data; + (void) fd; + + remote = (struct t_relay_remote *)pointer; + if (!remote) + return WEECHAT_RC_ERROR; + + end_recv = 0; + while (!end_recv) + { + end_recv = 1; + + if (remote->tls) + { + if (!remote->gnutls_sess) + return WEECHAT_RC_ERROR; + num_read = gnutls_record_recv (remote->gnutls_sess, buffer, + sizeof (buffer) - 2); + } + else + { + num_read = recv (remote->sock, buffer, sizeof (buffer) - 2, 0); + } + + if (num_read > 0) + { + buffer[num_read] = '\0'; + if (remote->tls + && (gnutls_record_check_pending (remote->gnutls_sess) > 0)) + { + /* + * if there are unread data in the gnutls buffers, + * go on with recv + */ + end_recv = 0; + } + relay_remote_network_recv_buffer (remote, buffer, num_read); + } + else + { + if (remote->tls) + { + if ((num_read == 0) + || ((num_read != GNUTLS_E_AGAIN) + && (num_read != GNUTLS_E_INTERRUPTED))) + { + weechat_printf ( + NULL, + _("%sremote[%s]: reading data on socket: error %d %s"), + weechat_prefix ("error"), + remote->name, + num_read, + (num_read == 0) ? _("(connection closed by peer)") : + gnutls_strerror (num_read)); + relay_remote_network_disconnect (remote); + } + } + else + { + if ((num_read == 0) + || ((errno != EAGAIN) && (errno != EWOULDBLOCK))) + { + weechat_printf ( + NULL, + _("%sremote[%s]: reading data on socket: error %d %s"), + weechat_prefix ("error"), + remote->name, + errno, + (num_read == 0) ? _("(connection closed by peer)") : + strerror (errno)); + relay_remote_network_disconnect (remote); + } + } + } + } + + return WEECHAT_RC_OK; +} + +/* + * Connects to remote using websocket, with authentication. + */ + +void +relay_remote_network_connect_ws_auth (struct t_relay_remote *remote) +{ + char *password, *totp_secret, *totp; + char *salt_password, salt[64], str_auth[4096], str_auth_base64[4096]; + char str_http[8192], str_totp[128]; + char hash[512 / 8], hash_hexa[((512 / 8) * 2) + 1]; + char ws_key[16], ws_key_base64[64]; + int length, hash_size; + + relay_remote_set_status (remote, RELAY_STATUS_AUTHENTICATING); + + password = NULL; + totp_secret = NULL; + str_auth[0] = '\0'; + str_totp[0] = '\0'; + + password = weechat_string_eval_expression ( + weechat_config_string (remote->options[RELAY_REMOTE_OPTION_PASSWORD]), + NULL, NULL, NULL); + if (!password) + goto end; + totp_secret = weechat_string_eval_expression ( + weechat_config_string (remote->options[RELAY_REMOTE_OPTION_TOTP_SECRET]), + NULL, NULL, NULL); + if (!totp_secret) + goto end; + + switch (remote->password_hash_algo) + { + case RELAY_AUTH_PASSWORD_HASH_PLAIN: + snprintf (str_auth, sizeof (str_auth), "plain:%s", password); + break; + case RELAY_AUTH_PASSWORD_HASH_SHA256: + case RELAY_AUTH_PASSWORD_HASH_SHA512: + length = strlen (password) + 64; + salt_password = malloc (length); + if (salt_password) + { + snprintf (salt_password, length, + "%ld%s", + time (NULL), + password); + if (weechat_crypto_hash ( + salt_password, strlen (salt_password), + relay_auth_password_hash_algo_name[remote->password_hash_algo], + hash, &hash_size)) + { + weechat_string_base_encode ("16", hash, hash_size, hash_hexa); + snprintf (str_auth, sizeof (str_auth), + "hash:%s", hash_hexa); + } + free (salt_password); + } + break; + case RELAY_AUTH_PASSWORD_HASH_PBKDF2_SHA256: + case RELAY_AUTH_PASSWORD_HASH_PBKDF2_SHA512: + snprintf (salt, sizeof (salt), "%ld", time (NULL)); + if (weechat_crypto_hash_pbkdf2 ( + password, + strlen (password), + relay_auth_password_hash_algo_name[remote->password_hash_algo] + 7, + salt, strlen (salt), + remote->password_hash_iterations, + hash, &hash_size)) + { + weechat_string_base_encode ("16", hash, hash_size, hash_hexa); + snprintf (str_auth, sizeof (str_auth), + "hash:%s:%s:%d:%s", + relay_auth_password_hash_algo_name[remote->password_hash_algo], + salt, + remote->password_hash_iterations, + hash_hexa); + } + break; + } + + if (!str_auth[0]) + { + weechat_printf (NULL, _("%sremote[%s]: failed to build authentication"), + weechat_prefix ("error"), remote->name); + relay_remote_network_disconnect (remote); + goto end; + } + + /* generate random websocket key (16 bytes) */ + gcry_create_nonce (ws_key, sizeof (ws_key)); + weechat_string_base_encode ("64", ws_key, sizeof (ws_key), ws_key_base64); + if (remote->websocket_key) + free (remote->websocket_key); + remote->websocket_key = strdup (ws_key_base64); + + weechat_string_base_encode ("64", str_auth, strlen (str_auth), str_auth_base64); + + if (totp_secret && totp_secret[0]) + { + /* generate the TOTP with the secret */ + totp = weechat_info_get ("totp_generate", totp_secret); + if (totp) + { + snprintf (str_totp, sizeof (str_totp), + "x-weechat-totp: %s\r\n", + totp); + free (totp); + } + } + + snprintf ( + str_http, sizeof (str_http), + "GET /api HTTP/1.1\r\n" + "Authorization: Basic %s\r\n" + "%s" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Key: %s\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n" + "Host: %s:%d\r\n" + "\r\n", + str_auth_base64, + str_totp, + ws_key_base64, + remote->address, + remote->port); + relay_remote_network_send (remote, RELAY_MSG_STANDARD, + str_http, strlen (str_http)); + +end: + if (password) + free (password); + if (totp_secret) + free (totp_secret); +} + +/* + * Callback for connect hook. + */ + +int +relay_remote_network_connect_cb (const void *pointer, void *data, int status, + int gnutls_rc, int sock, const char *error, + const char *ip_address) +{ + struct t_relay_remote *remote; + /*int dhkey_size;*/ + + /* make C compiler happy */ + (void) data; + + remote = (struct t_relay_remote *)pointer; + + remote->hook_connect = NULL; + + remote->sock = sock; + + switch (status) + { + case WEECHAT_HOOK_CONNECT_OK: + weechat_printf (NULL, _("remote[%s]: connected to %s/%d (%s)"), + remote->name, remote->address, remote->port, + ip_address); + remote->hook_fd = weechat_hook_fd (remote->sock, 1, 0, 0, + &relay_remote_network_recv_cb, + remote, NULL); + /* authenticate with remote relay */ + relay_remote_network_connect_ws_auth (remote); + break; + case WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND: + weechat_printf (NULL, _("%sremote[%s]: address \"%s\" not found"), + weechat_prefix ("error"), remote->name, + remote->address); + if (error && error[0]) + { + weechat_printf (NULL, _("%sremote[%s]: error: %s"), + weechat_prefix ("error"), remote->name, error); + } + break; + case WEECHAT_HOOK_CONNECT_IP_ADDRESS_NOT_FOUND: + weechat_printf (NULL, _("%sremote[%s]: IP address not found"), + weechat_prefix ("error"), remote->name); + if (error && error[0]) + { + weechat_printf (NULL, _("%sremote[%s]: error: %s"), + weechat_prefix ("error"), remote->name, error); + } + break; + case WEECHAT_HOOK_CONNECT_CONNECTION_REFUSED: + weechat_printf (NULL, _("%sremote[%s]: connection refused"), + weechat_prefix ("error"), remote->name); + if (error && error[0]) + { + weechat_printf (NULL, _("%sremote[%s]: error: %s"), + weechat_prefix ("error"), remote->name, error); + } + break; + case WEECHAT_HOOK_CONNECT_PROXY_ERROR: + weechat_printf ( + NULL, + _("%sremote[%s]: proxy fails to establish connection to server (check " + "username/password if used and if server address/port is " + "allowed by proxy)"), + weechat_prefix ("error"), remote->name); + if (error && error[0]) + { + weechat_printf (NULL, _("%sremote[%s]: error: %s"), + weechat_prefix ("error"), remote->name, error); + } + break; + case WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR: + weechat_printf (NULL, _("%sremote[%s]: unable to set local hostname/IP"), + weechat_prefix ("error"), remote->name); + if (error && error[0]) + { + weechat_printf (NULL, _("%sremote[%s]: error: %s"), + weechat_prefix ("error"), remote->name, error); + } + break; + case WEECHAT_HOOK_CONNECT_GNUTLS_INIT_ERROR: + weechat_printf (NULL, _("%sremote[%s]: TLS init error"), + weechat_prefix ("error"), remote->name); + if (error && error[0]) + { + weechat_printf (NULL, _("%sremote[%s]: error: %s"), + weechat_prefix ("error"), remote->name, error); + } + break; + case WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR: + weechat_printf (NULL, _("%sremote[%s]: TLS handshake failed"), + weechat_prefix ("error"), remote->name); + if (error && error[0]) + { + weechat_printf (NULL, _("%sremote[%s]: error: %s"), + weechat_prefix ("error"), remote->name, error); + } + if (gnutls_rc == GNUTLS_E_DH_PRIME_UNACCEPTABLE) + { + /* dhkey_size = weechat_config_integer ( */ + /* remote->options[RELAY_REMOTE_OPTION_TLS_DHKEY_SIZE]); */ + /* weechat_printf ( */ + /* NULL, */ + /* _("%sremote[%s]: you should play with option " */ + /* "relay.remote.%s.tls_dhkey_size (current value is %d, try " */ + /* "a lower value like %d or %d)"), */ + /* weechat_prefix ("error"), */ + /* remote->name, */ + /* remote->name, */ + /* dhkey_size, */ + /* dhkey_size / 2, */ + /* dhkey_size / 4); */ + } + break; + case WEECHAT_HOOK_CONNECT_MEMORY_ERROR: + weechat_printf (NULL, _("%sremote[%s]: not enough memory"), + weechat_prefix ("error"), remote->name); + if (error && error[0]) + { + weechat_printf (NULL, _("%sremote[%s]: error: %s"), + weechat_prefix ("error"), remote->name, error); + } + break; + case WEECHAT_HOOK_CONNECT_TIMEOUT: + weechat_printf (NULL, _("%sremote[%s]: timeout"), + weechat_prefix ("error"), remote->name); + if (error && error[0]) + { + weechat_printf (NULL, _("%sremote[%s]: error: %s"), + weechat_prefix ("error"), remote->name, error); + } + break; + case WEECHAT_HOOK_CONNECT_SOCKET_ERROR: + weechat_printf (NULL, _("%sremote[%s]: unable to create socket"), + weechat_prefix ("error"), remote->name); + if (error && error[0]) + { + weechat_printf (NULL, _("%sremote[%s]: error: %s"), + weechat_prefix ("error"), remote->name, error); + } + break; + } + + return WEECHAT_RC_OK; +} + +/* + * Callback for handshake URL. + */ + +int +relay_remote_network_url_handshake_cb (const void *pointer, + void *data, + const char *url, + struct t_hashtable *options, + struct t_hashtable *output) +{ + struct t_relay_remote *remote; + const char *ptr_output, *ptr_resp_code, *ptr_error; + cJSON *json_body, *json_hash_algo, *json_hash_iterations, *json_totp; + + /* make C compiler happy */ + (void) data; + (void) url; + (void) options; + + remote = (struct t_relay_remote *)pointer; + + ptr_resp_code = weechat_hashtable_get (output, "response_code"); + if (ptr_resp_code && ptr_resp_code[0] && (strcmp (ptr_resp_code, "200") != 0)) + { + weechat_printf (NULL, + _("%s%s: handshake failed with URL %s, response code: %s"), + weechat_prefix ("error"), RELAY_PLUGIN_NAME, + weechat_config_string (remote->options[RELAY_REMOTE_OPTION_URL]), + ptr_resp_code); + return WEECHAT_RC_OK; + } + + ptr_error = weechat_hashtable_get (output, "error"); + if (ptr_error && ptr_error[0]) + { + weechat_printf (NULL, + _("%s%s: handshake failed with URL %s, error: %s"), + weechat_prefix ("error"), RELAY_PLUGIN_NAME, + weechat_config_string (remote->options[RELAY_REMOTE_OPTION_URL]), + ptr_error); + return WEECHAT_RC_OK; + } + + ptr_output = weechat_hashtable_get (output, "output"); + if (ptr_output && ptr_output[0]) + { + json_body = cJSON_Parse (weechat_hashtable_get (output, "output")); + if (json_body) + { + /* hash algorithm */ + json_hash_algo = cJSON_GetObjectItem (json_body, "password_hash_algo"); + if (json_hash_algo && cJSON_IsString (json_hash_algo)) + { + remote->password_hash_algo = relay_auth_password_hash_algo_search ( + cJSON_GetStringValue (json_hash_algo)); + } + /* hash iterations */ + json_hash_iterations = cJSON_GetObjectItem (json_body, "password_hash_iterations"); + if (json_hash_iterations && cJSON_IsNumber (json_hash_iterations)) + remote->password_hash_iterations = json_hash_iterations->valueint; + /* TOTP */ + json_totp = cJSON_GetObjectItem (json_body, "totp"); + if (json_totp && cJSON_IsBool (json_totp)) + remote->totp = (cJSON_IsTrue (json_totp)) ? 1 : 0; + } + } + + if (remote->password_hash_algo < 0) + { + weechat_printf (NULL, + _("%s%s: handshake failed with URL %s, error: %s"), + weechat_prefix ("error"), RELAY_PLUGIN_NAME, + weechat_config_string (remote->options[RELAY_REMOTE_OPTION_URL]), + _("hash algorithm not found")); + return WEECHAT_RC_OK; + } + + if (remote->password_hash_iterations < 0) + { + weechat_printf (NULL, + _("%s%s: handshake failed with URL %s, error: %s"), + weechat_prefix ("error"), RELAY_PLUGIN_NAME, + weechat_config_string (remote->options[RELAY_REMOTE_OPTION_URL]), + _("unknown number of hash iterations")); + return WEECHAT_RC_OK; + } + + if (remote->totp < 0) + { + weechat_printf (NULL, + _("%s%s: handshake failed with URL %s, error: %s"), + weechat_prefix ("error"), RELAY_PLUGIN_NAME, + weechat_config_string (remote->options[RELAY_REMOTE_OPTION_URL]), + _("unknown TOTP status")); + return WEECHAT_RC_OK; + } + + if (weechat_relay_plugin->debug >= 1) + { + weechat_printf (NULL, + _("%s: successful handshake with URL %s: " + "hash_algo=%s, iterations=%d, totp=%d"), + RELAY_PLUGIN_NAME, + weechat_config_string (remote->options[RELAY_REMOTE_OPTION_URL]), + relay_auth_password_hash_algo_name[remote->password_hash_algo], + remote->password_hash_iterations, + remote->totp); + } + + remote->hook_connect = weechat_hook_connect ( + NULL, /* proxy */ + remote->address, + remote->port, + 1, /* ipv6 */ + 0, /* retry */ + NULL, /* gnutls_sess */ + NULL, /* gnutls_cb */ + 0, /* gnutls_dhkey_size */ + NULL, /* gnutls_priorities */ + NULL, /* local_hostname */ + &relay_remote_network_connect_cb, + remote, + NULL); + + return WEECHAT_RC_OK; +} + +/* + * Builds a string with the API HTTP handshake request. + * + * Note: result must be free after use. + */ + +char * +relay_remote_network_get_handshake_request () +{ + char **body; + int i; + + body = weechat_string_dyn_alloc (256); + if (!body) + return NULL; + + weechat_string_dyn_concat (body, "{\"password_hash_algo\": [", -1); + /* all password hash algorithms are supported */ + for (i = 0; i < RELAY_NUM_PASSWORD_HASH_ALGOS; i++) + { + if (i > 0) + weechat_string_dyn_concat (body, ", ", -1); + weechat_string_dyn_concat (body, "\"", -1); + weechat_string_dyn_concat (body, + relay_auth_password_hash_algo_name[i], -1); + weechat_string_dyn_concat (body, "\"", -1); + } + weechat_string_dyn_concat (body, "]}", -1); + return weechat_string_dyn_free (body, 0); +} + +/* + * Connects to a remote WeeChat relay/api. + * + * Returns: + * 1: OK + * 0: error + */ + +int +relay_remote_network_connect (struct t_relay_remote *remote) +{ + char *url, *body; + struct t_hashtable *options; + + url = NULL; + body = NULL; + options = NULL; + + if (!remote) + return 0; + + if (remote->sock != -1) + { + weechat_printf ( + NULL, + _("%s%s: already connected to remote \"%s\"!"), + weechat_prefix ("error"), RELAY_PLUGIN_NAME, remote->name); + return 0; + } + + relay_remote_set_status (remote, RELAY_STATUS_CONNECTING); + + weechat_printf (NULL, + _("remote[%s]: connecting to remote %s/%d%s..."), + remote->name, + remote->address, + remote->port, + (remote->tls) ? " (TLS)" : ""); + + url = relay_remote_network_get_url_resource (remote, "handshake"); + if (!url) + goto error; + + options = weechat_hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, NULL); + if (!options) + goto error; + + weechat_hashtable_set (options, "post", "1"); + weechat_hashtable_set (options, + "httpheader", + "Accept: application/json\n" + "Content-Type: application/json; charset=utf-8"); + body = relay_remote_network_get_handshake_request (); + if (!body) + goto error; + + weechat_hashtable_set (options, "postfields", body); + + remote->hook_url_handshake = weechat_hook_url ( + url, options, 5 * 1000, + &relay_remote_network_url_handshake_cb, remote, NULL); + + free (url); + free (body); + weechat_hashtable_free (options); + + return 1; + +error: + weechat_printf (NULL, + _("remote[%s]: failed to connect, not enough memory"), + remote->name); + if (url) + free (url); + if (body) + free (body); + if (options) + weechat_hashtable_free (options); + return 0; +} diff --git a/src/plugins/relay/api/remote/relay-remote-network.h b/src/plugins/relay/api/remote/relay-remote-network.h new file mode 100644 index 000000000..7635beb63 --- /dev/null +++ b/src/plugins/relay/api/remote/relay-remote-network.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat 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 3 of the License, or + * (at your option) any later version. + * + * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef WEECHAT_PLUGIN_RELAY_REMOTE_NETWORK_H +#define WEECHAT_PLUGIN_RELAY_REMOTE_NETWORK_H + +extern int relay_remote_network_connect (struct t_relay_remote *remote); +extern void relay_remote_network_disconnect (struct t_relay_remote *remote); + +#endif /* WEECHAT_PLUGIN_RELAY_REMOTE_NETWORK_H */ diff --git a/src/plugins/relay/irc/relay-irc.c b/src/plugins/relay/irc/relay-irc.c index c0885511c..8389ffa8f 100644 --- a/src/plugins/relay/irc/relay-irc.c +++ b/src/plugins/relay/irc/relay-irc.c @@ -279,7 +279,7 @@ relay_irc_sendf (struct t_relay_client *client, const char *format, ...) if (message) { snprintf (message, length, "%s\r\n", ptr_msg2); - relay_client_send (client, RELAY_CLIENT_MSG_STANDARD, + relay_client_send (client, RELAY_MSG_STANDARD, message, strlen (message), NULL); free (message); } diff --git a/src/plugins/relay/relay-client.c b/src/plugins/relay/relay-client.c index e1567722f..7b627d6fd 100644 --- a/src/plugins/relay/relay-client.c +++ b/src/plugins/relay/relay-client.c @@ -52,9 +52,6 @@ char *relay_client_data_type_string[] = /* strings for data types */ { "text", "binary", "http" }; -char *relay_client_msg_type_string[] = /* prefix in raw buffer for message */ -{ "", "[PING]\n", "[PONG]\n", "[CLOSE]\n" }; - struct t_relay_client *relay_clients = NULL; struct t_relay_client *last_relay_client = NULL; int relay_client_count = 0; /* number of clients */ @@ -313,9 +310,9 @@ relay_client_recv_text_single_line (struct t_relay_client *client) pos - client->partial_message + 1); if (raw_msg) { - relay_raw_print (client, RELAY_CLIENT_MSG_STANDARD, - RELAY_RAW_FLAG_RECV, - raw_msg, strlen (raw_msg) + 1); + relay_raw_print_client (client, RELAY_MSG_STANDARD, + RELAY_RAW_FLAG_RECV, + raw_msg, strlen (raw_msg) + 1); free (raw_msg); } @@ -380,10 +377,10 @@ relay_client_recv_text_multi_line (struct t_relay_client *client) return; /* print message in raw buffer */ - relay_raw_print (client, RELAY_CLIENT_MSG_STANDARD, - RELAY_RAW_FLAG_RECV, - client->partial_message, - strlen (client->partial_message) + 1); + relay_raw_print_client (client, RELAY_MSG_STANDARD, + RELAY_RAW_FLAG_RECV, + client->partial_message, + strlen (client->partial_message) + 1); /* * interpret text from client, according to the relay @@ -457,7 +454,7 @@ relay_client_recv_text_buffer (struct t_relay_client *client, index = 0; while (index < length_buffer) { - msg_type = RELAY_CLIENT_MSG_STANDARD; + msg_type = RELAY_MSG_STANDARD; /* * in case of websocket, we can receive PING from client: @@ -466,30 +463,30 @@ relay_client_recv_text_buffer (struct t_relay_client *client, if (client->websocket == RELAY_CLIENT_WEBSOCKET_READY) { msg_type = (unsigned char)buffer[index]; - if (msg_type == RELAY_CLIENT_MSG_PING) + if (msg_type == RELAY_MSG_PING) { /* print message in raw buffer */ - relay_raw_print (client, RELAY_CLIENT_MSG_PING, - RELAY_RAW_FLAG_RECV | RELAY_RAW_FLAG_BINARY, - buffer + index + 1, - strlen (buffer + index + 1)); + relay_raw_print_client (client, RELAY_MSG_PING, + RELAY_RAW_FLAG_RECV | RELAY_RAW_FLAG_BINARY, + buffer + index + 1, + strlen (buffer + index + 1)); /* answer with a PONG */ relay_client_send (client, - RELAY_CLIENT_MSG_PONG, + RELAY_MSG_PONG, buffer + index + 1, strlen (buffer + index + 1), NULL); } - else if (msg_type == RELAY_CLIENT_MSG_CLOSE) + else if (msg_type == RELAY_MSG_CLOSE) { /* print message in raw buffer */ - relay_raw_print (client, RELAY_CLIENT_MSG_CLOSE, - RELAY_RAW_FLAG_RECV | RELAY_RAW_FLAG_BINARY, - buffer + index + 1, - strlen (buffer + index + 1)); + relay_raw_print_client (client, RELAY_MSG_CLOSE, + RELAY_RAW_FLAG_RECV | RELAY_RAW_FLAG_BINARY, + buffer + index + 1, + strlen (buffer + index + 1)); /* answer with a CLOSE */ relay_client_send (client, - RELAY_CLIENT_MSG_CLOSE, + RELAY_MSG_CLOSE, buffer + index + 1, strlen (buffer + index + 1), NULL); @@ -501,7 +498,7 @@ relay_client_recv_text_buffer (struct t_relay_client *client, index++; } - if (msg_type == RELAY_CLIENT_MSG_STANDARD) + if (msg_type == RELAY_MSG_STANDARD) { if ((client->websocket == RELAY_CLIENT_WEBSOCKET_INITIALIZING) || (client->recv_data_type == RELAY_CLIENT_DATA_HTTP)) @@ -549,28 +546,28 @@ relay_client_read_websocket_frames (struct t_relay_client *client, } switch (frames[i].opcode) { - case RELAY_CLIENT_MSG_PING: + case RELAY_MSG_PING: /* print message in raw buffer */ - relay_raw_print (client, RELAY_CLIENT_MSG_PING, - RELAY_RAW_FLAG_RECV | RELAY_RAW_FLAG_BINARY, - frames[i].payload, - frames[i].payload_size); + relay_raw_print_client (client, RELAY_MSG_PING, + RELAY_RAW_FLAG_RECV | RELAY_RAW_FLAG_BINARY, + frames[i].payload, + frames[i].payload_size); /* answer with a PONG */ relay_client_send (client, - RELAY_CLIENT_MSG_PONG, + RELAY_MSG_PONG, frames[i].payload, frames[i].payload_size, NULL); break; - case RELAY_CLIENT_MSG_CLOSE: + case RELAY_MSG_CLOSE: /* print message in raw buffer */ - relay_raw_print (client, RELAY_CLIENT_MSG_CLOSE, - RELAY_RAW_FLAG_RECV | RELAY_RAW_FLAG_BINARY, - frames[i].payload, - frames[i].payload_size); + relay_raw_print_client (client, RELAY_MSG_CLOSE, + RELAY_RAW_FLAG_RECV | RELAY_RAW_FLAG_BINARY, + frames[i].payload, + frames[i].payload_size); /* answer with a CLOSE */ relay_client_send (client, - RELAY_CLIENT_MSG_CLOSE, + RELAY_MSG_CLOSE, frames[i].payload, frames[i].payload_size, NULL); @@ -653,11 +650,14 @@ relay_client_recv_buffer (struct t_relay_client *client, frames = NULL; num_frames = 0; rc = relay_websocket_decode_frame ( - client, (buffer2) ? (unsigned char *)buffer2 : (unsigned char *)buffer, (buffer2) ? (unsigned long long)buffer2_size : (unsigned long long)buffer_size, + 1, /* expect_masked_frame */ + client->ws_deflate, &frames, - &num_frames); + &num_frames, + &client->partial_ws_frame, + &client->partial_ws_frame_size); if (buffer2) free (buffer2); if (!rc) @@ -899,7 +899,7 @@ relay_client_send_outqueue (struct t_relay_client *client) * (so that it is displayed only one time, even if * message is sent in many chunks) */ - relay_raw_print ( + relay_raw_print_client ( client, client->outqueue->raw_msg_type[i], client->outqueue->raw_flags[i], @@ -1033,7 +1033,7 @@ relay_client_timer_send_cb (const void *pointer, void *data, void relay_client_outqueue_add (struct t_relay_client *client, const char *data, int data_size, - enum t_relay_client_msg_type raw_msg_type[2], + enum t_relay_msg_type raw_msg_type[2], int raw_flags[2], const char *raw_message[2], int raw_size[2]) @@ -1059,7 +1059,7 @@ relay_client_outqueue_add (struct t_relay_client *client, new_outqueue->data_size = data_size; for (i = 0; i < 2; i++) { - new_outqueue->raw_msg_type[i] = RELAY_CLIENT_MSG_STANDARD; + new_outqueue->raw_msg_type[i] = RELAY_MSG_STANDARD; new_outqueue->raw_flags[i] = 0; new_outqueue->raw_message[i] = NULL; new_outqueue->raw_size[i] = 0; @@ -1104,12 +1104,12 @@ relay_client_outqueue_add (struct t_relay_client *client, int relay_client_send (struct t_relay_client *client, - enum t_relay_client_msg_type msg_type, + enum t_relay_msg_type msg_type, const char *data, int data_size, const char *message_raw_buffer) { int num_sent, raw_size[2], raw_flags[2], opcode, i; - enum t_relay_client_msg_type raw_msg_type[2]; + enum t_relay_msg_type raw_msg_type[2]; char *websocket_frame; unsigned long long length_frame; const char *ptr_data, *raw_msg[2]; @@ -1154,9 +1154,9 @@ relay_client_send (struct t_relay_client *client, { raw_msg[0] = data; raw_size[0] = data_size; - if ((msg_type == RELAY_CLIENT_MSG_PING) - || (msg_type == RELAY_CLIENT_MSG_PONG) - || (msg_type == RELAY_CLIENT_MSG_CLOSE) + if ((msg_type == RELAY_MSG_PING) + || (msg_type == RELAY_MSG_PONG) + || (msg_type == RELAY_MSG_CLOSE) || ((client->websocket != RELAY_CLIENT_WEBSOCKET_INITIALIZING) && (client->send_data_type == RELAY_CLIENT_DATA_BINARY))) { @@ -1179,13 +1179,13 @@ relay_client_send (struct t_relay_client *client, { switch (msg_type) { - case RELAY_CLIENT_MSG_PING: + case RELAY_MSG_PING: opcode = WEBSOCKET_FRAME_OPCODE_PING; break; - case RELAY_CLIENT_MSG_PONG: + case RELAY_MSG_PONG: opcode = WEBSOCKET_FRAME_OPCODE_PONG; break; - case RELAY_CLIENT_MSG_CLOSE: + case RELAY_MSG_CLOSE: opcode = WEBSOCKET_FRAME_OPCODE_CLOSE; break; default: @@ -1195,7 +1195,7 @@ relay_client_send (struct t_relay_client *client, break; } websocket_frame = relay_websocket_encode_frame ( - client, opcode, data, data_size, &length_frame); + client->ws_deflate, opcode, 0, data, data_size, &length_frame); if (websocket_frame) { ptr_data = websocket_frame; @@ -1223,8 +1223,9 @@ relay_client_send (struct t_relay_client *client, { if (raw_msg[i]) { - relay_raw_print (client, raw_msg_type[i], raw_flags[i], - raw_msg[i], raw_size[i]); + relay_raw_print_client (client, raw_msg_type[i], + raw_flags[i], + raw_msg[i], raw_size[i]); } } if (num_sent > 0) diff --git a/src/plugins/relay/relay-client.h b/src/plugins/relay/relay-client.h index b2b5a7661..9231f2d3a 100644 --- a/src/plugins/relay/relay-client.h +++ b/src/plugins/relay/relay-client.h @@ -50,18 +50,6 @@ enum t_relay_client_websocket_status RELAY_NUM_CLIENT_WEBSOCKET_STATUS, }; -/* type of message exchanged with the client (used for websockets) */ - -enum t_relay_client_msg_type -{ - RELAY_CLIENT_MSG_STANDARD, - RELAY_CLIENT_MSG_PING, - RELAY_CLIENT_MSG_PONG, - RELAY_CLIENT_MSG_CLOSE, - /* number of message types */ - RELAY_NUM_CLIENT_MSG_TYPES, -}; - /* fake send function (for tests) */ typedef void (t_relay_fake_send_func)(void *client, @@ -127,7 +115,6 @@ struct t_relay_client struct t_relay_client *next_client;/* link to next client */ }; -extern char *relay_client_msg_type_string[]; extern struct t_relay_client *relay_clients; extern struct t_relay_client *last_relay_client; extern int relay_client_count; @@ -141,7 +128,7 @@ extern void relay_client_recv_buffer (struct t_relay_client *client, const char *buffer, int buffer_size); extern int relay_client_recv_cb (const void *pointer, void *data, int fd); extern int relay_client_send (struct t_relay_client *client, - enum t_relay_client_msg_type msg_type, + enum t_relay_msg_type msg_type, const char *data, int data_size, const char *message_raw_buffer); extern int relay_client_timer_cb (const void *pointer, void *data, diff --git a/src/plugins/relay/relay-command.c b/src/plugins/relay/relay-command.c index 7b6643b3d..2da4f53a3 100644 --- a/src/plugins/relay/relay-command.c +++ b/src/plugins/relay/relay-command.c @@ -501,7 +501,6 @@ relay_command_remote (const void *pointer, void *data, if (weechat_strcmp (argv[1], "add") == 0) { WEECHAT_COMMAND_MIN_ARGS(4, "add"); - ptr_remote = relay_remote_search (argv[2]); if (ptr_remote) { @@ -511,7 +510,6 @@ relay_command_remote (const void *pointer, void *data, weechat_prefix ("error"), RELAY_PLUGIN_NAME, ptr_remote->name); return WEECHAT_RC_OK; } - if (!relay_remote_name_valid (argv[2])) { weechat_printf (NULL, @@ -521,8 +519,6 @@ relay_command_remote (const void *pointer, void *data, argv[2]); return WEECHAT_RC_OK; } - - if (!relay_remote_url_valid (argv[3])) { weechat_printf (NULL, @@ -532,11 +528,9 @@ relay_command_remote (const void *pointer, void *data, argv[3]); return WEECHAT_RC_OK; } - ptr_proxy = NULL; ptr_password = NULL; ptr_totp_secret = NULL; - for (i = 4; i < argc; i++) { if (strncmp (argv[i], "-proxy=", 7) == 0) @@ -561,7 +555,6 @@ relay_command_remote (const void *pointer, void *data, return WEECHAT_RC_OK; } } - ptr_remote = relay_remote_new (argv[2], argv[3], ptr_proxy, ptr_password, ptr_totp_secret); if (ptr_remote) @@ -576,14 +569,12 @@ relay_command_remote (const void *pointer, void *data, weechat_prefix ("error"), RELAY_PLUGIN_NAME, argv[2]); } - return WEECHAT_RC_OK; } if (weechat_strcmp (argv[1], "connect") == 0) { WEECHAT_COMMAND_MIN_ARGS(3, "connect"); - ptr_remote = relay_remote_search (argv[2]); if (!ptr_remote) { @@ -596,14 +587,13 @@ relay_command_remote (const void *pointer, void *data, "remote connect"); return WEECHAT_RC_OK; } - - WEECHAT_COMMAND_ERROR; + relay_remote_connect (ptr_remote); + return WEECHAT_RC_OK; } if (weechat_strcmp (argv[1], "rename") == 0) { WEECHAT_COMMAND_MIN_ARGS(4, "rename"); - /* look for remote by name */ ptr_remote = relay_remote_search (argv[2]); if (!ptr_remote) @@ -617,7 +607,6 @@ relay_command_remote (const void *pointer, void *data, "remote rename"); return WEECHAT_RC_OK; } - /* check if target name already exists */ ptr_remote2 = relay_remote_search (argv[3]); if (ptr_remote2) @@ -631,7 +620,6 @@ relay_command_remote (const void *pointer, void *data, "server rename"); return WEECHAT_RC_OK; } - /* rename remote */ if (relay_remote_rename (ptr_remote, argv[3])) { @@ -643,14 +631,12 @@ relay_command_remote (const void *pointer, void *data, argv[3]); return WEECHAT_RC_OK; } - WEECHAT_COMMAND_ERROR; } if (weechat_strcmp (argv[1], "disconnect") == 0) { WEECHAT_COMMAND_MIN_ARGS(3, "disconnect"); - ptr_remote = relay_remote_search (argv[2]); if (!ptr_remote) { @@ -663,14 +649,13 @@ relay_command_remote (const void *pointer, void *data, "remote disconnect"); return WEECHAT_RC_OK; } - - WEECHAT_COMMAND_ERROR; + relay_remote_disconnect (ptr_remote); + return WEECHAT_RC_OK; } if (weechat_strcmp (argv[1], "del") == 0) { WEECHAT_COMMAND_MIN_ARGS(3, "del"); - /* look for remote by name */ ptr_remote = relay_remote_search (argv[2]); if (!ptr_remote) @@ -705,7 +690,6 @@ relay_command_remote (const void *pointer, void *data, (remote_name) ? remote_name : "???"); if (remote_name) free (remote_name); - return WEECHAT_RC_OK; } diff --git a/src/plugins/relay/relay-http.c b/src/plugins/relay/relay-http.c index 8b64deb23..58879b4fa 100644 --- a/src/plugins/relay/relay-http.c +++ b/src/plugins/relay/relay-http.c @@ -833,7 +833,7 @@ relay_http_process_websocket (struct t_relay_client *client) if (handshake) { relay_client_send (client, - RELAY_CLIENT_MSG_STANDARD, + RELAY_MSG_STANDARD, handshake, strlen (handshake), NULL); free (handshake); @@ -877,10 +877,10 @@ relay_http_process_request (struct t_relay_client *client) { if (client->http_req->raw) { - relay_raw_print (client, RELAY_CLIENT_MSG_STANDARD, - RELAY_RAW_FLAG_RECV, - *(client->http_req->raw), - strlen (*(client->http_req->raw)) + 1); + relay_raw_print_client (client, RELAY_MSG_STANDARD, + RELAY_RAW_FLAG_RECV, + *(client->http_req->raw), + strlen (*(client->http_req->raw)) + 1); } /* if websocket is initializing */ @@ -1199,7 +1199,7 @@ relay_http_send (struct t_relay_client *client, if (!ptr_body || (*ptr_body_size <= 0)) { - num_bytes = relay_client_send (client, RELAY_CLIENT_MSG_STANDARD, + num_bytes = relay_client_send (client, RELAY_MSG_STANDARD, str_header, length_header, NULL); } else @@ -1227,7 +1227,7 @@ relay_http_send (struct t_relay_client *client, { raw_message = NULL; } - num_bytes = relay_client_send (client, RELAY_CLIENT_MSG_STANDARD, + num_bytes = relay_client_send (client, RELAY_MSG_STANDARD, http_message, length_msg, raw_message); if (raw_message) diff --git a/src/plugins/relay/relay-raw.c b/src/plugins/relay/relay-raw.c index f7e854e54..b18d7b796 100644 --- a/src/plugins/relay/relay-raw.c +++ b/src/plugins/relay/relay-raw.c @@ -31,6 +31,7 @@ #include "relay-buffer.h" #include "relay-client.h" #include "relay-config.h" +#include "relay-remote.h" struct t_gui_buffer *relay_raw_buffer = NULL; @@ -235,76 +236,95 @@ relay_raw_message_add_to_list (time_t date, int date_usec, } /* - * Adds a new raw message to list. + * Converts a binary message for raw display. */ -void -relay_raw_message_add (struct t_relay_client *client, - enum t_relay_client_msg_type msg_type, - int flags, - const char *data, int data_size) +char * +relay_raw_convert_binary_message (const char *data, int data_size) +{ + return weechat_string_hex_dump (data, data_size, 16, " > ", NULL); +} + +/* + * Converts a text message for raw display. + */ + +char * +relay_raw_convert_text_message (const char *data) { - char *buf, *buf2, *buf3, prefix[256], prefix_arrow[16]; const unsigned char *ptr_buf; const char *hexa = "0123456789ABCDEF"; - int pos_buf, pos_buf2, char_size, i, length; - struct t_relay_raw_message *new_raw_message; - struct timeval tv_now; - - buf = NULL; - buf2 = NULL; - buf3 = NULL; + char *buf, *buf2; + int i, pos_buf, pos_buf2, char_size; - if (flags & RELAY_RAW_FLAG_BINARY) - { - /* binary message */ - buf = weechat_string_hex_dump (data, data_size, 16, " > ", NULL); - snprintf (prefix, sizeof (prefix), " "); - } - else + buf = weechat_iconv_to_internal (NULL, data); + if (!buf) + return NULL; + buf2 = weechat_string_replace (buf, "\r", ""); + free (buf); + if (!buf2) + return NULL; + buf = buf2; + buf2 = malloc ((strlen (buf) * 4) + 1); + if (buf2) { - /* text message */ - buf = weechat_iconv_to_internal (NULL, data); - buf2 = weechat_string_replace (buf, "\r", ""); - if (buf2) - { - free (buf); - buf = buf2; - buf2 = NULL; - } - buf2 = malloc ((strlen (buf) * 4) + 1); - if (buf2) + ptr_buf = (unsigned char *)buf; + pos_buf = 0; + pos_buf2 = 0; + while (ptr_buf[pos_buf]) { - ptr_buf = (buf) ? (unsigned char *)buf : (unsigned char *)data; - pos_buf = 0; - pos_buf2 = 0; - while (ptr_buf[pos_buf]) + if ((ptr_buf[pos_buf] < 32) && (ptr_buf[pos_buf] != '\n')) { - if ((ptr_buf[pos_buf] < 32) && (ptr_buf[pos_buf] != '\n')) - { - buf2[pos_buf2++] = '\\'; - buf2[pos_buf2++] = 'x'; - buf2[pos_buf2++] = hexa[ptr_buf[pos_buf] / 16]; - buf2[pos_buf2++] = hexa[ptr_buf[pos_buf] % 16]; - pos_buf++; - } - else + buf2[pos_buf2++] = '\\'; + buf2[pos_buf2++] = 'x'; + buf2[pos_buf2++] = hexa[ptr_buf[pos_buf] / 16]; + buf2[pos_buf2++] = hexa[ptr_buf[pos_buf] % 16]; + pos_buf++; + } + else + { + char_size = weechat_utf8_char_size ((const char *)(ptr_buf + pos_buf)); + for (i = 0; i < char_size; i++) { - char_size = weechat_utf8_char_size ((const char *)(ptr_buf + pos_buf)); - for (i = 0; i < char_size; i++) - { - buf2[pos_buf2++] = ptr_buf[pos_buf++]; - } + buf2[pos_buf2++] = ptr_buf[pos_buf++]; } } - buf2[pos_buf2] = '\0'; } + buf2[pos_buf2] = '\0'; } + free (buf); + return buf2; +} + +/* + * Adds a new raw message to list. + */ + +void +relay_raw_message_add (enum t_relay_msg_type msg_type, + int flags, + const char *peer_id, + const char *data, int data_size) +{ + char *raw_data, *buf, prefix[1024], prefix_arrow[16]; + int length; + struct t_relay_raw_message *new_raw_message; + struct timeval tv_now; + + if (flags & RELAY_RAW_FLAG_BINARY) + raw_data = relay_raw_convert_binary_message (data, data_size); + else + raw_data = relay_raw_convert_text_message (data); + + if (!raw_data) + return; + + snprintf (prefix, sizeof (prefix), " "); if (!(flags & RELAY_RAW_FLAG_BINARY) - || (msg_type == RELAY_CLIENT_MSG_PING) - || (msg_type == RELAY_CLIENT_MSG_PONG) - || (msg_type == RELAY_CLIENT_MSG_CLOSE)) + || (msg_type == RELAY_MSG_PING) + || (msg_type == RELAY_MSG_PONG) + || (msg_type == RELAY_MSG_CLOSE)) { /* build prefix with arrow */ prefix_arrow[0] = '\0'; @@ -324,40 +344,22 @@ relay_raw_message_add (struct t_relay_client *client, break; } - if (client) - { - snprintf (prefix, sizeof (prefix), "%s%s %s[%s%d%s] %s%s%s%s", - (flags & RELAY_RAW_FLAG_SEND) ? - weechat_color ("chat_prefix_quit") : - weechat_color ("chat_prefix_join"), - prefix_arrow, - weechat_color ("chat_delimiters"), - weechat_color ("chat"), - client->id, - weechat_color ("chat_delimiters"), - weechat_color ("chat_server"), - relay_protocol_string[client->protocol], - (client->protocol_args) ? "." : "", - (client->protocol_args) ? client->protocol_args : ""); - } - else - { - snprintf (prefix, sizeof (prefix), "%s%s", - (flags & RELAY_RAW_FLAG_SEND) ? - weechat_color ("chat_prefix_quit") : - weechat_color ("chat_prefix_join"), - prefix_arrow); - } + snprintf (prefix, sizeof (prefix), "%s%s%s%s", + (flags & RELAY_RAW_FLAG_SEND) ? + weechat_color ("chat_prefix_quit") : + weechat_color ("chat_prefix_join"), + prefix_arrow, + (peer_id && peer_id[0]) ? " " : "", + (peer_id && peer_id[0]) ? peer_id : ""); } - length = strlen (relay_client_msg_type_string[msg_type]) + - strlen ((buf2) ? buf2 : ((buf) ? buf : data)) + 1; - buf3 = malloc (length); - if (buf3) + length = strlen (relay_msg_type_string[msg_type]) + strlen (raw_data) + 1; + buf = malloc (length); + if (buf) { - snprintf (buf3, length, "%s%s", - relay_client_msg_type_string[msg_type], - (buf2) ? buf2 : ((buf) ? buf : data)); + snprintf (buf, length, "%s%s", + relay_msg_type_string[msg_type], + raw_data); } gettimeofday (&tv_now, NULL); @@ -365,7 +367,7 @@ relay_raw_message_add (struct t_relay_client *client, tv_now.tv_sec, tv_now.tv_usec, prefix, - (buf3) ? buf3 : ((buf2) ? buf2 : ((buf) ? buf : data))); + (buf) ? buf : raw_data); if (new_raw_message) { @@ -377,26 +379,76 @@ relay_raw_message_add (struct t_relay_client *client, if (buf) free (buf); - if (buf2) - free (buf2); - if (buf3) - free (buf3); + free (raw_data); +} + +/* + * Prints a message for a client on relay raw buffer. + */ + +void +relay_raw_print_client (struct t_relay_client *client, + enum t_relay_msg_type msg_type, + int flags, + const char *data, int data_size) +{ + char peer_id[256]; + + /* auto-open relay raw buffer if debug for irc plugin is >= 1 */ + if (!relay_raw_buffer && (weechat_relay_plugin->debug >= 1)) + relay_raw_open (0); + + if (client) + { + snprintf (peer_id, sizeof (peer_id), "%s[%s%d%s] %s%s%s%s", + weechat_color ("chat_delimiters"), + weechat_color ("chat"), + client->id, + weechat_color ("chat_delimiters"), + weechat_color ("chat_server"), + relay_protocol_string[client->protocol], + (client->protocol_args) ? "." : "", + (client->protocol_args) ? client->protocol_args : ""); + } + else + { + peer_id[0] = '\0'; + } + + relay_raw_message_add (msg_type, flags, peer_id, data, data_size); } /* - * Prints a message on relay raw buffer. + * Prints a message for a remote on relay raw buffer. */ void -relay_raw_print (struct t_relay_client *client, - enum t_relay_client_msg_type msg_type, int flags, - const char *data, int data_size) +relay_raw_print_remote (struct t_relay_remote *remote, + enum t_relay_msg_type msg_type, + int flags, + const char *data, int data_size) { - /* auto-open Relay raw buffer if debug for irc plugin is >= 1 */ + char peer_id[256]; + + /* auto-open relay raw buffer if debug for irc plugin is >= 1 */ if (!relay_raw_buffer && (weechat_relay_plugin->debug >= 1)) relay_raw_open (0); - relay_raw_message_add (client, msg_type, flags, data, data_size); + if (remote) + { + snprintf (peer_id, sizeof (peer_id), "%s<%sR%s> %s%s", + weechat_color ("chat_delimiters"), + weechat_color ("chat"), + weechat_color ("chat_delimiters"), + weechat_color ("chat_server"), + remote->name); + } + else + { + peer_id[0] = '\0'; + } + + relay_raw_message_add (msg_type, flags, peer_id, data, data_size); } /* diff --git a/src/plugins/relay/relay-raw.h b/src/plugins/relay/relay-raw.h index e830d7f92..fd17e12c0 100644 --- a/src/plugins/relay/relay-raw.h +++ b/src/plugins/relay/relay-raw.h @@ -32,6 +32,8 @@ #define RELAY_RAW_FLAG_SEND (1 << 1) #define RELAY_RAW_FLAG_BINARY (1 << 2) +struct t_relay_remote; + struct t_relay_raw_message { time_t date; /* date/time of message */ @@ -53,9 +55,12 @@ extern struct t_relay_raw_message *relay_raw_message_add_to_list (time_t date, int date_usec, const char *prefix, const char *message); -extern void relay_raw_print (struct t_relay_client *client, - enum t_relay_client_msg_type msg_type, int flags, - const char *data, int data_size); +extern void relay_raw_print_client (struct t_relay_client *client, + enum t_relay_msg_type msg_type, int flags, + const char *data, int data_size); +extern void relay_raw_print_remote (struct t_relay_remote *remote, + enum t_relay_msg_type msg_type, int flags, + const char *data, int data_size); extern void relay_raw_message_free_all (); extern int relay_raw_add_to_infolist (struct t_infolist *infolist, struct t_relay_raw_message *raw_message); diff --git a/src/plugins/relay/relay-remote.c b/src/plugins/relay/relay-remote.c index 6924b76ab..15dc42b50 100644 --- a/src/plugins/relay/relay-remote.c +++ b/src/plugins/relay/relay-remote.c @@ -35,6 +35,10 @@ #include "relay.h" #include "relay-config.h" #include "relay-remote.h" +#include "relay-websocket.h" +#ifdef HAVE_CJSON +#include "api/remote/relay-remote-network.h" +#endif char *relay_remote_option_string[RELAY_REMOTE_NUM_OPTIONS] = @@ -226,6 +230,59 @@ relay_remote_send_signal (struct t_relay_remote *remote) } /* + * Extracts address from URL. + * + * Note: result must be free after use. + */ + +char * +relay_remote_get_address (const char *url) +{ + const char *ptr_start; + char *pos; + + if (!url) + return NULL; + + if (strncmp (url, "http://", 7) == 0) + ptr_start = url + 7; + else if (strncmp (url, "https://", 8) == 0) + ptr_start = url + 8; + else + return NULL; + + pos = strchr (ptr_start, ':'); + return (pos) ? + weechat_strndup (ptr_start, pos - ptr_start) : strdup (ptr_start); +} + +/* + * Extracts port from URL. + */ + +int +relay_remote_get_port (const char *url) +{ + char *pos, *error; + long port; + + if (!url) + goto error; + + pos = strrchr (url, ':'); + if (!pos) + goto error; + + error = NULL; + port = strtol (pos + 1, &error, 10); + if (error && !error[0]) + return (int)port; + +error: + return RELAY_REMOTE_DEFAULT_PORT; +} + +/* * Allocates and initializes new remote structure. * * Returns pointer to new remote, NULL if error. @@ -256,8 +313,16 @@ relay_remote_alloc (const char *name) new_remote->port = 0; new_remote->tls = 0; new_remote->status = RELAY_STATUS_DISCONNECTED; + new_remote->password_hash_algo = -1; + new_remote->password_hash_iterations = -1; + new_remote->totp = -1; + new_remote->websocket_key = NULL; new_remote->sock = -1; + new_remote->hook_url_handshake = NULL; + new_remote->hook_connect = NULL; + new_remote->hook_fd = NULL; new_remote->gnutls_sess = NULL; + new_remote->ws_deflate = relay_websocket_deflate_alloc (); new_remote->prev_remote = NULL; new_remote->next_remote = NULL; @@ -348,6 +413,11 @@ relay_remote_new_with_options (const char *name, struct t_config_option **option new_remote->options[i] = options[i]; } relay_remote_add (new_remote, &relay_remotes, &last_relay_remote); + new_remote->address = relay_remote_get_address ( + weechat_config_string (new_remote->options[RELAY_REMOTE_OPTION_URL])); + new_remote->port = relay_remote_get_port ( + weechat_config_string (new_remote->options[RELAY_REMOTE_OPTION_URL])); + relay_remotes_count++; relay_remote_send_signal (new_remote); @@ -409,6 +479,8 @@ struct t_relay_remote * relay_remote_new_with_infolist (struct t_infolist *infolist) { struct t_relay_remote *new_remote; + Bytef *ptr_dict; + int dict_size; new_remote = malloc (sizeof (*new_remote)); if (!new_remote) @@ -419,8 +491,57 @@ relay_remote_new_with_infolist (struct t_infolist *infolist) new_remote->port = weechat_infolist_integer (infolist, "port"); new_remote->tls = weechat_infolist_integer (infolist, "tls"); new_remote->status = weechat_infolist_integer (infolist, "status"); + new_remote->password_hash_algo = weechat_infolist_integer ( + infolist, "password_hash_algo"); + new_remote->password_hash_iterations = weechat_infolist_integer ( + infolist, "password_hash_iterations"); + new_remote->totp = weechat_infolist_integer (infolist, "totp"); + new_remote->websocket_key = strdup (weechat_infolist_string (infolist, "websocket_key")); new_remote->sock = weechat_infolist_integer (infolist, "sock"); + new_remote->hook_url_handshake = NULL; + new_remote->hook_connect = NULL; + new_remote->hook_fd = NULL; new_remote->gnutls_sess = NULL; + new_remote->ws_deflate = relay_websocket_deflate_alloc (); + new_remote->ws_deflate->enabled = weechat_infolist_integer (infolist, "ws_deflate_enabled"); + new_remote->ws_deflate->server_context_takeover = weechat_infolist_integer (infolist, "ws_deflate_server_context_takeover"); + new_remote->ws_deflate->client_context_takeover = weechat_infolist_integer (infolist, "ws_deflate_client_context_takeover"); + new_remote->ws_deflate->window_bits_deflate = weechat_infolist_integer (infolist, "ws_deflate_window_bits_deflate"); + new_remote->ws_deflate->window_bits_inflate = weechat_infolist_integer (infolist, "ws_deflate_window_bits_inflate"); + new_remote->ws_deflate->strm_deflate = NULL; + new_remote->ws_deflate->strm_inflate = NULL; + if (weechat_infolist_search_var (infolist, "ws_deflate_strm_deflate_dict")) + { + ptr_dict = weechat_infolist_buffer (infolist, "ws_deflate_strm_deflate_dict", &dict_size); + if (ptr_dict) + { + new_remote->ws_deflate->strm_deflate = calloc (1, sizeof (*new_remote->ws_deflate->strm_deflate)); + if (new_remote->ws_deflate->strm_deflate) + { + if (relay_websocket_deflate_init_stream_deflate (new_remote->ws_deflate)) + { + deflateSetDictionary (new_remote->ws_deflate->strm_deflate, + ptr_dict, dict_size); + } + } + } + } + if (weechat_infolist_search_var (infolist, "ws_deflate_strm_inflate_dict")) + { + ptr_dict = weechat_infolist_buffer (infolist, "ws_deflate_strm_inflate_dict", &dict_size); + if (ptr_dict) + { + new_remote->ws_deflate->strm_inflate = calloc (1, sizeof (*new_remote->ws_deflate->strm_inflate)); + if (new_remote->ws_deflate->strm_inflate) + { + if (relay_websocket_deflate_init_stream_inflate (new_remote->ws_deflate)) + { + inflateSetDictionary (new_remote->ws_deflate->strm_inflate, + ptr_dict, dict_size); + } + } + } + } new_remote->prev_remote = NULL; new_remote->next_remote = relay_remotes; if (relay_remotes) @@ -449,12 +570,40 @@ relay_remote_set_status (struct t_relay_remote *remote, * a disconnected state for remote in infolist (used on /upgrade -save) */ + if (remote->status == status) + return; + remote->status = status; relay_remote_send_signal (remote); } /* + * Connects to a remote WeeChat relay/api. + * + * Returns: + * 1: OK + * 0: error + */ + +int +relay_remote_connect (struct t_relay_remote *remote) +{ +#ifndef HAVE_CJSON + weechat_printf (NULL, + _("%s%s: error: unable to connect to remote relay via API " + "(cJSON support is not enabled)"), + weechat_prefix ("error"), RELAY_PLUGIN_NAME); + return 0; +#endif /* HAVE_CJSON */ + + if (!remote) + return 0; + + return relay_remote_network_connect (remote); +} + +/* * Renames a remote. * * Returns: @@ -543,6 +692,7 @@ relay_remote_free (struct t_relay_remote *remote) } if (remote->address) free (remote->address); + relay_websocket_deflate_free (remote->ws_deflate); free (remote); @@ -570,9 +720,7 @@ void relay_remote_disconnect (struct t_relay_remote *remote) { if (remote->sock >= 0) - { - relay_remote_set_status (remote, RELAY_STATUS_DISCONNECTED); - } + relay_remote_network_disconnect (remote); } /* @@ -609,6 +757,8 @@ relay_remote_add_to_infolist (struct t_infolist *infolist, int force_disconnected_state) { struct t_infolist_item *ptr_item; + Bytef *dict; + uInt dict_size; if (!infolist || !remote) return 0; @@ -625,6 +775,14 @@ relay_remote_add_to_infolist (struct t_infolist *infolist, return 0; if (!weechat_infolist_new_var_integer (ptr_item, "tls", remote->tls)) return 0; + if (!weechat_infolist_new_var_integer (ptr_item, "password_hash_algo", remote->password_hash_algo)) + return 0; + if (!weechat_infolist_new_var_integer (ptr_item, "password_hash_iterations", remote->password_hash_iterations)) + return 0; + if (!weechat_infolist_new_var_integer (ptr_item, "totp", remote->totp)) + return 0; + if (!weechat_infolist_new_var_string (ptr_item, "websocket_key", remote->websocket_key)) + return 0; if (!RELAY_STATUS_HAS_ENDED(remote->status) && force_disconnected_state) { if (!weechat_infolist_new_var_integer (ptr_item, "status", RELAY_STATUS_DISCONNECTED)) @@ -639,6 +797,33 @@ relay_remote_add_to_infolist (struct t_infolist *infolist, if (!weechat_infolist_new_var_integer (ptr_item, "sock", remote->sock)) return 0; } + if (remote->ws_deflate->strm_deflate || remote->ws_deflate->strm_inflate) + { + /* save the deflate/inflate dictionary, as it's required after /upgrade */ + dict = malloc (32768); + if (dict) + { + if (remote->ws_deflate->strm_deflate) + { + if (deflateGetDictionary (remote->ws_deflate->strm_deflate, dict, &dict_size) == Z_OK) + { + weechat_infolist_new_var_buffer (ptr_item, + "ws_deflate_strm_deflate_dict", + dict, dict_size); + } + } + if (remote->ws_deflate->strm_inflate) + { + if (inflateGetDictionary (remote->ws_deflate->strm_inflate, dict, &dict_size) == Z_OK) + { + weechat_infolist_new_var_buffer (ptr_item, + "ws_deflate_strm_inflate_dict", + dict, dict_size); + } + } + free (dict); + } + } return 1; } @@ -657,24 +842,32 @@ relay_remote_print_log () { weechat_log_printf (""); weechat_log_printf ("[relay remote (addr:0x%lx)]", ptr_remote); - weechat_log_printf (" name. . . . . . . . . : '%s'", ptr_remote->name); - weechat_log_printf (" url . . . . . . . . . : '%s'", + weechat_log_printf (" name. . . . . . . . . . : '%s'", ptr_remote->name); + weechat_log_printf (" url . . . . . . . . . . : '%s'", weechat_config_string (ptr_remote->options[RELAY_REMOTE_OPTION_URL])); - weechat_log_printf (" proxy . . . . . . . . : '%s'", + weechat_log_printf (" proxy . . . . . . . . . : '%s'", weechat_config_string (ptr_remote->options[RELAY_REMOTE_OPTION_PROXY])); - weechat_log_printf (" password. . . . . . . : '%s'", + weechat_log_printf (" password. . . . . . . . : '%s'", weechat_config_string (ptr_remote->options[RELAY_REMOTE_OPTION_PASSWORD])); - weechat_log_printf (" totp_secret . . . . . : '%s'", + weechat_log_printf (" totp_secret . . . . . . : '%s'", weechat_config_string (ptr_remote->options[RELAY_REMOTE_OPTION_TOTP_SECRET])); - weechat_log_printf (" address . . . . . . . : '%s'", ptr_remote->address); - weechat_log_printf (" port. . . . . . . . . : %d", ptr_remote->port); - weechat_log_printf (" tls . . . . . . . . . : %d", ptr_remote->tls); - weechat_log_printf (" status. . . . . . . . : %d (%s)", + weechat_log_printf (" address . . . . . . . . : '%s'", ptr_remote->address); + weechat_log_printf (" port. . . . . . . . . . : %d", ptr_remote->port); + weechat_log_printf (" tls . . . . . . . . . . : %d", ptr_remote->tls); + weechat_log_printf (" status. . . . . . . . . : %d (%s)", ptr_remote->status, relay_status_string[ptr_remote->status]); - weechat_log_printf (" sock. . . . . . . . . : %d", ptr_remote->sock); - weechat_log_printf (" gnutls_sess . . . . . : 0x%lx", ptr_remote->gnutls_sess); - weechat_log_printf (" prev_remote . . . . . : 0x%lx", ptr_remote->prev_remote); - weechat_log_printf (" next_remote . . . . . : 0x%lx", ptr_remote->next_remote); + weechat_log_printf (" password_hash_algo. . . : %d", ptr_remote->password_hash_algo); + weechat_log_printf (" password_hash_iterations: %d", ptr_remote->password_hash_iterations); + weechat_log_printf (" totp. . . . . . . . . . : %d", ptr_remote->totp); + weechat_log_printf (" websocket_key . . . . . : 0x%ls", ptr_remote->websocket_key); + weechat_log_printf (" sock. . . . . . . . . . : %d", ptr_remote->sock); + weechat_log_printf (" hook_url_handshake. . . : 0x%lx", ptr_remote->hook_url_handshake); + weechat_log_printf (" hook_connect. . . . . . : 0x%lx", ptr_remote->hook_connect); + weechat_log_printf (" hook_fd . . . . . . . . : 0x%lx", ptr_remote->hook_fd); + weechat_log_printf (" gnutls_sess . . . . . . : 0x%lx", ptr_remote->gnutls_sess); + relay_websocket_deflate_print_log (ptr_remote->ws_deflate, ""); + weechat_log_printf (" prev_remote . . . . . . : 0x%lx", ptr_remote->prev_remote); + weechat_log_printf (" next_remote . . . . . . : 0x%lx", ptr_remote->next_remote); } } diff --git a/src/plugins/relay/relay-remote.h b/src/plugins/relay/relay-remote.h index 2e6b938dc..b2f0b2cdb 100644 --- a/src/plugins/relay/relay-remote.h +++ b/src/plugins/relay/relay-remote.h @@ -22,6 +22,8 @@ #include <gnutls/gnutls.h> +#define RELAY_REMOTE_DEFAULT_PORT 9000 + enum t_relay_remote_option { RELAY_REMOTE_OPTION_URL = 0, /* remote URL */ @@ -42,8 +44,17 @@ struct t_relay_remote int port; /* port number */ int tls; /* 1 if TLS is enabled */ enum t_relay_status status; /* status (connecting, active,..) */ + int password_hash_algo; /* hash algo (from handshake) */ + int password_hash_iterations; /* hash iterations (from handshake) */ + int totp; /* TOTP enabled (from handshake) */ + char *websocket_key; /* random key sent to the remote */ + /* in the websocket handshake */ int sock; /* connected socket */ + struct t_hook *hook_url_handshake; /* URL hook for the handshake */ + struct t_hook *hook_connect; /* connection hook */ + struct t_hook *hook_fd; /* hook for socket */ gnutls_session_t gnutls_sess; /* gnutls session (only if TLS used) */ + struct t_relay_websocket_deflate *ws_deflate; /* websocket deflate data */ struct t_relay_remote *prev_remote;/* link to previous remote */ struct t_relay_remote *next_remote;/* link to next remote */ }; @@ -76,6 +87,7 @@ extern struct t_relay_remote *relay_remote_new (const char *name, extern struct t_relay_remote *relay_remote_new_with_infolist (struct t_infolist *infolist); extern void relay_remote_set_status (struct t_relay_remote *remote, enum t_relay_status status); +extern int relay_remote_connect (struct t_relay_remote *remote); extern int relay_remote_rename (struct t_relay_remote *remote, const char *name); extern void relay_remote_free (struct t_relay_remote *remote); extern void relay_remote_free_all (); diff --git a/src/plugins/relay/relay-websocket.c b/src/plugins/relay/relay-websocket.c index 3ba4d408e..5259f471d 100644 --- a/src/plugins/relay/relay-websocket.c +++ b/src/plugins/relay/relay-websocket.c @@ -23,6 +23,7 @@ #include <unistd.h> #include <stdio.h> #include <string.h> +#include <gcrypt.h> #include <zlib.h> #include "../weechat-plugin.h" @@ -34,13 +35,6 @@ /* - * globally unique identifier that is concatenated to HTTP header - * "Sec-WebSocket-Key" - */ -#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" - - -/* * Allocates a t_relay_websocket_deflate structure. */ @@ -547,17 +541,23 @@ error: * frame is first decompressed if "permessage-deflate" websocket extension * is used). * + * If argument "expect_masked_frame" is 1 and a frame is not masked, + * the function returns an error. + * * Returns: * 1: frame(s) decoded successfully * 0: error decoding frame (connection must be closed if it happens) */ int -relay_websocket_decode_frame (struct t_relay_client *client, - const unsigned char *buffer, +relay_websocket_decode_frame (const unsigned char *buffer, unsigned long long buffer_length, + int expect_masked_frame, + struct t_relay_websocket_deflate *ws_deflate, struct t_relay_websocket_frame **frames, - int *num_frames) + int *num_frames, + char **partial_ws_frame, + int *partial_ws_frame_size) { unsigned long long i, index_buffer, index_buffer_start_frame; unsigned long long length_frame_size, length_frame; @@ -565,7 +565,7 @@ relay_websocket_decode_frame (struct t_relay_client *client, size_t size_decompressed; char *payload_decompressed; struct t_relay_websocket_frame *frames2, *ptr_frame; - int size; + int size, masked_frame, mask[4]; if (!buffer || !frames || !num_frames) return 0; @@ -586,11 +586,15 @@ relay_websocket_decode_frame (struct t_relay_client *client, opcode = buffer[index_buffer] & 15; + /* check if frame is masked */ + masked_frame = (buffer[index_buffer + 1] & 128) ? 1 : 0; + /* - * check if frame is masked: client MUST send a masked frame; if frame is - * not masked, we MUST reject it and close the connection (see RFC 6455) + * error if the frame is not masked and we expect it to be masked, + * in this case we must reject it and close the connection + * (see RFC 6455) */ - if (!(buffer[index_buffer + 1] & 128)) + if (!masked_frame && expect_masked_frame) return 0; /* decode frame length */ @@ -611,15 +615,17 @@ relay_websocket_decode_frame (struct t_relay_client *client, index_buffer += length_frame_size; } - /* read masks (4 bytes) */ - if (index_buffer + 4 > buffer_length) - goto missing_data; - int masks[4]; - for (i = 0; i < 4; i++) + if (masked_frame) { - masks[i] = (int)((unsigned char)buffer[index_buffer + i]); + /* read mask (4 bytes) */ + if (index_buffer + 4 > buffer_length) + goto missing_data; + for (i = 0; i < 4; i++) + { + mask[i] = (int)((unsigned char)buffer[index_buffer + i]); + } + index_buffer += 4; } - index_buffer += 4; /* check if we have enough data */ if ((length_frame > buffer_length) @@ -646,13 +652,13 @@ relay_websocket_decode_frame (struct t_relay_client *client, switch (opcode) { case WEBSOCKET_FRAME_OPCODE_PING: - ptr_frame->opcode = RELAY_CLIENT_MSG_PING; + ptr_frame->opcode = RELAY_MSG_PING; break; case WEBSOCKET_FRAME_OPCODE_CLOSE: - ptr_frame->opcode = RELAY_CLIENT_MSG_CLOSE; + ptr_frame->opcode = RELAY_MSG_CLOSE; break; default: - ptr_frame->opcode = RELAY_CLIENT_MSG_STANDARD; + ptr_frame->opcode = RELAY_MSG_STANDARD; break; } @@ -663,9 +669,16 @@ relay_websocket_decode_frame (struct t_relay_client *client, ptr_frame->payload_size = length_frame; /* fill payload */ - for (i = 0; i < length_frame; i++) + if (masked_frame) + { + for (i = 0; i < length_frame; i++) + { + ptr_frame->payload[i] = (int)((unsigned char)buffer[index_buffer + i]) ^ mask[i % 4]; + } + } + else { - ptr_frame->payload[i] = (int)((unsigned char)buffer[index_buffer + i]) ^ masks[i % 4]; + memcpy (ptr_frame->payload, buffer + index_buffer, length_frame); } ptr_frame->payload[length_frame] = '\0'; @@ -673,58 +686,58 @@ relay_websocket_decode_frame (struct t_relay_client *client, * decompress data if frame is not empty and if "permessage-deflate" * is enabled */ - if ((length_frame > 0) && client->ws_deflate->enabled) + if ((length_frame > 0) && ws_deflate && ws_deflate->enabled) { - if (!client->ws_deflate->strm_inflate) + if (!ws_deflate->strm_inflate) { - client->ws_deflate->strm_inflate = calloc ( - 1, sizeof (*client->ws_deflate->strm_inflate)); - if (!client->ws_deflate->strm_inflate) + ws_deflate->strm_inflate = calloc ( + 1, sizeof (*ws_deflate->strm_inflate)); + if (!ws_deflate->strm_inflate) return 0; - if (!relay_websocket_deflate_init_stream_inflate (client->ws_deflate)) + if (!relay_websocket_deflate_init_stream_inflate (ws_deflate)) return 0; } payload_decompressed = relay_websocket_inflate ( ptr_frame->payload, ptr_frame->payload_size, - client->ws_deflate->strm_inflate, + ws_deflate->strm_inflate, &size_decompressed); if (!payload_decompressed) return 0; free (ptr_frame->payload); ptr_frame->payload = payload_decompressed; ptr_frame->payload_size = size_decompressed; - if (!client->ws_deflate->client_context_takeover) - relay_websocket_deflate_free_stream_inflate (client->ws_deflate); + if (!ws_deflate->client_context_takeover) + relay_websocket_deflate_free_stream_inflate (ws_deflate); } index_buffer += length_frame; } - if (client->partial_ws_frame) + if (*partial_ws_frame) { - free (client->partial_ws_frame); - client->partial_ws_frame = NULL; - client->partial_ws_frame_size = 0; + free (*partial_ws_frame); + *partial_ws_frame = NULL; + *partial_ws_frame_size = 0; } return 1; missing_data: - if (client->partial_ws_frame) + if (*partial_ws_frame) { - free (client->partial_ws_frame); - client->partial_ws_frame = NULL; - client->partial_ws_frame_size = 0; + free (*partial_ws_frame); + *partial_ws_frame = NULL; + *partial_ws_frame_size = 0; } size = buffer_length - index_buffer_start_frame; if (size >= 0) { - client->partial_ws_frame = malloc (size); - if (!client->partial_ws_frame) + *partial_ws_frame = malloc (size); + if (!*partial_ws_frame) return 0; - memcpy (client->partial_ws_frame, buffer + index_buffer_start_frame, size); - client->partial_ws_frame_size = size; + memcpy (*partial_ws_frame, buffer + index_buffer_start_frame, size); + *partial_ws_frame_size = size; } return 1; } @@ -779,12 +792,16 @@ relay_websocket_deflate (const void *data, size_t size, z_stream *strm, * Returns websocket frame, NULL if error. * Argument "length_frame" is set with the length of frame built. * + * Argument "mask_frame" must be 1 when sending to server (remote) and 0 when + * sending to a client. + * * Note: result must be freed after use. */ char * -relay_websocket_encode_frame (struct t_relay_client *client, +relay_websocket_encode_frame (struct t_relay_websocket_deflate *ws_deflate, int opcode, + int mask_frame, const char *payload, unsigned long long payload_size, unsigned long long *length_frame) @@ -792,8 +809,8 @@ relay_websocket_encode_frame (struct t_relay_client *client, const char *ptr_data; char *payload_compressed; size_t size_compressed; - unsigned char *frame; - unsigned long long index, data_size; + unsigned char *frame, *ptr_mask; + unsigned long long i, index, data_size; *length_frame = 0; @@ -810,21 +827,22 @@ relay_websocket_encode_frame (struct t_relay_client *client, if (((opcode == WEBSOCKET_FRAME_OPCODE_TEXT) || (opcode == WEBSOCKET_FRAME_OPCODE_BINARY)) && (payload_size > 0) - && client->ws_deflate->enabled) + && ws_deflate + && ws_deflate->enabled) { - if (!client->ws_deflate->strm_deflate) + if (!ws_deflate->strm_deflate) { - client->ws_deflate->strm_deflate = calloc ( - 1, sizeof (*client->ws_deflate->strm_deflate)); - if (!client->ws_deflate->strm_deflate) + ws_deflate->strm_deflate = calloc ( + 1, sizeof (*ws_deflate->strm_deflate)); + if (!ws_deflate->strm_deflate) return NULL; - if (!relay_websocket_deflate_init_stream_deflate (client->ws_deflate)) + if (!relay_websocket_deflate_init_stream_deflate (ws_deflate)) return NULL; } payload_compressed = relay_websocket_deflate ( payload, payload_size, - client->ws_deflate->strm_deflate, + ws_deflate->strm_deflate, &size_compressed); if (!payload_compressed) return NULL; @@ -838,13 +856,13 @@ relay_websocket_encode_frame (struct t_relay_client *client, { data_size -= 4; } - if (!client->ws_deflate->server_context_takeover) - relay_websocket_deflate_free_stream_deflate (client->ws_deflate); + if (!ws_deflate->server_context_takeover) + relay_websocket_deflate_free_stream_deflate (ws_deflate); /* set bit RSV1: indicate permessage-deflate compressed data */ opcode |= 0x40; } - frame = malloc (data_size + 10); + frame = malloc (data_size + 14); if (!frame) { if (payload_compressed) @@ -884,9 +902,26 @@ relay_websocket_encode_frame (struct t_relay_client *client, index = 10; } + if (mask_frame) + { + frame[1] |= 128; + ptr_mask = frame + index; + gcry_create_nonce (ptr_mask, 4); + index += 4; + } + /* copy buffer after data_size */ memcpy (frame + index, ptr_data, data_size); + /* mask frame */ + if (mask_frame) + { + for (i = 0; i < data_size; i++) + { + frame[index + i] = frame[index + i] ^ ptr_mask[i % 4]; + } + } + *length_frame = index + data_size; if (payload_compressed) diff --git a/src/plugins/relay/relay-websocket.h b/src/plugins/relay/relay-websocket.h index 6e48569fd..e9d09f3e6 100644 --- a/src/plugins/relay/relay-websocket.h +++ b/src/plugins/relay/relay-websocket.h @@ -24,6 +24,12 @@ #include <zlib.h> +/* + * globally unique identifier that is concatenated to HTTP header + * "Sec-WebSocket-Key" + */ +#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + #define WEBSOCKET_FRAME_OPCODE_CONTINUATION 0x00 #define WEBSOCKET_FRAME_OPCODE_TEXT 0x01 #define WEBSOCKET_FRAME_OPCODE_BINARY 0x02 @@ -64,13 +70,17 @@ extern int relay_websocket_client_handshake_valid (struct t_relay_http_request * extern void relay_websocket_parse_extensions (const char *extensions, struct t_relay_websocket_deflate *ws_deflate); extern char *relay_websocket_build_handshake (struct t_relay_http_request *request); -extern int relay_websocket_decode_frame (struct t_relay_client *client, - const unsigned char *buffer, +extern int relay_websocket_decode_frame (const unsigned char *buffer, unsigned long long length, + int expect_masked_frame, + struct t_relay_websocket_deflate *ws_deflate, struct t_relay_websocket_frame **frames, - int *num_frames); -extern char *relay_websocket_encode_frame (struct t_relay_client *client, + int *num_frames, + char **partial_ws_frame, + int *partial_ws_frame_size); +extern char *relay_websocket_encode_frame (struct t_relay_websocket_deflate *ws_deflate, int opcode, + int mask_frame, const char *payload, unsigned long long payload_size, unsigned long long *length_frame); diff --git a/src/plugins/relay/relay.c b/src/plugins/relay/relay.c index bf3188937..f643ec860 100644 --- a/src/plugins/relay/relay.c +++ b/src/plugins/relay/relay.c @@ -58,6 +58,8 @@ char *relay_status_name[] = /* name of status (for signal/info) */ { "connecting", "waiting_auth", "connected", "auth_failed", "disconnected" }; +char *relay_msg_type_string[] = /* prefix in raw buffer for msg */ +{ "", "[PING]\n", "[PONG]\n", "[CLOSE]\n" }; struct t_hdata *relay_hdata_buffer = NULL; struct t_hdata *relay_hdata_lines = NULL; diff --git a/src/plugins/relay/relay.h b/src/plugins/relay/relay.h index f933021b7..c78efea74 100644 --- a/src/plugins/relay/relay.h +++ b/src/plugins/relay/relay.h @@ -62,6 +62,18 @@ enum t_relay_status RELAY_NUM_STATUS, }; +/* type of message exchanged with the peer (client/remote) */ + +enum t_relay_msg_type +{ + RELAY_MSG_STANDARD, + RELAY_MSG_PING, + RELAY_MSG_PONG, + RELAY_MSG_CLOSE, + /* number of message types */ + RELAY_NUM_MSG_TYPES, +}; + #define RELAY_STATUS_HAS_ENDED(status) \ ((status == RELAY_STATUS_AUTH_FAILED) || \ (status == RELAY_STATUS_DISCONNECTED)) @@ -74,6 +86,7 @@ enum t_relay_status extern char *relay_protocol_string[]; extern char *relay_status_string[]; extern char *relay_status_name[]; +extern char *relay_msg_type_string[]; extern int relay_protocol_search (const char *name); extern int relay_status_search (const char *name); diff --git a/src/plugins/relay/weechat/relay-weechat-msg.c b/src/plugins/relay/weechat/relay-weechat-msg.c index a1e373bdb..2985eb6e1 100644 --- a/src/plugins/relay/weechat/relay-weechat-msg.c +++ b/src/plugins/relay/weechat/relay-weechat-msg.c @@ -1116,7 +1116,7 @@ relay_weechat_msg_compress_zlib (struct t_relay_client *client, msg->id); /* send compressed data */ - relay_client_send (client, RELAY_CLIENT_MSG_STANDARD, + relay_client_send (client, RELAY_MSG_STANDARD, (const char *)dest, dest_size + 5, raw_message); @@ -1189,7 +1189,7 @@ relay_weechat_msg_compress_zstd (struct t_relay_client *client, msg->id); /* send compressed data */ - relay_client_send (client, RELAY_CLIENT_MSG_STANDARD, + relay_client_send (client, RELAY_MSG_STANDARD, (const char *)dest, comp_size + 5, raw_message); @@ -1250,7 +1250,7 @@ relay_weechat_msg_send (struct t_relay_client *client, /* send uncompressed data */ snprintf (raw_message, sizeof (raw_message), "obj: %d bytes, id: %s", msg->data_size, msg->id); - relay_client_send (client, RELAY_CLIENT_MSG_STANDARD, + relay_client_send (client, RELAY_MSG_STANDARD, msg->data, msg->data_size, raw_message); } |