Desktop

/* $Id: imap4.c,v 1.54 2011/09/15 11:32:20 khorben Exp $ */
/* Copyright (c) 2011 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Mailer */
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* FIXME:
* - support multiple connections? */
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <glib.h>
#include <System.h>
#include "Mailer.h"
/* IMAP4 */
/* private */
/* types */
struct _AccountFolder
{
Folder * folder;
char * name;
AccountMessage ** messages;
size_t messages_cnt;
AccountFolder ** folders;
size_t folders_cnt;
};
struct _AccountMessage
{
Message * message;
unsigned int id;
};
typedef enum _IMAP4CommandStatus
{
I4CS_QUEUED = 0,
I4CS_SENT,
I4CS_ERROR,
I4CS_PARSING,
I4CS_OK
} IMAP4CommandStatus;
typedef enum _IMAP4ConfigValue
{
I4CV_USERNAME = 0,
I4CV_PASSWORD,
I4CV_HOSTNAME,
I4CV_PORT,
I4CV_SSL,
I4CV_PADDING0,
I4CV_PREFIX
} IMAP4Config;
#define I4CV_LAST I4CV_PREFIX
#define I4CV_COUNT (I4CV_LAST + 1)
typedef enum _IMAP4Context
{
I4C_INIT = 0,
I4C_FETCH,
I4C_LIST,
I4C_LOGIN,
I4C_NOOP,
I4C_SELECT,
I4C_STATUS
} IMAP4Context;
typedef enum _IMAP4FetchStatus
{
I4FS_ID = 0,
I4FS_HEADERS,
I4FS_BODY
} IMAP4FetchStatus;
typedef struct _IMAP4Command
{
uint16_t id;
IMAP4CommandStatus status;
IMAP4Context context;
char * buf;
size_t buf_cnt;
union
{
struct
{
AccountFolder * folder;
AccountMessage * message;
unsigned int id;
IMAP4FetchStatus status;
} fetch;
struct
{
AccountFolder * parent;
} list;
struct
{
AccountFolder * folder;
AccountMessage * message;
} select;
struct
{
AccountFolder * folder;
} status;
} data;
} IMAP4Command;
typedef struct _IMAP4
{
int fd;
SSL_CTX * ssl_ctx;
SSL * ssl;
guint source;
GIOChannel * channel;
char * rd_buf;
size_t rd_buf_cnt;
guint rd_source;
guint wr_source;
IMAP4Command * queue;
size_t queue_cnt;
uint16_t queue_id;
AccountFolder folders;
} IMAP4;
/* variables */
static char const _imap4_type[] = "IMAP4";
static char const _imap4_name[] = "IMAP4 server";
AccountConfig _imap4_config[I4CV_COUNT + 1] =
{
{ "username", "Username", ACT_STRING, NULL },
{ "password", "Password", ACT_PASSWORD, NULL },
{ "hostname", "Server hostname", ACT_STRING, NULL },
{ "port", "Server port", ACT_UINT16, 143 },
{ "ssl", "Use SSL", ACT_BOOLEAN, 0 },
#if 0 /* XXX not implemented yet */
{ "sent", "Sent mails folder", ACT_NONE, NULL },
{ "draft", "Draft mails folder", ACT_NONE, NULL },
#endif
{ NULL, NULL, ACT_SEPARATOR, NULL },
{ "prefix", "Prefix", ACT_STRING, NULL },
{ NULL, NULL, ACT_NONE, NULL }
};
/* prototypes */
/* plug-in */
static int _imap4_init(AccountPlugin * plugin);
static int _imap4_destroy(AccountPlugin * plugin);
static int _imap4_refresh(AccountPlugin * plugin, AccountFolder * folder,
AccountMessage * message);
/* useful */
static IMAP4Command * _imap4_command(AccountPlugin * plugin,
IMAP4Context context, char const * command);
static int _imap4_parse(AccountPlugin * plugin);
static void _imap4_reset(AccountPlugin * plugin);
static AccountFolder * _imap4_folder_new(AccountPlugin * plugin,
AccountFolder * parent, char const * name);
static void _imap4_folder_delete(AccountPlugin * plugin,
AccountFolder * folder);
static AccountFolder * _imap4_folder_get_folder(AccountPlugin * plugin,
AccountFolder * folder, char const * name);
static AccountMessage * _imap4_folder_get_message(AccountPlugin * plugin,
AccountFolder * folder, unsigned int id);
static AccountMessage * _imap4_message_new(AccountPlugin * plugin,
AccountFolder * folder, unsigned int id);
static void _imap4_message_delete(AccountPlugin * plugin,
AccountMessage * message);
/* callbacks */
static gboolean _on_connect(gpointer data);
static gboolean _on_noop(gpointer data);
static gboolean _on_reset(gpointer data);
static gboolean _on_watch_can_connect(GIOChannel * source,
GIOCondition condition, gpointer data);
static gboolean _on_watch_can_handshake(GIOChannel * source,
GIOCondition condition, gpointer data);
static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition,
gpointer data);
static gboolean _on_watch_can_read_ssl(GIOChannel * source,
GIOCondition condition, gpointer data);
static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition,
gpointer data);
static gboolean _on_watch_can_write_ssl(GIOChannel * source,
GIOCondition condition, gpointer data);
/* public */
/* variables */
AccountPlugin account_plugin =
{
NULL,
_imap4_type,
_imap4_name,
NULL,
_imap4_config,
_imap4_init,
_imap4_destroy,
NULL,
_imap4_refresh,
NULL
};
/* private */
/* imap4_init */
static int _imap4_init(AccountPlugin * plugin)
{
IMAP4 * imap4;
if((imap4 = malloc(sizeof(*imap4))) == NULL)
return -1;
memset(imap4, 0, sizeof(*imap4));
plugin->priv = imap4;
imap4->fd = -1;
SSL_load_error_strings();
SSL_library_init();
imap4->source = g_idle_add(_on_connect, plugin);
return 0;
}
/* imap4_destroy */
static int _imap4_destroy(AccountPlugin * plugin)
{
IMAP4 * imap4 = plugin->priv;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if(imap4 == NULL) /* XXX _imap4_destroy() may be called uninitialized */
return 0;
_imap4_reset(plugin);
#if 0 /* XXX do not free() */
_imap4_folder_delete(plugin, &imap4->folders);
#endif
free(imap4);
return 0;
}
/* imap4_refresh */
static int _imap4_refresh(AccountPlugin * plugin, AccountFolder * folder,
AccountMessage * message)
{
IMAP4Command * cmd;
int len;
char * buf;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() %u\n", __func__, (message != NULL)
? message->id : 0);
#endif
if((len = snprintf(NULL, 0, "EXAMINE \"%s\"", folder->name)) < 0
|| (buf = malloc(++len)) == NULL)
return -1;
snprintf(buf, len, "EXAMINE \"%s\"", folder->name);
cmd = _imap4_command(plugin, I4C_SELECT, buf);
free(buf);
if(cmd == NULL)
return -1;
cmd->data.select.folder = folder;
cmd->data.select.message = message;
return 0;
}
/* useful */
/* imap4_command */
static IMAP4Command * _imap4_command(AccountPlugin * plugin,
IMAP4Context context, char const * command)
{
IMAP4 * imap4 = plugin->priv;
IMAP4Command * p;
size_t len;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, command);
#endif
/* abort if the command is invalid */
if(command == NULL || (len = strlen(command) + 6) == 0)
return NULL;
/* abort if there is no active connection */
if(imap4->channel == NULL)
return NULL;
/* queue the command */
len += 2;
if((p = realloc(imap4->queue, sizeof(*p) * (imap4->queue_cnt + 1)))
== NULL)
return NULL;
imap4->queue = p;
p = &imap4->queue[imap4->queue_cnt];
p->id = imap4->queue_id++;
p->context = context;
p->status = I4CS_QUEUED;
if((p->buf = malloc(len + 1)) == NULL)
return NULL;
p->buf_cnt = snprintf(p->buf, len + 1, "a%04x %s\r\n", p->id, command);
memset(&p->data, 0, sizeof(p->data));
if(imap4->queue_cnt++ == 0)
{
if(imap4->source != 0)
{
/* cancel the pending NOOP operation */
g_source_remove(imap4->source);
imap4->source = 0;
}
imap4->wr_source = g_io_add_watch(imap4->channel, G_IO_OUT,
(imap4->ssl != NULL) ? _on_watch_can_write_ssl
: _on_watch_can_write, plugin);
}
return p;
}
/* imap4_parse */
static int _parse_context(AccountPlugin * plugin, char const * answer);
static int _context_fetch(AccountPlugin * plugin, char const * answer);
static int _context_init(AccountPlugin * plugin);
static int _context_list(AccountPlugin * plugin, char const * answer);
static int _context_login(AccountPlugin * plugin, char const * answer);
static int _context_select(AccountPlugin * plugin);
static int _context_status(AccountPlugin * plugin, char const * answer);
static int _imap4_parse(AccountPlugin * plugin)
{
AccountPluginHelper * helper = plugin->helper;
IMAP4 * imap4 = plugin->priv;
size_t i;
size_t j;
IMAP4Command * cmd;
char buf[8];
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
for(i = 0, j = 0;; j = ++i)
{
for(; i < imap4->rd_buf_cnt; i++)
if(imap4->rd_buf[i] == '\r' && i + 1 < imap4->rd_buf_cnt
&& imap4->rd_buf[++i] == '\n')
break;
if(i == imap4->rd_buf_cnt)
break;
if(imap4->queue_cnt == 0)
continue;
imap4->rd_buf[i - 1] = '\0';
cmd = &imap4->queue[0];
if(cmd->status == I4CS_SENT)
{
snprintf(buf, sizeof(buf), "a%04x ", cmd->id);
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() expecting \"%s\"\n",
__func__, buf);
#endif
if(strncmp(&imap4->rd_buf[j], "* ", 2) == 0)
j += 2;
else if(strncmp(&imap4->rd_buf[j], buf, 6) == 0)
{
j += 6;
cmd->status = I4CS_PARSING;
if(strncmp("BAD ", &imap4->rd_buf[j], 4) == 0)
helper->error(helper->account,
&imap4->rd_buf[j + 4],
1);
}
}
if(_parse_context(plugin, &imap4->rd_buf[j]) != 0)
cmd->status = I4CS_ERROR;
}
if(j != 0)
{
imap4->rd_buf_cnt -= j;
memmove(imap4->rd_buf, &imap4->rd_buf[j], imap4->rd_buf_cnt);
}
if(imap4->queue == NULL)
return 0;
return (imap4->queue[0].status != I4CS_ERROR) ? 0 : -1;
}
static int _parse_context(AccountPlugin * plugin, char const * answer)
{
int ret = -1;
IMAP4 * imap4 = plugin->priv;
IMAP4Command * cmd = &imap4->queue[0];
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\") %u, %u\n", __func__, answer,
cmd->context, cmd->status);
#endif
switch(cmd->context)
{
case I4C_FETCH:
return _context_fetch(plugin, answer);
case I4C_INIT:
return _context_init(plugin);
case I4C_LIST:
return _context_list(plugin, answer);
case I4C_LOGIN:
return _context_login(plugin, answer);
case I4C_NOOP:
if(cmd->status != I4CS_PARSING)
return 0;
cmd->status = I4CS_OK;
return 0;
case I4C_SELECT:
return _context_select(plugin);
case I4C_STATUS:
return _context_status(plugin, answer);
}
return ret;
}
static int _context_fetch(AccountPlugin * plugin, char const * answer)
{
AccountPluginHelper * helper = plugin->helper;
IMAP4 * imap4 = plugin->priv;
IMAP4Command * cmd = &imap4->queue[0];
AccountFolder * folder = cmd->data.fetch.folder;
AccountMessage * message = cmd->data.fetch.message;
unsigned int id = cmd->data.fetch.id;
char * p;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(%p, \"%s\")\n", __func__, (void *)folder,
answer);
#endif
if(cmd->status == I4CS_PARSING)
{
cmd->status = I4CS_OK;
return 0;
}
switch(cmd->data.fetch.status)
{
case I4FS_BODY:
#if 1 /* FIXME dirty hack for now */
if(strcmp(answer, ")") == 0)
{
cmd->data.fetch.status = I4FS_ID;
return 0;
}
#endif
helper->message_set_body(message->message, answer,
strlen(answer), 1);
helper->message_set_body(message->message, "\r\n", 2,
1);
return 0;
case I4FS_ID:
id = strtol(answer, &p, 10);
if(answer[0] == '\0' || *p != ' ')
return 0;
if(cmd->data.fetch.id != id)
cmd->data.fetch.id = id;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() %u\n", __func__, id);
#endif
answer = p;
if(strncmp(answer, " FETCH ", 7) != 0)
return 0;
if((message = _imap4_folder_get_message(plugin, folder,
id)) != NULL)
{
cmd->data.fetch.status = I4FS_HEADERS;
cmd->data.fetch.message = message;
}
return (message != NULL) ? 0 : -1;
case I4FS_HEADERS:
if(strcmp(answer, "") == 0)
{
cmd->data.fetch.status = I4FS_BODY;
helper->message_set_body(message->message, NULL,
0, 0);
}
else
helper->message_set_header(message->message,
answer);
return 0;
}
return -1;
}
static int _context_init(AccountPlugin * plugin)
{
IMAP4 * imap4 = plugin->priv;
IMAP4Command * cmd = &imap4->queue[0];
char const * p;
char const * q;
gchar * r;
cmd->status = I4CS_OK;
if((p = plugin->config[I4CV_USERNAME].value) == NULL || *p == '\0')
return -1;
if((q = plugin->config[I4CV_PASSWORD].value) == NULL || *q == '\0')
return -1;
r = g_strdup_printf("%s %s %s", "LOGIN", p, q);
cmd = _imap4_command(plugin, I4C_LOGIN, r);
g_free(r);
return (cmd != NULL) ? 0 : -1;
}
static int _context_list(AccountPlugin * plugin, char const * answer)
{
IMAP4 * imap4 = plugin->priv;
IMAP4Command * cmd = &imap4->queue[0];
AccountFolder * folder;
AccountFolder * parent = cmd->data.list.parent;
char const * p;
gchar * q;
char const haschildren[] = "\\HasChildren";
int recurse = 0;
char reference = '\0';
char buf[64];
p = answer;
if(strncmp("OK", p, 2) == 0)
{
cmd->status = I4CS_OK;
return 0;
}
if(strncmp("LIST ", p, 5) != 0)
return -1;
p += 5;
if(*p == '(') /* parse flags */
{
for(p++; *p != '\0' && *p != ')'; p++)
if(*p != '\\')
continue;
else if(strncmp(p, haschildren, sizeof(haschildren) - 1)
== 0) /* FIXME */
{
p += sizeof(haschildren) - 2;
recurse = 1;
}
/* FIXME else skip until end of flag */
p++;
}
if(*p == ' ') /* skip spaces */
for(p++; *p != '\0' && *p == ' '; p++);
if(*p == '\"') /* skip reference */
{
if(p[1] != '\0' && p[1] != '"' && p[2] == '"')
reference = p[1];
for(p++; *p != '\0' && *p++ != '\"';);
}
if(*p == ' ') /* skip spaces */
for(p++; *p != '\0' && *p == ' '; p++);
if(*p == '\"' && sscanf(++p, "%63[^\"]", buf) == 1
&& (folder = _imap4_folder_get_folder(plugin, parent,
buf)) != NULL)
{
buf[63] = '\0';
/* FIXME escape the mailbox name (double quotes...) */
q = g_strdup_printf("%s \"%s\" (%s)", "STATUS", buf,
"MESSAGES RECENT UNSEEN");
if((cmd = _imap4_command(plugin, I4C_STATUS, q)) != NULL)
cmd->data.status.folder = folder;
g_free(q);
if(cmd != NULL && recurse == 1 && reference != '\0')
{
q = g_strdup_printf("%s \"\" \"%s%c%%\"", "LIST", buf,
reference);
if((cmd = _imap4_command(plugin, I4C_LIST, q)) != NULL)
cmd->data.list.parent = folder;
g_free(q);
}
}
return (cmd != NULL) ? 0 : -1;
}
static int _context_login(AccountPlugin * plugin, char const * answer)
{
AccountPluginHelper * helper = plugin->helper;
IMAP4 * imap4 = plugin->priv;
IMAP4Command * cmd = &imap4->queue[0];
char const * prefix = plugin->config[I4CV_PREFIX].value;
gchar * q;
if(cmd->status != I4CS_PARSING)
return 0;
if(strncmp("OK", answer, 2) != 0)
return -helper->error(helper->account, "Authentication failed",
1);
cmd->status = I4CS_OK;
if((q = g_strdup_printf("%s \"\" \"%s%%\"", "LIST", (prefix != NULL)
? prefix : "")) == NULL)
return -1;
cmd = _imap4_command(plugin, I4C_LIST, q);
g_free(q);
if(cmd == NULL)
return -1;
cmd->data.list.parent = &imap4->folders;
return 0;
}
static int _context_select(AccountPlugin * plugin)
{
IMAP4 * imap4 = plugin->priv;
IMAP4Command * cmd = &imap4->queue[0];
AccountFolder * folder;
AccountMessage * message;
char buf[32];
if(cmd->status != I4CS_PARSING)
return 0;
cmd->status = I4CS_OK;
if((folder = cmd->data.select.folder) == NULL)
return 0; /* XXX really is an error */
if((message = cmd->data.select.message) == NULL)
/* FIXME queue commands in batches instead */
snprintf(buf, sizeof(buf), "%s %s %s", "FETCH", "1:*",
"BODY.PEEK[HEADER]");
else
snprintf(buf, sizeof(buf), "%s %u %s", "FETCH", message->id,
"BODY.PEEK[]");
if((cmd = _imap4_command(plugin, I4C_FETCH, buf)) == NULL)
return -1;
cmd->data.fetch.folder = folder;
cmd->data.fetch.message = message;
cmd->data.fetch.id = (message != NULL) ? message->id : 0;
cmd->data.fetch.status = I4FS_ID;
return 0;
}
static int _context_status(AccountPlugin * plugin, char const * answer)
{
IMAP4 * imap4 = plugin->priv;
IMAP4Command * cmd = &imap4->queue[0];
char const * p;
unsigned int m;
unsigned int r;
p = answer;
if(strncmp("OK", p, 2) == 0)
{
cmd->status = I4CS_OK;
return 0;
}
if(strncmp("STATUS ", p, 7) != 0)
return 0;
p += 7;
if(*p == '\"') /* skip reference */
for(p++; *p != '\0' && *p++ != '\"';);
if(*p == ' ') /* skip spaces */
for(p++; *p != '\0' && *p == ' '; p++);
if(*p != '(')
return 0;
if(sscanf(p, "(MESSAGES %u RECENT %u)", &m, &r) != 2)
return 0;
/* FIXME implement */
return 0;
}
/* imap4_reset */
static void _imap4_reset(AccountPlugin * plugin)
{
IMAP4 * imap4 = plugin->priv;
size_t i;
if(imap4->rd_source != 0)
g_source_remove(imap4->rd_source);
imap4->rd_source = 0;
if(imap4->wr_source != 0)
g_source_remove(imap4->wr_source);
imap4->wr_source = 0;
if(imap4->source != 0)
g_source_remove(imap4->source);
imap4->source = 0;
if(imap4->channel != NULL)
{
g_io_channel_shutdown(imap4->channel, TRUE, NULL);
g_io_channel_unref(imap4->channel);
imap4->fd = -1;
}
imap4->channel = NULL;
for(i = 0; i < imap4->queue_cnt; i++)
free(imap4->queue[i].buf);
free(imap4->queue);
imap4->queue = NULL;
imap4->queue_cnt = 0;
if(imap4->ssl_ctx != NULL)
SSL_CTX_free(imap4->ssl_ctx);
imap4->ssl_ctx = NULL;
if(imap4->fd >= 0)
close(imap4->fd);
imap4->fd = -1;
}
/* imap4_folder_new */
static AccountFolder * _imap4_folder_new(AccountPlugin * plugin,
AccountFolder * parent, char const * name)
{
AccountPluginHelper * helper = plugin->helper;
AccountFolder * folder;
AccountFolder ** p;
IMAP4 * imap4 = plugin->priv;
FolderType type = FT_FOLDER;
struct
{
char const * name;
FolderType type;
} name_type[] =
{
{ "Inbox", FT_INBOX },
{ "Drafts", FT_DRAFTS },
{ "Trash", FT_TRASH },
{ "Sent", FT_SENT }
};
size_t i;
size_t len;
if((p = realloc(parent->folders, sizeof(*p) * (parent->folders_cnt
+ 1))) == NULL)
return NULL;
parent->folders = p;
if((folder = object_new(sizeof(*folder))) == NULL)
return NULL;
folder->name = strdup(name);
if(parent == &imap4->folders)
for(i = 0; i < sizeof(name_type) / sizeof(*name_type); i++)
if(strcasecmp(name_type[i].name, name) == 0)
{
type = name_type[i].type;
name = name_type[i].name;
break;
}
/* shorten the name as required if has a parent */
if(parent->name != NULL && (len = strlen(parent->name)) > 0
&& strncmp(name, parent->name, len) == 0)
name = &name[len + 1];
folder->folder = helper->folder_new(helper->account, folder,
parent->folder, type, name);
folder->messages = NULL;
folder->messages_cnt = 0;
folder->folders = NULL;
folder->folders_cnt = 0;
if(folder->folder == NULL || folder->name == NULL)
{
_imap4_folder_delete(plugin, folder);
return NULL;
}
parent->folders[parent->folders_cnt++] = folder;
return folder;
}
/* imap4_folder_delete */
static void _imap4_folder_delete(AccountPlugin * plugin, AccountFolder * folder)
{
size_t i;
if(folder->folder != NULL)
plugin->helper->folder_delete(folder->folder);
free(folder->name);
for(i = 0; i < folder->messages_cnt; i++)
_imap4_message_delete(plugin, folder->messages[i]);
free(folder->messages);
for(i = 0; i < folder->folders_cnt; i++)
_imap4_folder_delete(plugin, folder->folders[i]);
free(folder->folders);
object_delete(folder);
}
/* imap4_folder_get_folder */
static AccountFolder * _imap4_folder_get_folder(AccountPlugin * plugin,
AccountFolder * folder, char const * name)
{
size_t i;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\")\n", __func__, folder->name,
name);
#endif
for(i = 0; i < folder->folders_cnt; i++)
if(strcmp(folder->folders[i]->name, name) == 0)
return folder->folders[i];
return _imap4_folder_new(plugin, folder, name);
}
/* imap4_folder_get_message */
static AccountMessage * _imap4_folder_get_message(AccountPlugin * plugin,
AccountFolder * folder, unsigned int id)
{
size_t i;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\", %u)\n", __func__, folder->name, id);
#endif
for(i = 0; i < folder->messages_cnt; i++)
if(folder->messages[i]->id == id)
return folder->messages[i];
return _imap4_message_new(plugin, folder, id);
}
/* imap4_message_new */
static AccountMessage * _imap4_message_new(AccountPlugin * plugin,
AccountFolder * folder, unsigned int id)
{
AccountPluginHelper * helper = plugin->helper;
AccountMessage * message;
AccountMessage ** p;
if((p = realloc(folder->messages, sizeof(*p)
* (folder->messages_cnt + 1))) == NULL)
return NULL;
folder->messages = p;
if((message = object_new(sizeof(*message))) == NULL)
return NULL;
message->id = id;
if((message->message = helper->message_new(helper->account,
folder->folder, message)) == NULL)
{
_imap4_message_delete(plugin, message);
return NULL;
}
folder->messages[folder->messages_cnt++] = message;
return message;
}
/* imap4_message_delete */
static void _imap4_message_delete(AccountPlugin * plugin,
AccountMessage * message)
{
if(message->message != NULL)
plugin->helper->message_delete(message->message);
object_delete(message);
}
/* callbacks */
/* on_idle */
static int _connect_channel(AccountPlugin * plugin);
static gboolean _on_connect(gpointer data)
{
AccountPlugin * plugin = data;
AccountPluginHelper * helper = plugin->helper;
IMAP4 * imap4 = plugin->priv;
char const * hostname;
char const * p;
struct hostent * he;
unsigned short port;
struct sockaddr_in sa;
int res;
char buf[128];
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
imap4->source = 0;
if((hostname = plugin->config[I4CV_HOSTNAME].value) == NULL)
{
helper->error(NULL, "No hostname set", 1);
return FALSE;
}
if((he = gethostbyname(hostname)) == NULL)
{
helper->error(NULL, hstrerror(h_errno), 1);
return _on_reset(plugin);
}
if((p = plugin->config[I4CV_PORT].value) == NULL)
return FALSE;
port = (unsigned long)p;
/* setup SSL */
if(plugin->config[I4CV_SSL].value != NULL)
if((imap4->ssl_ctx = SSL_CTX_new(SSLv3_client_method())) == NULL
|| SSL_CTX_set_cipher_list(imap4->ssl_ctx,
SSL_DEFAULT_CIPHER_LIST) != 1)
{
helper->error(NULL, ERR_error_string(ERR_get_error(),
buf), 1);
return _on_reset(plugin);
}
if((imap4->fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
helper->error(NULL, strerror(errno), 1);
return _on_reset(plugin);
}
if((res = fcntl(imap4->fd, F_GETFL)) >= 0
&& fcntl(imap4->fd, F_SETFL, res | O_NONBLOCK) == -1)
/* ignore this error */
helper->error(NULL, strerror(errno), 1);
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = *((uint32_t*)he->h_addr_list[0]);
helper->status(helper->account, "Connecting to %s (%s:%u)", hostname,
inet_ntoa(sa.sin_addr), port);
if((connect(imap4->fd, (struct sockaddr *)&sa, sizeof(sa)) != 0
&& errno != EINPROGRESS)
|| _connect_channel(plugin) != 0)
{
helper->error(NULL, strerror(errno), 1);
return _on_reset(plugin);
}
imap4->wr_source = g_io_add_watch(imap4->channel, G_IO_OUT,
_on_watch_can_connect, plugin);
return FALSE;
}
static int _connect_channel(AccountPlugin * plugin)
{
AccountPluginHelper * helper = plugin->helper;
IMAP4 * imap4 = plugin->priv;
GError * error = NULL;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
/* prepare queue */
if((imap4->queue = malloc(sizeof(*imap4->queue))) == NULL)
return -helper->error(helper->account, strerror(errno), 1);
imap4->queue[0].id = 0;
imap4->queue[0].context = I4C_INIT;
imap4->queue[0].status = I4CS_SENT;
imap4->queue[0].buf = NULL;
imap4->queue[0].buf_cnt = 0;
imap4->queue_cnt = 1;
imap4->queue_id = 1;
/* setup channel */
imap4->channel = g_io_channel_unix_new(imap4->fd);
g_io_channel_set_encoding(imap4->channel, NULL, &error);
g_io_channel_set_buffered(imap4->channel, FALSE);
return 0;
}
/* on_noop */
static gboolean _on_noop(gpointer data)
{
AccountPlugin * plugin = data;
IMAP4 * imap4 = plugin->priv;
_imap4_command(plugin, I4C_NOOP, "NOOP");
imap4->source = 0;
return FALSE;
}
/* on_reset */
static gboolean _on_reset(gpointer data)
{
AccountPlugin * plugin = data;
IMAP4 * imap4 = plugin->priv;
_imap4_reset(plugin);
imap4->source = g_timeout_add(3000, _on_connect, plugin);
return FALSE;
}
/* on_watch_can_connect */
static gboolean _on_watch_can_connect(GIOChannel * source,
GIOCondition condition, gpointer data)
{
AccountPlugin * plugin = data;
AccountPluginHelper * helper = plugin->helper;
IMAP4 * imap4 = plugin->priv;
char buf[128];
if(condition != G_IO_OUT || source != imap4->channel)
return FALSE; /* should not happen */
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() connected\n", __func__);
#endif
imap4->wr_source = 0;
/* setup SSL */
if(imap4->ssl_ctx != NULL)
{
if((imap4->ssl = SSL_new(imap4->ssl_ctx)) == NULL)
{
helper->error(NULL, ERR_error_string(ERR_get_error(),
buf), 1);
return FALSE;
}
if(SSL_set_fd(imap4->ssl, imap4->fd) != 1)
; /* FIXME handle error */
SSL_set_connect_state(imap4->ssl);
/* perform initial handshake */
imap4->wr_source = g_io_add_watch(imap4->channel, G_IO_OUT,
_on_watch_can_handshake, plugin);
return FALSE;
}
/* wait for the server's banner */
imap4->rd_source = g_io_add_watch(imap4->channel, G_IO_IN,
_on_watch_can_read, plugin);
return FALSE;
}
/* on_watch_can_handshake */
static gboolean _on_watch_can_handshake(GIOChannel * source,
GIOCondition condition, gpointer data)
{
AccountPlugin * plugin = data;
IMAP4 * imap4 = plugin->priv;
int res;
if((condition != G_IO_IN && condition != G_IO_OUT)
|| source != imap4->channel || imap4->ssl == NULL)
return FALSE; /* should not happen */
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
imap4->wr_source = 0;
imap4->rd_source = 0;
if((res = SSL_do_handshake(imap4->ssl)) == 1)
{
/* wait for the server's banner */
imap4->rd_source = g_io_add_watch(imap4->channel, G_IO_IN,
_on_watch_can_read_ssl, plugin);
return FALSE;
}
else if(res == 0)
{
/* FIXME handle error */
return FALSE;
}
res = SSL_get_error(imap4->ssl, res);
if(res == SSL_ERROR_WANT_WRITE)
imap4->wr_source = g_io_add_watch(imap4->channel, G_IO_OUT,
_on_watch_can_handshake, plugin);
else if(res == SSL_ERROR_WANT_READ)
imap4->rd_source = g_io_add_watch(imap4->channel, G_IO_IN,
_on_watch_can_handshake, plugin);
/* FIXME else handle error */
return FALSE;
}
/* on_watch_can_read */
static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition,
gpointer data)
{
AccountPlugin * plugin = data;
IMAP4 * imap4 = plugin->priv;
char * p;
gsize cnt = 0;
GError * error = NULL;
GIOStatus status;
IMAP4Command * cmd;
if(condition != G_IO_IN || source != imap4->channel)
return FALSE; /* should not happen */
if((p = realloc(imap4->rd_buf, imap4->rd_buf_cnt + 256)) == NULL)
return TRUE; /* XXX retries immediately (delay?) */
imap4->rd_buf = p;
status = g_io_channel_read_chars(source,
&imap4->rd_buf[imap4->rd_buf_cnt], 256, &cnt, &error);
#ifdef DEBUG
fprintf(stderr, "%s", "DEBUG: IMAP4 SERVER: ");
fwrite(&imap4->rd_buf[imap4->rd_buf_cnt], sizeof(*p), cnt, stderr);
#endif
imap4->rd_buf_cnt += cnt;
switch(status)
{
case G_IO_STATUS_NORMAL:
break;
case G_IO_STATUS_ERROR:
plugin->helper->error(NULL, error->message, 1);
case G_IO_STATUS_EOF:
default:
imap4->rd_source = g_idle_add(_on_reset, plugin);
return FALSE;
}
if(_imap4_parse(plugin) != 0)
{
imap4->rd_source = g_idle_add(_on_reset, plugin);
return FALSE;
}
if(imap4->queue_cnt == 0)
{
imap4->rd_source = 0;
return FALSE;
}
cmd = &imap4->queue[0];
if(cmd->buf_cnt == 0)
{
if(cmd->status == I4CS_SENT || cmd->status == I4CS_PARSING)
return TRUE;
else if(cmd->status == I4CS_OK || cmd->status == I4CS_ERROR)
memmove(cmd, &imap4->queue[1], sizeof(*cmd)
* --imap4->queue_cnt);
}
imap4->rd_source = 0;
if(imap4->queue_cnt == 0)
imap4->source = g_timeout_add(30000, _on_noop, plugin);
else
imap4->wr_source = g_io_add_watch(imap4->channel, G_IO_OUT,
_on_watch_can_write, plugin);
return FALSE;
}
/* on_watch_can_read_ssl */
static gboolean _on_watch_can_read_ssl(GIOChannel * source,
GIOCondition condition, gpointer data)
{
AccountPlugin * plugin = data;
IMAP4 * imap4 = plugin->priv;
char * p;
int cnt;
IMAP4Command * cmd;
char buf[128];
const int inc = 1024; /* FIXME does not work with smaller values */
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if(source != imap4->channel)
return FALSE; /* should not happen */
if((p = realloc(imap4->rd_buf, imap4->rd_buf_cnt + inc)) == NULL)
return TRUE; /* XXX retries immediately (delay?) */
imap4->rd_buf = p;
if((cnt = SSL_read(imap4->ssl, &imap4->rd_buf[imap4->rd_buf_cnt], inc))
<= 0)
{
if(cnt < 0 && SSL_get_error(imap4->ssl, cnt)
== SSL_ERROR_WANT_WRITE)
{
imap4->rd_source = g_io_add_watch(imap4->channel,
G_IO_OUT, _on_watch_can_read_ssl,
plugin);
return FALSE;
}
else if(cnt < 0 && SSL_get_error(imap4->ssl, cnt)
== SSL_ERROR_WANT_READ)
{
imap4->rd_source = g_io_add_watch(imap4->channel,
G_IO_IN, _on_watch_can_read_ssl,
plugin);
return FALSE;
}
ERR_error_string(SSL_get_error(imap4->ssl, cnt), buf);
plugin->helper->error(NULL, buf, 1);
imap4->rd_source = g_idle_add(_on_reset, plugin);
return FALSE;
}
#ifdef DEBUG
fprintf(stderr, "%s", "DEBUG: IMAP4 SERVER: ");
fwrite(&imap4->rd_buf[imap4->rd_buf_cnt], sizeof(*p), cnt, stderr);
#endif
imap4->rd_buf_cnt += cnt;
if(_imap4_parse(plugin) != 0)
{
imap4->rd_source = g_idle_add(_on_reset, plugin);
return FALSE;
}
if(imap4->queue_cnt == 0)
{
imap4->rd_source = 0;
return FALSE;
}
cmd = &imap4->queue[0];
if(cmd->buf_cnt == 0)
{
if(cmd->status == I4CS_SENT || cmd->status == I4CS_PARSING)
return TRUE;
else if(cmd->status == I4CS_OK || cmd->status == I4CS_ERROR)
memmove(cmd, &imap4->queue[1], sizeof(*cmd)
* --imap4->queue_cnt);
}
imap4->rd_source = 0;
if(imap4->queue_cnt == 0)
imap4->source = g_timeout_add(30000, _on_noop, plugin);
else
imap4->wr_source = g_io_add_watch(imap4->channel, G_IO_OUT,
_on_watch_can_write_ssl, plugin);
return FALSE;
}
/* on_watch_can_write */
static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition,
gpointer data)
{
AccountPlugin * plugin = data;
IMAP4 * imap4 = plugin->priv;
IMAP4Command * cmd = &imap4->queue[0];
gsize cnt = 0;
GError * error = NULL;
GIOStatus status;
char * p;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if(condition != G_IO_OUT || source != imap4->channel
|| imap4->queue_cnt == 0 || cmd->buf_cnt == 0)
return FALSE; /* should not happen */
status = g_io_channel_write_chars(source, cmd->buf, cmd->buf_cnt, &cnt,
&error);
#ifdef DEBUG
fprintf(stderr, "%s", "DEBUG: IMAP4 CLIENT: ");
fwrite(cmd->buf, sizeof(*p), cnt, stderr);
#endif
if(cnt != 0)
{
cmd->buf_cnt -= cnt;
memmove(cmd->buf, &cmd->buf[cnt], cmd->buf_cnt);
if((p = realloc(cmd->buf, cmd->buf_cnt)) != NULL)
cmd->buf = p; /* we can ignore errors... */
else if(cmd->buf_cnt == 0)
cmd->buf = NULL; /* ...except when it's not one */
}
switch(status)
{
case G_IO_STATUS_NORMAL:
break;
case G_IO_STATUS_ERROR:
plugin->helper->error(NULL, error->message, 1);
case G_IO_STATUS_EOF:
default:
imap4->wr_source = g_idle_add(_on_reset, plugin);
return FALSE;
}
if(cmd->buf_cnt > 0)
return TRUE;
cmd->status = I4CS_SENT;
imap4->wr_source = 0;
if(imap4->rd_source == 0)
imap4->rd_source = g_io_add_watch(imap4->channel, G_IO_IN,
_on_watch_can_read, plugin);
return FALSE;
}
/* on_watch_can_write_ssl */
static gboolean _on_watch_can_write_ssl(GIOChannel * source,
GIOCondition condition, gpointer data)
{
AccountPlugin * plugin = data;
IMAP4 * imap4 = plugin->priv;
IMAP4Command * cmd = &imap4->queue[0];
int cnt;
char * p;
char buf[128];
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if(source != imap4->channel || imap4->queue_cnt == 0
|| cmd->buf_cnt == 0)
return FALSE; /* should not happen */
if((cnt = SSL_write(imap4->ssl, cmd->buf, cmd->buf_cnt)) <= 0)
{
if(cnt < 0 && SSL_get_error(imap4->ssl, cnt)
== SSL_ERROR_WANT_READ)
{
imap4->wr_source = g_io_add_watch(imap4->channel,
G_IO_IN, _on_watch_can_write_ssl,
plugin);
return FALSE;
}
else if(cnt < 0 && SSL_get_error(imap4->ssl, cnt)
== SSL_ERROR_WANT_WRITE)
{
imap4->wr_source = g_io_add_watch(imap4->channel,
G_IO_OUT, _on_watch_can_write_ssl,
plugin);
return FALSE;
}
ERR_error_string(SSL_get_error(imap4->ssl, cnt), buf);
plugin->helper->error(NULL, buf, 1);
imap4->wr_source = g_idle_add(_on_reset, plugin);
return FALSE;
}
#ifdef DEBUG
fprintf(stderr, "%s", "DEBUG: IMAP4 CLIENT: ");
fwrite(cmd->buf, sizeof(*p), cnt, stderr);
#endif
cmd->buf_cnt -= cnt;
memmove(cmd->buf, &cmd->buf[cnt], cmd->buf_cnt);
if((p = realloc(cmd->buf, cmd->buf_cnt)) != NULL)
cmd->buf = p; /* we can ignore errors... */
else if(cmd->buf_cnt == 0)
cmd->buf = NULL; /* ...except when it's not one */
if(cmd->buf_cnt > 0)
return TRUE;
cmd->status = I4CS_SENT;
imap4->wr_source = 0;
if(imap4->rd_source == 0)
imap4->rd_source = g_io_add_watch(imap4->channel, G_IO_IN,
_on_watch_can_read_ssl, plugin);
return FALSE;
}