1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "imap-urlauth-common.h"
4 #include "array.h"
5 #include "ioloop.h"
6 #include "net.h"
7 #include "fdpass.h"
8 #include "istream.h"
9 #include "ostream.h"
10 #include "str.h"
11 #include "strescape.h"
12 #include "eacces-error.h"
13 #include "llist.h"
14 #include "hostpid.h"
15 #include "execv-const.h"
16 #include "env-util.h"
17 #include "var-expand.h"
18 #include "restrict-access.h"
19 #include "master-service.h"
20 #include "master-interface.h"
21 
22 #include <unistd.h>
23 #include <sys/wait.h>
24 
25 #define IMAP_URLAUTH_PROTOCOL_MAJOR_VERSION 1
26 #define IMAP_URLAUTH_PROTOCOL_MINOR_VERSION 0
27 
28 #define IMAP_URLAUTH_WORKER_SOCKET "imap-urlauth-worker"
29 
30 /* max. length of input lines (URLs) */
31 #define MAX_INBUF_SIZE 2048
32 
33 /* Disconnect client after idling this many milliseconds */
34 #define CLIENT_IDLE_TIMEOUT_MSECS (10*60*1000)
35 
36 #define USER_EXECUTABLE "imap-urlauth-worker"
37 
38 #define IS_STANDALONE() \
39         (getenv(MASTER_IS_PARENT_ENV) == NULL)
40 
41 static struct event_category event_category_urlauth = {
42 	.name = "imap-urlauth",
43 };
44 
45 struct client *imap_urlauth_clients;
46 unsigned int imap_urlauth_client_count;
47 
48 static int client_worker_connect(struct client *client);
49 static void client_worker_disconnect(struct client *client);
50 static void client_worker_input(struct client *client);
51 
client_create(const char * service,const char * username,int fd_in,int fd_out,const struct imap_urlauth_settings * set,struct client ** client_r)52 int client_create(const char *service, const char *username,
53 		  int fd_in, int fd_out, const struct imap_urlauth_settings *set,
54 		  struct client **client_r)
55 {
56 	struct client *client;
57 	const char *app;
58 
59 	/* always use nonblocking I/O */
60 	net_set_nonblock(fd_in, TRUE);
61 	net_set_nonblock(fd_out, TRUE);
62 
63 	client = i_new(struct client, 1);
64 	client->fd_in = fd_in;
65 	client->fd_out = fd_out;
66 	client->fd_ctrl = -1;
67 	client->set = set;
68 
69 	client->event = event_create(NULL);
70 	event_set_forced_debug(client->event, set->mail_debug);
71 	event_add_category(client->event, &event_category_urlauth);
72 	event_set_append_log_prefix(client->event, t_strdup_printf(
73 		"user %s: ", username));
74 
75 	if (client_worker_connect(client) < 0) {
76 		event_unref(&client->event);
77 		i_free(client);
78 		return -1;
79 	}
80 
81 	/* determine user's special privileges */
82 	i_array_init(&client->access_apps, 4);
83 	if (username != NULL) {
84 		if (set->imap_urlauth_submit_user != NULL &&
85 		    strcmp(set->imap_urlauth_submit_user, username) == 0) {
86 			e_debug(client->event, "User has URLAUTH submit access");
87 			app = "submit+";
88 			array_push_back(&client->access_apps, &app);
89 		}
90 		if (set->imap_urlauth_stream_user != NULL &&
91 		    strcmp(set->imap_urlauth_stream_user, username) == 0) {
92 			e_debug(client->event, "User has URLAUTH stream access");
93 			app = "stream";
94 			array_push_back(&client->access_apps, &app);
95 		}
96 	}
97 
98 	client->username = i_strdup(username);
99 	client->service = i_strdup(service);
100 
101 	client->output = o_stream_create_fd(fd_out, SIZE_MAX);
102 
103 	imap_urlauth_client_count++;
104 	DLLIST_PREPEND(&imap_urlauth_clients, client);
105 
106 	imap_urlauth_refresh_proctitle();
107 	*client_r = client;
108 	return 0;
109 }
110 
client_send_line(struct client * client,const char * fmt,...)111 void client_send_line(struct client *client, const char *fmt, ...)
112 {
113 	va_list va;
114 	ssize_t ret;
115 
116 	if (client->output->closed)
117 		return;
118 
119 	va_start(va, fmt);
120 
121 	T_BEGIN {
122 		string_t *str;
123 
124 		str = t_str_new(256);
125 		str_vprintfa(str, fmt, va);
126 		str_append(str, "\n");
127 
128 		ret = o_stream_send(client->output,
129 				    str_data(str), str_len(str));
130 		i_assert(ret < 0 || (size_t)ret == str_len(str));
131 	} T_END;
132 
133 	va_end(va);
134 }
135 
client_worker_connect(struct client * client)136 static int client_worker_connect(struct client *client)
137 {
138 	static const char handshake[] = "VERSION\timap-urlauth-worker\t2\t0\n";
139 	const char *socket_path;
140 	ssize_t ret;
141 	unsigned char data;
142 
143 	socket_path = t_strconcat(client->set->base_dir,
144 				  "/"IMAP_URLAUTH_WORKER_SOCKET, NULL);
145 
146 	e_debug(client->event, "Connecting to worker socket %s", socket_path);
147 
148 	client->fd_ctrl = net_connect_unix_with_retries(socket_path, 1000);
149 	if (client->fd_ctrl < 0) {
150 		if (errno == EACCES) {
151 			e_error(client->event, "imap-urlauth-client: %s",
152 				eacces_error_get("net_connect_unix",
153 						 socket_path));
154 		} else {
155 			e_error(client->event, "imap-urlauth-client: "
156 				"net_connect_unix(%s) failed: %m",
157 				socket_path);
158 		}
159 		return -1;
160 	}
161 
162 	/* transfer one or two fds */
163 	data = (client->fd_in == client->fd_out ? '0' : '1');
164 	ret = fd_send(client->fd_ctrl, client->fd_in, &data, sizeof(data));
165 	if (ret > 0 && client->fd_in != client->fd_out) {
166 		data = '0';
167 		ret = fd_send(client->fd_ctrl, client->fd_out,
168 			      &data, sizeof(data));
169 	}
170 
171 	if (ret <= 0) {
172 		if (ret < 0) {
173 			e_error(client->event, "fd_send(%s, %d) failed: %m",
174 				socket_path, client->fd_ctrl);
175 		} else {
176 			e_error(client->event, "fd_send(%s, %d) failed to send byte",
177 				socket_path, client->fd_ctrl);
178 		}
179 		client_worker_disconnect(client);
180 		return -1;
181 	}
182 
183 	client->ctrl_output = o_stream_create_fd(client->fd_ctrl, SIZE_MAX);
184 
185 	/* send protocol version handshake */
186 	if (o_stream_send_str(client->ctrl_output, handshake) < 0) {
187 		e_error(client->event,
188 			"Error sending handshake to imap-urlauth worker: %m");
189 		client_worker_disconnect(client);
190 		return -1;
191 	}
192 
193 	client->ctrl_input =
194 		i_stream_create_fd(client->fd_ctrl, MAX_INBUF_SIZE);
195 	client->ctrl_io =
196 		io_add(client->fd_ctrl, IO_READ, client_worker_input, client);
197 	return 0;
198 }
199 
client_worker_disconnect(struct client * client)200 void client_worker_disconnect(struct client *client)
201 {
202 	client->worker_state = IMAP_URLAUTH_WORKER_STATE_INACTIVE;
203 
204 	io_remove(&client->ctrl_io);
205 	o_stream_destroy(&client->ctrl_output);
206 	i_stream_destroy(&client->ctrl_input);
207 	if (client->fd_ctrl >= 0) {
208 		net_disconnect(client->fd_ctrl);
209 		client->fd_ctrl = -1;
210 	}
211 }
212 
213 static int
client_worker_input_line(struct client * client,const char * response)214 client_worker_input_line(struct client *client, const char *response)
215 {
216 	const char *const *apps;
217 	unsigned int count, i;
218 	bool restart;
219 	string_t *str;
220 	int ret;
221 
222 	switch (client->worker_state) {
223 	case IMAP_URLAUTH_WORKER_STATE_INACTIVE:
224 		if (strcasecmp(response, "OK") != 0) {
225 			client_disconnect(client, "Worker handshake failed");
226 			return -1;
227 		}
228 		client->worker_state = IMAP_URLAUTH_WORKER_STATE_CONNECTED;
229 
230 		str = t_str_new(256);
231 		str_append(str, "ACCESS\t");
232 		if (client->username != NULL)
233 			str_append_tabescaped(str, client->username);
234 		str_append(str, "\t");
235 		str_append_tabescaped(str, client->service);
236 		if (client->set->mail_debug)
237 			str_append(str, "\tdebug");
238 		if (array_count(&client->access_apps) > 0) {
239 			str_append(str, "\tapps=");
240 			apps = array_get(&client->access_apps, &count);
241 			str_append(str, apps[0]);
242 			for (i = 1; i < count; i++) {
243 				str_append_c(str, ',');
244 				str_append_tabescaped(str, apps[i]);
245 			}
246 		}
247 		str_append(str, "\n");
248 
249 		ret = o_stream_send(client->ctrl_output,
250 				    str_data(str), str_len(str));
251 		i_assert(ret < 0 || (size_t)ret == str_len(str));
252 		if (ret < 0) {
253 			client_disconnect(client,
254 				"Failed to send ACCESS control command to worker");
255 			return -1;
256 		}
257 		break;
258 
259 	case IMAP_URLAUTH_WORKER_STATE_CONNECTED:
260 		if (strcasecmp(response, "OK") != 0) {
261 			client_disconnect(client,
262 				"Failed to negotiate access parameters");
263 			return -1;
264 		}
265 		client->worker_state = IMAP_URLAUTH_WORKER_STATE_ACTIVE;
266 		break;
267 
268 	case IMAP_URLAUTH_WORKER_STATE_ACTIVE:
269 		restart = TRUE;
270 		if (strcasecmp(response, "DISCONNECTED") == 0) {
271 			/* worker detected client disconnect */
272 			restart = FALSE;
273 		} else if (strcasecmp(response, "FINISHED") != 0) {
274 			/* unknown response */
275 			client_disconnect(client,
276 				"Worker finished with unknown response");
277 			return -1;
278 		}
279 
280 		e_debug(client->event, "Worker finished successfully");
281 
282 		if (restart) {
283 			/* connect to new worker for accessing different user */
284 			client_worker_disconnect(client);
285 			if (client_worker_connect(client) < 0) {
286 				client_disconnect(client,
287 					"Failed to connect to new worker");
288 				return -1;
289 			}
290 
291 			/* indicate success of "END" command */
292 			client_send_line(client, "OK");
293 		} else {
294 			client_disconnect(client, "Client disconnected");
295 		}
296 		return -1;
297  	default:
298 		i_unreached();
299 	}
300 	return 0;
301 }
302 
client_worker_input(struct client * client)303 void client_worker_input(struct client *client)
304 {
305 	struct istream *input = client->ctrl_input;
306 	const char *line;
307 
308 	if (input->closed) {
309 		/* disconnected */
310 		client_disconnect(client, "Worker disconnected unexpectedly");
311 		return;
312 	}
313 
314 	switch (i_stream_read(input)) {
315 	case -1:
316 		/* disconnected */
317 		client_disconnect(client, "Worker disconnected unexpectedly");
318 		return;
319 	case -2:
320 		/* input buffer full */
321 		client_disconnect(client, "Worker sent too large input");
322 		return;
323 	}
324 
325 	while ((line = i_stream_next_line(input)) != NULL) {
326 		if (client_worker_input_line(client, line) < 0)
327 			return;
328 	}
329 }
330 
client_destroy(struct client * client,const char * reason)331 void client_destroy(struct client *client, const char *reason)
332 {
333 	i_assert(reason != NULL || client->disconnected);
334 
335 	if (!client->disconnected)
336 		e_info(client->event, "Disconnected: %s", reason);
337 
338 	imap_urlauth_client_count--;
339 	DLLIST_REMOVE(&imap_urlauth_clients, client);
340 
341 	timeout_remove(&client->to_idle);
342 
343 	client_worker_disconnect(client);
344 
345 	o_stream_destroy(&client->output);
346 
347 	fd_close_maybe_stdio(&client->fd_in, &client->fd_out);
348 
349 	event_unref(&client->event);
350 
351 	i_free(client->username);
352 	i_free(client->service);
353 	array_free(&client->access_apps);
354 	i_free(client);
355 
356 	master_service_client_connection_destroyed(master_service);
357 	imap_urlauth_refresh_proctitle();
358 }
359 
client_destroy_timeout(struct client * client)360 static void client_destroy_timeout(struct client *client)
361 {
362 	client_destroy(client, NULL);
363 }
364 
client_disconnect(struct client * client,const char * reason)365 void client_disconnect(struct client *client, const char *reason)
366 {
367 	if (client->disconnected)
368 		return;
369 
370 	client->disconnected = TRUE;
371 	e_info(client->event, "Disconnected: %s", reason);
372 
373 	client->to_idle = timeout_add(0, client_destroy_timeout, client);
374 }
375 
clients_destroy_all(void)376 void clients_destroy_all(void)
377 {
378 	while (imap_urlauth_clients != NULL)
379 		client_destroy(imap_urlauth_clients, "Server shutting down.");
380 }
381