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