1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "ioloop.h"
5 #include "net.h"
6 #include "istream.h"
7 #include "ostream.h"
8 #include "str.h"
9 #include "strescape.h"
10 #include "dsync-client.h"
11
12 #include <unistd.h>
13
14 #define DSYNC_FAIL_TIMEOUT_MSECS (1000*5)
15 #define DOVEADM_HANDSHAKE "VERSION\tdoveadm-server\t1\t0\n"
16
17 struct dsync_client {
18 char *path;
19 int fd;
20 struct io *io;
21 struct istream *input;
22 struct ostream *output;
23 struct timeout *to;
24
25 char *dsync_params;
26 char *username;
27 char *state;
28 enum dsync_type sync_type;
29 dsync_callback_t *callback;
30 void *context;
31
32 time_t last_connect_failure;
33 bool handshaked:1;
34 bool cmd_sent:1;
35 };
36
37 struct dsync_client *
dsync_client_init(const char * path,const char * dsync_params)38 dsync_client_init(const char *path, const char *dsync_params)
39 {
40 struct dsync_client *client;
41
42 client = i_new(struct dsync_client, 1);
43 client->path = i_strdup(path);
44 client->fd = -1;
45 client->dsync_params = i_strdup(dsync_params);
46 return client;
47 }
48
dsync_callback(struct dsync_client * client,const char * state,enum dsync_reply reply)49 static void dsync_callback(struct dsync_client *client,
50 const char *state, enum dsync_reply reply)
51 {
52 dsync_callback_t *callback = client->callback;
53 void *context = client->context;
54
55 timeout_remove(&client->to);
56
57 client->callback = NULL;
58 client->context = NULL;
59
60 /* make sure callback doesn't try to reuse this connection, since
61 we can't currently handle it */
62 i_assert(!client->cmd_sent);
63 client->cmd_sent = TRUE;
64 callback(reply, state, context);
65 client->cmd_sent = FALSE;
66 }
67
dsync_close(struct dsync_client * client)68 static void dsync_close(struct dsync_client *client)
69 {
70 client->cmd_sent = FALSE;
71 client->handshaked = FALSE;
72 i_free_and_null(client->state);
73 i_free_and_null(client->username);
74
75 if (client->fd == -1)
76 return;
77
78 io_remove(&client->io);
79 o_stream_destroy(&client->output);
80 i_stream_destroy(&client->input);
81 if (close(client->fd) < 0)
82 i_error("close(dsync) failed: %m");
83 client->fd = -1;
84 }
85
dsync_disconnect(struct dsync_client * client)86 static void dsync_disconnect(struct dsync_client *client)
87 {
88 dsync_close(client);
89 if (client->callback != NULL)
90 dsync_callback(client, "", DSYNC_REPLY_FAIL);
91 }
92
dsync_client_deinit(struct dsync_client ** _client)93 void dsync_client_deinit(struct dsync_client **_client)
94 {
95 struct dsync_client *client = *_client;
96
97 *_client = NULL;
98
99 dsync_disconnect(client);
100 i_free(client->dsync_params);
101 i_free(client->path);
102 i_free(client);
103 }
104
dsync_input_line(struct dsync_client * client,const char * line)105 static int dsync_input_line(struct dsync_client *client, const char *line)
106 {
107 const char *state;
108
109 if (!client->handshaked) {
110 if (strcmp(line, "+") != 0) {
111 i_error("%s: Unexpected handshake: %s",
112 client->path, line);
113 return -1;
114 }
115 client->handshaked = TRUE;
116 return 0;
117 }
118 if (client->callback == NULL) {
119 i_error("%s: Unexpected input: %s", client->path, line);
120 return -1;
121 }
122 if (client->state == NULL) {
123 client->state = i_strdup(t_strcut(line, '\t'));
124 return 0;
125 }
126 state = t_strdup(client->state);
127 line = t_strdup(line);
128 dsync_close(client);
129
130 if (line[0] == '+')
131 dsync_callback(client, state, DSYNC_REPLY_OK);
132 else if (line[0] == '-') {
133 if (strcmp(line+1, "NOUSER") == 0)
134 dsync_callback(client, "", DSYNC_REPLY_NOUSER);
135 else if (strcmp(line+1, "NOREPLICATE") == 0)
136 dsync_callback(client, "", DSYNC_REPLY_NOREPLICATE);
137 else
138 dsync_callback(client, "", DSYNC_REPLY_FAIL);
139 } else {
140 i_error("%s: Invalid input: %s", client->path, line);
141 return -1;
142 }
143 /* FIXME: disconnect after each request for now.
144 doveadm server's getopt() handling seems to break otherwise.
145 also with multiple UIDs doveadm-server fails because setid() fails */
146 return -1;
147 }
148
dsync_input(struct dsync_client * client)149 static void dsync_input(struct dsync_client *client)
150 {
151 const char *line;
152
153 while ((line = i_stream_read_next_line(client->input)) != NULL) {
154 if (dsync_input_line(client, line) < 0) {
155 dsync_disconnect(client);
156 return;
157 }
158 }
159 if (client->input->eof)
160 dsync_disconnect(client);
161 }
162
dsync_connect(struct dsync_client * client)163 static int dsync_connect(struct dsync_client *client)
164 {
165 if (client->fd != -1)
166 return 0;
167
168 if (client->last_connect_failure == ioloop_time)
169 return -1;
170
171 client->fd = net_connect_unix(client->path);
172 if (client->fd == -1) {
173 i_error("net_connect_unix(%s) failed: %m", client->path);
174 client->last_connect_failure = ioloop_time;
175 return -1;
176 }
177 client->last_connect_failure = 0;
178 client->io = io_add(client->fd, IO_READ, dsync_input, client);
179 client->input = i_stream_create_fd(client->fd, SIZE_MAX);
180 client->output = o_stream_create_fd(client->fd, SIZE_MAX);
181 o_stream_set_no_error_handling(client->output, TRUE);
182 o_stream_nsend_str(client->output, DOVEADM_HANDSHAKE);
183 return 0;
184 }
185
dsync_fail_timeout(struct dsync_client * client)186 static void dsync_fail_timeout(struct dsync_client *client)
187 {
188 dsync_disconnect(client);
189 }
190
dsync_client_sync(struct dsync_client * client,const char * username,const char * state,bool full,dsync_callback_t * callback,void * context)191 void dsync_client_sync(struct dsync_client *client,
192 const char *username, const char *state, bool full,
193 dsync_callback_t *callback, void *context)
194 {
195 string_t *cmd;
196 unsigned int pos;
197 char *p;
198
199 i_assert(callback != NULL);
200 i_assert(!dsync_client_is_busy(client));
201
202 client->username = i_strdup(username);
203 client->cmd_sent = TRUE;
204 client->callback = callback;
205 client->context = context;
206 if (full)
207 client->sync_type = DSYNC_TYPE_FULL;
208 else if (state != NULL && state[0] != '\0')
209 client->sync_type = DSYNC_TYPE_INCREMENTAL;
210 else
211 client->sync_type = DSYNC_TYPE_NORMAL;
212
213 if (dsync_connect(client) < 0) {
214 i_assert(client->to == NULL);
215 client->to = timeout_add(DSYNC_FAIL_TIMEOUT_MSECS,
216 dsync_fail_timeout, client);
217 } else {
218 /* <flags> <username> <command> [<args>] */
219 cmd = t_str_new(256);
220 str_append_c(cmd, '\t');
221 str_append_tabescaped(cmd, username);
222 str_append(cmd, "\tsync\t");
223 pos = str_len(cmd);
224 /* insert the parameters. we can do it simply by converting
225 spaces into tabs, it's unlikely we'll ever need anything
226 more complex here. */
227 str_append(cmd, client->dsync_params);
228 p = str_c_modifiable(cmd) + pos;
229 for (; *p != '\0'; p++) {
230 if (*p == ' ')
231 *p = '\t';
232 }
233 if (full)
234 str_append(cmd, "\t-f");
235 str_append(cmd, "\t-s\t");
236 if (state != NULL)
237 str_append(cmd, state);
238 str_append_c(cmd, '\n');
239 o_stream_nsend(client->output, str_data(cmd), str_len(cmd));
240 }
241 }
242
dsync_client_is_busy(struct dsync_client * client)243 bool dsync_client_is_busy(struct dsync_client *client)
244 {
245 return client->cmd_sent;
246 }
247
dsync_client_get_username(struct dsync_client * conn)248 const char *dsync_client_get_username(struct dsync_client *conn)
249 {
250 return conn->username;
251 }
252
dsync_client_get_type(struct dsync_client * conn)253 enum dsync_type dsync_client_get_type(struct dsync_client *conn)
254 {
255 return conn->sync_type;
256 }
257
dsync_client_get_state(struct dsync_client * conn)258 const char *dsync_client_get_state(struct dsync_client *conn)
259 {
260 if (conn->fd == -1) {
261 if (conn->last_connect_failure == 0)
262 return "Not connected";
263 return t_strdup_printf("Failed to connect to '%s' - last attempt %ld secs ago", conn->path,
264 (long)(ioloop_time - conn->last_connect_failure));
265 }
266 if (!dsync_client_is_busy(conn))
267 return "Idle";
268 if (!conn->handshaked)
269 return "Waiting for handshake";
270 if (conn->state == NULL)
271 return "Waiting for dsync to finish";
272 else
273 return "Waiting for dsync to finish (second line)";
274 }
275