/* * relay-weechat-protocol.c - WeeChat protocol for relay to client * * Copyright (C) 2003-2020 Sébastien Helleu * * 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 . */ #include #include #include #include "../../weechat-plugin.h" #include "../relay.h" #include "relay-weechat.h" #include "relay-weechat-protocol.h" #include "relay-weechat-msg.h" #include "relay-weechat-nicklist.h" #include "../relay-buffer.h" #include "../relay-client.h" #include "../relay-auth.h" #include "../relay-config.h" #include "../relay-raw.h" /* * Checks if the buffer pointer is a relay buffer (relay raw/list). * * Returns: * 1: buffer is a relay buffer (raw/list) * 0: buffer is NOT a relay buffer */ int relay_weechat_is_relay_buffer (struct t_gui_buffer *buffer) { return ((relay_raw_buffer && (buffer == relay_raw_buffer)) || (relay_buffer && (buffer == relay_buffer))) ? 1 : 0; } /* * Gets buffer pointer with argument from a command. * * The argument "arg" can be a pointer ("0x12345678") or a full name * ("irc.freenode.#weechat"). * * Returns pointer to buffer found, NULL if not found. */ struct t_gui_buffer * relay_weechat_protocol_get_buffer (const char *arg) { struct t_gui_buffer *ptr_buffer; unsigned long value; int rc; struct t_hdata *ptr_hdata; ptr_buffer = NULL; if (strncmp (arg, "0x", 2) == 0) { rc = sscanf (arg, "%lx", &value); if ((rc != EOF) && (rc != 0)) ptr_buffer = (struct t_gui_buffer *)value; if (ptr_buffer) { ptr_hdata = weechat_hdata_get ("buffer"); if (!weechat_hdata_check_pointer (ptr_hdata, weechat_hdata_get_list (ptr_hdata, "gui_buffers"), ptr_buffer)) { /* invalid pointer! */ ptr_buffer = NULL; } } } else ptr_buffer = weechat_buffer_search ("==", arg); return ptr_buffer; } /* * Gets integer value of a synchronization flag. */ int relay_weechat_protocol_sync_flag (const char *flag) { if (strcmp (flag, "buffer") == 0) return RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER; if (strcmp (flag, "nicklist") == 0) return RELAY_WEECHAT_PROTOCOL_SYNC_NICKLIST; if (strcmp (flag, "buffers") == 0) return RELAY_WEECHAT_PROTOCOL_SYNC_BUFFERS; if (strcmp (flag, "upgrade") == 0) return RELAY_WEECHAT_PROTOCOL_SYNC_UPGRADE; /* unknown flag */ return 0; } /* * Checks if buffer is synchronized with at least one of the flags given. * * First searches buffer with full_name in hashtable "buffers_sync" (if buffer * is not NULL). * If buffer is NULL or not found, searches "*" (which means "all buffers"). * * The "flags" argument can be a combination (logical OR) of: * RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER * RELAY_WEECHAT_PROTOCOL_SYNC_NICKLIST * RELAY_WEECHAT_PROTOCOL_SYNC_BUFFERS * RELAY_WEECHAT_PROTOCOL_SYNC_UPGRADE * * Returns: * 1: buffer is synchronized with at least one flag given * 0: buffer is NOT synchronized with any of the flags given */ int relay_weechat_protocol_is_sync (struct t_relay_client *ptr_client, struct t_gui_buffer *buffer, int flags) { int *ptr_flags; /* search buffer using its full name */ if (buffer) { ptr_flags = weechat_hashtable_get (RELAY_WEECHAT_DATA(ptr_client, buffers_sync), weechat_buffer_get_string (buffer, "full_name")); if (ptr_flags) return ((*ptr_flags) & flags) ? 1 : 0; } /* search special name "*" as fallback */ ptr_flags = weechat_hashtable_get (RELAY_WEECHAT_DATA(ptr_client, buffers_sync), "*"); if (ptr_flags) return ((*ptr_flags) & flags) ? 1 : 0; /* * buffer not found at all in hashtable (neither name, neither "*") * => it is NOT synchronized */ return 0; } /* * Replies to a client handshake command. */ void relay_weechat_protocol_handshake_reply (struct t_relay_client *client, const char *command) { struct t_relay_weechat_msg *msg; struct t_hashtable *hashtable; char *totp_secret, string[64]; totp_secret = weechat_string_eval_expression ( weechat_config_string (relay_config_network_totp_secret), NULL, NULL, NULL); hashtable = weechat_hashtable_new (32, WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_STRING, NULL, NULL); if (hashtable) { weechat_hashtable_set ( hashtable, "auth_password", (client->auth_password >= 0) ? relay_auth_password_name[client->auth_password] : ""); snprintf (string, sizeof (string), "%d", client->hash_iterations); weechat_hashtable_set ( hashtable, "hash_iterations", string); weechat_hashtable_set ( hashtable, "nonce", client->nonce); weechat_hashtable_set ( hashtable, "totp", (totp_secret && totp_secret[0]) ? "on" : "off"); msg = relay_weechat_msg_new (command); if (msg) { relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_HASHTABLE); relay_weechat_msg_add_hashtable (msg, hashtable); /* send message */ relay_weechat_msg_send (client, msg); relay_weechat_msg_free (msg); } weechat_hashtable_free (hashtable); } if (totp_secret) free (totp_secret); } /* * Callback for command "handshake" (from client). */ RELAY_WEECHAT_PROTOCOL_CALLBACK(handshake) { char **options, **auths, *pos; int i, j, index_auth, auth_found, auth_allowed, compression; int password_received, plain_text_password; RELAY_WEECHAT_PROTOCOL_MIN_ARGS(0); if (client->status != RELAY_STATUS_WAITING_AUTH) return WEECHAT_RC_OK; auth_found = -1; password_received = 0; options = (argc > 0) ? weechat_string_split_command (argv_eol[0], ',') : NULL; if (options) { for (i = 0; options[i]; i++) { pos = strchr (options[i], '='); if (pos) { pos[0] = '\0'; pos++; if (strcmp (options[i], "password") == 0) { password_received = 1; auths = weechat_string_split ( pos, ":", NULL, WEECHAT_STRING_SPLIT_STRIP_LEFT | WEECHAT_STRING_SPLIT_STRIP_RIGHT | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS, 0, NULL); if (auths) { for (j = 0; auths[j]; j++) { index_auth = relay_auth_password_search ( auths[j]); if ((index_auth >= 0) && (index_auth > auth_found)) { auth_allowed = weechat_string_match_list ( relay_auth_password_name[index_auth], (const char **)relay_config_network_auth_password_list, 1); if (auth_allowed) auth_found = index_auth; } } weechat_string_free_split (auths); } } else if (strcmp (options[i], "compression") == 0) { compression = relay_weechat_compression_search (pos); if (compression >= 0) RELAY_WEECHAT_DATA(client, compression) = compression; } } } weechat_string_free_split_command (options); } if (!password_received) { plain_text_password = weechat_string_match_list ( relay_auth_password_name[0], (const char **)relay_config_network_auth_password_list, 1); if (plain_text_password) auth_found = 0; } client->auth_password = auth_found; relay_weechat_protocol_handshake_reply (client, command); return WEECHAT_RC_OK; } /* * Callback for command "init" (from client). * * Format is: init arg1=value1,arg2=value2 * * Allowed arguments: * password plain text password (recommended with SSL only) * password_hash hashed password, value is: algorithm:[parameters:]hash * supported algorithms: sha256, sha512 and pbkdf2 * for pbkdf2, parameters are: algorithm, salt, iterations * hash is given in hexadecimal * totp time-based one time password used as secondary * authentication factor * compression zlib (default) or off * * Message looks like: * init password=mypass * init password=mypass,compression=zlib * init password=mypass,compression=off * init password_hash=sha256:71c480df93d6ae2f1efad1447c66c9…,totp=123456 * init password_hash=pbkdf2:sha256:414232…:100000:01757d53157c…,totp=123456 */ RELAY_WEECHAT_PROTOCOL_CALLBACK(init) { char **options, *pos, *relay_password, *totp_secret, *info_totp_args, *info_totp; int i, compression, length, password_received, totp_received; RELAY_WEECHAT_PROTOCOL_MIN_ARGS(0); relay_password = weechat_string_eval_expression ( weechat_config_string (relay_config_network_password), NULL, NULL, NULL); totp_secret = weechat_string_eval_expression ( weechat_config_string (relay_config_network_totp_secret), NULL, NULL, NULL); password_received = 0; totp_received = 0; options = (argc > 0) ? weechat_string_split_command (argv_eol[0], ',') : NULL; if (options) { for (i = 0; options[i]; i++) { pos = strchr (options[i], '='); if (pos) { pos[0] = '\0'; pos++; if (strcmp (options[i], "password") == 0) { password_received = 1; if (relay_auth_password (client, pos, relay_password)) RELAY_WEECHAT_DATA(client, password_ok) = 1; } else if (strcmp (options[i], "password_hash") == 0) { password_received = 1; if (relay_auth_password_hash (client, pos, relay_password)) RELAY_WEECHAT_DATA(client, password_ok) = 1; } else if (strcmp (options[i], "totp") == 0) { totp_received = 1; if (totp_secret) { length = strlen (totp_secret) + strlen (pos) + 16 + 1; info_totp_args = malloc (length); if (info_totp_args) { /* validate the OTP received from the client */ snprintf (info_totp_args, length, "%s,%s,0,%d", totp_secret, /* the shared secret */ pos, /* the OTP from client */ weechat_config_integer (relay_config_network_totp_window)); info_totp = weechat_info_get ("totp_validate", info_totp_args); if (info_totp && (strcmp (info_totp, "1") == 0)) RELAY_WEECHAT_DATA(client, totp_ok) = 1; if (info_totp) free (info_totp); free (info_totp_args); } } } else if (strcmp (options[i], "compression") == 0) { compression = relay_weechat_compression_search (pos); if (compression >= 0) RELAY_WEECHAT_DATA(client, compression) = compression; } } } weechat_string_free_split_command (options); } /* if no password received and password is empty, it's OK */ if (!password_received && (!relay_password || !relay_password[0])) RELAY_WEECHAT_DATA(client, password_ok) = 1; /* if no TOTP received and totp_secret is empty, it's OK */ if (!totp_received && (!totp_secret || !totp_secret[0])) RELAY_WEECHAT_DATA(client, totp_ok) = 1; if (RELAY_WEECHAT_DATA(client, password_ok) && RELAY_WEECHAT_DATA(client, totp_ok)) { weechat_hook_signal_send ("relay_client_auth_ok", WEECHAT_HOOK_SIGNAL_POINTER, client); relay_client_set_status (client, RELAY_STATUS_CONNECTED); } else { relay_client_set_status (client, RELAY_STATUS_AUTH_FAILED); } if (relay_password) free (relay_password); if (totp_secret) free (totp_secret); return WEECHAT_RC_OK; } /* * Callback for command "hdata" (from client). * * Message looks like: * hdata buffer:gui_buffers(*) number,name,type,nicklist,title * hdata buffer:gui_buffers(*)/own_lines/first_line(*)/data date,displayed,prefix,message */ RELAY_WEECHAT_PROTOCOL_CALLBACK(hdata) { struct t_relay_weechat_msg *msg; RELAY_WEECHAT_PROTOCOL_MIN_ARGS(1); msg = relay_weechat_msg_new (id); if (msg) { if (!relay_weechat_msg_add_hdata (msg, argv[0], (argc > 1) ? argv_eol[1] : NULL)) { relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_HDATA); relay_weechat_msg_add_string (msg, NULL); /* h-path */ relay_weechat_msg_add_string (msg, NULL); /* keys */ relay_weechat_msg_add_int (msg, 0); /* count */ } relay_weechat_msg_send (client, msg); relay_weechat_msg_free (msg); } return WEECHAT_RC_OK; } /* * Callback for command "info" (from client). * * Message looks like: * info version */ RELAY_WEECHAT_PROTOCOL_CALLBACK(info) { struct t_relay_weechat_msg *msg; char *info; RELAY_WEECHAT_PROTOCOL_MIN_ARGS(1); msg = relay_weechat_msg_new (id); if (msg) { info = weechat_info_get (argv[0], (argc > 1) ? argv_eol[1] : NULL); relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_INFO); relay_weechat_msg_add_string (msg, argv[0]); relay_weechat_msg_add_string (msg, info); relay_weechat_msg_send (client, msg); relay_weechat_msg_free (msg); if (info) free (info); } return WEECHAT_RC_OK; } /* * Callback for command "infolist" (from client). * * Message looks like: * infolist buffer */ RELAY_WEECHAT_PROTOCOL_CALLBACK(infolist) { struct t_relay_weechat_msg *msg; unsigned long value; char *args; int rc; RELAY_WEECHAT_PROTOCOL_MIN_ARGS(1); msg = relay_weechat_msg_new (id); if (msg) { value = 0; args = NULL; if (argc > 1) { rc = sscanf (argv[1], "%lx", &value); if ((rc == EOF) || (rc == 0)) value = 0; if (argc > 2) args = argv_eol[2]; } relay_weechat_msg_add_infolist (msg, argv[0], (void *)value, args); relay_weechat_msg_send (client, msg); relay_weechat_msg_free (msg); } return WEECHAT_RC_OK; } /* * Callback for command "nicklist" (from client). * * Message looks like: * nicklist irc.freenode.#weechat * nicklist 0x12345678 */ RELAY_WEECHAT_PROTOCOL_CALLBACK(nicklist) { struct t_relay_weechat_msg *msg; struct t_gui_buffer *ptr_buffer; RELAY_WEECHAT_PROTOCOL_MIN_ARGS(0); ptr_buffer = NULL; if (argc > 0) { ptr_buffer = relay_weechat_protocol_get_buffer (argv[0]); if (!ptr_buffer) { if (weechat_relay_plugin->debug >= 1) { weechat_printf (NULL, _("%s: invalid buffer pointer in message: " "\"%s %s\""), RELAY_PLUGIN_NAME, command, argv_eol[0]); } return WEECHAT_RC_OK; } } msg = relay_weechat_msg_new (id); if (msg) { relay_weechat_msg_add_nicklist (msg, ptr_buffer, NULL); relay_weechat_msg_send (client, msg); relay_weechat_msg_free (msg); } return WEECHAT_RC_OK; } /* * Callback for command "input" (from client). * * Message looks like: * input core.weechat /help filter * input irc.freenode.#weechat hello guys! * input 0x12345678 hello guys! */ RELAY_WEECHAT_PROTOCOL_CALLBACK(input) { struct t_gui_buffer *ptr_buffer; struct t_hashtable *options; const char *ptr_weechat_commands; char *pos; RELAY_WEECHAT_PROTOCOL_MIN_ARGS(1); ptr_buffer = relay_weechat_protocol_get_buffer (argv[0]); if (!ptr_buffer) { if (weechat_relay_plugin->debug >= 1) { weechat_printf (NULL, _("%s: invalid buffer pointer in message: " "\"%s %s\""), RELAY_PLUGIN_NAME, command, argv[0]); } return WEECHAT_RC_OK; } pos = strchr (argv_eol[0], ' '); if (!pos) return WEECHAT_RC_OK; pos++; options = weechat_hashtable_new (8, WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_STRING, NULL, NULL); if (!options) { weechat_printf (NULL, _("%s%s: not enough memory"), weechat_prefix ("error"), RELAY_PLUGIN_NAME); return WEECHAT_RC_OK; } ptr_weechat_commands = weechat_config_string ( relay_config_weechat_commands); if (ptr_weechat_commands && ptr_weechat_commands[0]) { weechat_hashtable_set ( options, "commands", weechat_config_string (relay_config_weechat_commands)); } /* * delay the execution of command after we go back in the WeeChat * main loop (some commands like /upgrade executed now can cause * a crash) */ weechat_hashtable_set (options, "delay", "1"); /* execute the command, with the delay */ weechat_command_options (ptr_buffer, pos, options); weechat_hashtable_free (options); return WEECHAT_RC_OK; } /* * Callback for signals "buffer_*". */ int relay_weechat_protocol_signal_buffer_cb (const void *pointer, void *data, const char *signal, const char *type_data, void *signal_data) { struct t_relay_client *ptr_client; struct t_gui_line *ptr_line; struct t_hdata *ptr_hdata_line, *ptr_hdata_line_data; struct t_gui_line_data *ptr_line_data; struct t_gui_buffer *ptr_buffer; struct t_relay_weechat_msg *msg; char cmd_hdata[64], str_signal[128]; const char *ptr_old_full_name; int *ptr_old_flags, flags; /* make C compiler happy */ (void) data; (void) type_data; ptr_client = (struct t_relay_client *)pointer; if (!ptr_client || !relay_client_valid (ptr_client)) return WEECHAT_RC_OK; snprintf (str_signal, sizeof (str_signal), "_%s", signal); if (strcmp (signal, "buffer_opened") == 0) { ptr_buffer = (struct t_gui_buffer *)signal_data; if (!ptr_buffer) return WEECHAT_RC_OK; /* send signal only if sync with flag "buffers" or "buffer" */ if (relay_weechat_protocol_is_sync (ptr_client, ptr_buffer, RELAY_WEECHAT_PROTOCOL_SYNC_BUFFERS | RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER)) { msg = relay_weechat_msg_new (str_signal); if (msg) { snprintf (cmd_hdata, sizeof (cmd_hdata), "buffer:0x%lx", (unsigned long)ptr_buffer); relay_weechat_msg_add_hdata (msg, cmd_hdata, "number,full_name,short_name," "nicklist,title,local_variables," "prev_buffer,next_buffer"); relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } } else if (strcmp (signal, "buffer_type_changed") == 0) { ptr_buffer = (struct t_gui_buffer *)signal_data; if (!ptr_buffer) return WEECHAT_RC_OK; /* send signal only if sync with flag "buffers" or "buffer" */ if (relay_weechat_protocol_is_sync (ptr_client, ptr_buffer, RELAY_WEECHAT_PROTOCOL_SYNC_BUFFERS | RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER)) { msg = relay_weechat_msg_new (str_signal); if (msg) { snprintf (cmd_hdata, sizeof (cmd_hdata), "buffer:0x%lx", (unsigned long)ptr_buffer); relay_weechat_msg_add_hdata (msg, cmd_hdata, "number,full_name,type"); relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } } else if (strcmp (signal, "buffer_moved") == 0) { ptr_buffer = (struct t_gui_buffer *)signal_data; if (!ptr_buffer) return WEECHAT_RC_OK; /* send signal only if sync with flag "buffers" or "buffer" */ if (relay_weechat_protocol_is_sync (ptr_client, ptr_buffer, RELAY_WEECHAT_PROTOCOL_SYNC_BUFFERS | RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER)) { msg = relay_weechat_msg_new (str_signal); if (msg) { snprintf (cmd_hdata, sizeof (cmd_hdata), "buffer:0x%lx", (unsigned long)ptr_buffer); relay_weechat_msg_add_hdata (msg, cmd_hdata, "number,full_name," "prev_buffer,next_buffer"); relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } } else if ((strcmp (signal, "buffer_merged") == 0) || (strcmp (signal, "buffer_unmerged") == 0)) { ptr_buffer = (struct t_gui_buffer *)signal_data; if (!ptr_buffer) return WEECHAT_RC_OK; /* send signal only if sync with flag "buffers" or "buffer" */ if (relay_weechat_protocol_is_sync (ptr_client, ptr_buffer, RELAY_WEECHAT_PROTOCOL_SYNC_BUFFERS | RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER)) { msg = relay_weechat_msg_new (str_signal); if (msg) { snprintf (cmd_hdata, sizeof (cmd_hdata), "buffer:0x%lx", (unsigned long)ptr_buffer); relay_weechat_msg_add_hdata (msg, cmd_hdata, "number,full_name," "prev_buffer,next_buffer"); relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } } else if ((strcmp (signal, "buffer_hidden") == 0) || (strcmp (signal, "buffer_unhidden") == 0)) { ptr_buffer = (struct t_gui_buffer *)signal_data; if (!ptr_buffer) return WEECHAT_RC_OK; /* send signal only if sync with flag "buffers" or "buffer" */ if (relay_weechat_protocol_is_sync (ptr_client, ptr_buffer, RELAY_WEECHAT_PROTOCOL_SYNC_BUFFERS | RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER)) { msg = relay_weechat_msg_new (str_signal); if (msg) { snprintf (cmd_hdata, sizeof (cmd_hdata), "buffer:0x%lx", (unsigned long)ptr_buffer); relay_weechat_msg_add_hdata (msg, cmd_hdata, "number,full_name," "prev_buffer,next_buffer"); relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } } else if (strcmp (signal, "buffer_renamed") == 0) { ptr_buffer = (struct t_gui_buffer *)signal_data; if (!ptr_buffer) return WEECHAT_RC_OK; /* rename old buffer name if present in hashtable "buffers_sync" */ ptr_old_full_name = weechat_buffer_get_string (ptr_buffer, "old_full_name"); if (ptr_old_full_name && ptr_old_full_name[0]) { ptr_old_flags = weechat_hashtable_get ( RELAY_WEECHAT_DATA(ptr_client, buffers_sync), ptr_old_full_name); if (ptr_old_flags) { flags = *ptr_old_flags; weechat_hashtable_remove ( RELAY_WEECHAT_DATA(ptr_client, buffers_sync), ptr_old_full_name); weechat_hashtable_set ( RELAY_WEECHAT_DATA(ptr_client, buffers_sync), weechat_buffer_get_string (ptr_buffer, "full_name"), &flags); } } /* send signal only if sync with flag "buffers" or "buffer" */ if (relay_weechat_protocol_is_sync (ptr_client, ptr_buffer, RELAY_WEECHAT_PROTOCOL_SYNC_BUFFERS | RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER)) { msg = relay_weechat_msg_new (str_signal); if (msg) { snprintf (cmd_hdata, sizeof (cmd_hdata), "buffer:0x%lx", (unsigned long)ptr_buffer); relay_weechat_msg_add_hdata (msg, cmd_hdata, "number,full_name,short_name," "local_variables"); relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } } else if (strcmp (signal, "buffer_title_changed") == 0) { ptr_buffer = (struct t_gui_buffer *)signal_data; if (!ptr_buffer) return WEECHAT_RC_OK; /* send signal only if sync with flag "buffers" or "buffer" */ if (relay_weechat_protocol_is_sync (ptr_client, ptr_buffer, RELAY_WEECHAT_PROTOCOL_SYNC_BUFFERS | RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER)) { msg = relay_weechat_msg_new (str_signal); if (msg) { snprintf (cmd_hdata, sizeof (cmd_hdata), "buffer:0x%lx", (unsigned long)ptr_buffer); relay_weechat_msg_add_hdata (msg, cmd_hdata, "number,full_name,title"); relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } } else if (strncmp (signal, "buffer_localvar_", 16) == 0) { ptr_buffer = (struct t_gui_buffer *)signal_data; if (!ptr_buffer) return WEECHAT_RC_OK; /* send signal only if sync with flag "buffers" or "buffer" */ if (relay_weechat_protocol_is_sync (ptr_client, ptr_buffer, RELAY_WEECHAT_PROTOCOL_SYNC_BUFFERS | RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER)) { msg = relay_weechat_msg_new (str_signal); if (msg) { snprintf (cmd_hdata, sizeof (cmd_hdata), "buffer:0x%lx", (unsigned long)ptr_buffer); relay_weechat_msg_add_hdata (msg, cmd_hdata, "number,full_name,local_variables"); relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } } else if (strcmp (signal, "buffer_cleared") == 0) { ptr_buffer = (struct t_gui_buffer *)signal_data; if (!ptr_buffer || relay_weechat_is_relay_buffer (ptr_buffer)) return WEECHAT_RC_OK; /* send signal only if sync with flag "buffer" */ if (relay_weechat_protocol_is_sync (ptr_client, ptr_buffer, RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER)) { msg = relay_weechat_msg_new (str_signal); if (msg) { snprintf (cmd_hdata, sizeof (cmd_hdata), "buffer:0x%lx", (unsigned long)ptr_buffer); relay_weechat_msg_add_hdata (msg, cmd_hdata, "number,full_name"); relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } } else if (strcmp (signal, "buffer_line_added") == 0) { ptr_line = (struct t_gui_line *)signal_data; if (!ptr_line) return WEECHAT_RC_OK; ptr_hdata_line = weechat_hdata_get ("line"); if (!ptr_hdata_line) return WEECHAT_RC_OK; ptr_hdata_line_data = weechat_hdata_get ("line_data"); if (!ptr_hdata_line_data) return WEECHAT_RC_OK; ptr_line_data = weechat_hdata_pointer (ptr_hdata_line, ptr_line, "data"); if (!ptr_line_data) return WEECHAT_RC_OK; ptr_buffer = weechat_hdata_pointer (ptr_hdata_line_data, ptr_line_data, "buffer"); if (!ptr_buffer || relay_weechat_is_relay_buffer (ptr_buffer)) return WEECHAT_RC_OK; /* send signal only if sync with flag "buffer" */ if (relay_weechat_protocol_is_sync (ptr_client, ptr_buffer, RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER)) { msg = relay_weechat_msg_new (str_signal); if (msg) { snprintf (cmd_hdata, sizeof (cmd_hdata), "line_data:0x%lx", (unsigned long)ptr_line_data); relay_weechat_msg_add_hdata (msg, cmd_hdata, "buffer,date,date_printed," "displayed,highlight,tags_array," "prefix,message"); relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } } else if (strcmp (signal, "buffer_closing") == 0) { ptr_buffer = (struct t_gui_buffer *)signal_data; if (!ptr_buffer) return WEECHAT_RC_OK; /* send signal only if sync with flag "buffers" or "buffer" */ if (relay_weechat_protocol_is_sync (ptr_client, ptr_buffer, RELAY_WEECHAT_PROTOCOL_SYNC_BUFFERS | RELAY_WEECHAT_PROTOCOL_SYNC_BUFFER)) { msg = relay_weechat_msg_new (str_signal); if (msg) { snprintf (cmd_hdata, sizeof (cmd_hdata), "buffer:0x%lx", (unsigned long)ptr_buffer); relay_weechat_msg_add_hdata (msg, cmd_hdata, "number,full_name"); relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } /* remove buffer from hashtables */ weechat_hashtable_remove ( RELAY_WEECHAT_DATA(ptr_client, buffers_sync), weechat_buffer_get_string (ptr_buffer, "full_name")); weechat_hashtable_remove ( RELAY_WEECHAT_DATA(ptr_client, buffers_nicklist), ptr_buffer); } return WEECHAT_RC_OK; } /* * Callback for entries in hashtable "buffers_nicklist" of client (sends * nicklist for each buffer in this hashtable). */ void relay_weechat_protocol_nicklist_map_cb (void *data, struct t_hashtable *hashtable, const void *key, const void *value) { struct t_relay_client *ptr_client; struct t_gui_buffer *ptr_buffer; struct t_relay_weechat_nicklist *ptr_nicklist; struct t_hdata *ptr_hdata; struct t_relay_weechat_msg *msg; /* make C compiler happy */ (void) hashtable; ptr_client = (struct t_relay_client *)data; ptr_buffer = (struct t_gui_buffer *)key; ptr_nicklist = (struct t_relay_weechat_nicklist *)value; ptr_hdata = weechat_hdata_get ("buffer"); if (ptr_hdata) { if (weechat_hdata_check_pointer (ptr_hdata, weechat_hdata_get_list (ptr_hdata, "gui_buffers"), ptr_buffer)) { /* * if no diff at all, or if diffs are bigger than nicklist: * send whole nicklist */ if (ptr_nicklist && ((ptr_nicklist->items_count == 0) || (ptr_nicklist->items_count >= weechat_buffer_get_integer (ptr_buffer, "nicklist_count") + 1))) { ptr_nicklist = NULL; } /* send nicklist diffs or full nicklist */ msg = relay_weechat_msg_new ((ptr_nicklist) ? "_nicklist_diff" : "_nicklist"); if (msg) { relay_weechat_msg_add_nicklist (msg, ptr_buffer, ptr_nicklist); relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } } } /* * Callback for nicklist timer. */ int relay_weechat_protocol_timer_nicklist_cb (const void *pointer, void *data, int remaining_calls) { struct t_relay_client *ptr_client; /* make C compiler happy */ (void) data; (void) remaining_calls; ptr_client = (struct t_relay_client *)pointer; if (!ptr_client || !relay_client_valid (ptr_client)) return WEECHAT_RC_OK; weechat_hashtable_map (RELAY_WEECHAT_DATA(ptr_client, buffers_nicklist), &relay_weechat_protocol_nicklist_map_cb, ptr_client); weechat_hashtable_remove_all (RELAY_WEECHAT_DATA(ptr_client, buffers_nicklist)); RELAY_WEECHAT_DATA(ptr_client, hook_timer_nicklist) = NULL; return WEECHAT_RC_OK; } /* * Callback for hsignals "nicklist_*". */ int relay_weechat_protocol_hsignal_nicklist_cb (const void *pointer, void *data, const char *signal, struct t_hashtable *hashtable) { struct t_relay_client *ptr_client; struct t_gui_nick_group *parent_group, *group; struct t_gui_nick *nick; struct t_gui_buffer *ptr_buffer; struct t_relay_weechat_nicklist *ptr_nicklist; char diff; /* make C compiler happy */ (void) data; ptr_client = (struct t_relay_client *)pointer; if (!ptr_client || !relay_client_valid (ptr_client)) return WEECHAT_RC_OK; /* check if buffer is synchronized with flag "nicklist" */ ptr_buffer = weechat_hashtable_get (hashtable, "buffer"); if (!relay_weechat_protocol_is_sync (ptr_client, ptr_buffer, RELAY_WEECHAT_PROTOCOL_SYNC_NICKLIST)) return WEECHAT_RC_OK; parent_group = weechat_hashtable_get (hashtable, "parent_group"); group = weechat_hashtable_get (hashtable, "group"); nick = weechat_hashtable_get (hashtable, "nick"); /* if there is no parent group (for example "root" group), ignore the signal */ if (!parent_group) return WEECHAT_RC_OK; ptr_nicklist = weechat_hashtable_get (RELAY_WEECHAT_DATA(ptr_client, buffers_nicklist), ptr_buffer); if (!ptr_nicklist) { ptr_nicklist = relay_weechat_nicklist_new (); if (!ptr_nicklist) return WEECHAT_RC_OK; ptr_nicklist->nicklist_count = weechat_buffer_get_integer (ptr_buffer, "nicklist_count"); weechat_hashtable_set (RELAY_WEECHAT_DATA(ptr_client, buffers_nicklist), ptr_buffer, ptr_nicklist); } /* set diff type */ diff = RELAY_WEECHAT_NICKLIST_DIFF_UNKNOWN; if ((strcmp (signal, "nicklist_group_added") == 0) || (strcmp (signal, "nicklist_nick_added") == 0)) { diff = RELAY_WEECHAT_NICKLIST_DIFF_ADDED; } else if ((strcmp (signal, "nicklist_group_removing") == 0) || (strcmp (signal, "nicklist_nick_removing") == 0)) { diff = RELAY_WEECHAT_NICKLIST_DIFF_REMOVED; } else if ((strcmp (signal, "nicklist_group_changed") == 0) || (strcmp (signal, "nicklist_nick_changed") == 0)) { diff = RELAY_WEECHAT_NICKLIST_DIFF_CHANGED; } if (diff != RELAY_WEECHAT_NICKLIST_DIFF_UNKNOWN) { /* * add items if nicklist was not empty or very small (otherwise we will * send full nicklist) */ if (ptr_nicklist->nicklist_count > 1) { /* add nicklist item for parent group and group/nick */ relay_weechat_nicklist_add_item (ptr_nicklist, RELAY_WEECHAT_NICKLIST_DIFF_PARENT, parent_group, NULL); relay_weechat_nicklist_add_item (ptr_nicklist, diff, group, nick); } /* add timer to send nicklist */ if (RELAY_WEECHAT_DATA(ptr_client, hook_timer_nicklist)) { weechat_unhook (RELAY_WEECHAT_DATA(ptr_client, hook_timer_nicklist)); RELAY_WEECHAT_DATA(ptr_client, hook_timer_nicklist) = NULL; } relay_weechat_hook_timer_nicklist (ptr_client); } return WEECHAT_RC_OK; } /* * Callback for signals "upgrade*". */ int relay_weechat_protocol_signal_upgrade_cb (const void *pointer, void *data, const char *signal, const char *type_data, void *signal_data) { struct t_relay_client *ptr_client; struct t_relay_weechat_msg *msg; char str_signal[128]; /* make C compiler happy */ (void) data; (void) type_data; (void) signal_data; ptr_client = (struct t_relay_client *)pointer; if (!ptr_client || !relay_client_valid (ptr_client)) return WEECHAT_RC_OK; snprintf (str_signal, sizeof (str_signal), "_%s", signal); if ((strcmp (signal, "upgrade") == 0) || (strcmp (signal, "upgrade_ended") == 0)) { /* send signal only if client is synchronized with flag "upgrade" */ if (relay_weechat_protocol_is_sync (ptr_client, NULL, RELAY_WEECHAT_PROTOCOL_SYNC_UPGRADE)) { msg = relay_weechat_msg_new (str_signal); if (msg) { relay_weechat_msg_send (ptr_client, msg); relay_weechat_msg_free (msg); } } } return WEECHAT_RC_OK; } /* * Callback for command "sync" (from client). * * Message looks like: * sync * sync * buffer * sync irc.freenode.#weechat buffer,nicklist */ RELAY_WEECHAT_PROTOCOL_CALLBACK(sync) { char **buffers, **flags; const char *ptr_full_name; int num_buffers, num_flags, i, add_flags, mask, *ptr_old_flags, new_flags; struct t_gui_buffer *ptr_buffer; RELAY_WEECHAT_PROTOCOL_MIN_ARGS(0); buffers = weechat_string_split ((argc > 0) ? argv[0] : "*", ",", NULL, WEECHAT_STRING_SPLIT_STRIP_LEFT | WEECHAT_STRING_SPLIT_STRIP_RIGHT | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS, 0, &num_buffers); if (buffers) { add_flags = RELAY_WEECHAT_PROTOCOL_SYNC_ALL; if (argc > 1) { add_flags = 0; flags = weechat_string_split (argv[1], ",", NULL, WEECHAT_STRING_SPLIT_STRIP_LEFT | WEECHAT_STRING_SPLIT_STRIP_RIGHT | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS, 0, &num_flags); if (flags) { for (i = 0; i < num_flags; i++) { add_flags |= relay_weechat_protocol_sync_flag (flags[i]); } weechat_string_free_split (flags); } } if (add_flags) { for (i = 0; i < num_buffers; i++) { ptr_full_name = NULL; mask = RELAY_WEECHAT_PROTOCOL_SYNC_FOR_BUFFER; if (strcmp (buffers[i], "*") == 0) { ptr_full_name = buffers[i]; mask = RELAY_WEECHAT_PROTOCOL_SYNC_ALL; } else { ptr_buffer = relay_weechat_protocol_get_buffer (buffers[i]); if (ptr_buffer) { ptr_full_name = weechat_buffer_get_string (ptr_buffer, "full_name"); } } if (ptr_full_name) { ptr_old_flags = weechat_hashtable_get (RELAY_WEECHAT_DATA(client, buffers_sync), ptr_full_name); new_flags = ((ptr_old_flags) ? *ptr_old_flags : 0); new_flags |= (add_flags & mask); if (new_flags) { weechat_hashtable_set (RELAY_WEECHAT_DATA(client, buffers_sync), ptr_full_name, &new_flags); } } } } weechat_string_free_split (buffers); } return WEECHAT_RC_OK; } /* * Callback for command "desync" (from client). * * Message looks like: * desync * desync * nicklist * desync irc.freenode.#weechat buffer,nicklist */ RELAY_WEECHAT_PROTOCOL_CALLBACK(desync) { char **buffers, **flags; const char *ptr_full_name; int num_buffers, num_flags, i, sub_flags, mask, *ptr_old_flags, new_flags; struct t_gui_buffer *ptr_buffer; RELAY_WEECHAT_PROTOCOL_MIN_ARGS(0); buffers = weechat_string_split ((argc > 0) ? argv[0] : "*", ",", NULL, WEECHAT_STRING_SPLIT_STRIP_LEFT | WEECHAT_STRING_SPLIT_STRIP_RIGHT | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS, 0, &num_buffers); if (buffers) { sub_flags = RELAY_WEECHAT_PROTOCOL_SYNC_ALL; if (argc > 1) { sub_flags = 0; flags = weechat_string_split (argv[1], ",", NULL, WEECHAT_STRING_SPLIT_STRIP_LEFT | WEECHAT_STRING_SPLIT_STRIP_RIGHT | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS, 0, &num_flags); if (flags) { for (i = 0; i < num_flags; i++) { sub_flags |= relay_weechat_protocol_sync_flag (flags[i]); } weechat_string_free_split (flags); } } if (sub_flags) { for (i = 0; i < num_buffers; i++) { ptr_full_name = NULL; mask = RELAY_WEECHAT_PROTOCOL_SYNC_FOR_BUFFER; if (strcmp (buffers[i], "*") == 0) { ptr_full_name = buffers[i]; mask = RELAY_WEECHAT_PROTOCOL_SYNC_ALL; } else { ptr_buffer = relay_weechat_protocol_get_buffer (buffers[i]); if (ptr_buffer) { ptr_full_name = weechat_buffer_get_string (ptr_buffer, "full_name"); } } if (ptr_full_name) { ptr_old_flags = weechat_hashtable_get (RELAY_WEECHAT_DATA(client, buffers_sync), ptr_full_name); new_flags = ((ptr_old_flags) ? *ptr_old_flags : 0); new_flags &= ~(sub_flags & mask); if (new_flags) { weechat_hashtable_set (RELAY_WEECHAT_DATA(client, buffers_sync), ptr_full_name, &new_flags); } else { weechat_hashtable_remove (RELAY_WEECHAT_DATA(client, buffers_sync), ptr_full_name); } } } } weechat_string_free_split (buffers); } return WEECHAT_RC_OK; } /* * Callback for command "test" (from client). * * Message looks like: * test */ RELAY_WEECHAT_PROTOCOL_CALLBACK(test) { struct t_relay_weechat_msg *msg; RELAY_WEECHAT_PROTOCOL_MIN_ARGS(0); msg = relay_weechat_msg_new (id); if (msg) { /* char */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_CHAR); relay_weechat_msg_add_char (msg, 'A'); /* integer */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_INT); relay_weechat_msg_add_int (msg, 123456); /* integer (negative) */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_INT); relay_weechat_msg_add_int (msg, -123456); /* long */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_LONG); relay_weechat_msg_add_long (msg, 1234567890L); /* long (negative) */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_LONG); relay_weechat_msg_add_long (msg, -1234567890L); /* string */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_STRING); relay_weechat_msg_add_string (msg, "a string"); /* empty string */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_STRING); relay_weechat_msg_add_string (msg, ""); /* NULL string */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_STRING); relay_weechat_msg_add_string (msg, NULL); /* buffer */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_BUFFER); relay_weechat_msg_add_buffer (msg, "buffer", 6); /* NULL buffer */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_BUFFER); relay_weechat_msg_add_buffer (msg, NULL, 0); /* pointer */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_POINTER); relay_weechat_msg_add_pointer (msg, (void *)0x1234abcd); /* NULL pointer */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_POINTER); relay_weechat_msg_add_pointer (msg, NULL); /* time */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_TIME); relay_weechat_msg_add_time (msg, 1321993456); /* array of strings: { "abc", "de" } */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_ARRAY); relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_STRING); relay_weechat_msg_add_int (msg, 2); relay_weechat_msg_add_string (msg, "abc"); relay_weechat_msg_add_string (msg, "de"); /* array of integers: { 123, 456, 789 } */ relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_ARRAY); relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_INT); relay_weechat_msg_add_int (msg, 3); relay_weechat_msg_add_int (msg, 123); relay_weechat_msg_add_int (msg, 456); relay_weechat_msg_add_int (msg, 789); /* send message */ relay_weechat_msg_send (client, msg); relay_weechat_msg_free (msg); } return WEECHAT_RC_OK; } /* * Callback for command "ping" (from client). * * Message looks like: * ping * ping 1370802127000 */ RELAY_WEECHAT_PROTOCOL_CALLBACK(ping) { struct t_relay_weechat_msg *msg; RELAY_WEECHAT_PROTOCOL_MIN_ARGS(0); msg = relay_weechat_msg_new ("_pong"); if (msg) { relay_weechat_msg_add_type (msg, RELAY_WEECHAT_MSG_OBJ_STRING); relay_weechat_msg_add_string (msg, (argc > 0) ? argv_eol[0] : ""); /* send message */ relay_weechat_msg_send (client, msg); relay_weechat_msg_free (msg); } return WEECHAT_RC_OK; } /* * Callback for command "quit" (from client). * * Message looks like: * test */ RELAY_WEECHAT_PROTOCOL_CALLBACK(quit) { RELAY_WEECHAT_PROTOCOL_MIN_ARGS(0); relay_client_set_status (client, RELAY_STATUS_DISCONNECTED); return WEECHAT_RC_OK; } /* * Reads a command from a client. */ void relay_weechat_protocol_recv (struct t_relay_client *client, const char *data) { char *pos, *id, *command, **argv, **argv_eol; int i, argc, return_code; struct t_relay_weechat_protocol_cb protocol_cb[] = { { "handshake", &relay_weechat_protocol_cb_handshake }, { "init", &relay_weechat_protocol_cb_init }, { "hdata", &relay_weechat_protocol_cb_hdata }, { "info", &relay_weechat_protocol_cb_info }, { "infolist", &relay_weechat_protocol_cb_infolist }, { "nicklist", &relay_weechat_protocol_cb_nicklist }, { "input", &relay_weechat_protocol_cb_input }, { "sync", &relay_weechat_protocol_cb_sync }, { "desync", &relay_weechat_protocol_cb_desync }, { "test", &relay_weechat_protocol_cb_test }, { "ping", &relay_weechat_protocol_cb_ping }, { "quit", &relay_weechat_protocol_cb_quit }, { NULL, NULL } }; if (!data || !data[0] || RELAY_CLIENT_HAS_ENDED(client)) return; /* display debug message */ if (weechat_relay_plugin->debug >= 2) { weechat_printf (NULL, "%s: recv from client %s%s%s: \"%s\"", RELAY_PLUGIN_NAME, RELAY_COLOR_CHAT_CLIENT, client->desc, RELAY_COLOR_CHAT, data); } /* extract id */ id = NULL; if (data[0] == '(') { pos = strchr (data, ')'); if (pos) { id = weechat_strndup (data + 1, pos - data - 1); data = pos + 1; while (data[0] == ' ') { data++; } } } /* search end of data */ pos = strchr (data, ' '); if (pos) command = weechat_strndup (data, pos - data); else command = strdup (data); if (!command) { if (id) free (id); return; } argc = 0; argv = NULL; argv_eol = NULL; if (pos) { while (pos[0] == ' ') { pos++; } argv = weechat_string_split (pos, " ", NULL, WEECHAT_STRING_SPLIT_STRIP_LEFT | WEECHAT_STRING_SPLIT_STRIP_RIGHT | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS, 0, &argc); argv_eol = weechat_string_split (pos, " ", NULL, WEECHAT_STRING_SPLIT_STRIP_LEFT | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS | WEECHAT_STRING_SPLIT_KEEP_EOL, 0, NULL); } for (i = 0; protocol_cb[i].name; i++) { if (strcmp (protocol_cb[i].name, command) == 0) { if ((strcmp (protocol_cb[i].name, "handshake") != 0) && (strcmp (protocol_cb[i].name, "init") != 0) && (!RELAY_WEECHAT_DATA(client, password_ok) || !RELAY_WEECHAT_DATA(client, totp_ok))) { /* * command is not handshake/init and password or totp are not * set? then close connection! */ relay_client_set_status (client, RELAY_STATUS_AUTH_FAILED); } else { return_code = (int) (protocol_cb[i].cmd_function) (client, id, protocol_cb[i].name, argc, argv, argv_eol); if ((weechat_relay_plugin->debug >= 1) && (return_code == WEECHAT_RC_ERROR)) { weechat_printf (NULL, _("%s%s: failed to execute command \"%s\" " "for client %s%s%s"), weechat_prefix ("error"), RELAY_PLUGIN_NAME, command, RELAY_COLOR_CHAT_CLIENT, client->desc, RELAY_COLOR_CHAT); } } break; } } if (id) free (id); free (command); if (argv) weechat_string_free_split (argv); if (argv_eol) weechat_string_free_split (argv_eol); }