/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "llist.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "master-service.h" #include "ipc-group.h" #include "ipc-connection.h" #include "client.h" #include struct client { struct client *prev, *next; int fd; struct io *io; struct istream *input; struct ostream *output; }; static struct client *clients; static void client_input(struct client *client); static void client_cmd_input(enum ipc_cmd_status status, const char *line, void *context) { struct client *client = context; char chr = '\0'; switch (status) { case IPC_CMD_STATUS_REPLY: chr = ':'; break; case IPC_CMD_STATUS_OK: chr = '+'; break; case IPC_CMD_STATUS_ERROR: chr = '-'; break; } T_BEGIN { o_stream_nsend_str(client->output, t_strdup_printf("%c%s\n", chr, line)); } T_END; if (status != IPC_CMD_STATUS_REPLY && client->io == NULL) { client->io = io_add(client->fd, IO_READ, client_input, client); client_input(client); } } static void client_input(struct client *client) { struct ipc_group *group; struct ipc_connection *conn; char *line, *id, *data; unsigned int id_num; bool ret; while ((line = i_stream_read_next_line(client->input)) != NULL) { /* *| */ id = strchr(line, '\t'); if (id == NULL) data = NULL; else { *id++ = '\0'; data = strchr(id, '\t'); } if (data == NULL || data[1] == '\0') { o_stream_nsend_str(client->output, "-Invalid input\n"); continue; } *data++ = '\0'; group = ipc_group_lookup_name(line); ret = FALSE; if (strcmp(id, "*") == 0) { /* send to everyone */ if (group == NULL) { client_cmd_input(IPC_CMD_STATUS_OK, "", client); } else { ret = ipc_group_cmd(group, data, client_cmd_input, client); } } else if (str_to_uint(id, &id_num) < 0) { o_stream_nsend_str(client->output, t_strdup_printf("-Invalid IPC connection id: %s\n", id)); continue; } else if (group == NULL) { o_stream_nsend_str(client->output, t_strdup_printf("-Unknown IPC group: %s\n", line)); } else if ((conn = ipc_connection_lookup_id(group, id_num)) == NULL) { o_stream_nsend_str(client->output, t_strdup_printf("-Unknown IPC connection id: %u\n", id_num)); continue; } else { ipc_connection_cmd(conn, data, client_cmd_input, client); ret = TRUE; } if (ret) { /* we'll handle commands one at a time. stop reading input until this command is finished. */ io_remove(&client->io); break; } } if (client->input->eof || client->input->stream_errno != 0) client_destroy(&client); } struct client *client_create(int fd) { struct client *client; client = i_new(struct client, 1); client->fd = fd; client->io = io_add(fd, IO_READ, client_input, client); client->input = i_stream_create_fd(fd, SIZE_MAX); client->output = o_stream_create_fd(fd, SIZE_MAX); o_stream_set_no_error_handling(client->output, TRUE); DLLIST_PREPEND(&clients, client); return client; } void client_destroy(struct client **_client) { struct client *client = *_client; *_client = NULL; DLLIST_REMOVE(&clients, client); io_remove(&client->io); i_stream_destroy(&client->input); o_stream_destroy(&client->output); if (close(client->fd) < 0) i_error("close(client) failed: %m"); i_free(client); master_service_client_connection_destroyed(master_service); } void clients_destroy_all(void) { while (clients != NULL) { struct client *client = clients; client_destroy(&client); } }