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