/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "hostpid.h" #include "master-service.h" #include "ipc-server.h" #include #define IPC_SERVER_RECONNECT_MSECS (10*1000) #define IPC_SERVER_PROTOCOL_MAJOR_VERSION 1 #define IPC_SERVER_PROTOCOL_MINOR_VERSION 0 #define IPC_SERVER_HANDSHAKE "VERSION\tipc-server\t1\t0\nHANDSHAKE\t%s\t%s\n" struct ipc_cmd { struct ipc_server *server; unsigned int tag; }; struct ipc_server { char *name, *path; ipc_command_callback_t *callback; int ipc_cmd_refcount; int fd; struct io *io; struct timeout *to; struct istream *input; struct ostream *output; bool version_received:1; }; static void ipc_server_disconnect(struct ipc_server *server); static void ipc_server_connect(struct ipc_server *server); static void ipc_server_input_line(struct ipc_server *server, char *line) { struct ipc_cmd *cmd; unsigned int tag = 0; char *p; /* tag cmd */ p = strchr(line, '\t'); if (p != NULL) { *p++ = '\0'; if (str_to_uint(line, &tag) < 0) p = NULL; } if (p == NULL || *p == '\0') { i_error("IPC proxy sent invalid input: %s", line); return; } cmd = i_new(struct ipc_cmd, 1); cmd->server = server; cmd->tag = tag; server->ipc_cmd_refcount++; T_BEGIN { server->callback(cmd, p); } T_END; } static void ipc_server_input(struct ipc_server *server) { char *line; if (i_stream_read(server->input) < 0) { ipc_server_disconnect(server); ipc_server_connect(server); return; } if (!server->version_received) { if ((line = i_stream_next_line(server->input)) == NULL) return; if (!version_string_verify(line, "ipc-proxy", IPC_SERVER_PROTOCOL_MAJOR_VERSION)) { i_error("IPC proxy not compatible with this server " "(mixed old and new binaries?)"); ipc_server_disconnect(server); return; } server->version_received = TRUE; } while ((line = i_stream_next_line(server->input)) != NULL) ipc_server_input_line(server, line); } static void ipc_server_connect(struct ipc_server *server) { i_assert(server->fd == -1); timeout_remove(&server->to); server->fd = net_connect_unix(server->path); if (server->fd == -1) { i_error("connect(%s) failed: %m", server->path); server->to = timeout_add(IPC_SERVER_RECONNECT_MSECS, ipc_server_connect, server); return; } server->io = io_add(server->fd, IO_READ, ipc_server_input, server); server->input = i_stream_create_fd(server->fd, SIZE_MAX); server->output = o_stream_create_fd(server->fd, SIZE_MAX); o_stream_set_no_error_handling(server->output, TRUE); o_stream_nsend_str(server->output, t_strdup_printf(IPC_SERVER_HANDSHAKE, server->name, my_pid)); o_stream_cork(server->output); } static void ipc_server_disconnect(struct ipc_server *server) { if (server->fd == -1) return; io_remove(&server->io); i_stream_destroy(&server->input); o_stream_destroy(&server->output); if (close(server->fd) < 0) i_error("close(%s) failed: %m", server->path); server->fd = -1; } struct ipc_server * ipc_server_init(const char *ipc_socket_path, const char *name, ipc_command_callback_t *callback) { struct ipc_server *server; server = i_new(struct ipc_server, 1); server->name = i_strdup(name); server->path = i_strdup(ipc_socket_path); server->callback = callback; server->fd = -1; ipc_server_connect(server); return server; } void ipc_server_deinit(struct ipc_server **_server) { struct ipc_server *server = *_server; *_server = NULL; i_assert(server->ipc_cmd_refcount == 0); ipc_server_disconnect(server); timeout_remove(&server->to); i_free(server->name); i_free(server->path); i_free(server); } void ipc_cmd_send(struct ipc_cmd *cmd, const char *data) { o_stream_nsend_str(cmd->server->output, t_strdup_printf("%u\t:%s\n", cmd->tag, data)); } static void ipc_cmd_finish(struct ipc_cmd *cmd, const char *line) { o_stream_nsend_str(cmd->server->output, t_strdup_printf("%u\t%s\n", cmd->tag, line)); o_stream_uncork(cmd->server->output); i_assert(cmd->server->ipc_cmd_refcount > 0); cmd->server->ipc_cmd_refcount--; i_free(cmd); } void ipc_cmd_success(struct ipc_cmd **_cmd) { ipc_cmd_success_reply(_cmd, ""); } void ipc_cmd_success_reply(struct ipc_cmd **_cmd, const char *data) { struct ipc_cmd *cmd = *_cmd; *_cmd = NULL; ipc_cmd_finish(cmd, t_strconcat("+", data, NULL)); } void ipc_cmd_fail(struct ipc_cmd **_cmd, const char *errormsg) { struct ipc_cmd *cmd = *_cmd; i_assert(errormsg != NULL); *_cmd = NULL; ipc_cmd_finish(cmd, t_strconcat("-", errormsg, NULL)); }