#include #include #include #include #include #include #include WEECHAT_PLUGIN_NAME("neodrop_manager") WEECHAT_PLUGIN_DESCRIPTION("NeoDrop manager control buffer") WEECHAT_PLUGIN_AUTHOR("{{PRODUCT_NAME}}") WEECHAT_PLUGIN_VERSION("1.1") WEECHAT_PLUGIN_LICENSE("GPL3") #define CONTROL_SOCKET "{{MANAGER_CONTROL_SOCKET_PATH}}" #define SEND_LIMIT 8192U #define RECEIVE_LIMIT 16384U #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif struct t_weechat_plugin *weechat_plugin; static struct t_gui_buffer *manager_buffer; static struct t_hook *manager_fd_hook; static int manager_fd = -1; static char receive_buffer[RECEIVE_LIMIT]; static size_t receive_length; static void print_error(const char *message) { if (manager_buffer != NULL) weechat_printf(manager_buffer, "%s%s", weechat_color("lightred"), message); } static void disconnect_manager(int announce) { int was_connected = manager_fd >= 0; if (manager_fd_hook != NULL) { weechat_unhook(manager_fd_hook); manager_fd_hook = NULL; } if (manager_fd >= 0) close(manager_fd); manager_fd = -1; receive_length = 0; if (announce && was_connected) print_error("NeoDrop manager disconnected; reconnecting in the background."); } static void print_protocol_line(const char *line, size_t length) { const char *payload; const char *color = NULL; size_t payload_length; size_t index; char output[RECEIVE_LIMIT]; if (manager_buffer == NULL) return; if (length > 0 && line[length - 1] == '\r') length--; if (length >= 5 && memcmp(line, "LINE\t", 5) == 0) { payload = line + 5; payload_length = length - 5; } else if (length >= 6 && memcmp(line, "ERROR\t", 6) == 0) { payload = line + 6; payload_length = length - 6; color = "lightred"; } else { print_error("NeoDrop manager sent an invalid response."); return; } for (index = 0; index < payload_length; index++) { unsigned char byte = (unsigned char)payload[index]; output[index] = (byte < 0x20U || byte == 0x7fU) ? ' ' : (char)byte; } output[payload_length] = '\0'; if (color != NULL) weechat_printf(manager_buffer, "%s%s", weechat_color(color), output); else weechat_printf(manager_buffer, "%s", output); } static int manager_read_cb(const void *pointer, void *data, int fd) { ssize_t count; size_t consumed = 0; char *newline; (void)pointer; (void)data; (void)fd; if (receive_length >= RECEIVE_LIMIT - 1U) { print_error("NeoDrop manager response exceeded the line limit."); disconnect_manager(0); return WEECHAT_RC_OK; } count = recv(manager_fd, receive_buffer + receive_length, RECEIVE_LIMIT - receive_length - 1U, 0); if (count < 0 && errno == EINTR) return WEECHAT_RC_OK; if (count <= 0) { disconnect_manager(1); return WEECHAT_RC_OK; } receive_length += (size_t)count; while (consumed < receive_length) { size_t available = receive_length - consumed; newline = memchr(receive_buffer + consumed, '\n', available); if (newline == NULL) break; print_protocol_line(receive_buffer + consumed, (size_t)(newline - (receive_buffer + consumed))); consumed = (size_t)(newline - receive_buffer) + 1U; } if (consumed > 0) { receive_length -= consumed; memmove(receive_buffer, receive_buffer + consumed, receive_length); } if (receive_length >= RECEIVE_LIMIT - 1U) { print_error("NeoDrop manager response exceeded the line limit."); disconnect_manager(0); } return WEECHAT_RC_OK; } static int connect_manager(void) { struct sockaddr_un address; int fd; if (manager_fd >= 0) return 1; fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return 0; #ifdef SO_NOSIGPIPE { int enabled = 1; if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &enabled, sizeof(enabled)) != 0) { close(fd); return 0; } } #endif memset(&address, 0, sizeof(address)); address.sun_family = AF_UNIX; memcpy(address.sun_path, CONTROL_SOCKET, sizeof(CONTROL_SOCKET)); if (connect(fd, (struct sockaddr *)&address, sizeof(address)) != 0) { close(fd); return 0; } manager_fd = fd; manager_fd_hook = weechat_hook_fd(manager_fd, 1, 0, 0, &manager_read_cb, NULL, NULL); if (manager_fd_hook == NULL) { disconnect_manager(0); return 0; } return 1; } static void send_command(const char *command) { static const char prefix[] = "COMMAND\t"; char message[SEND_LIMIT]; size_t command_length; size_t message_length; size_t sent = 0; if (command == NULL || command[0] == '\0') return; command_length = strlen(command); if (command_length > SEND_LIMIT - sizeof("COMMAND\t\n")) { print_error("NeoDrop manager command exceeded the line limit."); return; } if (strpbrk(command, "\t\r\n") != NULL) { print_error("NeoDrop manager commands cannot contain tabs or newlines."); return; } if (!connect_manager()) { print_error("Unable to connect to the NeoDrop manager."); return; } memcpy(message, prefix, sizeof(prefix) - 1U); memcpy(message + sizeof(prefix) - 1U, command, command_length); message_length = sizeof(prefix) - 1U + command_length; message[message_length++] = '\n'; while (sent < message_length) { ssize_t count = send(manager_fd, message + sent, message_length - sent, MSG_NOSIGNAL); if (count < 0 && errno == EINTR) continue; if (count <= 0) { disconnect_manager(1); return; } sent += (size_t)count; } } static int reconnect_cb(const void *pointer, void *data, int remaining_calls) { (void)pointer; (void)data; (void)remaining_calls; (void)connect_manager(); return WEECHAT_RC_OK; } static int input_cb(const void *pointer, void *data, struct t_gui_buffer *buffer, const char *input) { (void)pointer; (void)data; (void)buffer; send_command(input); return WEECHAT_RC_OK; } static int close_cb(const void *pointer, void *data, struct t_gui_buffer *buffer) { (void)pointer; (void)data; (void)buffer; manager_buffer = NULL; return WEECHAT_RC_OK; } static int ensure_buffer(void) { if (manager_buffer == NULL) manager_buffer = weechat_buffer_search("neodrop_manager", "control"); if (manager_buffer == NULL) { manager_buffer = weechat_buffer_new("control", &input_cb, NULL, NULL, &close_cb, NULL, NULL); if (manager_buffer == NULL) return 0; } weechat_buffer_set_pointer(manager_buffer, "input_callback", &input_cb); weechat_buffer_set_pointer(manager_buffer, "close_callback", &close_cb); weechat_buffer_set(manager_buffer, "short_name", "NeoDrop"); weechat_buffer_set(manager_buffer, "title", "NeoDrop | list | put-text NAME TEXT | show/read/download/delete ID | edit ID | help"); weechat_buffer_set(manager_buffer, "hidden", "0"); weechat_buffer_set(manager_buffer, "notify", "3"); weechat_buffer_set(manager_buffer, "localvar_set_no_log", "1"); weechat_buffer_set(manager_buffer, "localvar_set_pinned", "true"); weechat_buffer_set(manager_buffer, "localvar_set_type", "other"); return 1; } static int command_cb(const void *pointer, void *data, struct t_gui_buffer *buffer, int argc, char **argv, char **argv_eol) { const char *command = NULL; int background_registration; (void)pointer; (void)data; (void)buffer; (void)argv; if (argc > 1 && argv_eol != NULL && argv_eol[1] != NULL && argv_eol[1][0] != '\0') command = argv_eol[1]; background_registration = command != NULL && strncmp(command, "register ", 9) == 0; if (!ensure_buffer()) return WEECHAT_RC_ERROR; if (!background_registration) weechat_buffer_set(manager_buffer, "display", "1"); if (command != NULL) send_command(command); else send_command("list"); return WEECHAT_RC_OK; } int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[]) { (void)argc; (void)argv; weechat_plugin = plugin; if (!ensure_buffer()) return WEECHAT_RC_ERROR; if (weechat_hook_command("drops", "open/control NeoDrop manager", "list || status || help || put-text || show || read || download || delete || edit || line || insert || delete-line || save || cancel", "Manage encrypted NeoDrop files and text revisions", "list|status|help|put-text|show|read|download|delete|edit|line|insert|delete-line|save|cancel", &command_cb, NULL, NULL) == NULL) return WEECHAT_RC_ERROR; if (weechat_hook_timer(5000, 0, 0, &reconnect_cb, NULL, NULL) == NULL) return WEECHAT_RC_ERROR; return WEECHAT_RC_OK; } int weechat_plugin_end(struct t_weechat_plugin *plugin) { (void)plugin; disconnect_manager(0); return WEECHAT_RC_OK; }