1 /* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "array.h"
5 #include "ioloop.h"
6 #include "net.h"
7 #include "istream.h"
8 #include "istream-chain.h"
9 #include "istream-dot.h"
10 #include "istream-seekable.h"
11 #include "ostream.h"
12 #include "iostream-rawlog.h"
13 #include "iostream-ssl.h"
14 #include "safe-mkstemp.h"
15 #include "base64.h"
16 #include "str.h"
17 #include "dns-lookup.h"
18 #include "pop3c-client.h"
19 
20 #include <unistd.h>
21 
22 #define POP3C_MAX_INBUF_SIZE (1024*32)
23 #define POP3C_DNS_LOOKUP_TIMEOUT_MSECS (1000*30)
24 #define POP3C_CONNECT_TIMEOUT_MSECS (1000*30)
25 #define POP3C_COMMAND_TIMEOUT_MSECS (1000*60*5)
26 
27 enum pop3c_client_state {
28 	/* No connection */
29 	POP3C_CLIENT_STATE_DISCONNECTED = 0,
30 	/* Trying to connect */
31 	POP3C_CLIENT_STATE_CONNECTING,
32 	POP3C_CLIENT_STATE_STARTTLS,
33 	/* Connected, trying to authenticate */
34 	POP3C_CLIENT_STATE_USER,
35 	POP3C_CLIENT_STATE_AUTH,
36 	POP3C_CLIENT_STATE_PASS,
37 	/* Post-authentication, asking for capabilities */
38 	POP3C_CLIENT_STATE_CAPA,
39 	/* Authenticated, ready to accept commands */
40 	POP3C_CLIENT_STATE_DONE
41 };
42 
43 struct pop3c_client_sync_cmd_ctx {
44 	enum pop3c_command_state state;
45 	char *reply;
46 };
47 
48 struct pop3c_client_cmd {
49 	struct istream *input;
50 	struct istream_chain *chain;
51 	bool reading_dot;
52 
53 	pop3c_cmd_callback_t *callback;
54 	void *context;
55 };
56 
57 struct pop3c_client {
58 	pool_t pool;
59 	struct event *event;
60 	struct pop3c_client_settings set;
61 	struct ssl_iostream_context *ssl_ctx;
62 	struct ip_addr ip;
63 
64 	int fd;
65 	struct io *io;
66 	struct istream *input, *raw_input;
67 	struct ostream *output, *raw_output;
68 	struct ssl_iostream *ssl_iostream;
69 	struct timeout *to;
70 	struct dns_lookup *dns_lookup;
71 
72 	enum pop3c_client_state state;
73 	enum pop3c_capability capabilities;
74 	const char *auth_mech;
75 
76 	pop3c_login_callback_t *login_callback;
77 	void *login_context;
78 
79 	ARRAY(struct pop3c_client_cmd) commands;
80 	const char *input_line;
81 	struct istream *dot_input;
82 
83 	bool running:1;
84 };
85 
86 static void
87 pop3c_dns_callback(const struct dns_lookup_result *result,
88 		   struct pop3c_client *client);
89 static void pop3c_client_connect_ip(struct pop3c_client *client);
90 static int pop3c_client_ssl_init(struct pop3c_client *client);
91 static void pop3c_client_input(struct pop3c_client *client);
92 
93 struct pop3c_client *
pop3c_client_init(const struct pop3c_client_settings * set,struct event * event_parent)94 pop3c_client_init(const struct pop3c_client_settings *set,
95 		  struct event *event_parent)
96 {
97 	struct pop3c_client *client;
98 	const char *error;
99 	pool_t pool;
100 
101 	pool = pool_alloconly_create("pop3c client", 1024);
102 	client = p_new(pool, struct pop3c_client, 1);
103 	client->pool = pool;
104 	client->event = event_create(event_parent);
105 	client->fd = -1;
106 	p_array_init(&client->commands, pool, 16);
107 
108 	client->set.debug = set->debug;
109 	client->set.host = p_strdup(pool, set->host);
110 	client->set.port = set->port;
111 	client->set.master_user = p_strdup_empty(pool, set->master_user);
112 	client->set.username = p_strdup(pool, set->username);
113 	client->set.password = p_strdup(pool, set->password);
114 	client->set.dns_client_socket_path =
115 		p_strdup(pool, set->dns_client_socket_path);
116 	client->set.temp_path_prefix = p_strdup(pool, set->temp_path_prefix);
117 	client->set.rawlog_dir = p_strdup(pool, set->rawlog_dir);
118 	client->set.ssl_mode = set->ssl_mode;
119 
120 	if (set->ssl_mode != POP3C_CLIENT_SSL_MODE_NONE) {
121 		ssl_iostream_settings_init_from(client->pool, &client->set.ssl_set, &set->ssl_set);
122 		client->set.ssl_set.verbose_invalid_cert = !client->set.ssl_set.allow_invalid_cert;
123 		if (ssl_iostream_client_context_cache_get(&set->ssl_set,
124 							  &client->ssl_ctx,
125 							  &error) < 0) {
126 			i_error("pop3c(%s:%u): Couldn't initialize SSL context: %s",
127 				set->host, set->port, error);
128 		}
129 	}
130 	return client;
131 }
132 
133 static void
client_login_callback(struct pop3c_client * client,enum pop3c_command_state state,const char * reason)134 client_login_callback(struct pop3c_client *client,
135 		      enum pop3c_command_state state, const char *reason)
136 {
137 	pop3c_login_callback_t *callback = client->login_callback;
138 	void *context = client->login_context;
139 
140 	if (client->login_callback != NULL) {
141 		client->login_callback = NULL;
142 		client->login_context = NULL;
143 		callback(state, reason, context);
144 	}
145 }
146 
147 static void
pop3c_client_async_callback(struct pop3c_client * client,enum pop3c_command_state state,const char * reply)148 pop3c_client_async_callback(struct pop3c_client *client,
149 			    enum pop3c_command_state state, const char *reply)
150 {
151 	struct pop3c_client_cmd *cmd, cmd_copy;
152 	bool running = client->running;
153 
154 	i_assert(reply != NULL);
155 	i_assert(array_count(&client->commands) > 0);
156 
157 	cmd = array_front_modifiable(&client->commands);
158 	if (cmd->input != NULL && state == POP3C_COMMAND_STATE_OK &&
159 	    !cmd->reading_dot) {
160 		/* read the full input into seekable-istream before calling
161 		   the callback */
162 		i_assert(client->dot_input == NULL);
163 		i_stream_chain_append(cmd->chain, client->input);
164 		client->dot_input = cmd->input;
165 		cmd->reading_dot = TRUE;
166 		return;
167 	}
168 	cmd_copy = *cmd;
169 	array_pop_front(&client->commands);
170 
171 	if (cmd_copy.input != NULL) {
172 		i_stream_seek(cmd_copy.input, 0);
173 		i_stream_unref(&cmd_copy.input);
174 	}
175 	if (cmd_copy.callback != NULL)
176 		cmd_copy.callback(state, reply, cmd_copy.context);
177 	if (running)
178 		io_loop_stop(current_ioloop);
179 }
180 
181 static void
pop3c_client_async_callback_disconnected(struct pop3c_client * client)182 pop3c_client_async_callback_disconnected(struct pop3c_client *client)
183 {
184 	pop3c_client_async_callback(client, POP3C_COMMAND_STATE_DISCONNECTED,
185 				    "Disconnected");
186 }
187 
pop3c_client_disconnect(struct pop3c_client * client)188 static void pop3c_client_disconnect(struct pop3c_client *client)
189 {
190 	client->state = POP3C_CLIENT_STATE_DISCONNECTED;
191 
192 	if (client->running)
193 		io_loop_stop(current_ioloop);
194 
195 	if (client->dns_lookup != NULL)
196 		dns_lookup_abort(&client->dns_lookup);
197 	timeout_remove(&client->to);
198 	io_remove(&client->io);
199 	i_stream_destroy(&client->input);
200 	o_stream_destroy(&client->output);
201 	ssl_iostream_destroy(&client->ssl_iostream);
202 	i_close_fd(&client->fd);
203 	while (array_count(&client->commands) > 0)
204 		pop3c_client_async_callback_disconnected(client);
205 	client_login_callback(client, POP3C_COMMAND_STATE_DISCONNECTED,
206 			      "Disconnected");
207 }
208 
pop3c_client_deinit(struct pop3c_client ** _client)209 void pop3c_client_deinit(struct pop3c_client **_client)
210 {
211 	struct pop3c_client *client = *_client;
212 
213 	pop3c_client_disconnect(client);
214 	if (client->ssl_ctx != NULL)
215 		ssl_iostream_context_unref(&client->ssl_ctx);
216 	event_unref(&client->event);
217 	pool_unref(&client->pool);
218 }
219 
pop3c_client_ioloop_changed(struct pop3c_client * client)220 static void pop3c_client_ioloop_changed(struct pop3c_client *client)
221 {
222 	if (client->to != NULL)
223 		client->to = io_loop_move_timeout(&client->to);
224 	if (client->io != NULL)
225 		client->io = io_loop_move_io(&client->io);
226 	if (client->output != NULL)
227 		o_stream_switch_ioloop(client->output);
228 }
229 
pop3c_client_timeout(struct pop3c_client * client)230 static void pop3c_client_timeout(struct pop3c_client *client)
231 {
232 	switch (client->state) {
233 	case POP3C_CLIENT_STATE_CONNECTING:
234 		i_error("pop3c(%s): connect(%s, %u) timed out after %u seconds",
235 			client->set.host, net_ip2addr(&client->ip),
236 			client->set.port, POP3C_CONNECT_TIMEOUT_MSECS/1000);
237 		break;
238 	case POP3C_CLIENT_STATE_DONE:
239 		i_error("pop3c(%s): Command timed out after %u seconds",
240 			client->set.host, POP3C_COMMAND_TIMEOUT_MSECS/1000);
241 		break;
242 	default:
243 		i_error("pop3c(%s): Authentication timed out after %u seconds",
244 			client->set.host, POP3C_CONNECT_TIMEOUT_MSECS/1000);
245 		break;
246 	}
247 	pop3c_client_disconnect(client);
248 }
249 
pop3c_client_dns_lookup(struct pop3c_client * client)250 static int pop3c_client_dns_lookup(struct pop3c_client *client)
251 {
252 	struct dns_lookup_settings dns_set;
253 
254 	i_assert(client->state == POP3C_CLIENT_STATE_CONNECTING);
255 
256 	if (client->set.dns_client_socket_path[0] == '\0') {
257 		struct ip_addr *ips;
258 		unsigned int ips_count;
259 		int ret;
260 
261 		ret = net_gethostbyname(client->set.host, &ips, &ips_count);
262 		if (ret != 0) {
263 			i_error("pop3c(%s): net_gethostbyname() failed: %s",
264 				client->set.host, net_gethosterror(ret));
265 			return -1;
266 		}
267 		i_assert(ips_count > 0);
268 		client->ip = ips[0];
269 		pop3c_client_connect_ip(client);
270 	} else {
271 		i_zero(&dns_set);
272 		dns_set.dns_client_socket_path =
273 			client->set.dns_client_socket_path;
274 		dns_set.timeout_msecs = POP3C_DNS_LOOKUP_TIMEOUT_MSECS;
275 		dns_set.event_parent = client->event;
276 		if (dns_lookup(client->set.host, &dns_set,
277 			       pop3c_dns_callback, client,
278 			       &client->dns_lookup) < 0)
279 			return -1;
280 	}
281 	return 0;
282 }
283 
pop3c_client_wait_one(struct pop3c_client * client)284 void pop3c_client_wait_one(struct pop3c_client *client)
285 {
286 	struct ioloop *ioloop, *prev_ioloop = current_ioloop;
287 	bool timeout_added = FALSE, failed = FALSE;
288 
289 	if (client->state == POP3C_CLIENT_STATE_DISCONNECTED &&
290 	    array_count(&client->commands) > 0) {
291 		while (array_count(&client->commands) > 0)
292 			pop3c_client_async_callback_disconnected(client);
293 		return;
294 	}
295 
296 	i_assert(client->fd != -1 ||
297 		 client->state == POP3C_CLIENT_STATE_CONNECTING);
298 	i_assert(array_count(&client->commands) > 0 ||
299 		 client->state == POP3C_CLIENT_STATE_CONNECTING);
300 
301 	ioloop = io_loop_create();
302 	pop3c_client_ioloop_changed(client);
303 
304 	if (client->ip.family == 0) {
305 		/* we're connecting, start DNS lookup after our ioloop
306 		   is created */
307 		if (pop3c_client_dns_lookup(client) < 0)
308 			failed = TRUE;
309 	} else if (client->to == NULL) {
310 		client->to = timeout_add(POP3C_COMMAND_TIMEOUT_MSECS,
311 					 pop3c_client_timeout, client);
312 		timeout_added = TRUE;
313 	}
314 
315 	if (!failed) {
316 		client->running = TRUE;
317 		io_loop_run(ioloop);
318 		client->running = FALSE;
319 	}
320 
321 	if (timeout_added && client->to != NULL)
322 		timeout_remove(&client->to);
323 
324 	io_loop_set_current(prev_ioloop);
325 	pop3c_client_ioloop_changed(client);
326 	io_loop_set_current(ioloop);
327 	io_loop_destroy(&ioloop);
328 }
329 
pop3c_client_starttls(struct pop3c_client * client)330 static void pop3c_client_starttls(struct pop3c_client *client)
331 {
332 	o_stream_nsend_str(client->output, "STLS\r\n");
333 	client->state = POP3C_CLIENT_STATE_STARTTLS;
334 }
335 
pop3c_client_authenticate1(struct pop3c_client * client)336 static void pop3c_client_authenticate1(struct pop3c_client *client)
337 {
338 	const struct pop3c_client_settings *set = &client->set;
339 
340 	if (client->set.debug) {
341 		if (set->master_user == NULL) {
342 			i_debug("pop3c(%s): Authenticating as '%s' (with USER+PASS)",
343 				client->set.host, set->username);
344 		} else {
345 			i_debug("pop3c(%s): Authenticating as master user '%s' for user '%s' (with SASL PLAIN)",
346 				client->set.host, set->master_user,
347 				set->username);
348 		}
349 	}
350 
351 	if (set->master_user == NULL) {
352 		o_stream_nsend_str(client->output,
353 			t_strdup_printf("USER %s\r\n", set->username));
354 		client->state = POP3C_CLIENT_STATE_USER;
355 	} else {
356 		client->state = POP3C_CLIENT_STATE_AUTH;
357 		o_stream_nsend_str(client->output, "AUTH PLAIN\r\n");
358 	}
359 }
360 
361 static const char *
pop3c_client_get_sasl_plain_request(struct pop3c_client * client)362 pop3c_client_get_sasl_plain_request(struct pop3c_client *client)
363 {
364 	const struct pop3c_client_settings *set = &client->set;
365 	string_t *in, *out;
366 
367 	in = t_str_new(128);
368 	if (set->master_user != NULL) {
369 		str_append(in, set->username);
370 		str_append_c(in, '\0');
371 		str_append(in, set->master_user);
372 	} else {
373 		str_append_c(in, '\0');
374 		str_append(in, set->username);
375 	}
376 	str_append_c(in, '\0');
377 	str_append(in, set->password);
378 
379 	out = t_str_new(128);
380 	base64_encode(str_data(in), str_len(in), out);
381 	str_append(out, "\r\n");
382 	return str_c(out);
383 }
384 
pop3c_client_login_finished(struct pop3c_client * client)385 static void pop3c_client_login_finished(struct pop3c_client *client)
386 {
387 	io_remove(&client->io);
388 	client->io = io_add(client->fd, IO_READ, pop3c_client_input, client);
389 
390 	timeout_remove(&client->to);
391 	client->state = POP3C_CLIENT_STATE_DONE;
392 
393 	if (client->running)
394 		io_loop_stop(current_ioloop);
395 }
396 
397 static int
pop3c_client_prelogin_input_line(struct pop3c_client * client,const char * line)398 pop3c_client_prelogin_input_line(struct pop3c_client *client, const char *line)
399 {
400 	bool success = line[0] == '+';
401 	const char *reply;
402 
403 	switch (client->state) {
404 	case POP3C_CLIENT_STATE_CONNECTING:
405 		if (!success) {
406 			i_error("pop3c(%s): Server sent invalid banner: %s",
407 				client->set.host, line);
408 			return -1;
409 		}
410 		if (client->set.ssl_mode == POP3C_CLIENT_SSL_MODE_STARTTLS)
411 			pop3c_client_starttls(client);
412 		else
413 			pop3c_client_authenticate1(client);
414 		break;
415 	case POP3C_CLIENT_STATE_STARTTLS:
416 		if (!success) {
417 			i_error("pop3c(%s): STLS failed: %s",
418 				client->set.host, line);
419 			return -1;
420 		}
421 		if (pop3c_client_ssl_init(client) < 0)
422 			pop3c_client_disconnect(client);
423 		break;
424 	case POP3C_CLIENT_STATE_USER:
425 		if (!success) {
426 			i_error("pop3c(%s): USER failed: %s",
427 				client->set.host, line);
428 			return -1;
429 		}
430 
431 		/* the PASS reply can take a long time.
432 		   switch to command timeout. */
433 		timeout_remove(&client->to);
434 		client->to = timeout_add(POP3C_COMMAND_TIMEOUT_MSECS,
435 					 pop3c_client_timeout, client);
436 
437 		o_stream_nsend_str(client->output,
438 			t_strdup_printf("PASS %s\r\n", client->set.password));
439 		client->state = POP3C_CLIENT_STATE_PASS;
440 		client->auth_mech = "USER+PASS";
441 		break;
442 	case POP3C_CLIENT_STATE_AUTH:
443 		if (line[0] != '+') {
444 			i_error("pop3c(%s): AUTH PLAIN failed: %s",
445 				client->set.host, line);
446 			return -1;
447 		}
448 		o_stream_nsend_str(client->output,
449 			pop3c_client_get_sasl_plain_request(client));
450 		client->state = POP3C_CLIENT_STATE_PASS;
451 		client->auth_mech = "AUTH PLAIN";
452 		break;
453 	case POP3C_CLIENT_STATE_PASS:
454 		if (client->login_callback != NULL) {
455 			reply = strncasecmp(line, "+OK ", 4) == 0 ? line + 4 :
456 				strncasecmp(line, "-ERR ", 5) == 0 ? line + 5 :
457 				line;
458 			client_login_callback(client, success ?
459 					      POP3C_COMMAND_STATE_OK :
460 					      POP3C_COMMAND_STATE_ERR, reply);
461 		} else if (!success) {
462 			i_error("pop3c(%s): Authentication via %s failed: %s",
463 				client->set.host, client->auth_mech, line);
464 		}
465 		if (!success)
466 			return -1;
467 
468 		o_stream_nsend_str(client->output, "CAPA\r\n");
469 		client->state = POP3C_CLIENT_STATE_CAPA;
470 		break;
471 	case POP3C_CLIENT_STATE_CAPA:
472 		if (strncasecmp(line, "-ERR", 4) == 0) {
473 			/* CAPA command not supported. some commands still
474 			   support UIDL though. */
475 			client->capabilities |= POP3C_CAPABILITY_UIDL;
476 			pop3c_client_login_finished(client);
477 			break;
478 		} else if (strcmp(line, ".") == 0) {
479 			pop3c_client_login_finished(client);
480 			break;
481 		}
482 		if ((client->set.parsed_features & POP3C_FEATURE_NO_PIPELINING) == 0 &&
483 		    strcasecmp(line, "PIPELINING") == 0)
484 			client->capabilities |= POP3C_CAPABILITY_PIPELINING;
485 		else if (strcasecmp(line, "TOP") == 0)
486 			client->capabilities |= POP3C_CAPABILITY_TOP;
487 		else if (strcasecmp(line, "UIDL") == 0)
488 			client->capabilities |= POP3C_CAPABILITY_UIDL;
489 		break;
490 	case POP3C_CLIENT_STATE_DISCONNECTED:
491 	case POP3C_CLIENT_STATE_DONE:
492 		i_unreached();
493 	}
494 	return 0;
495 }
496 
pop3c_client_prelogin_input(struct pop3c_client * client)497 static void pop3c_client_prelogin_input(struct pop3c_client *client)
498 {
499 	const char *line, *errstr;
500 
501 	i_assert(client->state != POP3C_CLIENT_STATE_DONE);
502 
503 	/* we need to read as much as we can with SSL streams to avoid
504 	   hanging */
505 	while ((line = i_stream_read_next_line(client->input)) != NULL) {
506 		if (pop3c_client_prelogin_input_line(client, line) < 0) {
507 			pop3c_client_disconnect(client);
508 			return;
509 		}
510 	}
511 
512 	if (client->input->closed || client->input->eof ||
513 	    client->input->stream_errno != 0) {
514 		/* disconnected */
515 		if (client->ssl_iostream == NULL) {
516 			i_error("pop3c(%s): Server disconnected unexpectedly",
517 				client->set.host);
518 		} else {
519 			errstr = ssl_iostream_get_last_error(client->ssl_iostream);
520 			if (errstr == NULL) {
521 				errstr = client->input->stream_errno == 0 ? "EOF" :
522 					strerror(client->input->stream_errno);
523 			}
524 			i_error("pop3c(%s): Server disconnected: %s",
525 				client->set.host, errstr);
526 		}
527 		pop3c_client_disconnect(client);
528 	}
529 }
530 
pop3c_client_ssl_handshaked(const char ** error_r,void * context)531 static int pop3c_client_ssl_handshaked(const char **error_r, void *context)
532 {
533 	struct pop3c_client *client = context;
534 	const char *error;
535 
536 	if (ssl_iostream_check_cert_validity(client->ssl_iostream,
537 					     client->set.host, &error) == 0) {
538 		if (client->set.debug) {
539 			i_debug("pop3c(%s): SSL handshake successful",
540 				client->set.host);
541 		}
542 		return 0;
543 	} else if (client->set.ssl_set.allow_invalid_cert) {
544 		if (client->set.debug) {
545 			i_debug("pop3c(%s): SSL handshake successful, "
546 				"ignoring invalid certificate: %s",
547 				client->set.host, error);
548 		}
549 		return 0;
550 	} else {
551 		*error_r = error;
552 		return -1;
553 	}
554 }
555 
pop3c_client_ssl_init(struct pop3c_client * client)556 static int pop3c_client_ssl_init(struct pop3c_client *client)
557 {
558 	const char *error;
559 
560 	if (client->ssl_ctx == NULL) {
561 		i_error("pop3c(%s): No SSL context", client->set.host);
562 		return -1;
563 	}
564 
565 	if (client->set.debug)
566 		i_debug("pop3c(%s): Starting SSL handshake", client->set.host);
567 
568 	if (client->raw_input != client->input) {
569 		/* recreate rawlog after STARTTLS */
570 		i_stream_ref(client->raw_input);
571 		o_stream_ref(client->raw_output);
572 		i_stream_destroy(&client->input);
573 		o_stream_destroy(&client->output);
574 		client->input = client->raw_input;
575 		client->output = client->raw_output;
576 	}
577 
578 	if (io_stream_create_ssl_client(client->ssl_ctx, client->set.host,
579 					&client->set.ssl_set, &client->input,
580 					&client->output, &client->ssl_iostream, &error) < 0) {
581 		i_error("pop3c(%s): Couldn't initialize SSL client: %s",
582 			client->set.host, error);
583 		return -1;
584 	}
585 	ssl_iostream_set_handshake_callback(client->ssl_iostream,
586 					    pop3c_client_ssl_handshaked,
587 					    client);
588 	if (ssl_iostream_handshake(client->ssl_iostream) < 0) {
589 		i_error("pop3c(%s): SSL handshake failed: %s", client->set.host,
590 			ssl_iostream_get_last_error(client->ssl_iostream));
591 		return -1;
592 	}
593 
594 	if (*client->set.rawlog_dir != '\0') {
595 		iostream_rawlog_create(client->set.rawlog_dir,
596 				       &client->input, &client->output);
597 	}
598 	return 0;
599 }
600 
pop3c_client_connected(struct pop3c_client * client)601 static void pop3c_client_connected(struct pop3c_client *client)
602 {
603 	int err;
604 
605 	err = net_geterror(client->fd);
606 	if (err != 0) {
607 		i_error("pop3c(%s): connect(%s, %u) failed: %s",
608 			client->set.host, net_ip2addr(&client->ip),
609 			client->set.port, strerror(err));
610 		pop3c_client_disconnect(client);
611 		return;
612 	}
613 	io_remove(&client->io);
614 	client->io = io_add(client->fd, IO_READ,
615 			    pop3c_client_prelogin_input, client);
616 
617 	if (client->set.ssl_mode == POP3C_CLIENT_SSL_MODE_IMMEDIATE) {
618 		if (pop3c_client_ssl_init(client) < 0)
619 			pop3c_client_disconnect(client);
620 	}
621 }
622 
pop3c_client_connect_ip(struct pop3c_client * client)623 static void pop3c_client_connect_ip(struct pop3c_client *client)
624 {
625 	client->fd = net_connect_ip(&client->ip, client->set.port, NULL);
626 	if (client->fd == -1) {
627 		pop3c_client_disconnect(client);
628 		return;
629 	}
630 
631 	client->input = client->raw_input =
632 		i_stream_create_fd(client->fd, POP3C_MAX_INBUF_SIZE);
633 	client->output = client->raw_output =
634 		o_stream_create_fd(client->fd, SIZE_MAX);
635 	o_stream_set_no_error_handling(client->output, TRUE);
636 
637 	if (*client->set.rawlog_dir != '\0' &&
638 	    client->set.ssl_mode != POP3C_CLIENT_SSL_MODE_IMMEDIATE) {
639 		iostream_rawlog_create(client->set.rawlog_dir,
640 				       &client->input, &client->output);
641 	}
642 	client->io = io_add(client->fd, IO_WRITE,
643 			    pop3c_client_connected, client);
644 	client->to = timeout_add(POP3C_CONNECT_TIMEOUT_MSECS,
645 				 pop3c_client_timeout, client);
646 	if (client->set.debug) {
647 		i_debug("pop3c(%s): Connecting to %s:%u", client->set.host,
648 			net_ip2addr(&client->ip), client->set.port);
649 	}
650 }
651 
652 static void
pop3c_dns_callback(const struct dns_lookup_result * result,struct pop3c_client * client)653 pop3c_dns_callback(const struct dns_lookup_result *result,
654 		   struct pop3c_client *client)
655 {
656 	client->dns_lookup = NULL;
657 
658 	if (result->ret != 0) {
659 		i_error("pop3c(%s): dns_lookup() failed: %s",
660 			client->set.host, result->error);
661 		pop3c_client_disconnect(client);
662 		return;
663 	}
664 
665 	i_assert(result->ips_count > 0);
666 	client->ip = result->ips[0];
667 	pop3c_client_connect_ip(client);
668 }
669 
pop3c_client_login(struct pop3c_client * client,pop3c_login_callback_t * callback,void * context)670 void pop3c_client_login(struct pop3c_client *client,
671 			pop3c_login_callback_t *callback, void *context)
672 {
673 	if (client->fd != -1) {
674 		i_assert(callback == NULL);
675 		return;
676 	}
677 	i_assert(client->login_callback == NULL);
678 	client->login_callback = callback;
679 	client->login_context = context;
680 	client->state = POP3C_CLIENT_STATE_CONNECTING;
681 
682 	if (client->set.debug)
683 		i_debug("pop3c(%s): Looking up IP address", client->set.host);
684 }
685 
pop3c_client_is_connected(struct pop3c_client * client)686 bool pop3c_client_is_connected(struct pop3c_client *client)
687 {
688 	return client->fd != -1;
689 }
690 
691 enum pop3c_capability
pop3c_client_get_capabilities(struct pop3c_client * client)692 pop3c_client_get_capabilities(struct pop3c_client *client)
693 {
694 	return client->capabilities;
695 }
696 
pop3c_client_dot_input(struct pop3c_client * client)697 static int pop3c_client_dot_input(struct pop3c_client *client)
698 {
699 	ssize_t ret;
700 
701 	while ((ret = i_stream_read(client->dot_input)) > 0 || ret == -2) {
702 		i_stream_skip(client->dot_input,
703 			      i_stream_get_data_size(client->dot_input));
704 	}
705 	if (ret == 0)
706 		return 0;
707 	i_assert(ret == -1);
708 
709 	if (client->dot_input->stream_errno == 0)
710 		ret = 1;
711 	client->dot_input = NULL;
712 
713 	if (ret > 0) {
714 		/* currently we don't actually care about preserving the
715 		   +OK reply line for multi-line replies, so just return
716 		   it as empty */
717 		pop3c_client_async_callback(client, POP3C_COMMAND_STATE_OK, "");
718 		return 1;
719 	} else {
720 		pop3c_client_async_callback_disconnected(client);
721 		return -1;
722 	}
723 }
724 
725 static int
pop3c_client_input_next_reply(struct pop3c_client * client)726 pop3c_client_input_next_reply(struct pop3c_client *client)
727 {
728 	const char *line;
729 	enum pop3c_command_state state;
730 
731 	line = i_stream_read_next_line(client->input);
732 	if (line == NULL)
733 		return client->input->eof ? -1 : 0;
734 
735 	if (strncasecmp(line, "+OK", 3) == 0) {
736 		line += 3;
737 		state = POP3C_COMMAND_STATE_OK;
738 	} else if (strncasecmp(line, "-ERR", 4) == 0) {
739 		line += 4;
740 		state = POP3C_COMMAND_STATE_ERR;
741 	} else {
742 		i_error("pop3c(%s): Server sent unrecognized line: %s",
743 			client->set.host, line);
744 		state = POP3C_COMMAND_STATE_ERR;
745 	}
746 	if (line[0] == ' ')
747 		line++;
748 	if (array_count(&client->commands) == 0) {
749 		i_error("pop3c(%s): Server sent line when no command was running: %s",
750 			client->set.host, line);
751 	} else {
752 		pop3c_client_async_callback(client, state, line);
753 	}
754 	return 1;
755 }
756 
pop3c_client_input(struct pop3c_client * client)757 static void pop3c_client_input(struct pop3c_client *client)
758 {
759 	int ret;
760 
761 	if (client->to != NULL)
762 		timeout_reset(client->to);
763 	do {
764 		if (client->dot_input != NULL) {
765 			/* continue reading the current multiline reply */
766 			if ((ret = pop3c_client_dot_input(client)) == 0)
767 				return;
768 		} else {
769 			ret = pop3c_client_input_next_reply(client);
770 		}
771 	} while (ret > 0);
772 
773 	if (ret < 0) {
774 		i_error("pop3c(%s): Server disconnected unexpectedly",
775 			client->set.host);
776 		pop3c_client_disconnect(client);
777 	}
778 }
779 
pop3c_client_cmd_reply(enum pop3c_command_state state,const char * reply,void * context)780 static void pop3c_client_cmd_reply(enum pop3c_command_state state,
781 				   const char *reply, void *context)
782 {
783 	struct pop3c_client_sync_cmd_ctx *ctx = context;
784 
785 	i_assert(ctx->reply == NULL);
786 
787 	ctx->state = state;
788 	ctx->reply = i_strdup(reply);
789 }
790 
pop3c_client_cmd_line(struct pop3c_client * client,const char * cmdline,const char ** reply_r)791 int pop3c_client_cmd_line(struct pop3c_client *client, const char *cmdline,
792 			  const char **reply_r)
793 {
794 	struct pop3c_client_sync_cmd_ctx ctx;
795 
796 	i_zero(&ctx);
797 	pop3c_client_cmd_line_async(client, cmdline, pop3c_client_cmd_reply, &ctx);
798 	while (ctx.reply == NULL)
799 		pop3c_client_wait_one(client);
800 	*reply_r = t_strdup(ctx.reply);
801 	i_free(ctx.reply);
802 	return ctx.state == POP3C_COMMAND_STATE_OK ? 0 : -1;
803 }
804 
805 struct pop3c_client_cmd *
pop3c_client_cmd_line_async(struct pop3c_client * client,const char * cmdline,pop3c_cmd_callback_t * callback,void * context)806 pop3c_client_cmd_line_async(struct pop3c_client *client, const char *cmdline,
807 			    pop3c_cmd_callback_t *callback, void *context)
808 {
809 	struct pop3c_client_cmd *cmd;
810 
811 	if ((client->capabilities & POP3C_CAPABILITY_PIPELINING) == 0) {
812 		while (array_count(&client->commands) > 0)
813 			pop3c_client_wait_one(client);
814 	}
815 	i_assert(client->state == POP3C_CLIENT_STATE_DISCONNECTED ||
816 		 client->state == POP3C_CLIENT_STATE_DONE);
817 	if (client->state == POP3C_CLIENT_STATE_DONE)
818 		o_stream_nsend_str(client->output, cmdline);
819 
820 	cmd = array_append_space(&client->commands);
821 	cmd->callback = callback;
822 	cmd->context = context;
823 	return cmd;
824 }
825 
pop3c_client_cmd_line_async_nocb(struct pop3c_client * client,const char * cmdline)826 void pop3c_client_cmd_line_async_nocb(struct pop3c_client *client,
827 				      const char *cmdline)
828 {
829 	pop3c_client_cmd_line_async(client, cmdline, NULL, NULL);
830 }
831 
seekable_fd_callback(const char ** path_r,void * context)832 static int seekable_fd_callback(const char **path_r, void *context)
833 {
834 	struct pop3c_client *client = context;
835 	string_t *path;
836 	int fd;
837 
838 	path = t_str_new(128);
839 	str_append(path, client->set.temp_path_prefix);
840 	fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1);
841 	if (fd == -1) {
842 		i_error("safe_mkstemp(%s) failed: %m", str_c(path));
843 		return -1;
844 	}
845 
846 	/* we just want the fd, unlink it */
847 	if (i_unlink(str_c(path)) < 0) {
848 		/* shouldn't happen.. */
849 		i_close_fd(&fd);
850 		return -1;
851 	}
852 
853 	*path_r = str_c(path);
854 	return fd;
855 }
856 
pop3c_client_cmd_stream(struct pop3c_client * client,const char * cmdline,struct istream ** input_r,const char ** error_r)857 int pop3c_client_cmd_stream(struct pop3c_client *client, const char *cmdline,
858 			    struct istream **input_r, const char **error_r)
859 {
860 	struct pop3c_client_sync_cmd_ctx ctx;
861 	const char *reply;
862 
863 	if (client->state == POP3C_CLIENT_STATE_DISCONNECTED) {
864 		*error_r = "Disconnected from server";
865 		return -1;
866 	}
867 
868 	i_zero(&ctx);
869 	*input_r = pop3c_client_cmd_stream_async(client, cmdline,
870 						 pop3c_client_cmd_reply, &ctx);
871 	while (ctx.reply == NULL)
872 		pop3c_client_wait_one(client);
873 	reply = t_strdup(ctx.reply);
874 	i_free(ctx.reply);
875 
876 	if (ctx.state == POP3C_COMMAND_STATE_OK)
877 		return 0;
878 	i_stream_unref(input_r);
879 	*error_r = reply;
880 	return -1;
881 }
882 
883 struct istream *
pop3c_client_cmd_stream_async(struct pop3c_client * client,const char * cmdline,pop3c_cmd_callback_t * callback,void * context)884 pop3c_client_cmd_stream_async(struct pop3c_client *client, const char *cmdline,
885 			      pop3c_cmd_callback_t *callback, void *context)
886 {
887 	struct istream *input, *inputs[2];
888 	struct pop3c_client_cmd *cmd;
889 
890 	cmd = pop3c_client_cmd_line_async(client, cmdline, callback, context);
891 
892 	input = i_stream_create_chain(&cmd->chain);
893 	inputs[0] = i_stream_create_dot(input, TRUE);
894 	inputs[1] = NULL;
895 	cmd->input = i_stream_create_seekable(inputs, POP3C_MAX_INBUF_SIZE,
896 					      seekable_fd_callback, client);
897 	i_stream_unref(&input);
898 	i_stream_unref(&inputs[0]);
899 
900 	i_stream_ref(cmd->input);
901 	return cmd->input;
902 }
903