1 /* $Id$ */
2 /* Copyright (c) 2011-2015 Pierre Pronchery <khorben@defora.org> */
3 /* This file is part of DeforaOS Desktop Mailer */
4 /* This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>. */
15 /* FIXME:
16 * - really queue commands with callbacks
17 * - openssl should be more explicit when SSL_set_fd() is missing (no BIO)
18 * - support multiple connections? */
19
20
21
22 #include <sys/socket.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <stdint.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <netinet/in.h>
31 #include <arpa/inet.h>
32 #include <openssl/err.h>
33 #include <openssl/x509.h>
34 #include <glib.h>
35 #include <System.h>
36 #include "Mailer/account.h"
37 #include "common.c"
38
39
40 /* POP3 */
41 /* private */
42 /* types */
43 struct _AccountFolder
44 {
45 Folder * folder;
46
47 AccountMessage ** messages;
48 size_t messages_cnt;
49 };
50
51 struct _AccountMessage
52 {
53 Message * message;
54
55 unsigned int id;
56 };
57
58 typedef enum _POP3CommandStatus
59 {
60 P3CS_QUEUED = 0,
61 P3CS_SENT,
62 P3CS_ERROR,
63 P3CS_PARSING,
64 P3CS_OK
65 } POP3CommandStatus;
66
67 typedef enum _POP3ConfigValue
68 {
69 P3CV_USERNAME = 0,
70 P3CV_PASSWORD,
71 P3CV_HOSTNAME,
72 P3CV_PORT,
73 P3CV_SSL,
74 P3CV_DELETE
75 } POP3ConfigValue;
76 #define P3CV_LAST P3CV_DELETE
77 #define P3CV_COUNT (P3CV_LAST + 1)
78
79 typedef enum _POP3Context
80 {
81 P3C_INIT = 0,
82 P3C_AUTHORIZATION_USER,
83 P3C_AUTHORIZATION_PASS,
84 P3C_NOOP,
85 P3C_TRANSACTION_LIST,
86 P3C_TRANSACTION_RETR,
87 P3C_TRANSACTION_STAT,
88 P3C_TRANSACTION_TOP
89 } POP3Context;
90
91 typedef struct _POP3Command
92 {
93 POP3CommandStatus status;
94 POP3Context context;
95 char * buf;
96 size_t buf_cnt;
97
98 union
99 {
100 struct
101 {
102 unsigned int id;
103 gboolean body;
104 AccountMessage * message;
105 } transaction_retr, transaction_top;
106 } data;
107 } POP3Command;
108
109 typedef struct _AccountPlugin
110 {
111 AccountPluginHelper * helper;
112
113 AccountConfig * config;
114
115 int fd;
116 SSL * ssl;
117 guint source;
118
119 AccountFolder inbox;
120 AccountFolder trash;
121
122 GIOChannel * channel;
123 char * rd_buf;
124 size_t rd_buf_cnt;
125 guint rd_source;
126 guint wr_source;
127
128 POP3Command * queue;
129 size_t queue_cnt;
130 } POP3;
131
132
133 /* variables */
134 static char const _pop3_type[] = "POP3";
135 static char const _pop3_name[] = "POP3 server";
136
137 static AccountConfig _pop3_config[P3CV_COUNT + 1] =
138 {
139 { "username", "Username", ACT_STRING, NULL },
140 { "password", "Password", ACT_PASSWORD, NULL },
141 { "hostname", "Server hostname", ACT_STRING, NULL },
142 { "port", "Server port", ACT_UINT16, 110 },
143 { "ssl", "Use SSL", ACT_BOOLEAN, NULL },
144 { "delete", "Delete read mails on server",
145 ACT_BOOLEAN, NULL },
146 { NULL, NULL, ACT_NONE, NULL }
147 };
148
149
150 /* prototypes */
151 /* plug-in */
152 static POP3 * _pop3_init(AccountPluginHelper * helper);
153 static int _pop3_destroy(POP3 * pop3);
154 static AccountConfig * _pop3_get_config(POP3 * pop3);
155 static int _pop3_start(POP3 * pop3);
156 static void _pop3_stop(POP3 * pop3);
157 static int _pop3_refresh(POP3 * pop3, AccountFolder * folder,
158 AccountMessage * message);
159
160 /* useful */
161 static POP3Command * _pop3_command(POP3 * pop3, POP3Context context,
162 char const * command);
163 static int _pop3_parse(POP3 * pop3);
164
165 /* events */
166 static void _pop3_event(POP3 * pop3, AccountEventType type);
167 static void _pop3_event_status(POP3 * pop3, AccountStatus status,
168 char const * message);
169
170 static AccountMessage * _pop3_message_get(POP3 * pop3,
171 AccountFolder * folder, unsigned int id);
172 static AccountMessage * _pop3_message_new(POP3 * pop3,
173 AccountFolder * folder, unsigned int id);
174 static void _pop3_message_delete(POP3 * pop3,
175 AccountMessage * message);
176
177 /* callbacks */
178 static gboolean _on_connect(gpointer data);
179 static gboolean _on_noop(gpointer data);
180 static gboolean _on_watch_can_connect(GIOChannel * source,
181 GIOCondition condition, gpointer data);
182 static gboolean _on_watch_can_handshake(GIOChannel * source,
183 GIOCondition condition, gpointer data);
184 static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition,
185 gpointer data);
186 static gboolean _on_watch_can_read_ssl(GIOChannel * source,
187 GIOCondition condition, gpointer data);
188 static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition,
189 gpointer data);
190 static gboolean _on_watch_can_write_ssl(GIOChannel * source,
191 GIOCondition condition, gpointer data);
192
193
194 /* public */
195 /* variables */
196 AccountPluginDefinition account_plugin =
197 {
198 _pop3_type,
199 _pop3_name,
200 NULL,
201 NULL,
202 _pop3_config,
203 _pop3_init,
204 _pop3_destroy,
205 _pop3_get_config,
206 NULL,
207 _pop3_start,
208 _pop3_stop,
209 _pop3_refresh
210 };
211
212
213 /* protected */
214 /* functions */
215 /* plug-in */
216 /* pop3_init */
_pop3_init(AccountPluginHelper * helper)217 static POP3 * _pop3_init(AccountPluginHelper * helper)
218 {
219 POP3 * pop3;
220
221 if((pop3 = malloc(sizeof(*pop3))) == NULL)
222 return NULL;
223 memset(pop3, 0, sizeof(*pop3));
224 pop3->helper = helper;
225 if((pop3->config = malloc(sizeof(_pop3_config))) == NULL)
226 {
227 free(pop3);
228 return NULL;
229 }
230 memcpy(pop3->config, &_pop3_config, sizeof(_pop3_config));
231 pop3->fd = -1;
232 pop3->inbox.folder = pop3->helper->folder_new(pop3->helper->account,
233 &pop3->inbox, NULL, FT_INBOX, "Inbox");
234 pop3->trash.folder = pop3->helper->folder_new(pop3->helper->account,
235 &pop3->trash, NULL, FT_TRASH, "Trash");
236 pop3->source = g_idle_add(_on_connect, pop3);
237 return pop3;
238 }
239
240
241 /* pop3_destroy */
_pop3_destroy(POP3 * pop3)242 static int _pop3_destroy(POP3 * pop3)
243 {
244 #ifdef DEBUG
245 fprintf(stderr, "DEBUG: %s()\n", __func__);
246 #endif
247
248 if(pop3 == NULL) /* XXX _pop3_destroy() may be called uninitialized */
249 return 0;
250 _pop3_stop(pop3);
251 free(pop3);
252 return 0;
253 }
254
255
256 /* pop3_get_config */
_pop3_get_config(POP3 * pop3)257 static AccountConfig * _pop3_get_config(POP3 * pop3)
258 {
259 return pop3->config;
260 }
261
262
263 /* pop3_start */
_pop3_start(POP3 * pop3)264 static int _pop3_start(POP3 * pop3)
265 {
266 _pop3_event(pop3, AET_STARTED);
267 if(pop3->fd >= 0 || pop3->source != 0)
268 /* already started */
269 return 0;
270 pop3->source = g_idle_add(_on_connect, pop3);
271 return 0;
272 }
273
274
275 /* pop3_stop */
_pop3_stop(POP3 * pop3)276 static void _pop3_stop(POP3 * pop3)
277 {
278 size_t i;
279
280 if(pop3->ssl != NULL)
281 SSL_free(pop3->ssl);
282 pop3->ssl = NULL;
283 if(pop3->rd_source != 0)
284 g_source_remove(pop3->rd_source);
285 free(pop3->rd_buf);
286 if(pop3->wr_source != 0)
287 g_source_remove(pop3->wr_source);
288 if(pop3->source != 0)
289 g_source_remove(pop3->source);
290 if(pop3->channel != NULL)
291 {
292 g_io_channel_shutdown(pop3->channel, TRUE, NULL);
293 g_io_channel_unref(pop3->channel);
294 pop3->fd = -1;
295 }
296 for(i = 0; i < pop3->queue_cnt; i++)
297 free(pop3->queue[i].buf);
298 free(pop3->queue);
299 if(pop3->fd >= 0)
300 close(pop3->fd);
301 _pop3_event(pop3, AET_STOPPED);
302 }
303
304
305 /* pop3_refresh */
_pop3_refresh(POP3 * pop3,AccountFolder * folder,AccountMessage * message)306 static int _pop3_refresh(POP3 * pop3, AccountFolder * folder,
307 AccountMessage * message)
308 {
309 POP3Command * cmd;
310 char buf[32];
311
312 #ifdef DEBUG
313 fprintf(stderr, "DEBUG: %s() %u\n", __func__, (message != NULL)
314 ? message->id : 0);
315 #endif
316 if(message == NULL)
317 return 0;
318 snprintf(buf, sizeof(buf), "%s %u", "RETR", message->id);
319 if((cmd = _pop3_command(pop3, P3C_TRANSACTION_RETR, buf)) == NULL)
320 return -1;
321 cmd->data.transaction_retr.id = message->id;
322 return 0;
323 }
324
325
326 /* private */
327 /* functions */
328 /* useful */
329 /* pop3_command */
_pop3_command(POP3 * pop3,POP3Context context,char const * command)330 static POP3Command * _pop3_command(POP3 * pop3, POP3Context context,
331 char const * command)
332 {
333 POP3Command * p;
334 size_t len;
335
336 #ifdef DEBUG
337 fprintf(stderr, "DEBUG: %s(\"%s\") %p\n", __func__, command,
338 (void *)pop3->channel);
339 #endif
340 /* abort if the command is invalid */
341 if(command == NULL || (len = strlen(command)) == 0)
342 return NULL;
343 /* abort if there is no active connection */
344 if(pop3->channel == NULL)
345 return NULL;
346 /* queue the command */
347 len += 3;
348 if((p = realloc(pop3->queue, sizeof(*p) * (pop3->queue_cnt + 1)))
349 == NULL)
350 return NULL;
351 pop3->queue = p;
352 p = &pop3->queue[pop3->queue_cnt];
353 p->context = context;
354 p->status = P3CS_QUEUED;
355 if((p->buf = malloc(len)) == NULL)
356 return NULL;
357 p->buf_cnt = snprintf(p->buf, len, "%s\r\n", command);
358 memset(&p->data, 0, sizeof(p->data));
359 if(pop3->queue_cnt++ == 0)
360 {
361 if(pop3->source != 0)
362 {
363 /* cancel the pending NOOP operation */
364 g_source_remove(pop3->source);
365 pop3->source = 0;
366 }
367 pop3->wr_source = g_io_add_watch(pop3->channel, G_IO_OUT,
368 (pop3->ssl != NULL) ? _on_watch_can_write_ssl
369 : _on_watch_can_write, pop3);
370 }
371 return p;
372 }
373
374
375 /* pop3_parse */
376 static int _parse_context(POP3 * pop3, char const * answer);
377 static int _parse_context_transaction_retr(POP3 * pop3,
378 char const * answer);
379
_pop3_parse(POP3 * pop3)380 static int _pop3_parse(POP3 * pop3)
381 {
382 int ret = 0;
383 AccountPluginHelper * helper = pop3->helper;
384 size_t i;
385 size_t j;
386
387 #ifdef DEBUG
388 fprintf(stderr, "DEBUG: %s()\n", __func__);
389 #endif
390 for(i = 0, j = 0;; j = ++i)
391 {
392 /* look for carriage return sequences */
393 for(; i < pop3->rd_buf_cnt; i++)
394 if(pop3->rd_buf[i] == '\r' && i + 1 < pop3->rd_buf_cnt
395 && pop3->rd_buf[++i] == '\n')
396 break;
397 /* if no carriage return was found wait for more input */
398 if(i == pop3->rd_buf_cnt)
399 break;
400 /* if no command was sent read more lines */
401 if(pop3->queue_cnt == 0)
402 continue;
403 pop3->rd_buf[i - 1] = '\0';
404 if(pop3->queue[0].status == P3CS_SENT
405 && strncmp("-ERR", &pop3->rd_buf[j], 4) == 0)
406 {
407 pop3->queue[0].status = P3CS_ERROR;
408 helper->error(helper->account, &pop3->rd_buf[j + 4], 1);
409 }
410 else if(pop3->queue[0].status == P3CS_SENT
411 && strncmp("+OK", &pop3->rd_buf[j], 3) == 0)
412 pop3->queue[0].status = P3CS_PARSING;
413 if(_parse_context(pop3, &pop3->rd_buf[j]) != 0)
414 {
415 pop3->queue[0].status = P3CS_ERROR;
416 ret = -1;
417 }
418 }
419 if(j != 0)
420 {
421 pop3->rd_buf_cnt -= j;
422 memmove(pop3->rd_buf, &pop3->rd_buf[j], pop3->rd_buf_cnt);
423 }
424 return ret;
425 }
426
_parse_context(POP3 * pop3,char const * answer)427 static int _parse_context(POP3 * pop3, char const * answer)
428 {
429 int ret = -1;
430 POP3Command * cmd = &pop3->queue[0];
431 char const * p;
432 char * q;
433 unsigned int u;
434 unsigned int v;
435
436 #ifdef DEBUG
437 fprintf(stderr, "DEBUG: %s(\"%s\") %u, %u\n", __func__, answer,
438 pop3->queue[0].context, pop3->queue[0].status);
439 #endif
440 switch(cmd->context)
441 {
442 case P3C_INIT:
443 if(cmd->status != P3CS_PARSING)
444 return 0;
445 cmd->status = P3CS_OK;
446 if((p = pop3->config[0].value) == NULL)
447 return -1;
448 q = g_strdup_printf("%s %s", "USER", p);
449 cmd = _pop3_command(pop3, P3C_AUTHORIZATION_USER, q);
450 g_free(q);
451 return (cmd != NULL) ? 0 : -1;
452 case P3C_AUTHORIZATION_USER:
453 if(cmd->status != P3CS_PARSING)
454 return 0;
455 cmd->status = P3CS_OK;
456 if((p = pop3->config[1].value) == NULL)
457 p = ""; /* assumes an empty password */
458 q = g_strdup_printf("%s %s", "PASS", p);
459 cmd = _pop3_command(pop3, P3C_AUTHORIZATION_PASS, q);
460 g_free(q);
461 return (cmd != NULL) ? 0 : -1;
462 case P3C_AUTHORIZATION_PASS:
463 if(cmd->status != P3CS_PARSING)
464 return 0;
465 cmd->status = P3CS_OK;
466 return (_pop3_command(pop3, P3C_TRANSACTION_STAT,
467 "STAT") != NULL) ? 0 : -1;
468 case P3C_NOOP:
469 if(strncmp(answer, "+OK", 3) == 0)
470 cmd->status = P3CS_OK;
471 return 0;
472 case P3C_TRANSACTION_LIST:
473 if(cmd->status != P3CS_PARSING)
474 return 0;
475 if(strncmp(answer, "+OK", 3) == 0)
476 return 0;
477 if(strcmp(answer, ".") == 0)
478 {
479 cmd->status = P3CS_OK;
480 return 0;
481 }
482 if(sscanf(answer, "%u %u", &u, &v) != 2)
483 return -1;
484 /* FIXME may not be supported by the server */
485 q = g_strdup_printf("%s %u 0", "TOP", u);
486 cmd = _pop3_command(pop3, P3C_TRANSACTION_TOP, q);
487 free(q);
488 cmd->data.transaction_top.id = u;
489 return (cmd != NULL) ? 0 : -1;
490 case P3C_TRANSACTION_RETR:
491 case P3C_TRANSACTION_TOP: /* same as RETR without the body */
492 return _parse_context_transaction_retr(pop3, answer);
493 case P3C_TRANSACTION_STAT:
494 if(cmd->status != P3CS_PARSING)
495 return 0;
496 if(sscanf(answer, "+OK %u %u", &u, &v) != 2)
497 return -1;
498 cmd->status = P3CS_OK;
499 return (_pop3_command(pop3, P3C_TRANSACTION_LIST,
500 "LIST") != NULL) ? 0 : -1;
501 }
502 return ret;
503 }
504
_parse_context_transaction_retr(POP3 * pop3,char const * answer)505 static int _parse_context_transaction_retr(POP3 * pop3,
506 char const * answer)
507 {
508 AccountPluginHelper * helper = pop3->helper;
509 POP3Command * cmd = &pop3->queue[0];
510 AccountMessage * message;
511
512 if(cmd->status != P3CS_PARSING)
513 return 0;
514 if((message = cmd->data.transaction_retr.message) == NULL
515 && strncmp(answer, "+OK", 3) == 0)
516 {
517 cmd->data.transaction_retr.body = FALSE;
518 message = _pop3_message_get(pop3, &pop3->inbox,
519 cmd->data.transaction_retr.id);
520 cmd->data.transaction_retr.message = message;
521 return 0;
522 }
523 if(strcmp(answer, ".") == 0)
524 {
525 cmd->status = P3CS_OK;
526 return 0;
527 }
528 if(answer[0] == '\0')
529 {
530 cmd->data.transaction_retr.body = TRUE;
531 helper->message_set_body(message->message, NULL, 0, 0);
532 return 0;
533 }
534 if(cmd->data.transaction_retr.body)
535 {
536 helper->message_set_body(message->message, answer,
537 strlen(answer), 1);
538 helper->message_set_body(message->message, "\r\n", 2, 1);
539 }
540 else
541 helper->message_set_header(message->message, answer);
542 return 0;
543 }
544
545
546 /* pop3_event */
_pop3_event(POP3 * pop3,AccountEventType type)547 static void _pop3_event(POP3 * pop3, AccountEventType type)
548 {
549 AccountPluginHelper * helper = pop3->helper;
550 AccountEvent event;
551
552 memset(&event, 0, sizeof(event));
553 switch((event.status.type = type))
554 {
555 case AET_STARTED:
556 case AET_STOPPED:
557 break;
558 default:
559 /* XXX not handled */
560 return;
561 }
562 helper->event(helper->account, &event);
563 }
564
565
566 /* pop3_event_status */
_pop3_event_status(POP3 * pop3,AccountStatus status,char const * message)567 static void _pop3_event_status(POP3 * pop3, AccountStatus status,
568 char const * message)
569 {
570 AccountPluginHelper * helper = pop3->helper;
571 AccountEvent event;
572
573 memset(&event, 0, sizeof(event));
574 event.status.type = AET_STATUS;
575 event.status.status = status;
576 event.status.message = message;
577 helper->event(helper->account, &event);
578 }
579
580
581 /* pop3_message_get */
_pop3_message_get(POP3 * pop3,AccountFolder * folder,unsigned int id)582 static AccountMessage * _pop3_message_get(POP3 * pop3,
583 AccountFolder * folder, unsigned int id)
584 {
585 size_t i;
586
587 #ifdef DEBUG
588 fprintf(stderr, "DEBUG: %s(%p, %u)\n", __func__, (void *)folder, id);
589 #endif
590 for(i = 0; i < folder->messages_cnt; i++)
591 if(folder->messages[i]->id == id)
592 return folder->messages[i];
593 return _pop3_message_new(pop3, folder, id);
594 }
595
596
597 /* pop3_message_new */
_pop3_message_new(POP3 * pop3,AccountFolder * folder,unsigned int id)598 static AccountMessage * _pop3_message_new(POP3 * pop3,
599 AccountFolder * folder, unsigned int id)
600 {
601 AccountPluginHelper * helper = pop3->helper;
602 AccountMessage * message;
603 AccountMessage ** p;
604
605 if((p = realloc(folder->messages, sizeof(*p)
606 * (folder->messages_cnt + 1))) == NULL)
607 return NULL;
608 folder->messages = p;
609 if((message = object_new(sizeof(*message))) == NULL)
610 return NULL;
611 message->id = id;
612 if((message->message = helper->message_new(helper->account,
613 folder->folder, message)) == NULL)
614 {
615 _pop3_message_delete(pop3, message);
616 return NULL;
617 }
618 folder->messages[folder->messages_cnt++] = message;
619 return message;
620 }
621
622
623 /* pop3_message_delete */
_pop3_message_delete(POP3 * pop3,AccountMessage * message)624 static void _pop3_message_delete(POP3 * pop3,
625 AccountMessage * message)
626 {
627 if(message->message != NULL)
628 pop3->helper->message_delete(message->message);
629 object_delete(message);
630 }
631
632
633 /* callbacks */
634 /* on_idle */
635 static int _connect_channel(POP3 * pop3);
636
_on_connect(gpointer data)637 static gboolean _on_connect(gpointer data)
638 {
639 POP3 * pop3 = data;
640 AccountPluginHelper * helper = pop3->helper;
641 char const * hostname;
642 char const * p;
643 uint16_t port;
644 struct addrinfo * ai;
645 struct sockaddr_in * sa;
646 int res;
647 char buf[128];
648
649 #ifdef DEBUG
650 fprintf(stderr, "DEBUG: %s()\n", __func__);
651 #endif
652 pop3->source = 0;
653 /* get the hostname and port */
654 if((hostname = pop3->config[P3CV_HOSTNAME].value) == NULL)
655 {
656 helper->error(helper->account, "No hostname set", 1);
657 return FALSE;
658 }
659 if((p = pop3->config[P3CV_PORT].value) == NULL)
660 return FALSE;
661 port = (unsigned long)p;
662 /* lookup the address */
663 if(_common_lookup(hostname, port, &ai) != 0)
664 {
665 helper->error(helper->account, error_get(NULL), 1);
666 return FALSE;
667 }
668 /* create the socket */
669 if((pop3->fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
670 {
671 helper->error(helper->account, strerror(errno), 1);
672 _pop3_stop(pop3);
673 return FALSE;
674 }
675 if((res = fcntl(pop3->fd, F_GETFL)) >= 0
676 && fcntl(pop3->fd, F_SETFL, res | O_NONBLOCK) == -1)
677 /* ignore this error */
678 /* FIXME report properly as a warning instead */
679 helper->error(NULL, strerror(errno), 1);
680 /* report the current status */
681 if(ai->ai_family == AF_INET)
682 {
683 sa = (struct sockaddr_in *)ai->ai_addr;
684 snprintf(buf, sizeof(buf), "Connecting to %s (%s:%u)", hostname,
685 inet_ntoa(sa->sin_addr), port);
686 }
687 else
688 snprintf(buf, sizeof(buf), "Connecting to %s", hostname);
689 _pop3_event_status(pop3, AS_CONNECTING, buf);
690 /* connect to the remote host */
691 if((connect(pop3->fd, (struct sockaddr *)&sa, sizeof(sa)) != 0
692 && errno != EINPROGRESS)
693 || _connect_channel(pop3) != 0)
694 {
695 snprintf(buf, sizeof(buf), "%s (%s)", "Connection failed",
696 strerror(errno));
697 helper->error(helper->account, buf, 1);
698 _pop3_stop(pop3);
699 freeaddrinfo(ai);
700 return FALSE;
701 }
702 pop3->wr_source = g_io_add_watch(pop3->channel, G_IO_OUT,
703 _on_watch_can_connect, pop3);
704 freeaddrinfo(ai);
705 return FALSE;
706 }
707
_connect_channel(POP3 * pop3)708 static int _connect_channel(POP3 * pop3)
709 {
710 AccountPluginHelper * helper = pop3->helper;
711 GError * error = NULL;
712
713 #ifdef DEBUG
714 fprintf(stderr, "DEBUG: %s()\n", __func__);
715 #endif
716 /* prepare queue */
717 if((pop3->queue = malloc(sizeof(*pop3->queue))) == NULL)
718 return -helper->error(helper->account, strerror(errno), 1);
719 pop3->queue[0].context = P3C_INIT;
720 pop3->queue[0].status = P3CS_SENT;
721 pop3->queue[0].buf = NULL;
722 pop3->queue[0].buf_cnt = 0;
723 pop3->queue_cnt = 1;
724 /* setup channel */
725 pop3->channel = g_io_channel_unix_new(pop3->fd);
726 g_io_channel_set_encoding(pop3->channel, NULL, &error);
727 g_io_channel_set_buffered(pop3->channel, FALSE);
728 return 0;
729 }
730
731
732 /* on_noop */
_on_noop(gpointer data)733 static gboolean _on_noop(gpointer data)
734 {
735 POP3 * pop3 = data;
736
737 _pop3_command(pop3, P3C_NOOP, "NOOP");
738 pop3->source = 0;
739 return FALSE;
740 }
741
742
743 /* on_watch_can_connect */
_on_watch_can_connect(GIOChannel * source,GIOCondition condition,gpointer data)744 static gboolean _on_watch_can_connect(GIOChannel * source,
745 GIOCondition condition, gpointer data)
746 {
747 POP3 * pop3 = data;
748 AccountPluginHelper * helper = pop3->helper;
749 int res;
750 socklen_t s = sizeof(res);
751 char const * hostname = pop3->config[P3CV_HOSTNAME].value;
752 uint16_t port = (unsigned long)pop3->config[P3CV_PORT].value;
753 struct addrinfo * ai;
754 struct sockaddr_in * sa;
755 SSL_CTX * ssl_ctx;
756 char buf[128];
757
758 if(condition != G_IO_OUT || source != pop3->channel)
759 return FALSE; /* should not happen */
760 #ifdef DEBUG
761 fprintf(stderr, "DEBUG: %s() connected\n", __func__);
762 #endif
763 if(getsockopt(pop3->fd, SOL_SOCKET, SO_ERROR, &res, &s) != 0
764 || res != 0)
765 {
766 snprintf(buf, sizeof(buf), "%s (%s)", "Connection failed",
767 strerror(res));
768 helper->error(helper->account, buf, 1);
769 _pop3_stop(pop3);
770 return FALSE;
771 }
772 /* XXX remember the address instead */
773 if(_common_lookup(hostname, port, &ai) == 0)
774 {
775 if(ai->ai_family == AF_INET)
776 {
777 sa = (struct sockaddr_in *)ai->ai_addr;
778 snprintf(buf, sizeof(buf), "Connected to %s (%s:%u)",
779 hostname, inet_ntoa(sa->sin_addr),
780 port);
781 }
782 else
783 snprintf(buf, sizeof(buf), "Connected to %s", hostname);
784 _pop3_event_status(pop3, AS_CONNECTED, buf);
785 freeaddrinfo(ai);
786 }
787 pop3->wr_source = 0;
788 /* setup SSL */
789 if(pop3->config[P3CV_SSL].value != NULL)
790 {
791 if((ssl_ctx = helper->get_ssl_context(helper->account)) == NULL)
792 /* FIXME report error */
793 return FALSE;
794 if((pop3->ssl = SSL_new(ssl_ctx)) == NULL)
795 {
796 helper->error(helper->account, ERR_error_string(
797 ERR_get_error(), buf), 1);
798 return FALSE;
799 }
800 if(SSL_set_fd(pop3->ssl, pop3->fd) != 1)
801 {
802 ERR_error_string(ERR_get_error(), buf);
803 SSL_free(pop3->ssl);
804 pop3->ssl = NULL;
805 helper->error(helper->account, buf, 1);
806 return FALSE;
807 }
808 SSL_set_connect_state(pop3->ssl);
809 /* perform initial handshake */
810 pop3->wr_source = g_io_add_watch(pop3->channel, G_IO_OUT,
811 _on_watch_can_handshake, pop3);
812 return FALSE;
813 }
814 /* wait for the server's banner */
815 pop3->rd_source = g_io_add_watch(pop3->channel, G_IO_IN,
816 _on_watch_can_read, pop3);
817 return FALSE;
818 }
819
820
821 /* on_watch_can_handshake */
822 static int _handshake_verify(POP3 * pop3);
823
_on_watch_can_handshake(GIOChannel * source,GIOCondition condition,gpointer data)824 static gboolean _on_watch_can_handshake(GIOChannel * source,
825 GIOCondition condition, gpointer data)
826 {
827 POP3 * pop3 = data;
828 AccountPluginHelper * helper = pop3->helper;
829 int res;
830 int err;
831 char buf[128];
832
833 if((condition != G_IO_IN && condition != G_IO_OUT)
834 || source != pop3->channel || pop3->ssl == NULL)
835 return FALSE; /* should not happen */
836 #ifdef DEBUG
837 fprintf(stderr, "DEBUG: %s()\n", __func__);
838 #endif
839 pop3->wr_source = 0;
840 pop3->rd_source = 0;
841 if((res = SSL_do_handshake(pop3->ssl)) == 1)
842 {
843 if(_handshake_verify(pop3) != 0)
844 {
845 _pop3_stop(pop3);
846 return FALSE;
847 }
848 /* wait for the server's banner */
849 pop3->rd_source = g_io_add_watch(pop3->channel, G_IO_IN,
850 _on_watch_can_read_ssl, pop3);
851 return FALSE;
852 }
853 err = SSL_get_error(pop3->ssl, res);
854 ERR_error_string(err, buf);
855 if(res == 0)
856 {
857 helper->error(helper->account, buf, 1);
858 _pop3_stop(pop3);
859 return FALSE;
860 }
861 if(err == SSL_ERROR_WANT_WRITE)
862 pop3->wr_source = g_io_add_watch(pop3->channel, G_IO_OUT,
863 _on_watch_can_handshake, pop3);
864 else if(err == SSL_ERROR_WANT_READ)
865 pop3->rd_source = g_io_add_watch(pop3->channel, G_IO_IN,
866 _on_watch_can_handshake, pop3);
867 else
868 {
869 helper->error(helper->account, buf, 1);
870 _pop3_stop(pop3);
871 return FALSE;
872 }
873 return FALSE;
874 }
875
_handshake_verify(POP3 * pop3)876 static int _handshake_verify(POP3 * pop3)
877 {
878 AccountPluginHelper * helper = pop3->helper;
879 X509 * x509;
880 char buf[256] = "";
881
882 if(SSL_get_verify_result(pop3->ssl) != X509_V_OK)
883 return helper->confirm(helper->account, "The certificate could"
884 " not be verified.\nConnect anyway?");
885 x509 = SSL_get_peer_certificate(pop3->ssl);
886 X509_NAME_get_text_by_NID(X509_get_subject_name(x509), NID_commonName,
887 buf, sizeof(buf));
888 if(strcasecmp(buf, pop3->config[P3CV_HOSTNAME].value) != 0)
889 return helper->confirm(helper->account, "The certificate could"
890 " not be matched.\nConnect anyway?");
891 return 0;
892 }
893
894
895 /* on_watch_can_read */
_on_watch_can_read(GIOChannel * source,GIOCondition condition,gpointer data)896 static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition,
897 gpointer data)
898 {
899 POP3 * pop3 = data;
900 AccountPluginHelper * helper = pop3->helper;
901 char * p;
902 gsize cnt = 0;
903 GError * error = NULL;
904 GIOStatus status;
905 POP3Command * cmd;
906 const int inc = 256;
907
908 #ifdef DEBUG
909 fprintf(stderr, "DEBUG: %s()\n", __func__);
910 #endif
911 if(condition != G_IO_IN || source != pop3->channel)
912 return FALSE; /* should not happen */
913 if((p = realloc(pop3->rd_buf, pop3->rd_buf_cnt + inc)) == NULL)
914 return TRUE; /* XXX retries immediately (delay?) */
915 pop3->rd_buf = p;
916 status = g_io_channel_read_chars(source,
917 &pop3->rd_buf[pop3->rd_buf_cnt], inc, &cnt, &error);
918 #ifdef DEBUG
919 fprintf(stderr, "%s", "DEBUG: POP3 SERVER: ");
920 fwrite(&pop3->rd_buf[pop3->rd_buf_cnt], sizeof(*p), cnt, stderr);
921 #endif
922 pop3->rd_buf_cnt += cnt;
923 switch(status)
924 {
925 case G_IO_STATUS_NORMAL:
926 break;
927 case G_IO_STATUS_ERROR:
928 helper->error(helper->account, error->message, 1);
929 g_error_free(error);
930 _pop3_stop(pop3);
931 return FALSE;
932 case G_IO_STATUS_EOF:
933 default:
934 _pop3_event_status(pop3, AS_DISCONNECTED, NULL);
935 _pop3_stop(pop3);
936 return FALSE;
937 }
938 if(_pop3_parse(pop3) != 0)
939 {
940 _pop3_stop(pop3);
941 return FALSE;
942 }
943 if(pop3->queue_cnt == 0)
944 return TRUE;
945 cmd = &pop3->queue[0];
946 if(cmd->buf_cnt == 0)
947 {
948 if(cmd->status == P3CS_SENT || cmd->status == P3CS_PARSING)
949 /* begin or keep parsing */
950 return TRUE;
951 else if(cmd->status == P3CS_OK || cmd->status == P3CS_ERROR)
952 /* the current command is completed */
953 memmove(cmd, &pop3->queue[1], sizeof(*cmd)
954 * --pop3->queue_cnt);
955 }
956 if(pop3->queue_cnt == 0)
957 {
958 _pop3_event_status(pop3, AS_IDLE, NULL);
959 pop3->source = g_timeout_add(30000, _on_noop, pop3);
960 }
961 else
962 pop3->wr_source = g_io_add_watch(pop3->channel, G_IO_OUT,
963 _on_watch_can_write, pop3);
964 return TRUE;
965 }
966
967
968 /* on_watch_can_read_ssl */
_on_watch_can_read_ssl(GIOChannel * source,GIOCondition condition,gpointer data)969 static gboolean _on_watch_can_read_ssl(GIOChannel * source,
970 GIOCondition condition, gpointer data)
971 {
972 POP3 * pop3 = data;
973 char * p;
974 int cnt;
975 POP3Command * cmd;
976 char buf[128];
977 const int inc = 16384; /* XXX not reliable with a smaller value */
978
979 #ifdef DEBUG
980 fprintf(stderr, "DEBUG: %s()\n", __func__);
981 #endif
982 if((condition != G_IO_IN && condition != G_IO_OUT)
983 || source != pop3->channel)
984 return FALSE; /* should not happen */
985 if((p = realloc(pop3->rd_buf, pop3->rd_buf_cnt + inc)) == NULL)
986 return TRUE; /* XXX retries immediately (delay?) */
987 pop3->rd_buf = p;
988 if((cnt = SSL_read(pop3->ssl, &pop3->rd_buf[pop3->rd_buf_cnt], inc))
989 <= 0)
990 {
991 if(SSL_get_error(pop3->ssl, cnt) == SSL_ERROR_WANT_WRITE)
992 /* call SSL_read() again when it can send data */
993 pop3->rd_source = g_io_add_watch(pop3->channel,
994 G_IO_OUT, _on_watch_can_read_ssl, pop3);
995 else if(SSL_get_error(pop3->ssl, cnt) == SSL_ERROR_WANT_READ)
996 /* call SSL_read() again when it can read data */
997 pop3->rd_source = g_io_add_watch(pop3->channel, G_IO_IN,
998 _on_watch_can_read_ssl, pop3);
999 else
1000 {
1001 /* unknown error */
1002 pop3->rd_source = 0;
1003 ERR_error_string(SSL_get_error(pop3->ssl, cnt), buf);
1004 _pop3_event_status(pop3, AS_DISCONNECTED, buf);
1005 _pop3_stop(pop3);
1006 }
1007 return FALSE;
1008 }
1009 #ifdef DEBUG
1010 fprintf(stderr, "%s", "DEBUG: POP3 SERVER: ");
1011 fwrite(&pop3->rd_buf[pop3->rd_buf_cnt], sizeof(*p), cnt, stderr);
1012 #endif
1013 pop3->rd_buf_cnt += cnt;
1014 if(_pop3_parse(pop3) != 0)
1015 {
1016 _pop3_stop(pop3);
1017 return FALSE;
1018 }
1019 if(pop3->queue_cnt == 0)
1020 return TRUE;
1021 cmd = &pop3->queue[0];
1022 if(cmd->buf_cnt == 0)
1023 {
1024 if(cmd->status == P3CS_SENT || cmd->status == P3CS_PARSING)
1025 /* begin or keep parsing */
1026 return TRUE;
1027 else if(cmd->status == P3CS_OK || cmd->status == P3CS_ERROR)
1028 /* the current command is completed */
1029 memmove(cmd, &pop3->queue[1], sizeof(*cmd)
1030 * --pop3->queue_cnt);
1031 }
1032 if(pop3->queue_cnt == 0)
1033 {
1034 _pop3_event_status(pop3, AS_IDLE, NULL);
1035 pop3->source = g_timeout_add(30000, _on_noop, pop3);
1036 }
1037 else
1038 pop3->wr_source = g_io_add_watch(pop3->channel, G_IO_OUT,
1039 _on_watch_can_write_ssl, pop3);
1040 return TRUE;
1041 }
1042
1043
1044 /* on_watch_can_write */
_on_watch_can_write(GIOChannel * source,GIOCondition condition,gpointer data)1045 static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition,
1046 gpointer data)
1047 {
1048 POP3 * pop3 = data;
1049 AccountPluginHelper * helper = pop3->helper;
1050 POP3Command * cmd = &pop3->queue[0];
1051 gsize cnt = 0;
1052 GError * error = NULL;
1053 GIOStatus status;
1054 char * p;
1055
1056 #ifdef DEBUG
1057 fprintf(stderr, "DEBUG: %s()\n", __func__);
1058 #endif
1059 if(condition != G_IO_OUT || source != pop3->channel
1060 || pop3->queue_cnt == 0 || cmd->buf_cnt == 0)
1061 return FALSE; /* should not happen */
1062 status = g_io_channel_write_chars(source, cmd->buf, cmd->buf_cnt, &cnt,
1063 &error);
1064 #ifdef DEBUG
1065 fprintf(stderr, "%s", "DEBUG: POP3 CLIENT: ");
1066 fwrite(cmd->buf, sizeof(*p), cnt, stderr);
1067 #endif
1068 if(cnt != 0)
1069 {
1070 cmd->buf_cnt -= cnt;
1071 memmove(cmd->buf, &cmd->buf[cnt], cmd->buf_cnt);
1072 if((p = realloc(cmd->buf, cmd->buf_cnt)) != NULL)
1073 cmd->buf = p; /* we can ignore errors... */
1074 else if(cmd->buf_cnt == 0)
1075 cmd->buf = NULL; /* ...except when it's not one */
1076 }
1077 switch(status)
1078 {
1079 case G_IO_STATUS_NORMAL:
1080 break;
1081 case G_IO_STATUS_ERROR:
1082 helper->error(helper->account, error->message, 1);
1083 g_error_free(error);
1084 _pop3_stop(pop3);
1085 return FALSE;
1086 case G_IO_STATUS_EOF:
1087 default:
1088 _pop3_event_status(pop3, AS_DISCONNECTED, NULL);
1089 _pop3_stop(pop3);
1090 return FALSE;
1091 }
1092 if(cmd->buf_cnt > 0)
1093 return TRUE;
1094 cmd->status = P3CS_SENT;
1095 pop3->wr_source = 0;
1096 if(pop3->rd_source == 0)
1097 /* XXX should not happen */
1098 pop3->rd_source = g_io_add_watch(pop3->channel, G_IO_IN,
1099 _on_watch_can_read, pop3);
1100 return FALSE;
1101 }
1102
1103
1104 /* on_watch_can_write_ssl */
_on_watch_can_write_ssl(GIOChannel * source,GIOCondition condition,gpointer data)1105 static gboolean _on_watch_can_write_ssl(GIOChannel * source,
1106 GIOCondition condition, gpointer data)
1107 {
1108 POP3 * pop3 = data;
1109 POP3Command * cmd = &pop3->queue[0];
1110 int cnt;
1111 char * p;
1112 char buf[128];
1113
1114 #ifdef DEBUG
1115 fprintf(stderr, "DEBUG: %s()\n", __func__);
1116 #endif
1117 if((condition != G_IO_OUT && condition != G_IO_IN)
1118 || source != pop3->channel || pop3->queue_cnt == 0
1119 || cmd->buf_cnt == 0)
1120 return FALSE; /* should not happen */
1121 if((cnt = SSL_write(pop3->ssl, cmd->buf, cmd->buf_cnt)) <= 0)
1122 {
1123 if(SSL_get_error(pop3->ssl, cnt) == SSL_ERROR_WANT_READ)
1124 pop3->wr_source = g_io_add_watch(pop3->channel, G_IO_IN,
1125 _on_watch_can_write_ssl, pop3);
1126 else if(SSL_get_error(pop3->ssl, cnt) == SSL_ERROR_WANT_WRITE)
1127 pop3->wr_source = g_io_add_watch(pop3->channel,
1128 G_IO_OUT, _on_watch_can_write_ssl,
1129 pop3);
1130 else
1131 {
1132 ERR_error_string(SSL_get_error(pop3->ssl, cnt), buf);
1133 _pop3_event_status(pop3, AS_DISCONNECTED, buf);
1134 _pop3_stop(pop3);
1135 }
1136 return FALSE;
1137 }
1138 #ifdef DEBUG
1139 fprintf(stderr, "%s", "DEBUG: POP3 CLIENT: ");
1140 fwrite(cmd->buf, sizeof(*p), cnt, stderr);
1141 #endif
1142 cmd->buf_cnt -= cnt;
1143 memmove(cmd->buf, &cmd->buf[cnt], cmd->buf_cnt);
1144 if((p = realloc(cmd->buf, cmd->buf_cnt)) != NULL)
1145 cmd->buf = p; /* we can ignore errors... */
1146 else if(cmd->buf_cnt == 0)
1147 cmd->buf = NULL; /* ...except when it's not one */
1148 if(cmd->buf_cnt > 0)
1149 return TRUE;
1150 cmd->status = P3CS_SENT;
1151 pop3->wr_source = 0;
1152 if(pop3->rd_source == 0)
1153 /* XXX should not happen */
1154 pop3->rd_source = g_io_add_watch(pop3->channel, G_IO_IN,
1155 _on_watch_can_read_ssl, pop3);
1156 return FALSE;
1157 }
1158