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