Mailer
/* $Id$ */
/* Copyright (c) 2006-2018 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Mailer */
/* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <System.h>
#include "folder.h"
#include "mailer.h"
#include "message.h"
#include "account.h"
#include "../config.h"
/* constants */
#ifndef PREFIX
# define PREFIX "/usr/local"
#endif
#ifndef LIBDIR
# define LIBDIR PREFIX "/lib"
#endif
#define ACCOUNT "account"
/* Account */
/* private */
/* types */
struct _Account
{
Mailer * mailer;
char * type;
char * title;
GtkTreeStore * store;
GtkTreeRowReference * row;
Plugin * plugin;
AccountPluginDefinition * definition;
AccountPlugin * account;
int enabled;
AccountIdentity * identity;
AccountPluginHelper helper;
};
/* prototypes */
/* accessors */
static gboolean _account_get_iter(Account * account, GtkTreeIter * iter);
static SSL_CTX * _account_helper_get_ssl_context(Account * account);
/* useful */
static int _account_helper_error(Account * account, char const * message,
int ret);
static void _account_helper_event(Account * account, AccountEvent * event);
static char * _account_helper_authenticate(Account * account,
char const * message);
static int _account_helper_confirm(Account * account, char const * message);
static Folder * _account_helper_folder_new(Account * account,
AccountFolder * folder, Folder * parent, FolderType type,
char const * name);
static void _account_helper_folder_delete(Folder * folder);
static Message * _account_helper_message_new(Account * account, Folder * folder,
AccountMessage * message);
static void _account_helper_message_delete(Message * message);
static int _account_helper_message_set_body(Message * message, char const * buf,
size_t cnt, int append);
/* constants */
static const AccountPluginHelper _account_plugin_helper =
{
NULL,
_account_helper_get_ssl_context,
_account_helper_error,
_account_helper_event,
_account_helper_authenticate,
_account_helper_confirm,
_account_helper_folder_new,
_account_helper_folder_delete,
_account_helper_message_new,
_account_helper_message_delete,
message_set_flag,
message_set_header,
_account_helper_message_set_body
};
/* public */
/* functions */
/* account_new */
Account * account_new(Mailer * mailer, char const * type, char const * title,
GtkTreeStore * store)
{
Account * account;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(%p, \"%s\", \"%s\", %p)\n", __func__,
(void *)mailer, type, title, (void *)store);
#endif
if(type == NULL)
{
error_set_code(1, "%s", strerror(EINVAL));
return NULL;
}
if((account = object_new(sizeof(*account))) == NULL)
return NULL;
memset(account, 0, sizeof(*account));
account->mailer = mailer;
account->type = string_new(type);
if(title != NULL)
account->title = string_new(title);
account->plugin = plugin_new(LIBDIR, PACKAGE, "account", type);
account->definition = (account->plugin != NULL)
? plugin_lookup(account->plugin, "account_plugin") : NULL;
/* check for errors */
if(account->type == NULL || account->plugin == NULL
|| (title != NULL && account->title == NULL)
|| account->definition == NULL
|| account->definition->init == NULL
|| account->definition->destroy == NULL
|| account->definition->get_config == NULL)
{
account_delete(account);
error_set_code(1, "%s%s", "Invalid plug-in ", type);
return NULL;
}
if(store != NULL)
account_store(account, store);
memcpy(&account->helper, &_account_plugin_helper,
sizeof(account->helper));
account->helper.account = account;
account->enabled = 1;
account->identity = NULL;
return account;
}
/* account_delete */
void account_delete(Account * account)
{
if(account->row != NULL)
gtk_tree_row_reference_free(account->row);
account_quit(account);
string_delete(account->title);
string_delete(account->type);
if(account->plugin != NULL)
plugin_delete(account->plugin);
object_delete(account);
}
/* accessors */
/* account_get_config */
AccountConfig const * account_get_config(Account * account)
{
if(account->account == NULL)
return account->definition->config;
return account->definition->get_config(account->account);
}
/* account_get_enabled */
int account_get_enabled(Account * account)
{
return account->enabled;
}
/* account_get_folders */
GtkTreeStore * account_get_folders(Account * account)
{
return account->store;
}
/* account_get_name */
char const * account_get_name(Account * account)
{
return account->definition->name;
}
/* account_get_title */
char const * account_get_title(Account * account)
{
return account->title;
}
/* account_get_type */
char const * account_get_type(Account * account)
{
return account->type;
}
/* account_set_enabled */
void account_set_enabled(Account * account, int enabled)
{
account->enabled = enabled ? 1 : 0;
}
/* account_set_title */
int account_set_title(Account * account, char const * title)
{
if(account->title != NULL)
free(account->title);
if((account->title = strdup(title != NULL ? title : "")) == NULL)
return mailer_error(NULL, "strdup", 1);
return 0;
}
/* useful */
/* account_config_load */
int account_config_load(Account * account, Config * config)
{
AccountConfig * p = account_get_config(account);
char const * value;
char * q;
long l;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\", %p)\n", __func__, account->title,
(void *)config);
#endif
if(p == NULL || account->title == NULL)
return 0;
for(; p->name != NULL; p++)
{
if((value = config_get(config, account->title, p->name))
== NULL)
continue;
switch(p->type)
{
case ACT_PASSWORD:
/* FIXME unscramble */
case ACT_FILE:
case ACT_STRING:
free(p->value);
p->value = strdup(value);
break;
case ACT_UINT16:
l = strtol(value, &q, 0);
if(value[0] != '\0' && *q == '\0')
p->value = (void *)l;
break;
case ACT_BOOLEAN:
p->value = (strcmp(value, "yes") == 0
|| strcmp(value, "1") == 0)
? (void *)1 : NULL;
break;
case ACT_NONE:
case ACT_SEPARATOR:
break;
}
}
return 0;
}
/* account_config_save */
int account_config_save(Account * account, Config * config)
{
AccountConfig * p = account_get_config(account);
uint16_t u16;
char buf[6];
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\", %p)\n", __func__, account->title,
(void *)config);
#endif
if(account->title == NULL)
return 0;
if(config_set(config, account->title, "type", account->type) != 0)
return 1;
if(p == NULL)
return 0;
for(; p->name != NULL; p++)
{
switch(p->type)
{
case ACT_PASSWORD:
/* FIXME scramble */
case ACT_FILE:
case ACT_STRING:
if(config_set(config, account->title, p->name,
p->value) != 0)
return 1;
break;
case ACT_UINT16:
u16 = (uint16_t)p->value;
snprintf(buf, sizeof(buf), "%hu", u16);
if(config_set(config, account->title, p->name,
buf) != 0)
return 1;
break;
case ACT_BOOLEAN:
if(config_set(config, account->title, p->name,
(p->value != NULL) ? "1"
: "0") != 0)
return 1;
break;
case ACT_NONE:
case ACT_SEPARATOR:
break;
}
}
return 0;
}
/* account_init */
int account_init(Account * account)
{
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, account->title);
#endif
if(account->account != NULL)
return 0;
account->account = account->definition->init(&account->helper);
return (account->account != NULL) ? 0 : -1;
}
/* account_quit */
int account_quit(Account * account)
{
if(account->definition != NULL && account->account != NULL)
account->definition->destroy(account->account);
account->account = NULL;
return 0;
}
/* account_refresh */
void account_refresh(Account * account)
{
account_stop(account);
account_start(account);
}
/* account_select */
GtkTextBuffer * account_select(Account * account, Folder * folder,
Message * message)
{
AccountFolder * af;
AccountMessage * am = NULL;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\", %p)\n", __func__,
account_get_name(account), folder_get_name(folder),
(void *)message);
#endif
if((af = folder_get_data(folder)) == NULL)
return NULL;
if(message != NULL && (am = message_get_data(message)) == NULL)
return NULL;
if(account->definition->refresh != NULL
&& account->definition->refresh(account->account, af,
am) != 0)
return NULL;
return (message != NULL) ? message_get_body(message) : NULL;
}
/* account_select_source */
GtkTextBuffer * account_select_source(Account * account, Folder * folder,
Message * message)
{
GtkTextBuffer * ret;
char * p;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\", %p)\n", __func__,
folder_get_name(folder), (void *)message);
#endif
if(account->definition->get_source == NULL)
return NULL;
ret = gtk_text_buffer_new(NULL);
if((p = account->definition->get_source(account->account,
folder_get_data(folder),
message_get_data(message))) != NULL)
{
gtk_text_buffer_set_text(ret, p, -1);
free(p);
}
return ret;
}
/* account_start */
int account_start(Account * account)
{
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, account->title);
#endif
if(account->account == NULL
&& account_init(account) != 0)
return -1;
if(account->definition->start == NULL)
return 0;
return account->definition->start(account->account);
}
/* account_stop */
void account_stop(Account * account)
{
if(account->definition->stop == NULL)
return;
account->definition->stop(account->account);
}
/* account_store */
void account_store(Account * account, GtkTreeStore * store)
{
GtkIconTheme * theme;
GdkPixbuf * pixbuf;
GtkTreeIter iter;
GtkTreePath * path;
if(account->store != NULL)
return;
account->store = store;
theme = gtk_icon_theme_get_default();
pixbuf = gtk_icon_theme_load_icon(theme, "mailer-accounts", 16, 0,
NULL);
gtk_tree_store_append(store, &iter, NULL);
gtk_tree_store_set(store, &iter, MFC_ACCOUNT, account, MFC_ICON, pixbuf,
MFC_NAME, account->title, -1);
path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
account->row = gtk_tree_row_reference_new(GTK_TREE_MODEL(store), path);
gtk_tree_path_free(path);
}
/* private */
/* functions */
/* accessors */
/* account_get_iter */
static gboolean _account_get_iter(Account * account, GtkTreeIter * iter)
{
GtkTreePath * path;
if(account->row == NULL || (path = gtk_tree_row_reference_get_path(
account->row)) == NULL)
return FALSE;
return gtk_tree_model_get_iter(GTK_TREE_MODEL(account->store), iter,
path);
}
/* account_helper_get_ssl_context */
static SSL_CTX * _account_helper_get_ssl_context(Account * account)
{
return mailer_get_ssl_context(account->mailer);
}
/* useful */
/* account_helper_error */
static int _account_helper_error(Account * account, char const * message,
int ret)
{
Mailer * mailer = (account != NULL) ? account->mailer : NULL;
size_t len;
char * p;
if(account != NULL)
{
len = strlen(account->title) + strlen(message) + 3;
if((p = malloc(len)) != NULL)
{
snprintf(p, len, "%s: %s", account->title, message);
mailer_set_status(mailer, p);
free(p);
return ret;
}
}
return mailer_error(mailer, message, ret);
}
/* account_helper_event */
static void _helper_event_status(Account * account, AccountEvent * event);
static void _account_helper_event(Account * account, AccountEvent * event)
{
switch(event->type)
{
case AET_STARTED:
case AET_STOPPED:
/* FIXME forward this information */
break;
case AET_STATUS:
_helper_event_status(account, event);
break;
}
}
static void _helper_event_status(Account * account, AccountEvent * event)
{
Mailer * mailer = account->mailer;
char const * message = event->status.message;
if(message == NULL)
switch(event->status.status)
{
case AS_IDLE:
message = "Ready";
break;
default:
break;
}
if(message == NULL)
return;
mailer_set_status(mailer, message);
}
/* account_helper_authenticate */
static char * _account_helper_authenticate(Account * account,
char const * message)
{
char * ret = NULL;
GtkWidget * dialog;
GtkWidget * vbox;
GtkWidget * widget;
dialog = gtk_dialog_new();
/* XXX translate this, enumerate the methods available */
gtk_window_set_title(GTK_WINDOW(dialog), "Authentication");
#if GTK_CHECK_VERSION(2, 14, 0)
vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
#else
vbox = GTK_DIALOG(dialog)->vbox;
#endif
widget = gtk_label_new(message);
gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, TRUE, 0);
widget = gtk_entry_new();
gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, TRUE, 0);
gtk_dialog_add_buttons(GTK_DIALOG(dialog),
GTK_STOCK_OK, GTK_RESPONSE_OK,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
gtk_widget_show_all(vbox);
if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK)
ret = strdup(gtk_entry_get_text(GTK_ENTRY(widget)));
gtk_widget_destroy(dialog);
return ret;
}
/* account_helper_confirm */
static int _account_helper_confirm(Account * account, char const * message)
{
int ret;
GtkWidget * dialog;
/* XXX set mailer's main window as the parent? */
dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_QUESTION,
GTK_BUTTONS_YES_NO,
#if GTK_CHECK_VERSION(2, 6, 0)
"%s", "Confirm");
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
#endif
"%s", message);
/* XXX translate this */
gtk_window_set_title(GTK_WINDOW(dialog), "Confirm");
ret = (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) ? 0 : 1;
gtk_widget_destroy(dialog);
return ret;
}
/* account_helper_folder_new */
static Folder * _account_helper_folder_new(Account * account,
AccountFolder * folder, Folder * parent, FolderType type,
char const * name)
{
Folder * ret = NULL;
GtkTreeModel * model = GTK_TREE_MODEL(account->store);
GtkTreeIter aiter;
GtkTreeIter * paiter = NULL;
GtkTreeIter piter;
GtkTreeIter * ppiter = NULL;
GtkTreeIter siter;
GtkTreeIter * psiter = NULL;
GtkTreeIter iter;
gint i;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\", %p, %p, %u, \"%s\")\n", __func__,
account->title, (void *)folder, (void *)parent, type,
name);
#endif
if(account->row == NULL)
return NULL;
/* lookup the account */
if(_account_get_iter(account, &aiter) == TRUE)
paiter = &aiter;
/* lookup the parent folder */
if(parent != NULL && folder_get_iter(parent, &piter) == TRUE)
ppiter = &piter;
else
ppiter = paiter;
/* lookup the following folder in sort order */
if(ppiter != NULL)
for(i = 0; gtk_tree_model_iter_nth_child(model, &siter, ppiter,
i) != FALSE; i++)
{
psiter = &siter;
gtk_tree_model_get(model, &siter, MFC_FOLDER, &ret, -1);
if(type == FT_INBOX && folder_get_type(ret) != FT_INBOX)
break;
if(type < folder_get_type(ret))
break;
if(folder_get_type(ret) == type && strcmp(name,
folder_get_name(ret)) < 0)
break;
psiter = NULL;
}
/* insert the folder in the model */
gtk_tree_store_insert_before(account->store, &iter, ppiter, psiter);
/* actually register the folder */
if((ret = folder_new(folder, type, name, account->store, &iter))
== NULL)
gtk_tree_store_remove(account->store, &iter);
else
gtk_tree_store_set(account->store, &iter, MFC_ACCOUNT, account,
-1);
return ret;
}
/* account_helper_folder_delete */
static void _account_helper_folder_delete(Folder * folder)
{
/* FIXME remove from the account */
folder_delete(folder);
}
/* account_helper_message_new */
static Message * _account_helper_message_new(Account * account, Folder * folder,
AccountMessage * message)
{
Message * ret;
GtkTreeStore * store;
GtkTreeIter iter;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if(folder == NULL)
return message_new(message, NULL, NULL);
store = folder_get_messages(folder);
gtk_tree_store_append(store, &iter, NULL);
if((ret = message_new(message, store, &iter)) == NULL)
gtk_tree_store_remove(store, &iter);
else
{
gtk_tree_store_set(store, &iter, MHC_ACCOUNT, account,
MHC_FOLDER, folder, -1);
mailer_set_status(account->mailer, NULL);
}
return ret;
}
/* account_helper_message_delete */
static void _account_helper_message_delete(Message * message)
{
GtkTreeStore * store;
GtkTreeIter iter;
if((store = message_get_store(message)) != NULL
&& message_get_iter(message, &iter) != FALSE)
gtk_tree_store_remove(store, &iter);
message_delete(message);
}
/* account_helper_message_set_body */
static int _account_helper_message_set_body(Message * message, char const * buf,
size_t cnt, int append)
{
return message_set_body(message, buf, cnt, (append != 0) ? TRUE
: FALSE);
}