AC_SUBST(PLUGINDIR)
AC_SUBST(BINDIR)
-GLIB_REQUIRED=2.10.0
-GOBJECT_REQUIRED=2.10.0
-GTK_REQUIRED=2.12.0
+GLIB_REQUIRED=2.16.0
+GOBJECT_REQUIRED=2.16.0
+GTK_REQUIRED=2.16.0
AC_SUBST(GLIB_REQUIRED)
AC_SUBST(GOBJECT_REQUIRED)
AC_SUBST(LIBXML_CFLAGS)
AC_SUBST(LIBXML_LIBS)
-
# Checks for header files.
AC_CHECK_HEADER(gcrypt.h,
[AC_CHECK_LIB(gcrypt, gcry_control,,
[AC_MSG_ERROR([libgcrypt not found.
Get latest version here:
http://www.gnupg.org/download])])
-if test x"$ac_cv_lib_gcry_control" = "xyes"; then
- GCRYPT_LIBS="-lgcrypt"
- AC_SUBST(GCRYPT_LIBS)
-fi
+AM_PATH_LIBGCRYPT(1.1.43,,
+ AC_MSG_ERROR([[
+***
+*** libgcrypt was not found. You may want to get it from
+*** ftp://ftp.gnupg.org/pub/gcrypt/alpha/libgcrypt/
+***
+]]))
# Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST
[ac_cv_enable_ldap_plugin=$enableval], [ac_cv_enable_ldap_plugin=no])
if test x"$ac_cv_enable_ldap_plugin" = xyes; then
AC_MSG_RESULT(yes)
- PLUGINS="ldap $PLUGINS"
+ AC_CHECK_LIB(resolv, res_query, LDAP_LIBS="$LDAP_LIBS -lresolv")
+ AC_CHECK_LIB(socket, bind, LDAP_LIBS="$LDAP_LIBS -lsocket")
+ AC_CHECK_LIB(nsl, gethostbyaddr, LDAP_LIBS="$LDAP_LIBS -lnsl")
+ AC_CHECK_LIB(lber, ber_get_tag, LDAP_LIBS="$LDAP_LIBS -llber",,
+ $LDAP_LIBS)
+
+ AC_CHECK_HEADERS(ldap.h lber.h,
+ [ ac_cv_enable_ldap=yes ],
+ [ ac_cv_enable_ldap=no ])
+
+ if test "$ac_cv_enable_ldap" = yes; then
+ AC_CHECK_LIB(ldap, ldap_open,
+ [ ac_cv_enable_ldap=yes ],
+ [ ac_cv_enable_ldap=no ],
+ $LDAP_LIBS)
+
+ AC_CHECK_LIB(ldap, ldap_start_tls_s,
+ [ ac_cv_have_tls=yes ],
+ [ ac_cv_have_tls=no ])
+ fi
+
+ if test "$ac_cv_enable_ldap" = yes; then
+ LDAP_LIBS="$LDAP_LIBS -lldap"
+ PLUGINS="ldap $PLUGINS"
+ else
+ AC_MSG_ERROR([Open LDAP is required. See http://www.openldap.org/])
+ fi
+
+ if test "$ac_cv_have_tls" = yes; then
+ AC_MSG_CHECKING([Whether GNUTLS is present])
+ PKG_CHECK_MODULES([GNUTLS], gnutls,
+ AC_DEFINE(LDAP_TLS, [],
+ [Is GNUTLS present on the system]))
+ AC_SUBST(GNUTLS_LIBS)
+ AC_SUBST(GNUTLS_CFLAGS)
+ fi
+
+ AC_SUBST(LDAP_LIBS)
else
AC_MSG_RESULT(no)
fi
#define NAME "Example plugin"
+/* Reference to self */
+static Plugin* self = NULL;
/* See plugin.h */
static PluginFeature* feature = NULL;
/* Description */
/* List of not active or inactive attributes this plugin supports*/
static GSList* remaining_attribs = NULL;
+/**
+ * Remove newly open book from closed_books
+ * @param book AddressBook to remove
+ */
+static void closed_books_remove(AddressBook* book) {
+ GList* found = g_list_find_custom(closed_books, book, address_book_compare);
+ if (found)
+ closed_books = g_list_remove_link(closed_books, found);
+}
+
/**
* Free list of abooks.
* @param error Pointer to memory where error is supposed to be saved
* @return TRUE if success FALSE otherwise
*/
gboolean plugin_abook_open(AddressBook* abook, gchar** error) {
+ closed_books_remove(abook);
if (abook->dirty) {
/* Open Address book */
if (*error) {
show_message(NULL, GTK_UTIL_MESSAGE_WARNING, "%s", *error);
g_free(*error);
*error = NULL;
+ closed_books = g_list_prepend(closed_books, abook);
return FALSE;
}
abook->dirty = FALSE;
}
abooks = g_list_prepend(abooks, abook);
+ abook->open = TRUE;
return TRUE;
}
gboolean plugin_abook_delete(AddressBook* abook, gchar** error) {
if (! abook)
return FALSE;
-
- plugin_abook_close(abook, error);
+
+ if (abook->open)
+ self->abook_close(abook, error);
+ /* Remove from closed books since deleting */
+ closed_books = g_list_remove(closed_books, abook);
if (abook->URL) {
/* Delete address book */
}
+
return TRUE;
}
abooks = g_list_remove(abooks, abook);
address_book_contacts_free(abook);
debug_print("List contains %d elements after\n", g_list_length(abooks));
+ closed_books = g_list_prepend(closed_books, abook);
+ abook->open = FALSE;
+
return TRUE;
}
* @param Pointer to memory where error is supposed to be saved
* @return FALSE if success TRUE otherwise
*/
-gboolean plugin_init(gchar** error) {
+gboolean plugin_init(gpointer self_ref, gchar** error) {
+ self = (Plugin *) self_ref;
if (*error != NULL) {
g_free(*error);
*error = NULL;
gboolean plugin_done(void) {
g_free(feature);
+ self = NULL;
+
return TRUE;
}
* Get functional type of plugin. Returned memory is owned by the plugin.
* @return type
*/
-const gchar* plugin_type(void) {
- return "DEMO";
+PluginType plugin_type(void) {
+ return PLUGIN_TYPE_SIMPLE;
}
/**
const gchar* plugin_license(void) {
return "GPL3+";
}
+
+/**
+ * Does the plugin needs credentials for address books
+ * @return bool
+ */
+gboolean plugin_need_credentials(void) {
+ return FALSE;
+}
+
+/**
+ * Get list of additional config
+ * @return NULL if no additional config is required, a list of
+ * ExtraConfig otherwise
+ */
+GSList* plugin_extra_config(void) {
+ return NULL;
+}
+
+/**
+ * Get file filter for this plugin
+ * @return filter or NULL. If returning NULL means data storage is
+ * URL based URI based otherwise
+ */
+const gchar* plugin_file_filter(void) {
+ return "demo";
+}
\ No newline at end of file
INCLUDES = \
-I${top_srcdir} \
-I${top_srcdir}/src \
- -I$(top_srcdir)/src/dbus \
-I${top_builddir} \
+ -I$(top_srcdir)/src/dbus \
@GLIB_CFLAGS@ \
- @GTK_CFLAGS@
+ @GTK_CFLAGS@ \
+ @GNUTLS_CFLAGS@
AM_CPPFLAGS = \
- -DG_LOG_DOMAIN=\"Claws-Contacts\"
+ -DG_LOG_DOMAIN=\"Claws-Contacts\" \
+ $(LIBGCRYPT_CFLAGS)
ldap_plugin_la_SOURCES = \
ldap-plugin.c
ldap_plugin_la_LIBADD= \
@GLIB_LIBS@ \
- @GTK_LIBS@
+ @GTK_LIBS@ \
+ @LDAP_LIBS@ \
+ @GNUTLS_LIBS@ \
+ $(LIBGCRYPT_LIBS)
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gprintf.h>
+#include <ldap.h>
+#include <stdlib.h>
#include "plugin.h"
#include "gtk-utils.h"
#include "utils.h"
#define NAME "LDAP plugin"
+#define TIMEOUT 20
+typedef struct {
+ gboolean ldaps;
+ gchar* host;
+ gint port;
+} Connection;
+
+typedef struct {
+ LDAP* ldap;
+ GSList* baseDN;
+ gchar* search_base;
+ gint max_entries;
+ gint timeout;
+} Server;
+
+typedef struct {
+ gchar* id;
+ const Contact* contact;
+ gboolean intersection;
+ Connection* connection;
+ Server* server;
+} AbookConnection;
+
+/* Reference to self */
+static Plugin* self = NULL;
/* See plugin.h */
static PluginFeature* feature = NULL;
/* Description */
/* List of not active or inactive attributes this plugin supports*/
static GSList* remaining_attribs = NULL;
+/* List of extra config */
+static GSList* extra_config = NULL;
+
static gchar self_home[] = "ldap";
static gchar configrc[] = "ldaprc";
static ConfigFile* config = NULL;
+static GSList* abook_connection = NULL;
+
+static const char *other_attributes[] = {
+ "telephoneNumber",
+ "description", /* comments */
+ "postalAddress",
+ "homePhone",
+ "homePostalAddress",
+ "mobile",
+ "ou",
+ "title",
+ "telexNumber",
+ "facsimileTelephoneNumber",
+ "street",
+ "postOfficeBox",
+ "postalCode",
+ "st", /* state or province */
+ "l", /* locality Name */
+ "departmentNumber",
+ "initials",
+ "labeledURI",
+ "pager",
+ "roomNumber",
+ NULL
+};
+
+static void deconnect(Server* server) {
+ if (server->ldap) {
+ ldap_unbind_ext(server->ldap, NULL, NULL);
+ server->ldap = NULL;
+ }
+ if (server->baseDN) {
+ gslist_free(&server->baseDN, g_free);
+ }
+ if (server->search_base) {
+ g_free(server->search_base);
+ server->search_base = NULL;
+ }
+}
+
+static void connect_info_free(Connection** connection) {
+ g_free((*connection)->host);
+ g_free(*connection);
+ *connection = NULL;
+}
+
+static AbookConnection* abook_connection_new() {
+ AbookConnection* ac = g_new0(AbookConnection, 1);
+ ac->id = NULL;
+ ac->connection = NULL;
+ ac->server = g_new0(Server, 1);
+ ac->server->timeout = TIMEOUT;
+
+ return ac;
+}
+
+static void abook_connection_free(gpointer abook_connection) {
+ AbookConnection* abook_con = (AbookConnection *) abook_connection;
+ if (! abook_con)
+ return;
+
+ if (abook_con->id) {
+ g_free(abook_con->id);
+ abook_con->id = NULL;
+ }
+ if (abook_con->connection) {
+ connect_info_free(&abook_con->connection);
+ }
+ if (abook_con->server) {
+ deconnect(abook_con->server);
+ g_free(abook_con->server);
+ abook_con->server = NULL;
+ }
+ g_free(abook_con);
+ abook_con = NULL;
+}
+
+static AbookConnection* get_abook_connection(AddressBook* abook) {
+ GSList* cur;
+ gchar* id;
+ AbookConnection* ac = NULL;
+ gboolean found = FALSE;
+
+ gchar* plain = g_strconcat(abook->abook_name, abook->URL, NULL);
+ id = sha256(plain);
+ g_free(plain);
+
+ for (cur = abook_connection; cur && !found; cur = g_slist_next(cur)) {
+ ac = (AbookConnection *) cur->data;
+ if (strcmp(ac->id, id) == 0)
+ found = TRUE;
+ else
+ ac = NULL;
+ }
+ g_free(id);
+
+ return ac;
+}
+
+static AddressBook* find_addressbook(AbookConnection* ac) {
+ GList* cur;
+ gchar* id = NULL;
+ AddressBook* abook = NULL;
+
+ for (cur = abooks; cur; cur = g_list_next(cur)) {
+ abook = (AddressBook *) cur->data;
+ gchar* plain = g_strconcat(abook->abook_name, abook->URL, NULL);
+ id = sha256(plain);
+ g_free(plain);
+ if (strcmp(ac->id, id) == 0) {
+ g_free(id);
+ return abook;
+ }
+ }
+
+ g_free(id);
+
+ return abook;
+}
+
+static Connection* split_url(AddressBook* abook, gchar** error) {
+ Connection* info = g_new0(Connection, 1);
+ gchar** cur;
+ gchar* tmp;
+
+ gchar** parts = g_strsplit(abook->URL, ":", 0);
+
+ if (*parts) {
+ cur = parts;
+ while (*cur && ! *error) {
+ if (strcmp("ldaps", *cur) == 0)
+ info->ldaps = TRUE;
+ else if (strcmp("ldap", *cur) == 0)
+ info->ldaps = FALSE;
+ else if (atoi(*cur) > 0)
+ info->port = atoi(*cur);
+ else {
+ if (info->host)
+ *error = g_strconcat(abook->URL, ": ", _("Wrong URL"), NULL);
+ else {
+ if (g_str_has_prefix(*cur, "//")) {
+ tmp = g_strdup(*cur+2);
+ }
+ else
+ tmp = g_strdup(*cur);
+ info->host = g_strdup(tmp);
+ g_free(tmp);
+ }
+ }
+ cur += 1;
+ }
+ g_strfreev(parts);
+ }
+ else
+ *error = g_strconcat(abook->URL, ": ", _("Wrong URL"), NULL);
+
+ if (info->ldaps) {
+ if (info->port < 1)
+ info->port = 636;
+ }
+ else {
+ if (info->port < 1)
+ info->port = 389;
+ }
+
+ return info;
+}
+
+static int ldap_bind(AbookConnection* abook_con) {
+ struct berval cred;
+ AddressBook* abook = find_addressbook(abook_con);
+ Server* server = abook_con->server;
+
+ if (abook && abook->password) {
+ cred.bv_val = abook->password;
+ cred.bv_len = strlen(abook->password);
+ }
+ else {
+ cred.bv_val = "";
+ cred.bv_len = 0;
+ }
+
+ debug_print("binding: DN->%s\n", (abook->username) ? abook->username : "null");
+ return ldap_sasl_bind_s(server->ldap, abook->username,
+ LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL);
+}
+
+static gchar* get_uri(Connection* info) {
+ gchar *tmp, *uri;
+
+ if (info->ldaps)
+ tmp = g_strconcat("ldaps://", info->host, NULL);
+ else
+ tmp = g_strconcat("ldap://", info->host, NULL);
+ if (info->port > 0)
+ uri = g_strdup_printf("%s:%d", tmp, info->port);
+ else
+ uri = g_strdup(tmp);
+
+ g_free(tmp);
+
+ return uri;
+}
+
+static void set_default_values(Server** server) {
+ GSList *config, *cur;
+ ExtraConfig* conf;
+
+ config = self->extra_config();
+ for (cur = config; cur; cur = g_slist_next(cur)) {
+ conf = (ExtraConfig *) cur->data;
+ if (conf->label && strcasecmp("timeout (sec)", conf->label) == 0)
+ (*server)->timeout = conf->default_value.spin_btn;
+ else if (conf->label && strcasecmp("search base", conf->label) == 0)
+ (*server)->search_base = g_strdup(conf->default_value.entry);
+ else if (conf->label && strcasecmp("max entries", conf->label) == 0)
+ (*server)->max_entries = conf->default_value.spin_btn;
+ }
+
+ gslist_free(&config, extra_config_free);
+}
+
+static Server* get_server_config(AddressBook* abook) {
+ GSList* cur;
+ ExtraConfig* conf;
+ Server* server = g_new0(Server, 1);
+
+ set_default_values(&server);
+
+ for (cur = abook->extra_config; cur; cur = g_slist_next(cur)) {
+ conf = (ExtraConfig *) cur->data;
+ if (conf->label && strcasecmp("timeout (sec)", conf->label) == 0)
+ server->timeout = conf->value.spin_btn;
+ else if (conf->label && strcasecmp("search base", conf->label) == 0)
+ server->search_base = g_strdup(conf->value.entry);
+ else if (conf->label && strcasecmp("max entries", conf->label) == 0)
+ server->max_entries = conf->value.spin_btn;
+ }
+
+ return server;
+}
+
+#define SEARCH_BASE ""
+#define TEST_FILTER "(objectClass=*)"
+#define TEST_ATTRIB "namingContexts"
+static void find_search_base(AbookConnection* abook_con, int* err, gchar** error) {
+ gchar* attribs[2];
+ struct timeval timeOut;
+ LDAPMessage* result = NULL;
+ LDAPMessage* entry;
+ BerElement *ber;
+ gchar *attribute;
+ struct berval **vals;
+ gint rc, i;
+ Server* server = abook_con->server;
+
+ if (server->timeout > 0)
+ timeOut.tv_sec = server->timeout;
+ else
+ timeOut.tv_sec = TIMEOUT;
+ timeOut.tv_usec = 0;
+ server->baseDN = NULL;
+ attribs[0] = TEST_ATTRIB;
+ attribs[1] = NULL;
+ rc = ldap_search_ext_s(server->ldap, SEARCH_BASE, LDAP_SCOPE_BASE,
+ TEST_FILTER, attribs, 0, NULL, NULL, &timeOut, 0, &result);
+
+ if (rc == LDAP_SUCCESS) {
+ /* Process entries */
+ for (entry = ldap_first_entry(server->ldap, result);
+ entry; entry = ldap_next_entry(server->ldap, entry)) {
+ /* Process attributes */
+ for (attribute = ldap_first_attribute(server->ldap, entry, &ber);
+ attribute; attribute = ldap_next_attribute(server->ldap, entry, ber)) {
+ if (strcasecmp(attribute, TEST_ATTRIB) == 0) {
+ vals = ldap_get_values_len(server->ldap, entry, attribute);
+ if (vals) {
+ for (i = 0; vals[i]; i++) {
+ server->baseDN = g_slist_prepend(
+ server->baseDN, g_strndup(vals[i]->bv_val,
+ vals[i]->bv_len));
+ }
+ }
+ ldap_value_free_len(vals);
+ }
+ ldap_memfree(attribute);
+ }
+ if (ber) {
+ ber_free(ber, 0);
+ }
+ ber = NULL;
+ }
+ }
+ else {
+ if (error)
+ *error = g_strdup(ldap_err2string(rc));
+ debug_print("%s\n", ldap_err2string(rc));
+ *err = rc;
+ }
+ if (result)
+ ldap_msgfree(result);
+}
+
+static gboolean ldap_connect(AbookConnection* abook_con, gchar** error) {
+ struct timeval timeOut;
+ int version = LDAP_VERSION3;
+ int reqcert = LDAP_OPT_X_TLS_ALLOW;
+ int rc;
+ gchar* uri;
+ gboolean TLS = FALSE;
+ AddressBook* abook = find_addressbook(abook_con);
+ Server* server = NULL;
+ Connection* info = NULL;
+
+ if (! abook || ! abook->URL || strlen(abook->URL) < 0) {
+ *error = g_strdup(_("Missing URL"));
+ return TRUE;
+ }
+
+ abook_con->server = server = get_server_config(abook);
+ abook_con->connection = info = split_url(abook, error);
+ if (*error)
+ return TRUE;
+
+ if (server->ldap) {
+ ldap_unbind_ext(server->ldap, NULL, NULL);
+ server->ldap = NULL;
+ }
+
+ timeOut.tv_usec = 0;
+ if (server->timeout > 0)
+ timeOut.tv_sec = server->timeout;
+ else
+ timeOut.tv_sec = TIMEOUT;
+
+ rc = ldap_set_option( NULL, LDAP_OPT_PROTOCOL_VERSION, &version);
+ if (rc != LDAP_OPT_SUCCESS) {
+ ldap_unbind_ext(server->ldap, NULL, NULL);
+ server->ldap = NULL;
+ *error = g_strdup(ldap_err2string(rc));
+ debug_print("%s\n", ldap_err2string(rc));
+ return TRUE;
+ }
+ ldap_set_option( NULL, LDAP_OPT_NETWORK_TIMEOUT, &timeOut);
+
+#ifdef LDAP_TLS
+ if (info->ldaps) {
+ debug_print("LDAP_OPT_X_TLS_ALLOW\n");
+ rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &reqcert);
+ if (rc != LDAP_OPT_SUCCESS) {
+ ldap_unbind_ext(server->ldap, NULL, NULL);
+ server->ldap = NULL;
+ *error = g_strdup(ldap_err2string(rc));
+ debug_print("%s\n", ldap_err2string(rc));
+ return TRUE;
+ }
+ }
+#else
+ if (info->ldaps) {
+ debug_print("LDAPS requested but SSL is unavailable. Trying LDAP\n");
+ }
+#endif
+
+ uri = get_uri(info);
+ rc = ldap_initialize(&server->ldap, uri);
+ g_free(uri);
+ if (rc != LDAP_SUCCESS) {
+ ldap_unbind_ext(server->ldap, NULL, NULL);
+ server->ldap = NULL;
+ *error = g_strdup(
+ _("initialize: LDAP session initialization failed."));
+ debug_print("initialize: LDAP session initialization failed\n");
+ return TRUE;
+ }
+
+#ifdef LDAP_TLS
+ if (! info->ldaps) {
+ rc = ldap_start_tls_s(server->ldap, NULL, NULL);
+ if (rc != LDAP_SUCCESS) {
+ debug_print("start_tls: %s\n", ldap_err2string(rc));
+ }
+ else
+ TLS = TRUE;
+ }
+#endif
+
+ /* anonymous? */
+ if (abook->username && strlen(abook->username) > 0) {
+ rc = ldap_bind(abook_con);
+ if (rc != LDAP_SUCCESS) {
+ ldap_unbind_ext(server->ldap, NULL, NULL);
+ server->ldap = NULL;
+ *error = g_strdup(ldap_err2string(rc));
+ debug_print("bind: %s\n", ldap_err2string(rc));
+ return TRUE;
+ }
+ }
+
+ debug_print("Connected to %s on port %d\n", info->host, info->port);
+ if (info->ldaps || TLS)
+ debug_print("Connected using %s\n", (info->ldaps) ? "SSL" : "TLS");
+
+ if (! server->search_base) {
+ find_search_base(abook_con, &rc, error);
+ if (rc == LDAP_SUCCESS)
+ server->search_base = g_strdup((gchar *) server->baseDN->data);
+ else {
+ ldap_unbind_ext(server->ldap, NULL, NULL);
+ server->ldap = NULL;
+ *error = g_strdup_printf(ldap_err2string(rc));
+ debug_print("Search base: %s\n", ldap_err2string(rc));
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void abooks_free() {
+ GList* cur;
+
+ if (! abooks && ! closed_books)
+ return;
+
+ for (cur = abooks; cur; cur = g_list_next(cur)) {
+ AddressBook* abook = (AddressBook *) cur->data;
+ address_book_free(&abook);
+ }
+ g_list_free(abooks);
+ abooks = NULL;
+
+ for (cur = closed_books; cur; cur = g_list_next(cur)) {
+ AddressBook* abook = (AddressBook *) cur->data;
+ address_book_free(&abook);
+ }
+ g_list_free(closed_books);
+ closed_books = NULL;
+}
+
+static void write_config_file() {
+ GList *cur;
+ gchar* error = NULL;
+ gchar* path = NULL;
+
+ if (config) {
+ path = g_strdup(config->path);
+ plugin_config_free(&config);
+ }
+ else {
+ gchar* basedir = get_self_home();
+ path = g_strconcat(basedir, G_DIR_SEPARATOR_S, self_home,
+ G_DIR_SEPARATOR_S, configrc, NULL);
+ g_free(basedir);
+ }
+ config = plugin_config_new(path);
+ g_free(path);
+ config->comment = g_strconcat("Configuration file for ", NAME,
+ ".\nDo not change unless you KNOW what your are doing.", NULL);
+ config->configured_books->group = g_strdup("configured address books");
+
+ for (cur = abooks; cur; cur = g_list_next(cur)) {
+ AddressBook* book = (AddressBook *) cur->data;
+ config->configured_books->books =
+ g_slist_prepend(config->configured_books->books, g_strdup(book->abook_name));
+ }
+
+ config->closed_books->group = g_strdup("closed address books");
+
+ for (cur = closed_books; cur; cur = g_list_next(cur)) {
+ AddressBook* book = (AddressBook *) cur->data;
+ config->closed_books->books =
+ g_slist_prepend(config->closed_books->books, g_strdup(book->abook_name));
+ }
+
+ plugin_config_set(config, &error);
+ if (error) {
+ show_message(NULL, GTK_UTIL_MESSAGE_ERROR, "%s", error);
+ g_free(error);
+ }
+}
+
+static const gchar* native2ldap(const gchar* attr) {
+ if (strcasecmp(attr, "email") == 0)
+ return "mail";
+ else if (strcasecmp(attr, "first-name") == 0)
+ return "givenName";
+ else if (strcasecmp(attr, "last-name") == 0)
+ return "sn";
+ else if (strcasecmp(attr, "nick-name") == 0)
+ return "displayName";
+ else if (strcasecmp(attr, "image") == 0)
+ return "jpegPhoto";
+ else
+ return attr;
+}
+
+static const gchar* ldap2native(const gchar* attr) {
+ if (strcasecmp(attr, "mail") == 0)
+ return "email";
+ else if (strcasecmp(attr, "givenName") == 0)
+ return "first-name";
+ else if (strcasecmp(attr, "sn") == 0)
+ return "last-name";
+ else if (strcasecmp(attr, "displayName") == 0)
+ return "nick-name";
+ else if (strcasecmp(attr, "jpegPhoto") == 0)
+ return "image";
+ else
+ return attr;
+}
+
+static gchar** attriblist2ldap() {
+ GHashTableIter iter;
+ gpointer key, value;
+ GSList *attr_list = NULL;
+ gchar** res;
+
+ g_hash_table_iter_init(&iter, attribs);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ const gchar* attr = native2ldap((gchar *) key);
+ attr_list = g_slist_prepend(attr_list, attr);
+ }
+
+ res = gslist_to_array(attr_list, NULL);
+ gslist_free(&attr_list, NULL);
+
+ return res;
+}
+
+static void set_attribs() {
+ const gchar** ptr = standard_attribs;
+ AttribDef* attrib_def;
+
+ if (attribs) {
+ hash_table_free(&attribs);
+ }
+ attribs = hash_table_new();
+ while(ptr && *ptr) {
+ attrib_def = g_new0(AttribDef, 1);
+ attrib_def->type = ATTRIB_TYPE_STRING;
+ attrib_def->attrib_name = g_strdup(*ptr);
+ g_hash_table_replace(attribs, g_strdup(*ptr++), attrib_def);
+ }
+
+ ptr = other_attributes;
+ while(ptr && *ptr) {
+ attrib_def = g_new0(AttribDef, 1);
+ attrib_def->type = ATTRIB_TYPE_STRING;
+ attrib_def->attrib_name = g_strdup(*ptr);
+ g_hash_table_replace(attribs, g_strdup(*ptr++), attrib_def);
+ }
+}
+
+static AddressBook* get_addressbook(const gchar* name) {
+ gchar **keys, **cur;
+ GSList* values = NULL;
+ gsize len;
+ GError* err = NULL;
+ gchar* value;
+ AddressBook* abook;
+ ExtraConfig* conf;
+
+ abook = g_new0(AddressBook, 1);
+ if (g_key_file_has_group(config->key_file, name)) {
+ abook->abook_name = g_strdup(name);
+ abook->dirty = TRUE;
+ keys = g_key_file_get_keys(config->key_file, name, &len, &err);
+ for (cur = keys; *cur; cur += 1) {
+ gslist_free(&values, g_free);
+ if (config_get_value(config, name, *cur, &values)) {
+ value = (gchar *) values->data;
+ if (strcmp("url", *cur) == 0)
+ abook->URL = g_strdup(value);
+ else if (strcmp("username", *cur) == 0)
+ abook->username = g_strdup(value);
+ else if (strcmp("password", *cur) == 0)
+ abook->password = aes256_decrypt(value, TRUE);
+ else {
+ conf = get_extra_config(self->extra_config(), *cur);
+ if (conf) {
+ switch (conf->type) {
+ case PLUGIN_CONFIG_EXTRA_CHECKBOX:
+ conf->value.check_btn =
+ (strcmp("true", value) == 0) ? TRUE : FALSE;
+ break;
+ case PLUGIN_CONFIG_EXTRA_ENTRY:
+ conf->value.entry = g_strdup(value);
+ break;
+ case PLUGIN_CONFIG_EXTRA_SPINBUTTON:
+ conf->value.spin_btn = atoi(value);
+ break;
+ }
+ abook->extra_config =
+ g_slist_prepend(abook->extra_config, conf);
+ }
+ }
+ }
+ }
+ if (values)
+ gslist_free(&values, g_free);
+ g_strfreev(keys);
+ }
+
+ return abook;
+}
+
+/**
+ * Remove addressbook from list
+ * @param list List to change
+ * @param book AddressBook to remove
+ * @return TRUE if item was removed
+ */
+static gboolean g_list_book_remove(GList** list, AddressBook* book) {
+ if (! list || !*list || ! book)
+ return FALSE;
+
+ GList* found = g_list_find_custom(*list, book, address_book_compare);
+ if (found)
+ *list = g_list_remove_link(*list, found);
+
+ return (found != NULL);
+}
+
+static gchar* filter_token_add(const gchar* token, const gchar* search) {
+ return g_strconcat("(", token, "=", search, ")", NULL);
+}
+
+#define FILTER "(objectClass=*)"
+//#define FILTER "(sn=Rasmussen)"
+static gchar* filter_build(Contact* contact, gboolean intersection) {
+ GString* filter;
+ //gchar* token;
+
+ if (contact) {
+ //if (i > 0 && !(i % 2)) {
+ // if (intersection)
+ // filter = g_string_prepend(filter, "(&");
+ // else
+ // filter = g_string_prepend(filter, "(|");
+ // filter = g_string_append(filter, token);
+ // filter = g_string_append(filter, ")");
+ //}
+ //else
+ // filter = g_string_append(filter, token);
+ //i++;
+ }
+ else {
+ filter = g_string_new(FILTER);
+ }
+
+ return g_string_free(filter, FALSE);
+}
+
+static gchar* filter_build_standard() {
+ gchar** list = standard_attribs;
+ gchar* token;
+ GString* filter = g_string_new("");
+ gboolean multi = FALSE;
+
+ while(*list) {
+ token = filter_token_add(native2ldap(*list++), "*");
+ if (multi) {
+ filter = g_string_prepend(filter, "(|");
+ filter = g_string_append(filter, token);
+ filter = g_string_append(filter, ")");
+ }
+ else
+ filter = g_string_append(filter, token);
+ multi = TRUE;
+ g_free(token);
+ }
+
+ return g_string_free(filter, FALSE);
+}
+
+static GList* fetch_data(Server* server, LDAPMessage* res) {
+ LDAPMessage* entry;
+ BerElement* ber;
+ Contact* contact;
+ Email* email;
+ gchar* attribute;
+ struct berval** vals;
+ GList* result = NULL;
+ gchar* value;
+ int i, count = 0;
+
+ /* Process entries */
+ for (entry = ldap_first_entry(server->ldap, res); entry;
+ entry = ldap_next_entry(server->ldap, entry), count++) {
+ debug_print("------------------------\n");
+ contact = contact_new();
+ value = ldap_get_dn(server->ldap, entry);
+ debug_print("Found: DN->%s\n", value);
+ g_free(value);
+ /* Process attributes */
+ for (attribute = ldap_first_attribute(server->ldap, entry, &ber);
+ attribute; attribute = ldap_next_attribute(server->ldap, entry, ber)) {
+
+ vals = ldap_get_values_len(server->ldap, entry, attribute);
+ if (vals) {
+ /* We only handle one value per attribute except for mail */
+ if (strcasecmp(attribute, "mail") == 0) {
+ for (i = 0; vals[i]; i++) {
+ email = g_new0(Email, 1);
+ value = g_strndup(vals[i]->bv_val, vals[i]->bv_len);
+ email->email = g_strdup(value);
+ debug_print("Found (email): %s->%s\n", attribute, value);
+ g_free(value);
+ contact->emails = g_slist_prepend(contact->emails, email);
+ }
+ }
+ else {
+ value = g_strndup(vals[0]->bv_val, vals[0]->bv_len);
+ debug_print("Found (other): %s->%s\n", attribute, value);
+ const gchar* key = ldap2native(attribute);
+ AttribDef* attr = pack_data(ATTRIB_TYPE_STRING, key, value);
+ g_hash_table_insert(contact->data, g_strdup(key), attr);
+ g_free(value);
+ }
+ }
+ ldap_memfree(attribute);
+ ldap_value_free_len(vals);
+ }
+ if (ber) {
+ ber_free(ber, 0);
+ }
+ ber = NULL;
+ //contact_dump(contact, stderr);
+ result = g_list_prepend(result, contact);
+ }
+ if (count)
+ debug_print("------------------------\n");
+ debug_print("Found: %d contacts\n", count);
+
+ return result;
+}
+
+static gboolean fetch_all(AddressBook* book,
+ AbookConnection* ac,
+ gchar** error) {
+ GSList* cur;
+ Server* server = ac->server;
+ gchar** ldap_attributes;
+ int rc = 0;
+ struct timeval timeOut;
+ LDAPMessage* res = NULL;
+ gchar* search_filter;
+ GString* filter;
+ gint max_size;
+
+ if (server->timeout > 0)
+ timeOut.tv_sec = server->timeout;
+ else
+ timeOut.tv_sec = TIMEOUT;
+ timeOut.tv_usec = 0;
+ max_size = server->max_entries;
+ ldap_attributes = attriblist2ldap();
+ search_filter = filter_build(ac->contact, ac->intersection);
+ filter = g_string_new("(&");
+ filter = g_string_append(filter, search_filter);
+ g_free(search_filter);
+ search_filter = filter_build_standard();
+ filter = g_string_append(filter, search_filter);
+ filter = g_string_append(filter, ")");
+ g_free(search_filter);
+ search_filter = g_string_free(filter, FALSE);
+ //debug_set_mode(TRUE);
+ debug_print("Filter: %s\n", search_filter);
+ //debug_set_mode(FALSE);
+ if (server->search_base) {
+ rc = ldap_search_ext_s(server->ldap, server->search_base,
+ LDAP_SCOPE_SUBTREE, search_filter, ldap_attributes,
+ 0, NULL, NULL, &timeOut, max_size, &res);
+ }
+ else {
+ for (cur = server->baseDN; cur; cur = g_slist_next(cur)) {
+ rc = ldap_search_ext_s(server->ldap, (gchar *) cur->data,
+ LDAP_SCOPE_SUBTREE, search_filter, ldap_attributes,
+ 0, NULL, NULL, &timeOut, max_size, &res);
+ if (rc == LDAP_SUCCESS) {
+ GList* list = fetch_data(server, res);
+ if (list) {
+ book->contacts = g_list_concat(book->contacts, list);
+ list = NULL;
+ }
+ }
+ if (res)
+ ldap_msgfree(res);
+ }
+ }
+ g_free(search_filter);
+ book->contacts = fetch_data(server, res);
+ //debug_set_mode(FALSE);
+ if (res)
+ ldap_msgfree(res);
+
+ g_strfreev(ldap_attributes);
+
+ if (rc) {
+ if (error)
+ *error = g_strdup(ldap_err2string(rc));
+ debug_print("bind: %s\n", ldap_err2string(rc));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void set_extra_config() {
+ ExtraConfig* ec;
+
+ ec = g_new0(ExtraConfig, 1);
+ ec->type = PLUGIN_CONFIG_EXTRA_ENTRY;
+ ec->label = g_strdup(_("Search base"));
+ ec->tooltip = g_strdup(_("This specifies the name of the directory to be searched "
+ "on the server. Examples include:\ndc=claws-mail,dc=org\n"
+ " ou=people,dc=domainname,dc=com\n"
+ " o=Organization Name,c=Country."));
+ extra_config = g_slist_append(extra_config, ec);
+
+ ec = g_new0(ExtraConfig, 1);
+ ec->type = PLUGIN_CONFIG_EXTRA_SPINBUTTON;
+ ec->label = g_strdup(_("Timeout (Sec)"));
+ ec->tooltip = g_strdup(_("Timeout in seconds. 0 means wait infinite."));
+ ec->default_value.spin_btn = 20;
+ extra_config = g_slist_append(extra_config, ec);
+
+ ec = g_new0(ExtraConfig, 1);
+ ec->type = PLUGIN_CONFIG_EXTRA_SPINBUTTON;
+ ec->label = g_strdup(_("Max entries"));
+ ec->tooltip = g_strdup(_("Maximum number of contacts to receive. 0 means all."));
+ ec->default_value.spin_btn = 30;
+ extra_config = g_slist_append(extra_config, ec);
+}
+
/**
* Free list of abooks.
* @param error Pointer to memory where error is supposed to be saved
* @return GSList* list of AttributeDef
*/
GSList* plugin_remaining_attribs(void) {
+ remaining_attribs = g_slist_append(remaining_attribs, NULL);
return gslist_deep_copy(remaining_attribs, attrib_def_copy);
}
* naming supported attributes.
*/
void plugin_attribs_set(GHashTable* attributes) {
- hash_table_free(&attribs);
- attribs = hash_table_copy(attributes);
}
/**
* @return TRUE if success FALSE otherwise
*/
gboolean plugin_abook_open(AddressBook* abook, gchar** error) {
+ gboolean removed;
+ AbookConnection* ac = get_abook_connection(abook);
+
+ if (! ac) {
+ ac = abook_connection_new();
+ gchar* plain = g_strconcat(abook->abook_name, abook->URL, NULL);
+ gchar* id = sha256(plain);
+ g_free(plain);
+ ac->id = g_strdup(id);
+ g_free(id);
+ abook_connection = g_slist_prepend(abook_connection, ac);
+ }
+
+ removed = g_list_book_remove(&closed_books, abook);
+ abooks = g_list_prepend(abooks, abook);
if (abook->dirty) {
/* Open Address book */
+ ldap_connect(ac, error);
if (*error) {
show_message(NULL, GTK_UTIL_MESSAGE_WARNING, "%s", *error);
g_free(*error);
*error = NULL;
+ if (removed)
+ closed_books = g_list_prepend(closed_books, abook);
+ g_list_book_remove(&abooks, abook);
+ return FALSE;
+ }
+ if (fetch_all(abook, ac, error)) {
+ show_message(NULL, GTK_UTIL_MESSAGE_WARNING, "%s", *error);
+ g_free(*error);
+ *error = NULL;
+ if (removed)
+ closed_books = g_list_prepend(closed_books, abook);
+ g_list_book_remove(&abooks, abook);
return FALSE;
}
abook->dirty = FALSE;
}
- abooks = g_list_prepend(abooks, abook);
+ abook->open = TRUE;
return TRUE;
}
gboolean plugin_abook_delete(AddressBook* abook, gchar** error) {
if (! abook)
return FALSE;
-
- plugin_abook_close(abook, error);
- if (abook->URL) {
- /* Delete address book */
+
+ AbookConnection* ac = get_abook_connection(abook);
+ if (! ac) {
+ if (error)
+ *error = g_strdup_printf(_("%s: Not found"), abook->abook_name);
+ return FALSE;
}
+
+ if (abook->open)
+ self->abook_close(abook, error);
+ /* Remove from closed books since deleting */
+ closed_books = g_list_remove(closed_books, abook);
+ abook_connection = g_slist_remove(abook_connection, ac);
+ abook_connection_free(ac);
+ ac = NULL;
return TRUE;
}
* @return TRUE if success FALSE otherwise
*/
gboolean plugin_abook_close(AddressBook* abook, gchar** error) {
+ AbookConnection* ac = get_abook_connection(abook);
+ if (! ac) {
+ if (error)
+ *error = g_strdup_printf(_("%s: Not found"), abook->abook_name);
+ return FALSE;
+ }
+
+ deconnect(ac->server);
debug_print("List contains %d elements before\n", g_list_length(abooks));
abooks = g_list_remove(abooks, abook);
address_book_contacts_free(abook);
debug_print("List contains %d elements after\n", g_list_length(abooks));
+ closed_books = g_list_prepend(closed_books, abook);
+ abook->open = FALSE;
+
return TRUE;
}
gboolean plugin_abook_set_config(AddressBook* old,
AddressBook* new,
gchar** error) {
+ AddressBook* abook;
+ AbookConnection* ac;
+ GSList* cur;
+
+ if (! config) {
+ if (error) {
+ *error = g_strdup(_("Missing config file"));
+ }
+ return TRUE;
+ }
+
+ ac = abook_connection_new();
if (new) {
/* Update config for existing address book */
+ self->abook_delete(old, error);
+ if (*error)
+ return TRUE;
+ abook = new;
}
else {
/* Write config for new address book */
+ abook = old;
}
+ if (! abook->abook_name || strlen(abook->abook_name) < 1) {
+ if (error) {
+ *error = g_strdup(_("Missing name for address book"));
+ }
+ abook_connection_free(ac);
+ ac = NULL;
+ return FALSE;
+ }
+
+ g_key_file_set_string(config->key_file, abook->abook_name, "url", abook->URL);
+ g_key_file_set_string(config->key_file, abook->abook_name, "username", abook->username);
+ g_key_file_set_string(config->key_file, abook->abook_name,
+ "password", aes256_encrypt(abook->password, TRUE));
+
+ if (abook->extra_config) {
+ for (cur = abook->extra_config; cur; cur = g_slist_next(cur)) {
+ ExtraConfig* conf = (ExtraConfig *) cur->data;
+ switch (conf->type) {
+ case PLUGIN_CONFIG_EXTRA_CHECKBOX:
+ g_key_file_set_boolean(config->key_file,
+ abook->abook_name, conf->label, conf->value.check_btn);
+ break;
+ case PLUGIN_CONFIG_EXTRA_ENTRY:
+ g_key_file_set_string(config->key_file,
+ abook->abook_name, conf->label, conf->value.entry);
+ break;
+ case PLUGIN_CONFIG_EXTRA_SPINBUTTON:
+ g_key_file_set_integer(config->key_file,
+ abook->abook_name, conf->label, conf->value.spin_btn);
+ break;
+ }
+ }
+ }
+
+ abook->dirty = TRUE;
+
+ gchar* plain = g_strconcat(abook->abook_name, abook->URL, NULL);
+ gchar* id = sha256(plain);
+ g_free(plain);
+ if (ac->id)
+ g_free(ac->id);
+ ac->id = g_strdup(id);
+ g_free(id);
+
return (*error) ? TRUE : FALSE;
}
GList *cur, *list = NULL;
for (cur = closed_books; cur; cur = g_list_next(cur)) {
+ /* No deep copy since list of contacts will always be NULL */
+ list = g_list_prepend(list, address_book_copy(cur->data, FALSE));
}
return list;
* @param Pointer to memory where error is supposed to be saved
* @return FALSE if success TRUE otherwise
*/
-gboolean plugin_init(gchar** error) {
+gboolean plugin_init(gpointer self_ref, gchar** error) {
gchar *basedir, *path;
- GSList *list = NULL, *cur;
+ GSList *cur;
ConfiguredBooks* cf_books;
ClosedBooks* cl_books;
+ self = (Plugin *) self_ref;
if (*error != NULL) {
g_free(*error);
*error = NULL;
basedir, G_DIR_SEPARATOR_S, self_home,
G_DIR_SEPARATOR_S, configrc, NULL);
+ set_extra_config();
config = plugin_config_new(path);
+ g_free(path);
config_get(config, error);
if (*error) {
}
else {
if (config) {
+ set_attribs();
+ if (debug_get_mode()) {
+ hash_table_dump(attribs, stderr);
+ }
+
cf_books = config->configured_books;
cl_books = config->closed_books;
if (cf_books) {
- g_free(path);
- path = g_strconcat(basedir, G_DIR_SEPARATOR_S,
- self_home, G_DIR_SEPARATOR_S, NULL);
for (cur = cf_books->books; cur; cur = g_slist_next(cur)) {
gchar* book = (gchar *) cur->data;
- if (! cl_books) {
- AddressBook* abook = address_book_new();
- abook->URL = g_strconcat(path, book, ".xml", NULL);
- plugin_abook_open(abook, error);
- }
+ AddressBook* abook = get_addressbook(book);
+ gboolean ok = self->abook_open(abook, error);
+ if (! ok)
+ closed_books = g_list_prepend(closed_books, abook);
}
}
- config_get_value(config, "supported attributes", "attributes", &list);
- attribs = hash_table_new();
- for (cur = list; cur; cur = g_slist_next(cur)) {
- gchar* key = (gchar *) cur->data;
- debug_print("Adding '%s' to attributes\n", key);
- AttribDef* attr = g_new0(AttribDef, 1);
- attr->attrib_name = g_strdup(key);
- attr->type = ATTRIB_TYPE_STRING;
- g_hash_table_replace(attribs, g_strdup(key), attr);
- }
- gslist_free(&list, g_free);
-
- config_get_value(config, "deactivated attributes", "attributes", &list);
- for (cur = list; cur; cur = g_slist_next(cur)) {
- gchar* key = (gchar *) cur->data;
- debug_print("Adding '%s' to deactivated attributes\n", key);
- AttribDef* attr = g_new0(AttribDef, 1);
- attr->attrib_name = g_strdup(key);
- attr->type = ATTRIB_TYPE_STRING;
- inactive_attribs = g_slist_prepend(inactive_attribs, attr);
+ if (cl_books) {
+ for (cur = cl_books->books; cur; cur = g_slist_next(cur)) {
+ gchar* book = (gchar *) cur->data;
+ AddressBook* abook = get_addressbook(book);
+ closed_books = g_list_prepend(closed_books, abook);
+ }
}
- gslist_free(&list, g_free);
-
- //hash_table_dump(attribs, stderr);
}
}
g_free(basedir);
- g_free(path);
-
+
return FALSE;
}
* @return TRUE if success FALSE otherwise
*/
gboolean plugin_done(void) {
+ write_config_file();
+ abooks_free();
+ plugin_config_free(&config);
g_free(feature);
+ hash_table_free(&attribs);
+ gslist_free(&remaining_attribs, NULL);
+ gslist_free(&abook_connection, abook_connection_free);
+ gslist_free(&extra_config, extra_config_free);
+
+ self = NULL;
return TRUE;
}
* @return description
*/
const gchar* plugin_desc(void) {
- return _("This plugin provides LDAP support.");
+ return _("This plugin provides LDAP support.\n"
+ "The plugin only supports LDAPv3 and the format\n"
+ "for a URL is as follows:\n\n"
+ "[(ldap|ldaps)://](FQDN|IP)[:port]\n\n"
+ "If neither ldap nor ldaps is specified ldap is assumed.\n"
+ "If port is not specified 389 is assumed for ldap\n"
+ "and 636 is assumed for ldaps.\n"
+ "If ldap is used for schema then TLS will be tried\n"
+ "automatically before using plain text connection.");
}
/**
* Get functional type of plugin. Returned memory is owned by the plugin.
* @return type
*/
-const gchar* plugin_type(void) {
- return "LDAP";
+PluginType plugin_type(void) {
+ return PLUGIN_TYPE_ADVANCED;
+}
+
+/**
+ * Get file filter for this plugin
+ * @return filter or NULL. If returning NULL means data storage is
+ * URL based URI based otherwise
+ */
+const gchar* plugin_file_filter(void) {
+ return NULL;
}
/**
return "GPL3+";
}
+/**
+ * Does the plugin needs credentials for address books
+ * @return bool
+ */
+gboolean plugin_need_credentials(void) {
+ return TRUE;
+}
+
+/**
+ * Get list of additional config
+ * @return NULL if no additional config is required, a list of
+ * ExtraConfig otherwise
+ */
+GSList* plugin_extra_config(void) {
+ return g_slist_reverse(gslist_deep_copy(extra_config, extra_config_copy));
+}
gint equal;
} Compare;
+static Plugin* self = NULL;
static PluginFeature* feature = NULL;
static const gchar subtype[] = "Claws-mail native addressbook";
static GHashTable* attribs = NULL;
}
cur = NULL;
- cur_attribs = plugin_attrib_list();
+ cur_attribs = self->attrib_list();
for (tmp = cur_attribs; tmp; tmp = g_slist_next(tmp)) {
AttribDef* attrdef = (AttribDef *) tmp->data;
cur = g_slist_prepend(cur, g_strdup(attrdef->attrib_name));
gslist_free(&cur, g_free);
cur = NULL;
- cur_attribs = plugin_inactive_attribs();
+ cur_attribs = self->inactive_attribs();
for (tmp = cur_attribs; tmp; tmp = g_slist_next(tmp)) {
AttribDef* attrdef = (AttribDef *) tmp->data;
cur = g_slist_prepend(cur, g_strdup(attrdef->attrib_name));
closed_books = g_list_remove_link(closed_books, found);
}
-gboolean plugin_init(gchar** error) {
+gboolean plugin_init(gpointer self_ref, gchar** error) {
gchar *basedir, *path;
GSList *list = NULL, *cur, *found;
ConfiguredBooks* cf_books;
ClosedBooks* cl_books;
+ self = (Plugin *) self_ref;
if (*error != NULL) {
g_free(*error);
*error = NULL;
if (*error || ! attribs) {
return TRUE;
}
- plugin_abook_open(NULL, error);
+ self->abook_open(NULL, error);
}
else {
basedir = get_self_home();
if (! cl_books || ! found) {
AddressBook* abook = address_book_new();
abook->URL = g_strconcat(path, book, ".xml", NULL);
- plugin_abook_open(abook, error);
+ gboolean ok = self->abook_open(abook, error);
+ if (! ok)
+ closed_books = g_list_prepend(closed_books, abook);
}
}
}
old_abook_free_dir();
gslist_free(&inactive_attribs, attrib_def_free);
+ self = NULL;
+
return TRUE;
}
return VERSION;
}
-const gchar* plugin_type(void) {
+PluginType plugin_type(void) {
+ return PLUGIN_TYPE_SIMPLE;
+}
+
+const gchar* plugin_file_filter(void) {
return "xml";
}
show_message(NULL, GTK_UTIL_MESSAGE_WARNING, "%s", *error);
g_free(*error);
*error = NULL;
+ closed_books = g_list_prepend(closed_books, abook);
return FALSE;
}
abook_set_next_uid(abook);
}
abooks = g_list_prepend(abooks, abook);
}
+ abook->open = TRUE;
return TRUE;
}
gboolean plugin_abook_delete(AddressBook* abook, gchar** error) {
if (! abook)
return FALSE;
-
- plugin_abook_close(abook, error);
+
+ if (abook->open)
+ self->abook_close(abook, error);
+ /* Remove from closed books since deleting */
+ closed_books = g_list_remove(closed_books, abook);
if (abook->URL) {
if (g_unlink(abook->URL) < 0) {
*error = g_new0(gchar, 1024);
strerror_r(errno, *error, 1024);
}
}
+
return TRUE;
}
address_book_contacts_free(abook);
debug_print("List contains %d elements after\n", g_list_length(abooks));
closed_books = g_list_prepend(closed_books, abook);
+ abook->open = FALSE;
+
return TRUE;
}
}
return list;
-}
\ No newline at end of file
+}
+
+gboolean plugin_need_credentials(void) {
+ return FALSE;
+}
+
+GSList* plugin_extra_config(void) {
+ return NULL;
+}
AM_CPPFLAGS = \
-DG_LOG_DOMAIN=\"Claws-Contacts\" \
- -DPLUGINDIR=\"@PLUGINDIR@\"
+ -DPLUGINDIR=\"@PLUGINDIR@\" \
+ $(LIBGCRYPT_CFLAGS)
claws_contacts_SOURCES = \
claws-contacts.c \
claws_contacts_LDADD= \
@GLIB_LIBS@ \
@GTK_LIBS@ \
- @GCRYPT_LIBS@ \
+ $(LIBGCRYPT_LIBS) \
@LIBXML_LIBS@ \
${top_builddir}/xmllib/libcontactxml.la \
${top_builddir}/src/dbus/libdbus.la
GtkFileFilter* filter;
gchar* home;
- file_filter = cur.plugin->filter();
+ file_filter = cur.plugin->file_filter();
if (file_filter) {
gchar* f = g_strconcat("*.", file_filter, NULL);
filter = gtk_file_filter_new();
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
home = cur.plugin->default_url(NULL);
- gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), home);
- g_free(home);
+ if (home) {
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), home);
+ g_free(home);
+ }
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
}
else {
show_message(win->window, GTK_UTIL_MESSAGE_INFO,
- _("Please highlight desired address book for deletion"));
+ _("Please highlight desired address book to edit"));
}
}
plugin = plugin_get_plugin(plugin_name);
if (plugin) {
if (address_book_edit(win->window, plugin, &book)) {
- if (g_file_test(book->URL, G_FILE_TEST_EXISTS)) {
+ if (plugin->file_filter() && strcmp("xml", plugin->file_filter()) == 0 &&
+ g_file_test(book->URL, G_FILE_TEST_EXISTS)) {
AddressBook* abook =
address_book_get(plugin, book->abook_name);
if (abook) {
}
}
}
- if (! addr_book_set_config(book, &error)) {
+ if (! plugin->abook_set_config(book, NULL, &error)) {
+ /*if (! addr_book_set_config(book, &error)) {*/
if (plugin->abook_open(book, &error))
update_abook_list(win);
}
}
}
+static void add_advanced_page(GtkNotebook* notebook,
+ Plugin* plugin,
+ AddressBook* address_book) {
+ GSList *cur, *extra;
+ GtkWidget *widget, *hbox, *vbox, *label;
+ gboolean new;
+
+ if (address_book->extra_config) {
+ extra = address_book->extra_config;
+ new = FALSE;
+ }
+ else {
+ extra = plugin->extra_config();
+ new = TRUE;
+ }
+
+ if (extra) {
+ vbox = gtk_vbox_new(FALSE, 0);
+ for (cur = extra; cur; cur = g_slist_next(cur)) {
+ ExtraConfig* ec = (ExtraConfig *) cur->data;
+ hbox = gtk_hbox_new(FALSE, 5);
+ switch (ec->type) {
+ case PLUGIN_CONFIG_EXTRA_CHECKBOX:
+ widget = gtk_check_button_new();
+ if (ec->label)
+ gtk_button_set_label(GTK_BUTTON(widget), ec->label);
+ if (ec->tooltip)
+ gtk_widget_set_tooltip_text(widget, ec->tooltip);
+ if (new)
+ gtk_toggle_button_set_active(
+ GTK_TOGGLE_BUTTON(widget), ec->default_value.check_btn);
+ else
+ gtk_toggle_button_set_active(
+ GTK_TOGGLE_BUTTON(widget), ec->value.check_btn);
+ label = gtk_label_new("");
+ break;
+ case PLUGIN_CONFIG_EXTRA_ENTRY:
+ widget = gtk_entry_new();
+ if (ec->label)
+ label = gtk_label_new(ec->label);
+ else
+ label = gtk_label_new("");
+ if (ec->tooltip)
+ gtk_widget_set_tooltip_text(widget, ec->tooltip);
+ if (ec->value.entry)
+ gtk_entry_set_text(GTK_ENTRY(widget), ec->value.entry);
+ else if (ec->default_value.entry)
+ gtk_entry_set_text(GTK_ENTRY(widget), ec->default_value.entry);
+ break;
+ case PLUGIN_CONFIG_EXTRA_SPINBUTTON:
+ widget = gtk_spin_button_new_with_range(0.0, 100.0, 1.0);
+ if (ec->label)
+ label = gtk_label_new(ec->label);
+ else
+ label = gtk_label_new("");
+ if (ec->tooltip)
+ gtk_widget_set_tooltip_text(widget, ec->tooltip);
+ if (new)
+ gtk_spin_button_set_value(
+ GTK_SPIN_BUTTON(widget), ec->default_value.spin_btn);
+ else
+ gtk_spin_button_set_value(
+ GTK_SPIN_BUTTON(widget), ec->value.spin_btn);
+ break;
+ }
+ gtk_widget_set_name(widget, ec->label);
+ gtk_widget_set_size_request(label, 100, -1);
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+ gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 2);
+ if (ec->type == PLUGIN_CONFIG_EXTRA_SPINBUTTON)
+ gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 2);
+ else
+ gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 2);
+ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 2);
+ }
+ label = gtk_label_new(_("Advanced settings"));
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, label);
+ if (! address_book->extra_config)
+ gslist_free(&extra, extra_config_free);
+ }
+}
+
+static void set_advanced_config(Plugin* plugin,
+ AddressBook* address_book,
+ GtkNotebook* notebook) {
+ GtkWidget *page, *widget;
+ GSList *config, *cur, *widgets = NULL, *ptr;
+ gboolean found;
+
+ config = plugin->extra_config();
+ page = gtk_notebook_get_nth_page(notebook, 1);
+
+ if (config && page) {
+ for (cur = config; cur; cur = g_slist_next(cur)) {
+ ExtraConfig* ec = (ExtraConfig *) cur->data;
+ gslist_free(&widgets, NULL);
+ widgets = find_name(GTK_CONTAINER(notebook), ec->label);
+ if (! widgets)
+ continue;
+
+ switch (ec->type) {
+ case PLUGIN_CONFIG_EXTRA_CHECKBOX:
+ found = FALSE;
+ for (ptr = widgets; ptr && !found; ptr = g_slist_next(ptr)) {
+ widget = (GtkWidget *) widgets->data;
+ if (debug_get_mode()) {
+ gchar* text = gtk_widget_get_tooltip_text(widget);
+ debug_print("%s\n", (text) ? text: "(NULL)");
+ g_free(text);
+ }
+ if (GTK_IS_CHECK_BUTTON(widget))
+ found = TRUE;
+ else
+ widget = NULL;
+ }
+ if (widget) {
+ ec->value.check_btn =
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+ }
+ break;
+ case PLUGIN_CONFIG_EXTRA_ENTRY:
+ found = FALSE;
+ for (ptr = widgets; ptr && !found; ptr = g_slist_next(ptr)) {
+ widget = (GtkWidget *) widgets->data;
+ if (debug_get_mode()) {
+ gchar* text = gtk_widget_get_tooltip_text(widget);
+ debug_print("%s\n", (text) ? text: "(NULL)");
+ g_free(text);
+ }
+ if (GTK_IS_ENTRY(widget))
+ found = TRUE;
+ else
+ widget = NULL;
+ }
+ if (widget) {
+ g_free(ec->value.entry);
+ ec->value.entry =
+ gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
+ }
+ break;
+ case PLUGIN_CONFIG_EXTRA_SPINBUTTON:
+ found = FALSE;
+ for (ptr = widgets; ptr && !found; ptr = g_slist_next(ptr)) {
+ widget = (GtkWidget *) widgets->data;
+ if (debug_get_mode()) {
+ gchar* text = gtk_widget_get_tooltip_text(widget);
+ debug_print("%s\n", (text) ? text: "(NULL)");
+ g_free(text);
+ }
+ if (GTK_IS_SPIN_BUTTON(widget))
+ found = TRUE;
+ else
+ widget = NULL;
+ }
+ if (widget) {
+ ec->value.spin_btn =
+ gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
+ }
+ break;
+ }
+ }
+ address_book->extra_config = config;
+ }
+ else
+ gslist_free(&config, extra_config_free);
+}
+
#define GTK_ENTRIES 4
-gboolean address_book_edit(
- GtkWidget* parent, Plugin* plugin, AddressBook** address_book) {
+gboolean address_book_edit(GtkWidget* parent,
+ Plugin* plugin,
+ AddressBook** address_book) {
GtkWidget* dialog;
GtkWidget* file_btn;
AddressBook* book;
GtkWidget* input[GTK_ENTRIES] = {NULL, NULL, NULL, NULL};
- GtkWidget *hbox, *vbox, *label = NULL, *frame;
+ GtkWidget *hbox, *vbox, *label = NULL, *frame, *notebook;
int i;
gboolean response = FALSE;
gboolean use_button = FALSE;
struct DataContainer data;
gboolean show_url = TRUE;
+ gchar* title = NULL;
for (i = 0; i < GTK_ENTRIES; i++) {
input[i] = gtk_entry_new();
if (book->password)
gtk_entry_set_text(GTK_ENTRY(input[3]), book->password);
gtk_entry_set_visibility(GTK_ENTRY(input[3]), FALSE);
+ title = g_strdup(_("Edit address book"));
}
else {
book = *address_book = address_book_new();
- show_url = FALSE;
+ title = g_strdup(_("New address book"));
+ /*if (! plugin->file_filter())
+ show_url = FALSE;*/
}
dialog = gtk_dialog_new_with_buttons(
- _("Edit address book"),
+ title,
GTK_WINDOW(parent),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_OK, GTK_RESPONSE_OK,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
NULL);
+ g_free(title);
gtk_widget_set_size_request(dialog, 360, -1);
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
if (show_url) {
label = gtk_label_new(_("URL"));
gtk_widget_set_tooltip_text(
- input[i], _("URL or path to this address book"));
- file_btn = gtk_button_new_from_stock(GTK_STOCK_OPEN);
- gtk_widget_set_tooltip_text(
- file_btn, _("Open file dialog"));
- data.plugin = plugin;
- data.addressbook = address_book;
- data.widget = input[i];
- g_signal_connect(file_btn, "clicked",
- G_CALLBACK(select_addr_book_cb), &data);
- use_button = TRUE;
+ input[i], _("URL or path to this address book\n"
+ "Must conform to plugin requirements."));
+ if (plugin->file_filter()) {
+ file_btn = gtk_button_new_from_stock(GTK_STOCK_OPEN);
+ gtk_widget_set_tooltip_text(
+ file_btn, _("Open file dialog"));
+ data.plugin = plugin;
+ data.addressbook = address_book;
+ data.widget = input[i];
+ g_signal_connect(file_btn, "clicked",
+ G_CALLBACK(select_addr_book_cb), &data);
+ use_button = TRUE;
+ }
}
else {
gtk_widget_destroy(input[i]);
label = gtk_label_new(_("Username"));
gtk_widget_set_tooltip_text(
input[i], _("And optional username"));
+ if (!plugin->need_credentials())
+ gtk_widget_set_sensitive(input[i], FALSE);
break;
case 3:
label = gtk_label_new(_("Password"));
gtk_widget_set_tooltip_text(
input[i], _("And optional password"));
+ gtk_entry_set_visibility(GTK_ENTRY(input[i]), FALSE);
+ if (!plugin->need_credentials())
+ gtk_widget_set_sensitive(input[i], FALSE);
break;
}
if (input[i]) {
- gtk_widget_set_size_request(label, 60, -1);
+ gtk_widget_set_size_request(label, 100, -1);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 2);
gtk_box_pack_start(GTK_BOX(hbox), input[i], TRUE, TRUE, 2);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 2);
}
}
- frame = gtk_frame_new(_("Address book settings"));
- gtk_container_add(GTK_CONTAINER(frame), vbox);
- gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), frame);
- gtk_widget_show_all(frame);
+ if (plugin->extra_config()) {
+ notebook = gtk_notebook_new();
+ label = gtk_label_new(_("Basic settings"));
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, label);
+ add_advanced_page(GTK_NOTEBOOK(notebook), plugin, book);
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), notebook);
+ gtk_widget_show_all(notebook);
+ }
+ else {
+ frame = gtk_frame_new(_("Address book settings"));
+ gtk_container_add(GTK_CONTAINER(frame), vbox);
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), frame);
+ gtk_widget_show_all(frame);
+ }
dialog_set_focus(GTK_DIALOG(dialog), GTK_STOCK_CANCEL);
gint result = gtk_dialog_run(GTK_DIALOG(dialog));
book->password =
gtk_editable_get_chars(GTK_EDITABLE(input[3]), 0, -1);
response = TRUE;
+ if (plugin->extra_config()) {
+ //gslist_free(&book->extra_config, extra_config_free);
+ set_advanced_config(plugin, book, GTK_NOTEBOOK(notebook));
+ }
break;
case GTK_RESPONSE_CANCEL:
break;
g_signal_connect(GTK_TREE_MODEL(list), "row-deleted",
G_CALLBACK(row_deleted_cb), cw);
cw->email_list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(list));
+ g_object_unref(list);
gtk_widget_set_tooltip_text(GTK_WIDGET(cw->email_list),
_("Double-click, enter, or space on cell will activate edit mode\n"
"Mouse-Left-click to drag and drop for reordering email list\n"
mainwindow->abook_list =
gtk_tree_view_new_with_model(GTK_TREE_MODEL(list));
+ g_object_unref(list);
gtk_widget_set_name(mainwindow->abook_list, "abook_list");
gtk_widget_set_tooltip_text(mainwindow->abook_list,
_("Address book in BOLD is the default address book"));
mainwindow->contact_list =
gtk_tree_view_new_with_model(GTK_TREE_MODEL(list));
+ g_object_unref(list);
gtk_widget_set_tooltip_text(GTK_WIDGET(mainwindow->contact_list),
_("Double-click, enter, or space on cell will activate edit mode"));
gtk_tree_view_set_rules_hint(
g_free(plugin);
}
-static gchar* format_hash(const guchar* md_string) {
- int len = gcry_md_get_algo_dlen(GCRY_MD_SHA256);
- int i;
- gchar* hex = g_new0(gchar, 2 * len + 1);
-
- for (i = 0; i < len; i++)
- sprintf(hex + 2 * i, "%02x", md_string[i]);
-
- return hex;
-}
-
-#define key "claws-mail address book"
static void compute_hash(Plugin* plugin) {
- gcry_error_t err = 0;
- gcry_md_hd_t digest = NULL;
- guchar* md_string = NULL;
gchar* cipher;
gchar* plain = g_strconcat(
plugin->desc(), " ",
plugin->version(), NULL);
- err = gcry_md_open(
- &digest, GCRY_MD_SHA256,
- GCRY_MD_FLAG_SECURE | GCRY_MD_FLAG_HMAC);
- if (err) {
- g_printerr("%s\n", gcry_strerror(err));
- goto done;
- }
- err = gcry_md_setkey(digest, key, strlen(key));
- if (err) {
- g_printerr("%s\n", gcry_strerror(err));
- goto done;
- }
-
- gcry_md_write(digest, plain, strlen(plain));
-
- md_string = gcry_md_read(digest, 0);
- cipher = format_hash(md_string);
+ cipher = sha256(plain);
+ g_free(plain);
debug_print("Computed hash: %s\n", cipher);
+
plugin->id = g_strdup(cipher);
g_free(cipher);
-
- done:
- gcry_md_close(digest);
- g_free(plain);
}
static Plugin* plugin_allready_loaded(Plugin* plugin, gchar** error) {
if (found) {
g_free(found->error);
found->error = NULL;
- if (found->init(error)) {
+ if (found->init(found, error)) {
if (*error)
found->error = g_strdup(*error);
else
get_contact, set_contact, delete_contact, search_contact, update_contact,
plugin_abook_open, plugin_abook_close, plugin_abook_delete,
plugin_addrbook_all_get, plugin_abook_set_config, plugin_type,
- plugin_url, plugin_attribs_set, plugin_commit_all,
+ plugin_file_filter, plugin_url, plugin_attribs_set, plugin_commit_all,
plugin_remaining_attribs, plugin_inactive_attribs,
- plugin_closed_books_get;
+ plugin_closed_books_get, plugin_need_credentials, plugin_extra_config;
plugin = g_new0(Plugin, 1);
if (plugin == NULL) {
!g_module_symbol(plugin->module, "plugin_desc", &plugin_desc) ||
!g_module_symbol(plugin->module, "plugin_default_url", &plugin_url) ||
!g_module_symbol(plugin->module, "plugin_type", &plugin_type) ||
+ !g_module_symbol(plugin->module, "plugin_file_filter", &plugin_file_filter) ||
!g_module_symbol(plugin->module, "plugin_version", &plugin_version) ||
!g_module_symbol(plugin->module, "plugin_provides", &plugin_provides) ||
!g_module_symbol(plugin->module, "plugin_license", &plugin_license) ||
!g_module_symbol(plugin->module, "plugin_abook_set_config", &plugin_abook_set_config) ||
!g_module_symbol(plugin->module, "plugin_commit_all", &plugin_commit_all) ||
!g_module_symbol(plugin->module, "plugin_init", &plugin_init) ||
+ !g_module_symbol(plugin->module, "plugin_need_credentials", &plugin_need_credentials) ||
+ !g_module_symbol(plugin->module, "plugin_extra_config", &plugin_extra_config) ||
!g_module_symbol(plugin->module, "plugin_closed_books_get", &plugin_closed_books_get) ||
!g_module_symbol(plugin->module, "plugin_remaining_attribs", &plugin_remaining_attribs) ||
!g_module_symbol(plugin->module, "plugin_inactive_attribs", &plugin_inactive_attribs)) {
}
plugin->init = plugin_init;
- plugin->filter = plugin_type;
+ plugin->file_filter = plugin_file_filter;
plugin->reset = plugin_reset;
plugin->name = plugin_name;
plugin->desc = plugin_desc;
plugin->type = plugin_type;
plugin->license = plugin_license;
plugin->filename = g_strdup(filename);
+ plugin->need_credentials = plugin_need_credentials;
+ plugin->extra_config = plugin_extra_config;
plugin->error = NULL;
plugin->abook_open = plugin_abook_open;
plugin->abook_close = plugin_abook_close;
}
}
else {
- if (plugin->init(error)) {
+ if (plugin->init(plugin, error)) {
if (*error)
plugin->error = g_strdup(*error);
else
gchar* id;
//gchar* addrbook_name;
GModule *module;
- gboolean (*init) (gchar** error);
+ gboolean (*init) (gpointer self, gchar** error);
void (*reset) (gchar** error);
const gchar* (*name) (void);
const gchar* (*desc) (void);
const gchar* (*version) (void);
- const gchar* (*type) (void);
+ PluginType (*type) (void);
const gchar* (*license) (void);
- const gchar* (*filter) (void);
+ const gchar* (*file_filter) (void);
+ gboolean (*need_credentials) (void);
+ GSList* (*extra_config) (void);
gchar* (*default_url) (const gchar* name);
GSList* (*remaining_attribs) (void);
GSList* (*inactive_attribs) (void);
#define IS_READ_WRITE(X) ((X & PLUGIN_READ_WRITE) == PLUGIN_READ_WRITE)
#define HAS_ADV_SEARCH(X) ((X & PLUGIN_ADVANCED_SEARCH) == PLUGIN_ADVANCED_SEARCH)
+typedef enum {
+ PLUGIN_CONFIG_EXTRA_ENTRY,
+ PLUGIN_CONFIG_EXTRA_CHECKBOX,
+ PLUGIN_CONFIG_EXTRA_SPINBUTTON
+} ExtraConfigType;
+
+typedef struct {
+ ExtraConfigType type;
+ gchar* label;
+ gchar* tooltip;
+ union {
+ gchar* entry;
+ gboolean check_btn;
+ gint spin_btn;
+ } value;
+ union {
+ gchar* entry;
+ gboolean check_btn;
+ gint spin_btn;
+ } default_value;
+} ExtraConfig;
+
+typedef enum {
+ /*
+ * Address books configured with this plugin only requires
+ * the following input to function
+ * Name: Address book name (Name must be uniq within this plugin)
+ * (URI|URL): Reference to storage (URI => File in local file system,
+ * URL => TCP based reference). If 'plugin_file_filter' returns NULL
+ * then this plugin uses URL type storage reference.
+ *
+ * Optional
+ * - Username
+ * - Password
+ */
+ PLUGIN_TYPE_SIMPLE,
+ /*
+ * Address books configured with this plugin requires additional
+ * input to function in which case the function 'plugin_extra_config'
+ * MUST return a GSList of ExtraConfig
+ */
+ PLUGIN_TYPE_ADVANCED
+} PluginType;
+
typedef struct {
guint support; /* One or more of the plugin features or'ed */
const gchar* subtype;
GList* contacts; /* contacts is expected to be a list of Contact */
gboolean dirty;
gulong next_uid;
+ gboolean open;
+ GSList* extra_config; /* List of ExtraConfig */
} AddressBook;
typedef struct {
/* Functions which must be implemented by any plugin */
-gboolean plugin_init(gchar** error);
+gboolean plugin_init(gpointer self, gchar** error);
void plugin_reset(gchar** error);
gboolean plugin_done(void);
const PluginFeature* plugin_provides(void);
const gchar* plugin_name(void);
const gchar* plugin_desc(void);
const gchar* plugin_version(void);
-const gchar* plugin_type(void);
+PluginType plugin_type(void);
+const gchar* plugin_file_filter(void);
const gchar* plugin_license(void);
+gboolean plugin_need_credentials(void);
gchar* plugin_default_url(const gchar* name);
+GSList* plugin_extra_config(void);
+
/*
* Returning NULL means list of supported attributes are infinite
* Returning an empty list means that no more supported attributes
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
+#include <gcrypt.h>
#include "utils.h"
#include "gtk-utils.h"
} CompareSListData;
*/
+typedef struct {
+ size_t size;
+ guchar* block;
+} NormBlock;
+
#define ERRBUFSIZ 255
/* Forward declarations */
void attrib_def_free(gpointer attrdef) {
AttribDef* a = (AttribDef *) attrdef;
+ if (! a)
+ return;
+
if (a->attrib_name) {
g_free(a->attrib_name);
a->attrib_name = NULL;
cl_books = config->closed_books;
if (cf_books && cf_books->group) {
- if (cf_books->books)
+ if (cf_books->books) {
list = gslist_to_array(cf_books->books, &num);
+ g_key_file_set_string_list(config->key_file, cf_books->group,
+ "books", (const gchar* const *) list, num);
+ g_strfreev(list);
+ }
else {
- list = g_new0(gchar *, 1);
+ g_key_file_set_string(config->key_file, cf_books->group,
+ "books", "");
}
- g_key_file_set_string_list(config->key_file, cf_books->group,
- "books", (const gchar* const *) list, num);
- g_strfreev(list);
}
if (cl_books && cl_books->group) {
- if (cl_books->books)
+ if (cl_books->books) {
list = gslist_to_array(cl_books->books, &num);
+ g_key_file_set_string_list(config->key_file, cl_books->group,
+ "books", (const gchar* const *) list, num);
+ g_strfreev(list);
+ }
else {
- list = g_new0(gchar *, 1);
+ g_key_file_set_string(config->key_file, cl_books->group,
+ "books", "");
}
- g_key_file_set_string_list(config->key_file, cl_books->group,
- "books", (const gchar* const *) list, num);
- g_strfreev(list);
}
}
}
static void config_close(ConfigFile* config, gchar** error) {
- gchar* data;
+ gchar *data, *dir;
GError* err = NULL;
FILE* fp;
gsize len;
g_clear_error(&err);
}
else {
- gchar* old = g_strconcat(config->path, ".bak", NULL);
- g_rename(config->path, old);
- g_free(old);
+ dir = g_path_get_dirname(config->path);
+ if (strcmp(".", dir) != 0) {
+ if (!g_file_test(dir, G_FILE_TEST_EXISTS)) {
+ if (g_mkdir(dir, 0700)) {
+ *error = g_strconcat(dir, ": Could not create", NULL);
+ goto end;
+ }
+ }
+ }
+ g_free(dir);
+ if (g_file_test(config->path, G_FILE_TEST_EXISTS)) {
+ gchar* old = g_strconcat(config->path, ".bak", NULL);
+ g_rename(config->path, old);
+ g_free(old);
+ }
fp = g_fopen(config->path, "w");
- fwrite(data, len, 1, fp);
- fclose(fp);
+ if (fp) {
+ fwrite(data, len, 1, fp);
+ fclose(fp);
+ }
+ else
+ *error = g_strconcat(config->path, ": Could not create", NULL);
}
+end:
g_free(data);
g_key_file_free(config->key_file);
config->key_file = NULL;
if (g_file_test(config->path, G_FILE_TEST_EXISTS)) {
g_key_file_load_from_file(
config->key_file, config->path, G_KEY_FILE_KEEP_COMMENTS, &err);
- if (err) {
+ if (err && err->code > 1) {
config_close(config, error);
- g_free(error);
+ g_free(*error);
if (error)
*error = g_strdup(err->message);
g_clear_error(&err);
}
g_strfreev(str);
- return TRUE;
+ return (len > 0);
}
gboolean config_set_value(ConfigFile* config, const gchar* group,
}
void plugin_config_free(ConfigFile** config_file) {
- ConfigFile* config = *config_file;
+ ConfigFile* config;
ConfiguredBooks* cf_books;
ClosedBooks* cl_books;
- if (config == NULL)
+ if (! config_file || ! *config_file)
return;
+ config = *config_file;
cf_books = config->configured_books;
cl_books = config->closed_books;
}
gchar* str = g_string_free(buf, FALSE);
gchar* tmp = str;
- tmp[strlen(tmp) - 1] = 0;
+ if (strlen(str) > 0 && str[strlen(str) - 1] == '|') {
+ tmp[strlen(tmp) - 1] = 0;
+ }
array = g_strsplit_set(tmp, "|", 0);
g_free(str);
void address_book_contacts_free(AddressBook* address_book) {
GList* cur;
+ if (! address_book)
+ return;
+
for (cur = address_book->contacts; cur; cur = g_list_next(cur)) {
Contact* c = (Contact *) cur->data;
contact_free(c);
}
void address_book_free(AddressBook** address_book) {
- AddressBook* a = *address_book;
+ AddressBook* a;
- if (! a)
+ if (! address_book || ! *address_book)
return;
-
+
+ a = *address_book;
g_free(a->abook_name);
g_free(a->URL);
g_free(a->username);
g_free(a->password);
address_book_contacts_free(a);
+ gslist_free(&a->extra_config, extra_config_free);
*address_book = NULL;
}
b->password = g_strdup(a->password);
b->next_uid = a->next_uid;
b->dirty = a->dirty;
+
if (deep) {
for (cur = a->contacts; cur; cur = g_list_next(cur)) {
b->contacts = g_list_prepend(
}
else
b->contacts = g_list_copy(a->contacts);
+
+ b->extra_config = g_slist_reverse(gslist_deep_copy(
+ a->extra_config, extra_config_copy));
debug_print("from: %d contacts to: %d contacts\n",
g_list_length(a->contacts), g_list_length(b->contacts));
GValue* email_member;
guint i;
+ if (! email)
+ return;
+
for (i = 0; i < email->n_values; i++) {
email_member = g_value_array_get_nth(email, i);
g_value_unset(email_member);
index = g_slist_index(list, data);
return index;
+}
+
+#define KEY "This is abook I1"
+static gboolean aes_init(gcry_cipher_hd_t* digest) {
+ gcry_error_t err = 0;
+ const gchar iv[] = "AbC1234567890xYz";
+
+ err = gcry_cipher_open(digest, GCRY_CIPHER_AES256,
+ GCRY_CIPHER_MODE_CBC, GCRY_CIPHER_SECURE | GCRY_CIPHER_CBC_MAC);
+ if (err) {
+ g_printerr("%s\n", gcry_strerror(err));
+ gcry_cipher_close(*digest);
+ return FALSE;
+ }
+
+ err = gcry_cipher_setkey(*digest, KEY, strlen(KEY));
+ if (err) {
+ g_printerr("%s\n", gcry_strerror(err));
+ gcry_cipher_close(*digest);
+ return FALSE;
+ }
+
+ err = gcry_cipher_setiv(*digest, iv, gcry_cipher_get_algo_blklen(GCRY_CIPHER_AES256));
+ if (err) {
+ g_printerr("%s\n", gcry_strerror(err));
+ gcry_cipher_close(*digest);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static NormBlock* normalize(const gchar* text) {
+ size_t blk_size = gcry_cipher_get_algo_blklen(GCRY_CIPHER_AES256);
+ gint passes = strlen(text) / blk_size;
+ gint rest = strlen(text) % blk_size;
+ NormBlock* norm_block = g_new0(NormBlock, 1);
+ norm_block->size = (passes * blk_size) + (rest) ? blk_size : 0;
+ gint i;
+
+ norm_block->block = g_new0(guchar, norm_block->size);
+ for (i = 0; i < strlen(text); i++) {
+ norm_block->block[i] = text[i];
+ }
+
+ return norm_block;
+}
+
+gchar* aes256_encrypt(const gchar* plain, gboolean base64_enc) {
+ gcry_error_t err = 0;
+ gcry_cipher_hd_t digest = NULL;
+ guchar *cipher;
+ NormBlock* text;
+ gchar* base64 = NULL;
+
+ if (! plain)
+ return NULL;
+
+ if (aes_init(&digest)) {
+ text = normalize(plain);
+ cipher = g_new0(guchar, text->size);
+ err = gcry_cipher_encrypt(digest, cipher, text->size, text->block, text->size);
+ if (err) {
+ g_printerr("%s\n", gcry_strerror(err));
+ }
+ else {
+ if (base64_enc)
+ base64 = g_base64_encode(cipher, text->size);
+ else
+ base64 = g_memdup(cipher, text->size);
+ }
+
+ g_free(cipher);
+ g_free(text->block);
+ g_free(text);
+ gcry_cipher_close(digest);
+ }
+
+ return base64;
+}
+
+gchar* aes256_decrypt(const gchar* cipher_text, gboolean base64_enc) {
+ gcry_error_t err = 0;
+ gcry_cipher_hd_t digest = NULL;
+ size_t size;
+ guchar* cipher;
+ guchar* plain = NULL;
+
+ if (! cipher_text)
+ return NULL;
+
+ if (base64_enc)
+ cipher = g_base64_decode(cipher_text, &size);
+ else
+ cipher = (guchar *) g_strdup(cipher_text);
+
+ if (aes_init(&digest)) {
+ plain = g_new0(guchar, size + 1);
+ err = gcry_cipher_decrypt(digest, plain, size + 1, cipher, size);
+ if (err) {
+ g_printerr("%s\n", gcry_strerror(err));
+ g_free(plain);
+ plain = NULL;
+ }
+
+ g_free(cipher);
+ gcry_cipher_close(digest);
+ }
+
+ return (gchar *) plain;
+}
+
+static gchar* format_hash(const guchar* md_string) {
+ int len = gcry_md_get_algo_dlen(GCRY_MD_SHA256);
+ int i;
+ gchar* hex = g_new0(gchar, 2 * len + 1);
+
+ for (i = 0; i < len; i++)
+ sprintf(hex + 2 * i, "%02x", md_string[i]);
+
+ return hex;
+}
+
+#define key "claws-mail address book"
+gchar* sha256(const gchar* plain) {
+ gcry_error_t err = 0;
+ gcry_md_hd_t digest = NULL;
+ guchar* md_string = NULL;
+ gchar* cipher;
+
+ if (! plain)
+ return NULL;
+
+ err = gcry_md_open(
+ &digest, GCRY_MD_SHA256,
+ GCRY_MD_FLAG_SECURE | GCRY_MD_FLAG_HMAC);
+ if (err) {
+ g_printerr("%s\n", gcry_strerror(err));
+ gcry_md_close(digest);
+ }
+
+ err = gcry_md_setkey(digest, key, strlen(key));
+ if (err) {
+ g_printerr("%s\n", gcry_strerror(err));
+ gcry_md_close(digest);
+ }
+
+ gcry_md_write(digest, plain, strlen(plain));
+
+ md_string = gcry_md_read(digest, 0);
+ cipher = format_hash(md_string);
+ gcry_md_close(digest);
+
+ return cipher;
+}
+
+void extra_config_free(gpointer data) {
+ ExtraConfig* ec;
+
+ if (! data)
+ return;
+
+ ec = (ExtraConfig *) data;
+ g_free(ec->label);
+ g_free(ec->tooltip);
+ if (ec->type == PLUGIN_CONFIG_EXTRA_ENTRY) {
+ g_free(ec->value.entry);
+ g_free(ec->default_value.entry);
+ }
+ g_free(ec);
+ ec = NULL;
+}
+
+gpointer extra_config_copy(gpointer data) {
+ ExtraConfig *a, *b;
+
+ if (! data)
+ return NULL;
+
+ a = (ExtraConfig *) data;
+ b = g_new0(ExtraConfig, 1);
+
+ b->label = g_strdup(a->label);
+ b->tooltip = g_strdup(a->tooltip);
+ b->type = a->type;
+ if (a->type == PLUGIN_CONFIG_EXTRA_ENTRY) {
+ b->value.entry = g_strdup(a->value.entry);
+ b->default_value.entry = g_strdup(a->default_value.entry);
+ }
+ else {
+ b->value = a->value;
+ b->default_value = a->default_value;
+ }
+
+ return b;
+}
+
+typedef struct {
+ GSList* list;
+ const gchar* name;
+} GtkIteratorData;
+
+static void gtk_iterator(GtkWidget* widget, gpointer data) {
+ GtkIteratorData* iter_data = (GtkIteratorData *) data;
+ const gchar* name;
+
+ if (GTK_IS_CONTAINER(widget))
+ gtk_container_foreach(GTK_CONTAINER(widget), gtk_iterator, iter_data);
+ name = gtk_widget_get_name(widget);
+ if (name && strcmp(name, iter_data->name) == 0)
+ iter_data->list = g_slist_prepend(iter_data->list, widget);
+}
+
+GSList* find_name(GtkContainer* container, const gchar* name) {
+ GSList* list = NULL;
+ GtkIteratorData* data;
+
+ if (! container || ! name)
+ return list;
+
+ data = g_new0(GtkIteratorData, 1);
+ data->name = name;
+
+ gtk_container_foreach(container, gtk_iterator, data);
+ list = data->list;
+ g_free(data);
+
+ return list;
+}
+
+ExtraConfig* get_extra_config(GSList* list, const gchar* name) {
+ GSList* cur;
+ ExtraConfig* conf = NULL;
+ gboolean found = FALSE;
+
+ if (! list || ! name)
+ return conf;
+
+ for (cur = list; cur && !found; cur = g_slist_next(cur)) {
+ conf = (ExtraConfig *) cur->data;
+ if (conf->label && strcmp(conf->label, name) == 0)
+ found = TRUE;
+ else
+ conf = NULL;
+ }
+
+ return extra_config_copy(conf);
}
\ No newline at end of file
gint gslist_compare_gchar(gconstpointer a, gconstpointer b);
GPtrArray* g_value_email_new();
void dbus_contact_print(DBusContact* contact, FILE* f);
+gchar* aes256_encrypt(const gchar* plain, gboolean base64_enc);
+gchar* aes256_decrypt(const gchar* cipher_text, gboolean base64_enc);
+gchar* sha256(const gchar* plain);
+void extra_config_free(gpointer data);
+gpointer extra_config_copy(gpointer data);
+ExtraConfig* get_extra_config(GSList* list, const gchar* name);
+GSList* find_name(GtkContainer* container, const gchar* name);
G_END_DECLS