Commit fa42589c fa42589cef090379bda4ea4e0838474520f9f4f6 by Sergey Poznyakoff

imap client: implement folder API.

* libmailutils/list/listlist.c (mu_list_append_list): Do nothing if the
source list is empty.

* include/mailutils/sys/imap.h (_mu_imap_url_init)
(_mu_imaps_url_init): New protos.
* libproto/imap/Makefile.am (libmu_imap_la_SOURCES): Restore url.c
* libproto/imap/mbox.c: Deleted
* libproto/imap/url.c: Rewrite.
* libproto/imap/folder.c: Rewrite from scratch.
* configure.ac: Build libproto/imap/tests/Makefile
* include/mailutils/imap.h (mu_imap_session_state)
(mu_imap_iserror, mu_imap_clearerr)
(mu_imap_login_secret): New protos.
* include/mailutils/sys/imap.h (_mu_imap_list_element_is_nil): New proto.

* libmailutils/mailbox/folder.c (mu_folder_list): Pass
MU_FOLDER_ATTRIBUTE_ALL.
* libproto/imap/fake-folder.c: Remove.
* libproto/imap/Makefile.am (libmu_imap_la_SOURCES): Remove fake-folder.c
Add url.c and folder.c
* libproto/imap/create.c (mu_imap_session_state)
(mu_imap_iserror, mu_imap_clearerr): New functions.
* libproto/imap/delete.c: Check input parameters.

* libproto/imap/fetch.c: Use _mu_imap_list_element_is_nil to check for
empty lists.
* libproto/imap/genlist.c: Likewise.
* libproto/imap/rename.c: Likewise.
* libproto/imap/subscribe.c: Likewise.
* libproto/imap/unsubscribe.c: Likewise.

* libproto/imap/resplist.c: Treat NIL and () equally.
* libproto/imap/login.c (mu_imap_login_secret): New function.

* mu/imap.c: Fix a typo.
1 parent 0e8ae1c3
......@@ -1460,6 +1460,7 @@ AC_CONFIG_FILES([
libproto/pop/Makefile
libproto/nntp/Makefile
libproto/imap/Makefile
libproto/imap/tests/Makefile
libmu_compat/Makefile
maidag/Makefile
mail/Makefile
......
......@@ -47,6 +47,10 @@ void mu_imap_destroy (mu_imap_t *pimap);
int mu_imap_connect (mu_imap_t imap);
int mu_imap_disconnect (mu_imap_t imap);
int mu_imap_session_state (mu_imap_t imap);
int mu_imap_iserror (mu_imap_t imap);
void mu_imap_clearerr (mu_imap_t imap);
int mu_imap_capability (mu_imap_t imap, int reread, mu_iterator_t *piter);
int mu_imap_capability_test (mu_imap_t imap, const char *name,
const char **pret);
......@@ -54,6 +58,8 @@ int mu_imap_capability_test (mu_imap_t imap, const char *name,
int mu_imap_starttls (mu_imap_t imap);
int mu_imap_login (mu_imap_t imap, const char *user, const char *pass);
int mu_imap_login_secret (mu_imap_t imap, const char *user,
mu_secret_t secret);
int mu_imap_logout (mu_imap_t imap);
int mu_imap_id (mu_imap_t imap, char **idenv, mu_assoc_t *passoc);
......
......@@ -215,6 +215,8 @@ int _mu_imap_response (mu_imap_t imap, mu_imap_response_action_t fun,
int _mu_imap_list_element_is_string (struct imap_list_element *elt,
const char *str);
int _mu_imap_list_element_is_nil (struct imap_list_element *elt);
int _mu_imap_list_nth_element_is_string (mu_list_t list, size_t n,
const char *str);
......@@ -226,6 +228,17 @@ int _mu_imap_parse_fetch_response (mu_list_t resp, mu_list_t *result_list);
void _mu_close_handler (mu_imap_t imap);
/* ----------------------------- */
/* URL Auxiliaries */
/* ----------------------------- */
int _mu_imap_url_init (mu_url_t url);
int _mu_imaps_url_init (mu_url_t url);
/* ----------------------------- */
/* Folder interface */
/* ----------------------------- */
# ifdef __cplusplus
}
# endif
......
......@@ -99,7 +99,9 @@ mu_list_insert_list (mu_list_t list, void *item, mu_list_t new_list,
void
mu_list_append_list (mu_list_t list, mu_list_t new_list)
{
if (list->count == 0)
if (new_list->count == 0)
return;
else if (list->count == 0)
{
list->head = new_list->head;
list->head.next->prev = list->head.prev->next = &list->head;
......
......@@ -368,7 +368,8 @@ mu_folder_list (mu_folder_t folder, const char *dirname, void *pattern,
size_t max_level,
mu_list_t *pflist)
{
return mu_folder_enumerate (folder, dirname, pattern, 0, max_level,
return mu_folder_enumerate (folder, dirname, pattern,
MU_FOLDER_ATTRIBUTE_ALL, max_level,
pflist, NULL, NULL);
}
......
......@@ -21,16 +21,14 @@ lib_LTLIBRARIES = libmu_imap.la
libmu_imap_la_LDFLAGS=-version-info @VI_CURRENT@:@VI_REVISION@:@VI_AGE@
libmu_imap_la_LIBADD = ${MU_LIB_AUTH} ${MU_LIB_MAILUTILS} @INTLLIBS@
# FIXME: Put these back when ready
SUBDIRS = . tests
# folder.c\
# mbox.c\
# url.c
# FIXME: Put this back when ready
# mbox.c
libmu_imap_la_SOURCES = \
appmsg.c\
appstr.c\
appstrsiz.c\
fake-folder.c\
fetch.c\
gencom.c\
genlist.c\
......@@ -68,5 +66,6 @@ libmu_imap_la_SOURCES = \
tag.c\
trace.c\
unselect.c\
unsubscribe.c
unsubscribe.c\
folder.c\
url.c
......
......@@ -64,3 +64,30 @@ _mu_imap_init (mu_imap_t imap)
imap->session_state = MU_IMAP_SESSION_INIT;
return 0;
}
int
mu_imap_session_state (mu_imap_t imap)
{
if (!imap)
return -1;
return imap->session_state;
}
int
mu_imap_iserror (mu_imap_t imap)
{
if (!imap)
return -1;
return imap->client_state == MU_IMAP_CLIENT_ERROR;
}
void
mu_imap_clearerr (mu_imap_t imap)
{
if (imap)
{
imap->client_state = MU_IMAP_CLIENT_READY;
if (imap->io)
mu_imapio_clearerr (imap->io);
}
}
......
......@@ -31,6 +31,9 @@ mu_imap_delete (mu_imap_t imap, const char *mailbox)
char const *argv[2];
static struct imap_command com;
if (!mailbox)
return EINVAL;
argv[0] = "DELETE";
argv[1] = mailbox;
......
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <mailutils/mailutils.h>
mu_record_t mu_imap_record = NULL;
mu_record_t mu_imaps_record = NULL;
......@@ -355,10 +355,10 @@ elt_to_string (struct imap_list_element *elt, char **pstr)
{
char *p;
if (elt->type != imap_eltype_string)
return EINVAL;
if (mu_c_strcasecmp (elt->v.string, "NIL") == 0)
if (_mu_imap_list_element_is_nil (elt))
p = NULL;
else if (elt->type != imap_eltype_string)
return EINVAL;
else
{
p = strdup (elt->v.string);
......@@ -386,13 +386,13 @@ _fill_subaddr (void *item, void *data)
return 0;
arg = _mu_imap_list_at (elt->v.list, 0);
if (arg && arg->type == imap_eltype_string && strcmp (arg->v.string, "NIL"))
if (arg && arg->type == imap_eltype_string)
personal = arg->v.string;
arg = _mu_imap_list_at (elt->v.list, 2);
if (arg && arg->type == imap_eltype_string && strcmp (arg->v.string, "NIL"))
if (arg && arg->type == imap_eltype_string)
local = arg->v.string;
arg = _mu_imap_list_at (elt->v.list, 3);
if (arg && arg->type == imap_eltype_string && strcmp (arg->v.string, "NIL"))
if (arg && arg->type == imap_eltype_string)
domain = arg->v.string;
if (domain && local)
......@@ -414,13 +414,10 @@ _fill_subaddr (void *item, void *data)
static int
elt_to_address (struct imap_list_element *elt, mu_address_t *paddr)
{
if (elt->type != imap_eltype_list)
{
if (mu_c_strcasecmp (elt->v.string, "NIL") == 0)
if (_mu_imap_list_element_is_nil (elt))
*paddr = NULL;
else
else if (elt->type != imap_eltype_list)
return EINVAL;
}
else
{
struct addr_env addr_env;
......
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2003, 2004, 2005, 2006, 2007, 2009,
2010, 2011 Free Software Foundation, Inc.
Copyright (C) 2011 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
......@@ -20,352 +19,395 @@
# include <config.h>
#endif
#ifdef ENABLE_IMAP
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <fnmatch.h>
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <mailutils/sys/imap.h>
#include <mailutils/sys/url.h>
#include <netinet/in.h>
#include <mailutils/auth.h>
#include <mailutils/attribute.h>
#include <mailutils/imap.h>
#include <mailutils/diag.h>
#include <mailutils/debug.h>
#include <mailutils/error.h>
#include <mailutils/errno.h>
#include <mailutils/header.h>
#include <mailutils/observer.h>
#include <mailutils/stream.h>
#include <mailutils/iterator.h>
#include <mailutils/argcv.h>
#include <mailutils/tls.h>
#include <mailutils/nls.h>
#include <mailutils/tls.h>
#include <mailutils/url.h>
#include <mailutils/list.h>
#include <mailutils/secret.h>
#include <mailutils/util.h>
#include <mailutils/cctype.h>
#include <mailutils/cstr.h>
#include <mailutils/sockaddr.h>
#include <mailutils/wordsplit.h>
#include <mailutils/sys/imap.h>
#include <mailutils/sys/folder.h>
/* For dbg purposes set to one to see different level of traffic. */
/* Print to stderr the command sent to the IMAP server. */
#define DEBUG_SHOW_COMMAND 0
/* Print to stderr the responses received from the IMAP server. */
#define DEBUG_SHOW_RESPONSE 0
/* Print to stderr the literal/quoted string received from the IMAP server. */
#define DEBUG_SHOW_DATA 0
/* Placeholders. */
#define _mu_imap_mailbox_init NULL
#define _mu_imaps_mailbox_init NULL
/* Variable use for the registrar. */
static struct _mu_record _imap_record =
{
MU_IMAP_PRIO,
MU_IMAP_SCHEME,
MU_RECORD_DEFAULT,
MU_URL_SCHEME | MU_URL_CRED | MU_URL_INET | MU_URL_PATH,
MU_URL_HOST,
_url_imap_init, /* url entry. */
_mailbox_imap_init, /* Mailbox entry. */
NULL, /* Mailer entry. */
_folder_imap_init, /* Folder entry. */
NULL, /* No need for a back pointer. */
NULL, /* _is_scheme method. */
NULL, /* _get_url method. */
NULL, /* _get_mailbox method. */
NULL, /* _get_mailer method. */
NULL /* _get_folder method. */
};
/* We export this variable: url parsing and the initialisation of the mailbox,
via the register entry/record. */
mu_record_t mu_imap_record = &_imap_record;
static void
_mu_imap_folder_destroy (mu_folder_t folder)
{
mu_imap_t imap = folder->data;
if (imap)
{
/*
mu_imap_logout (imap);
mu_imap_disconnect (imap);
*/
mu_imap_destroy (&imap);
folder->data = imap;
}
}
#ifdef WITH_TLS
static struct _mu_record _imaps_record =
/* Imap callbacks */
static void
_mu_folder_preauth_callback (void *data, int code, size_t sdat, void *pdat)
{
MU_IMAP_PRIO,
MU_IMAPS_SCHEME,
MU_RECORD_DEFAULT,
MU_URL_SCHEME | MU_URL_CRED | MU_URL_INET | MU_URL_PATH | MU_URL_PARAM,
MU_URL_HOST,
_url_imaps_init, /* url entry. */
_mailbox_imaps_init, /* Mailbox entry. */
NULL, /* Mailer entry. */
_folder_imap_init, /* Folder entry. */
NULL, /* No need for a back pointer. */
NULL, /* _is_scheme method. */
NULL, /* _get_url method. */
NULL, /* _get_mailbox method. */
NULL, /* _get_mailer method. */
NULL /* _get_folder method. */
};
mu_record_t mu_imaps_record = &_imaps_record;
#else
mu_record_t mu_imaps_record = NULL;
#endif /* WITH_TLS */
/* mu_folder_t folder = data;*/
const char *text = pdat;
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_TRACE1,
(_("IMAP server opened in preauth mode: %s"), text));
}
#ifndef HAVE_STRTOK_R
char *strtok_r (char *, const char *, char **);
#endif
static void
_mu_folder_bye_callback (void *data, int code, size_t sdat, void *pdat)
{
mu_folder_t folder = data;
const char *text = pdat;
mu_imap_t imap = folder->data;
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_TRACE1,
(_("IMAP server closing connection: %s"), text));
/*FIXME: mu_imap_disconnect (imap);*/
}
/* Concrete mu_folder_t IMAP implementation. */
static int folder_imap_open (mu_folder_t, int);
static int folder_imap_close (mu_folder_t);
static void folder_imap_destroy (mu_folder_t);
static int folder_imap_delete (mu_folder_t, const char *);
static int folder_imap_list (mu_folder_t, const char *, void *,
int, size_t,
mu_list_t,
mu_folder_enumerate_fp efp, void *edp);
static int folder_imap_lsub (mu_folder_t, const char *, const char *,
mu_list_t);
static int folder_imap_rename (mu_folder_t, const char *,
const char *);
static int folder_imap_subscribe (mu_folder_t, const char *);
static int folder_imap_unsubscribe (mu_folder_t, const char *);
static int folder_imap_get_authority (mu_folder_t, mu_authority_t *);
/* FETCH */
static int imap_fetch (f_imap_t);
static int imap_rfc822 (f_imap_t, char **);
static int imap_rfc822_size (f_imap_t, char **);
static int imap_rfc822_header (f_imap_t, char **);
static int imap_rfc822_text (f_imap_t, char **);
static int imap_fetch_flags (f_imap_t, char **);
static int imap_permanentflags (f_imap_t, char **);
static int imap_flags (char **, int *);
static int imap_bodystructure (f_imap_t, char **);
static int imap_body (f_imap_t, char **);
static int imap_internaldate (f_imap_t, char **);
static int imap_uid (f_imap_t, char **);
static int imap_status (f_imap_t);
static int imap_expunge (f_imap_t, unsigned int);
static int imap_search (f_imap_t);
/* String. */
static int imap_literal_string (f_imap_t, char **);
static int imap_string (f_imap_t, char **);
static int imap_quoted_string (f_imap_t, char **);
static int imap_mailbox_name_match (const char* pattern, const char* mailbox);
static int imap_token (char *, size_t, char **);
/* Capability */
static int parse_capa (f_imap_t f_imap, char *str);
static int read_capa (f_imap_t f_imap, int force);
static int check_capa (f_imap_t f_imap, char *capa);
/* Authentication methods */
static void
_mu_folder_bad_callback (void *data, int code, size_t sdat, void *pdat)
{
/* mu_folder_t folder = data;*/
const char *text = pdat;
typedef int (*auth_method_t) (mu_authority_t);
mu_error (_("IMAP server complains: %s"), text);
mu_error (_("This probably indicates a bug in Mailutils client code."));
mu_error (_("Please, report that to <%s>."), PACKAGE_BUGREPORT);
}
#if 0
static void
_mu_folder_fetch_callback (void *data, int code, size_t sdat, void *pdat)
{
mu_folder_t folder = data;
}
#endif
/* Simple User/pass authentication for imap. */
/* Set up an IMAP(S) connection for this folder */
static int
authenticate_imap_login (mu_authority_t auth)
_mu_imap_folder_open (mu_folder_t folder, int flags)
{
mu_folder_t folder = mu_authority_get_owner (auth);
f_imap_t f_imap = folder->data;
mu_ticket_t ticket;
int status = 0;
int rc;
mu_imap_t imap = folder->data;
struct mu_sockaddr *sa;
struct mu_sockaddr_hints hints;
char const *s;
int tls;
mu_stream_t transport;
/* FIXME: This monitor business is suspicious */
mu_monitor_wrlock (folder->monitor);
rc = mu_imap_session_state (imap);
mu_monitor_unlock (folder->monitor);
if (rc != MU_IMAP_SESSION_INIT)
return 0;
mu_url_sget_scheme (folder->url, &s);
tls = strcmp (s, "imaps") == 0;
memset (&hints, 0, sizeof (hints));
hints.flags = MU_AH_DETECT_FAMILY;
hints.port = tls ? MU_IMAP_DEFAULT_SSL_PORT : MU_IMAP_DEFAULT_PORT;
hints.protocol = IPPROTO_TCP;
hints.socktype = SOCK_STREAM;
if (check_capa (f_imap, "LOGINDISABLED") == 0)
rc = mu_sockaddr_from_url (&sa, folder->url, &hints);
if (rc)
{
MU_DEBUG (folder->debug, MU_DEBUG_TRACE, "LOGIN command disabled\n");
return ENOSYS;
s = mu_url_to_string (folder->url);
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
(_("cannot create sockaddr from URL %s: %s"),
s, mu_strerror (rc)));
return rc;
}
switch (f_imap->state)
rc = mu_tcp_stream_create_from_sa (&transport, sa, NULL, 0);
if (rc)
{
case IMAP_AUTH:
s = mu_url_to_string (folder->url);
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
(_("cannot create stream from URL %s: %s"),
s, mu_strerror (rc)));
mu_sockaddr_free (sa);
return rc;
}
#ifdef WITH_TLS
if (tls)
{
/* Grab the User and Passwd information. */
mu_authority_get_ticket (auth, &ticket);
if (f_imap->user)
free (f_imap->user);
/* Was it in the URL? */
status = mu_url_aget_user (folder->url, &f_imap->user);
if (status == MU_ERR_NOENT)
status = mu_ticket_get_cred (ticket, folder->url,
"Imap User: ", &f_imap->user, NULL);
if (status == MU_ERR_NOENT || f_imap->user == NULL)
return MU_ERR_NOUSERNAME;
else if (status)
return status;
mu_stream_t tlsstream;
status = mu_url_get_secret (folder->url, &f_imap->secret);
if (status == MU_ERR_NOENT)
status = mu_ticket_get_cred (ticket, folder->url,
"Imap Passwd: ",
NULL, &f_imap->secret);
rc = mu_tls_client_stream_create (&tlsstream, transport, transport, 0);
mu_stream_unref (transport);
if (rc)
{
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
(_("cannot create TLS stream: %s"),
mu_strerror (rc)));
return rc;
}
transport = tlsstream;
}
#endif
if (status == MU_ERR_NOENT || !f_imap->secret)
/* FIXME: Is this always right? The user might legitimately have
no password */
return MU_ERR_NOPASSWORD;
else if (status)
return status;
status = imap_writeline (f_imap, "g%lu LOGIN \"%s\" \"%s\"\r\n",
(unsigned long) f_imap->seq, f_imap->user,
mu_secret_password (f_imap->secret));
mu_secret_password_unref (f_imap->secret);
mu_secret_unref (f_imap->secret);
f_imap->secret = NULL;
CHECK_ERROR_CLOSE (folder, f_imap, status);
MU_DEBUG2 (folder->debug, MU_DEBUG_TRACE, "g%lu LOGIN %s *\n",
(unsigned long) f_imap->seq, f_imap->user);
f_imap->seq++;
free (f_imap->user);
f_imap->user = NULL;
f_imap->secret = NULL;
f_imap->state = IMAP_LOGIN;
mu_imap_set_carrier (imap, transport);
if (mu_debug_level_p (MU_DEBCAT_FOLDER, MU_DEBUG_PROT) ||
mu_debug_level_p (MU_DEBCAT_MAILBOX, MU_DEBUG_PROT))
mu_imap_trace (imap, MU_IMAP_TRACE_SET);
if (mu_debug_level_p (MU_DEBCAT_FOLDER, MU_DEBUG_TRACE6) ||
mu_debug_level_p (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE6))
mu_imap_trace_mask (imap, MU_IMAP_TRACE_SET, MU_XSCRIPT_SECURE);
if (mu_debug_level_p (MU_DEBCAT_FOLDER, MU_DEBUG_TRACE7) ||
mu_debug_level_p (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE7))
mu_imap_trace_mask (imap, MU_IMAP_TRACE_SET, MU_XSCRIPT_PAYLOAD);
/* Set callbacks */
mu_imap_register_callback_function (imap, MU_IMAP_CB_PREAUTH,
_mu_folder_preauth_callback,
folder);
mu_imap_register_callback_function (imap, MU_IMAP_CB_BYE,
_mu_folder_bye_callback,
folder);
mu_imap_register_callback_function (imap, MU_IMAP_CB_BAD,
_mu_folder_bad_callback,
folder);
#if 0
mu_imap_register_callback_function (imap, MU_IMAP_CB_FETCH,
_mu_folder_fetch_callback,
folder);
#endif
rc = mu_imap_connect (imap);
if (rc)
{
s = mu_url_to_string (folder->url);
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
(_("failed to connect to %s: %s"),
s, mu_strerror (rc)));
if (mu_imap_strerror (imap, &s))
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
(_("server response: %s"), s));
mu_imap_destroy (&imap);
return rc;
}
case IMAP_LOGIN:
/* Send it across. */
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
/* Clear the buffer it contains the passwd. */
memset (f_imap->buffer, '\0', f_imap->buflen);
f_imap->state = IMAP_LOGIN_ACK;
case IMAP_LOGIN_ACK:
/* Get the login ack. */
status = imap_parse (f_imap);
if (status)
return status;
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_AUTH_DONE;
default:
break; /* We're outta here. */
if (mu_imap_session_state (imap) == MU_IMAP_SESSION_NONAUTH)
{
rc = mu_authority_authenticate (folder->authority);
if (rc)
{
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
(_("IMAP authentication: %s"),
mu_strerror (rc)));
mu_folder_close (folder);
}
CLEAR_STATE (f_imap);
return 0;
}
return rc;
}
/*
The anonymous SASL mechanism is defined in rfc2245.txt as a single
message from client to server:
/* Close existing connection */
static int
_mu_imap_folder_close (mu_folder_t folder)
{
mu_imap_t imap = folder->data;
message = [email / token]
if (mu_imap_session_state (imap) > MU_IMAP_SESSION_INIT)
{
mu_imap_clearerr (imap);
mu_imap_logout (imap);
mu_imap_disconnect (imap);
}
So the message is optional.
return 0;
}
The command is:
struct enumerate_closure
{
mu_folder_t folder;
mu_folder_enumerate_fp fun;
void *data;
};
C: <tag> authenticate anonymous
static int
_enumerate_helper (void *item, void *data)
{
struct mu_list_response *rp = item;
struct enumerate_closure *clos = data;
The server responds with a request for continuation data (the "message"
in the SASL syntax). We respond with no data, which is legal.
return clos->fun (clos->folder, rp, clos->data);
}
S: +
C:
static int
_mu_imap_folder_list (mu_folder_t folder, const char *ref, void *name,
int flags, size_t max_level,
mu_list_t flist,
mu_folder_enumerate_fp efp, void *edp)
{
mu_imap_t imap = folder->data;
mu_list_t list;
int rc = mu_imap_list_new (imap, ref, name, &list);
The server should then respond with OK on success, or else a failure
code (NO or BAD).
if (rc)
return rc;
If OK, then we are authenticated!
if (max_level ||
(flags & MU_FOLDER_ATTRIBUTE_ALL) != MU_FOLDER_ATTRIBUTE_ALL)
{
/* Filter out the list, eliminating non-matching entries */
mu_iterator_t itr;
So, states are:
rc = mu_list_get_iterator (list, &itr);
if (rc)
{
mu_list_destroy (&list);
return rc;
}
AUTH_ANON_REQ
for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
mu_iterator_next (itr))
{
struct mu_list_response *rp;
> g%u AUTHENTICATE ANONYMOUS
mu_iterator_current (itr, (void**) &rp);
if (!(rp->type & flags) || (max_level && rp->level > max_level))
mu_iterator_ctl (itr, mu_itrctl_delete, NULL);
}
mu_iterator_destroy (&itr);
}
AUTH_ANON_WAIT_CONT
if (efp)
{
struct enumerate_closure clos;
< +
clos.folder = folder;
clos.fun = efp;
clos.data = edp;
AUTH_ANON_MSG
rc = mu_list_foreach (list, _enumerate_helper, &clos);
}
>
if (flist)
mu_list_append_list (flist, list);
AUTH_ANON_WAIT_RESP
mu_list_destroy (&list);
< NO/BAD/OK
return rc;
}
*/
static int
_mu_imap_folder_lsub (mu_folder_t folder, const char *ref, const char *name,
mu_list_t flist)
{
mu_imap_t imap = folder->data;
return mu_imap_lsub (imap, ref, name, flist);
}
/* Subscribe to the named mailbox. */
static int
authenticate_imap_sasl_anon (mu_authority_t auth)
_mu_imap_folder_subscribe (mu_folder_t folder, const char *name)
{
mu_folder_t folder = mu_authority_get_owner (auth);
f_imap_t f_imap = folder->data;
int status = 0;
mu_imap_t imap = folder->data;
return mu_imap_subscribe (imap, name);
}
/* Unsubscribe from the mailbox. */
static int
_mu_imap_folder_unsubscribe (mu_folder_t folder, const char *name)
{
mu_imap_t imap = folder->data;
return mu_imap_unsubscribe (imap, name);
}
assert (f_imap->state == IMAP_AUTH);
/* Remove a mailbox. */
static int
_mu_imap_folder_delete (mu_folder_t folder, const char *name)
{
mu_imap_t imap = folder->data;
return mu_imap_delete (imap, name);
}
if (check_capa (f_imap, "AUTH=ANONYMOUS"))
{
MU_DEBUG (folder->debug, MU_DEBUG_PROT,
"ANONYMOUS capability not present\n");
return ENOSYS;
}
/* Rename OLDPATH to NEWPATH */
static int
_mu_imap_folder_rename (mu_folder_t folder, const char *oldpath,
const char *newpath)
{
mu_imap_t imap = folder->data;
return mu_imap_rename (imap, oldpath, newpath);
}
/* FIXME: auth_state is never set explicitely before this function */
switch (f_imap->auth_state)
{
case IMAP_AUTH_ANON_REQ_WRITE:
{
MU_DEBUG1 (folder->debug, MU_DEBUG_PROT,
"g%lu AUTHENTICATE ANONYMOUS\n",
(unsigned long) f_imap->seq);
status = imap_writeline (f_imap, "g%lu AUTHENTICATE ANONYMOUS\r\n",
(unsigned long) f_imap->seq);
f_imap->seq++;
CHECK_ERROR_CLOSE (folder, f_imap, status);
f_imap->auth_state = IMAP_AUTH_ANON_REQ_SEND;
}
typedef int (*auth_method_t) (mu_authority_t);
case IMAP_AUTH_ANON_REQ_SEND:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->auth_state = IMAP_AUTH_ANON_WAIT_CONT;
static int
authenticate_imap_login (mu_authority_t auth)
{
mu_folder_t folder = mu_authority_get_owner (auth);
mu_imap_t imap = folder->data;
mu_ticket_t ticket;
char *user;
int rc;
mu_secret_t secret;
case IMAP_AUTH_ANON_WAIT_CONT:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
if (strncmp ("+", f_imap->buffer, 2) == 0)
rc = mu_imap_capability_test (imap, "LOGINDISABLED", NULL);
if (rc == 0)
{
f_imap->auth_state = IMAP_AUTH_ANON_MSG;
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
(_("IMAP LOGIN disabled")));
return rc;
}
else
else if (rc != MU_ERR_NOENT)
{
/* Something is wrong! */
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
(_("cannot test server capabilities (%s), continuing anyway"),
mu_strerror (rc)));
return rc;
}
f_imap->auth_state = IMAP_AUTH_ANON_MSG;
case IMAP_AUTH_ANON_MSG:
MU_DEBUG (folder->debug, MU_DEBUG_PROT, "\n");
status = imap_writeline (f_imap, "\r\n");
CHECK_ERROR_CLOSE (folder, f_imap, status);
f_imap->auth_state = IMAP_AUTH_ANON_MSG_SEND;
case IMAP_AUTH_ANON_MSG_SEND:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->auth_state = IMAP_AUTH_ANON_WAIT_RESP;
/* Grab the User and Passwd information. */
mu_authority_get_ticket (auth, &ticket);
/* Was it in the URL? */
rc = mu_url_aget_user (folder->url, &user);
if (rc == MU_ERR_NOENT)
rc = mu_ticket_get_cred (ticket, folder->url, "Imap User: ", &user, NULL);
if (rc == MU_ERR_NOENT || user == NULL)
return MU_ERR_NOUSERNAME;
else if (rc)
return rc;
case IMAP_AUTH_ANON_WAIT_RESP:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
rc = mu_url_get_secret (folder->url, &secret);
if (rc == MU_ERR_NOENT)
rc = mu_ticket_get_cred (ticket, folder->url,
"Imap Passwd: ", NULL, &secret);
default:
break; /* We're outta here. */
if (rc == MU_ERR_NOENT || !secret)
{
/* FIXME: Is this always right? The user might legitimately have
no password */
free (user);
return MU_ERR_NOPASSWORD;
}
CLEAR_STATE (f_imap);
return 0;
else if (rc)
{
free (user);
return rc;
}
rc = mu_imap_login_secret (imap, user, secret);
mu_secret_unref (secret);
return rc;
}
struct auth_tab
......@@ -379,7 +421,7 @@ struct auth_tab
static struct auth_tab auth_tab[] = {
{ "login", authenticate_imap_login },
{ "anon", authenticate_imap_sasl_anon },
/* { "anon", authenticate_imap_sasl_anon },*/
{ NULL }
};
......@@ -398,74 +440,13 @@ find_auth_method (const char *name)
static int
authenticate_imap_select (mu_authority_t auth)
{
mu_folder_t folder = mu_authority_get_owner (auth);
f_imap_t f_imap = folder->data;
struct auth_tab *p;
int status = ENOSYS;
for (p = auth_tab; status == ENOSYS && p->name; p++)
{
f_imap->state = IMAP_AUTH;
status = p->method (auth);
}
return status;
}
/* Initialize the concrete IMAP mailbox: overload the folder functions */
int
_folder_imap_init (mu_folder_t folder)
{
int status;
f_imap_t f_imap;
int rc = ENOSYS;
/* Set the authority early:
(1) so we can check for errors.
(2) allow the client to get the authority for setting the ticket
before the open. */
status = folder_imap_get_authority (folder, NULL);
if (status != 0)
return status;
for (p = auth_tab; rc == ENOSYS && p->name; p++)
rc = p->method (auth);
f_imap = folder->data = calloc (1, sizeof (*f_imap));
if (f_imap == NULL)
return ENOMEM;
f_imap->folder = folder;
f_imap->state = IMAP_NO_STATE;
folder->_destroy = folder_imap_destroy;
folder->_open = folder_imap_open;
folder->_close = folder_imap_close;
folder->_list = folder_imap_list;
folder->_lsub = folder_imap_lsub;
folder->_subscribe = folder_imap_subscribe;
folder->_unsubscribe = folder_imap_unsubscribe;
folder->_delete = folder_imap_delete;
folder->_rename = folder_imap_rename;
return 0;
}
/* Destroy the folder resources. */
static void
folder_imap_destroy (mu_folder_t folder)
{
if (folder->data)
{
f_imap_t f_imap = folder->data;
if (f_imap->buffer)
free (f_imap->buffer);
if (f_imap->capav)
mu_argcv_free (f_imap->capac, f_imap->capav);
free (f_imap);
folder->data = NULL;
}
return rc;
}
static int
......@@ -473,2152 +454,139 @@ folder_set_auth_method (mu_folder_t folder, auth_method_t method)
{
if (!folder->authority)
{
int status = mu_authority_create (&folder->authority, NULL, folder);
if (status)
return status;
int rc = mu_authority_create (&folder->authority, NULL, folder);
if (rc)
return rc;
}
return mu_authority_set_authenticate (folder->authority, method, folder);
}
static int
folder_imap_get_authority (mu_folder_t folder, mu_authority_t *pauth)
_imap_folder_setup_authority (mu_folder_t folder)
{
int status = 0;
if (folder->authority == NULL)
int rc = 0;
if (!folder->authority)
{
/* assert (folder->url); */
const char *auth;
if (folder->url == NULL)
return EINVAL;
if (folder->url->auth == NULL
|| strcmp (folder->url->auth, "*") == 0)
if (mu_url_sget_auth (folder->url, &auth))
rc = folder_set_auth_method (folder, authenticate_imap_select);
else if (strcmp (auth, "*") == 0)
rc = folder_set_auth_method (folder, authenticate_imap_select);
else
{
struct mu_wordsplit ws;
ws.ws_delim = ",";
if (mu_wordsplit (auth, &ws,
MU_WRDSF_NOVAR | MU_WRDSF_NOCMD |
MU_WRDSF_DELIM))
{
status = folder_set_auth_method (folder, authenticate_imap_select);
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
(_("cannot split out auth part: %s"),
mu_wordsplit_strerror (&ws)));
rc = MU_ERR_FAILURE;
}
else
{
char *p, *sp;
int i;
for (p = strtok_r (folder->url->auth, ",", &sp);
status == 0 && p;
p = strtok_r (NULL, ",", &sp))
for (i = 0; i < ws.ws_wordc; i++)
{
auth_method_t method = find_auth_method (p);
auth_method_t method = find_auth_method (ws.ws_wordv[i]);
if (method)
status = folder_set_auth_method (folder, method);
rc = folder_set_auth_method (folder, method);
else
status = MU_ERR_BAD_AUTH_SCHEME;
{
mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
(_("unrecognized AUTH scheme %s"),
ws.ws_wordv[i]));
rc = MU_ERR_BAD_AUTH_SCHEME;
}
}
mu_wordsplit_free (&ws);
}
}
}
if (status)
return status;
if (pauth)
*pauth = folder->authority;
return status;
return rc;
}
/* Capability handling */
static int
parse_capa (f_imap_t f_imap, char *str)
int
_mu_imap_folder_init (mu_folder_t folder)
{
if (f_imap->capav)
mu_argcv_free (f_imap->capac, f_imap->capav);
return mu_argcv_get (str, "", NULL, &f_imap->capac, &f_imap->capav);
}
mu_imap_t imap;
int rc;
static int
read_capa (f_imap_t f_imap, int force)
{
int status = 0;
rc = _imap_folder_setup_authority (folder);
if (rc)
return rc;
if (force)
{
mu_argcv_free (f_imap->capac, f_imap->capav);
f_imap->capac = 0;
f_imap->capav = NULL;
}
rc = mu_imap_create (&imap);
if (rc)
return rc;
if (!f_imap->capav)
{
status = imap_writeline (f_imap, "g%lu CAPABILITY\r\n",
(unsigned long) f_imap->seq++);
status = imap_send (f_imap);
status = imap_parse (f_imap);
}
return status;
}
folder->data = imap;
static int
check_capa (f_imap_t f_imap, char *capa)
{
int i;
folder->_destroy = _mu_imap_folder_destroy;
read_capa (f_imap, 0);
for (i = 0; i < f_imap->capac; i++)
if (mu_c_strcasecmp (f_imap->capav[i], capa) == 0)
return 0;
return 1;
}
folder->_open = _mu_imap_folder_open;
folder->_close = _mu_imap_folder_close;
folder->_list = _mu_imap_folder_list;
folder->_lsub = _mu_imap_folder_lsub;
static int
imap_reader (void *iodata)
{
f_imap_t iop = iodata;
int status = imap_parse (iop);
CHECK_EAGAIN (iop, status);
MU_DEBUG (iop->folder->debug, MU_DEBUG_PROT, iop->buffer);
return status;
folder->_subscribe = _mu_imap_folder_subscribe;
folder->_unsubscribe = _mu_imap_folder_unsubscribe;
folder->_delete = _mu_imap_folder_delete;
folder->_rename = _mu_imap_folder_rename;
return 0;
}
static int
imap_writer (void *iodata, char *buf)
static struct _mu_record _imap_record =
{
f_imap_t iop = iodata;
int status;
MU_DEBUG2 (iop->folder->debug, MU_DEBUG_PROT, "g%lu %s\n",
(unsigned long)iop->seq, buf);
status = imap_writeline (iop, "g%lu %s\r\n",
(unsigned long)iop->seq++, buf);
CHECK_ERROR (iop, status);
status = imap_send (iop);
CHECK_ERROR (iop, status);
return status;
}
MU_IMAP_PRIO,
MU_IMAP_SCHEME,
MU_RECORD_DEFAULT,
MU_URL_SCHEME | MU_URL_CRED | MU_URL_INET | MU_URL_PATH,
MU_URL_HOST,
_mu_imap_url_init, /* url entry. */
_mu_imap_mailbox_init, /* Mailbox entry. */
NULL, /* Mailer entry. */
_mu_imap_folder_init, /* Folder entry. */
NULL, /* No need for a back pointer. */
NULL, /* _is_scheme method. */
NULL, /* _get_url method. */
NULL, /* _get_mailbox method. */
NULL, /* _get_mailer method. */
NULL /* _get_folder method. */
};
static void
imap_stream_ctl (void *iodata, mu_stream_t *pold, mu_stream_t new)
{
f_imap_t iop = iodata;
if (pold)
*pold = iop->folder->stream;
if (new)
iop->folder->stream = new;
}
mu_record_t mu_imap_record = &_imap_record;
static int
tls (mu_folder_t folder)
{
#ifdef WITH_TLS
int status;
f_imap_t f_imap = folder->data;
char *keywords[] = { "STARTTLS", "CAPABILITY", NULL };
if (!mu_tls_enable || check_capa (f_imap, "STARTTLS"))
return -1;
status = mu_tls_begin (f_imap, imap_reader, imap_writer,
imap_stream_ctl, keywords);
MU_DEBUG1 (folder->debug, MU_DEBUG_PROT, "TLS negotiation %s\n",
status == 0 ? "succeeded" : "failed");
return status;
#else
return -1;
#endif /* WITH_TLS */
}
/* Create/Open the stream for IMAP. */
static int
folder_imap_open (mu_folder_t folder, int flags)
static struct _mu_record _imaps_record =
{
f_imap_t f_imap = folder->data;
const char *host;
unsigned port = f_imap->imaps ? MU_IMAPS_PORT : MU_IMAP_PORT;
int status = 0;
/* If we are already open for business, noop. */
mu_monitor_wrlock (folder->monitor);
if (f_imap->isopen)
{
mu_monitor_unlock (folder->monitor);
return 0;
}
mu_monitor_unlock (folder->monitor);
/* Fetch the server name and the port in the mu_url_t. */
status = mu_url_sget_host (folder->url, &host);
if (status != 0)
return status;
mu_url_get_port (folder->url, &port);
folder->flags = flags;
switch (f_imap->state)
{
case IMAP_NO_STATE:
/* allocate working io buffer. */
if (f_imap->buffer == NULL)
{
/* There is no particular limit on the length of a command/response
in IMAP. We start with 255, which is quite reasonnable and grow
as we go along. */
f_imap->buflen = 255;
f_imap->buffer = calloc (f_imap->buflen + 1, 1);
if (f_imap->buffer == NULL)
{
CHECK_ERROR (f_imap, ENOMEM);
}
status = mu_memory_stream_create (&f_imap->string.stream, NULL, MU_STREAM_RDWR);
CHECK_ERROR (f_imap, status);
}
else
{
/* Clear from any residue. */
memset (f_imap->buffer, '\0', f_imap->buflen);
mu_stream_truncate (f_imap->string.stream, 0);
f_imap->string.offset = 0;
f_imap->string.nleft = 0;
}
f_imap->ptr = f_imap->buffer;
/* Create the networking stack. */
if (folder->stream == NULL)
{
status = mu_tcp_stream_create (&folder->stream, host, port, folder->flags);
CHECK_ERROR (f_imap, status);
#ifdef WITH_TLS
if (f_imap->imaps)
{
mu_stream_t newstr;
status = mu_tls_client_stream_create (&newstr,
folder->stream,
folder->stream, 0);
if (status != 0)
{
mu_error ("folder_imap_open: mu_tls_client_stream_create: %s",
mu_strerror (status));
return status;
}
folder->stream = newstr;
}
#endif /* WITH_TLS */
/* Ask for the stream internal buffering mechanism scheme. */
mu_stream_setbufsiz (folder->stream, BUFSIZ);
}
else
mu_stream_close (folder->stream);
MU_DEBUG2 (folder->debug, MU_DEBUG_PROT, "imap_open (%s:%ld)\n",
host, port);
f_imap->state = IMAP_OPEN_CONNECTION;
case IMAP_OPEN_CONNECTION:
/* Establish the connection. */
if (!mu_stream_is_open (folder->stream))
{
status = mu_stream_open (folder->stream);
CHECK_EAGAIN (f_imap, status);
/* Can't recover bailout. */
CHECK_ERROR_CLOSE (folder, f_imap, status);
}
f_imap->state = IMAP_GREETINGS;
case IMAP_GREETINGS:
{
/* Swallow the greetings. */
status = imap_readline (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->ptr = f_imap->buffer;
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
/* Are they open for business ? The server send an untagged response
for greeting. Tecnically it can be OK/PREAUTH/BYE. The BYE is
the one that we do not want, server being unfriendly. */
if (mu_c_strncasecmp (f_imap->buffer, "* PREAUTH", 9) == 0)
{
f_imap->state = IMAP_AUTH_DONE;
}
else
{
if (mu_c_strncasecmp (f_imap->buffer, "* OK", 4) != 0)
CHECK_ERROR_CLOSE (folder, f_imap, EACCES);
f_imap->state = IMAP_AUTH;
}
}
if (!f_imap->imaps)
tls (folder);
case IMAP_AUTH:
case IMAP_LOGIN:
case IMAP_LOGIN_ACK:
assert (folder->authority);
{
status = mu_authority_authenticate (folder->authority);
if (status)
{
/* Fake folder_imap_close into closing the folder.
FIXME: The entire state machine should probably
be revised... */
f_imap->isopen++;
f_imap->state = IMAP_NO_STATE;
folder_imap_close (folder);
return status;
}
}
case IMAP_AUTH_DONE:
default:
break;
}
f_imap->state = IMAP_NO_STATE;
mu_monitor_wrlock (folder->monitor);
f_imap->isopen++;
mu_monitor_unlock (folder->monitor);
return 0;
}
/* Shutdown the connection. */
static int
folder_imap_close (mu_folder_t folder)
{
f_imap_t f_imap = folder->data;
int status = 0;
mu_monitor_wrlock (folder->monitor);
f_imap->isopen--;
if (f_imap->isopen)
{
mu_monitor_unlock (folder->monitor);
return 0;
}
mu_monitor_unlock (folder->monitor);
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%lu LOGOUT\r\n",
(unsigned long) f_imap->seq++);
CHECK_ERROR (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_LOGOUT;
case IMAP_LOGOUT:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_LOGOUT_ACK;
case IMAP_LOGOUT_ACK:
/* Check for "* Bye" from the imap server. */
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
/* This is done when we received the BYE in the parser code. */
/* mu_stream_close (folder->stream); */
/* f_imap->isopen = 0 ; */
default:
break;
}
f_imap->state = IMAP_NO_STATE;
f_imap->selected = NULL;
return 0;
}
/* Remove a mailbox. */
static int
folder_imap_delete (mu_folder_t folder, const char *name)
{
f_imap_t f_imap = folder->data;
int status = 0;
if (name == NULL)
return EINVAL;
status = mu_folder_open (folder, folder->flags);
if (status != 0)
return status;
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%lu DELETE %s\r\n",
(unsigned long) f_imap->seq++,
name);
CHECK_ERROR (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_DELETE;
case IMAP_DELETE:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_DELETE_ACK;
case IMAP_DELETE_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
f_imap->state = IMAP_NO_STATE;
return status;
}
void
guess_level (struct mu_list_response *resp, size_t prefix_len)
{
size_t lev = 0;
if (!resp->separator)
lev = 0;
else
{
char *p = resp->name + prefix_len;
if (p[0] == resp->separator)
for ( ; p; p = strchr (p + 1, resp->separator))
lev++;
}
resp->level = lev;
}
/* Moves all matching items from list DST to SRC.
Items are moved verbatim (i.e. pointers are moved). Non-matching
items are deleted. After calling this function, SRC must be
destroyed.
While moving, this function also computes the recursion level.
Matching is determined based on PATTERN, by NAMECMP function,
and MAX_LEVEL. Both can be zero. */
static void
list_copy (mu_list_t dst, mu_list_t src,
size_t prefix_len,
int (*namecmp) (const char* pattern, const char* mailbox),
const char *pattern, size_t max_level)
{
mu_iterator_t itr;
if (!src)
return;
mu_list_get_iterator (src, &itr);
for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
mu_iterator_next (itr))
{
char *name;
struct mu_list_response *p;
mu_iterator_current (itr, (void **)&p);
guess_level (p, prefix_len);
name = p->name + prefix_len;
if (name[0] == p->separator && pattern[0] != p->separator)
name++;
if ((max_level == 0 || p->level <= max_level)
&& (!namecmp || namecmp (pattern, name) == 0))
mu_list_append (dst, p);
else
free (p);
}
mu_iterator_destroy (&itr);
mu_list_set_destroy_item (src, NULL);
}
/* Convert glob(3)-style pattern to IMAP one
Rules:
Wildcard Replace with
-------- ------------
* * for recursive searches, % otherwise
? %
[..] %
NOTE:
1. The '*' can be made more selective by taking into account the
required maximum recursion level and counting directory separators
('/') in the input pattern.
2. The resulting pattern matches, in general, a wider set of strings, so
each matched string should be additionally compared against the
original pattern.
*/
char *
glob_to_imap (const char *pat, int recursive)
{
char *p, *q;
char *ret = strdup (pat);
if (!ret)
return NULL;
for (p = q = ret; *q; )
{
switch (*q)
{
case '?':
*p++ = '%';
q++;
break;
case '*':
*p++ = recursive ? '*' : '%';
q++;
break;
case '[':
for (; *q; q++)
if (*q == '\\')
q++;
else if (*q == ']')
{
q++;
break;
}
*p++ = '%';
break;
case '\\':
q++;
if (*q)
*p++ = *q++;
break;
default:
*p++ = *q++;
break;
}
}
*p = 0;
return ret;
}
/* FIXME: Flags unused */
static int
folder_imap_list (mu_folder_t folder, const char *ref, void *name,
int flags, size_t max_level,
mu_list_t flist,
mu_folder_enumerate_fp efp, void *edp)
{
f_imap_t f_imap = folder->data;
int status = 0;
char *path = NULL;
status = mu_folder_open (folder, folder->flags);
if (status != 0)
return status;
if (ref == NULL)
ref = "";
if (name == NULL)
name = "";
f_imap->enum_fun = efp;
f_imap->enum_stop = 0;
f_imap->enum_data = edp;
switch (f_imap->state)
{
case IMAP_NO_STATE:
path = glob_to_imap (name, max_level != 1);
status = imap_writeline (f_imap, "g%lu LIST \"%s\" \"%s\"\r\n",
(unsigned long) f_imap->seq++, ref, path);
free (path);
CHECK_ERROR (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_LIST;
case IMAP_LIST:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_LIST_ACK;
case IMAP_LIST_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
f_imap->enum_fun = NULL;
f_imap->enum_stop = 0;
f_imap->enum_data = NULL;
list_copy (flist, f_imap->flist, strlen (ref),
imap_mailbox_name_match, name, max_level);
mu_list_destroy (&f_imap->flist);
f_imap->state = IMAP_NO_STATE;
return status;
}
static int
folder_imap_lsub (mu_folder_t folder, const char *ref, const char *name,
mu_list_t flist)
{
f_imap_t f_imap = folder->data;
int status = 0;
status = mu_folder_open (folder, folder->flags);
if (status != 0)
return status;
if (ref == NULL)
ref = "";
if (name == NULL)
name = "";
f_imap->enum_fun = NULL;
f_imap->enum_stop = 0;
f_imap->enum_data = NULL;
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%lu LSUB \"%s\" \"%s\"\r\n",
(unsigned long) f_imap->seq++, ref, name);
CHECK_ERROR (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_LSUB;
case IMAP_LSUB:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_LSUB_ACK;
case IMAP_LSUB_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
/* Build the folder list. */
list_copy (flist, f_imap->flist, strlen (ref), NULL, NULL, 0);
mu_list_destroy (&f_imap->flist);
f_imap->state = IMAP_NO_STATE;
return 0;
}
static int
folder_imap_rename (mu_folder_t folder, const char *oldpath,
const char *newpath)
{
f_imap_t f_imap = folder->data;
int status = 0;
if (oldpath == NULL || newpath == NULL)
return EINVAL;
status = mu_folder_open (folder, folder->flags);
if (status != 0)
return status;
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%lu RENAME %s %s\r\n",
(unsigned long) f_imap->seq++,
oldpath, newpath);
CHECK_ERROR (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_RENAME;
case IMAP_RENAME:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_RENAME_ACK;
case IMAP_RENAME_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
f_imap->state = IMAP_NO_STATE;
return status;
}
static int
folder_imap_subscribe (mu_folder_t folder, const char *name)
{
f_imap_t f_imap = folder->data;
int status = 0;
status = mu_folder_open (folder, folder->flags);
if (status != 0)
return status;
if (name == NULL)
return EINVAL;
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%lu SUBSCRIBE %s\r\n",
(unsigned long) f_imap->seq++, name);
CHECK_ERROR (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_SUBSCRIBE;
case IMAP_SUBSCRIBE:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_SUBSCRIBE_ACK;
case IMAP_SUBSCRIBE_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
f_imap->state = IMAP_NO_STATE;
return status;
}
static int
folder_imap_unsubscribe (mu_folder_t folder, const char *name)
{
f_imap_t f_imap = folder->data;
int status = 0;
status = mu_folder_open (folder, folder->flags);
if (status != 0)
return status;
if (name == NULL)
return EINVAL;
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%lu UNSUBSCRIBE %s\r\n",
(unsigned long) f_imap->seq++, name);
CHECK_ERROR (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_UNSUBSCRIBE;
case IMAP_UNSUBSCRIBE:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_UNSUBSCRIBE_ACK;
case IMAP_UNSUBSCRIBE_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (folder->debug, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
f_imap->state = IMAP_NO_STATE;
return status;
}
/* A literal is a sequence of zero or more octets (including CR and LF),
prefix-quoted with an octet count in the form of an open brace ("{"),
the number of octets, close brace ("}"), and CRLF. The sequence is read
and put in the string buffer. */
static int
imap_literal_string (f_imap_t f_imap, char **ptr)
{
size_t len, len0, total;
int status = 0;
int nl;
if (f_imap->string.nleft==0)
{
status = imap_readline (f_imap);
*ptr = f_imap->buffer;
return status;
}
/* The (len + 1) in the for is to count the strip '\r' by imap_readline. */
for (len0 = len = total = 0; total < f_imap->string.nleft; total += len + 1)
{
status = imap_readline (f_imap);
if (DEBUG_SHOW_DATA)
fprintf (stderr, "%s", f_imap->buffer);
if (status != 0)
{
/* Return what we got so far. */
break;
}
f_imap->ptr = f_imap->buffer;
/* How much ? */
len0 = len = f_imap->nl - f_imap->buffer;
/* Check if the last read did not finish on a line, if yes do not copy in
string buffer the terminating sequence ")\r\n". We are doing this
by checking if the amount(total) we got so far + the len of the line
+1 (taking to account the strip '\r') goes behond the request. */
if ((total + len + 1) > f_imap->string.nleft)
{
len0 = len = f_imap->string.nleft - total;
/* ALERT: if we ask for a substring, for example we have :
"123456\n", and ask for body[]<0.7> the server will send
body[] {7} --> "123456\r". There was not enough space
to fit the nl .. annoying. Take care of this here. */
if (f_imap->buffer[len - 1] == '\r')
len0--;
}
mu_stream_write (f_imap->string.stream, f_imap->buffer,
len0, f_imap->string.offset, NULL);
f_imap->string.offset += len0;
/* Depending on the type of request we incremente the xxxx_lines
and xxxx_sizes. */
nl = (memchr (f_imap->buffer, '\n', len0)) ? 1 : 0;
if (f_imap->string.msg_imap)
{
switch (f_imap->string.type)
{
case IMAP_HEADER:
f_imap->string.msg_imap->header_lines += nl;
f_imap->string.msg_imap->header_size += len0;
break;
case IMAP_BODY:
f_imap->string.msg_imap->body_lines += nl;
f_imap->string.msg_imap->body_size += len0;
break;
case IMAP_MESSAGE:
f_imap->string.msg_imap->mu_message_lines += nl;
/* The message size is known by sending RFC822.SIZE. */
default:
break;
}
}
}
f_imap->string.nleft -= total;
/* We may have trailing junk like the closing ")\r\n" from a literal string
glob it by moving the command buffer, or doing a full readline. */
if (len == (size_t)(f_imap->nl - f_imap->buffer))
{
len = 0;
status = imap_readline (f_imap);
}
*ptr = f_imap->buffer + len;
return status;
}
/* A quoted string is a sequence of zero or more 7-bit characters,
excluding CR and LF, with double quote (<">) characters at each end.
Same thing as the literal, diferent format the result is put in the
string buffer for the mailbox/callee. */
static int
imap_quoted_string (f_imap_t f_imap, char **ptr)
{
char *bquote;
int escaped = 0;
int len;
(*ptr)++;
bquote = *ptr;
while (**ptr && (**ptr != '"' || escaped))
{
escaped = (**ptr == '\\') ? 1 : 0;
(*ptr)++;
}
len = *ptr - bquote;
mu_stream_write (f_imap->string.stream, bquote, len,
f_imap->string.offset, NULL);
f_imap->string.offset += len;
if (**ptr == '"')
(*ptr)++;
if (DEBUG_SHOW_DATA)
fprintf (stderr, "%.*s", len, bquote);
return 0;
}
/* A number consists of one or more digit characters, and represents a
numeric value. */
static int
imap_digits (f_imap_t f_imap, char **ptr)
{
char *start = *ptr;
int len;
for (++*ptr; **ptr && mu_isdigit(**ptr); ++*ptr)
;
len = *ptr - start;
mu_stream_write (f_imap->string.stream, start, len,
f_imap->string.offset, NULL);
f_imap->string.offset += len;
return 0;
}
/* Find which type of string the response is: literal or quoted and let the
function fill the string buffer. */
static int
imap_string (f_imap_t f_imap, char **ptr)
{
int status = 0;
/* Skip whites. */
while (**ptr == ' ')
(*ptr)++;
switch (**ptr)
{
case '{':
f_imap->string.nleft = strtol ((*ptr) + 1, ptr, 10);
if (**ptr == '}')
{
(*ptr)++;
/* Reset the buffer to the beginning. */
f_imap->ptr = f_imap->buffer;
status = imap_literal_string (f_imap, ptr);
}
break;
case '"':
status = imap_quoted_string (f_imap, ptr);
break;
/* NIL */
case 'N':
case 'n':
(*ptr)++; /* N|n */
(*ptr)++; /* I|i */
(*ptr)++; /* L|l */
break;
default:
if (mu_isdigit (**ptr))
status = imap_digits (f_imap, ptr);
else
/* Problem. FIXME: Return a more appropriate error code */
status = MU_ERR_FAILURE;
break;
}
return status;
}
/* FIXME: does not work for nonblocking. */
static int
imap_list (f_imap_t f_imap)
{
char *tok;
char *sp = NULL;
size_t len = f_imap->nl - f_imap->buffer - 1;
char *buffer;
struct mu_list_response *lr;
int status = 0;
int argc;
char **argv;
if (f_imap->enum_stop)
return 0;
buffer = malloc (len + 1);
if (!buffer)
return ENOMEM;
memcpy (buffer, f_imap->buffer, len);
buffer[len] = '\0';
lr = calloc (1, sizeof (*lr));
if (!lr)
return ENOMEM;
if (!f_imap->flist)
{
mu_list_create (&f_imap->flist);
mu_list_set_destroy_item (f_imap->flist, mu_list_response_free);
}
/* Glob untag. */
tok = strtok_r (buffer, " ", &sp);
/* Glob LIST. */
tok = strtok_r (NULL, " ", &sp);
/* Get the attibutes. */
tok = strtok_r (NULL, ")", &sp);
if (tok)
{
char *s = NULL;
char *p = tok;
while ((tok = strtok_r (p, " ()", &s)) != NULL)
{
if (mu_c_strcasecmp (tok, "\\Noselect") == 0)
lr->type |= MU_FOLDER_ATTRIBUTE_DIRECTORY;
else if (mu_c_strcasecmp (tok, "\\Noinferiors") == 0)
lr->type |= MU_FOLDER_ATTRIBUTE_FILE;
else if (mu_c_strcasecmp (tok, "\\Marked") == 0
|| mu_c_strcasecmp (tok, "\\Unmarked") == 0)
/* nothing */;
else
lr->type |= MU_FOLDER_ATTRIBUTE_DIRECTORY;
p = NULL;
}
}
status = mu_argcv_get (sp, "", NULL, &argc, &argv);
if (status == 0)
{
char *s;
/* Hiearchy delimeter. */
tok = argv[0];
if (tok && tok[1] == 0 && mu_c_strcasecmp (tok, "NIL"))
lr->separator = tok[0];
/* The path. */
tok = argv[1];
s = strchr (tok, '{');
if (s)
{
size_t n = strtoul (s + 1, NULL, 10);
lr->name = calloc (n + 1, 1);
if (!lr->name)
status = ENOMEM;
else
{
f_imap->ptr = f_imap->buffer;
imap_readline (f_imap);
memcpy (lr->name, f_imap->buffer, n);
}
}
else if ((status = imap_string (f_imap, &tok)) == 0)
{
mu_off_t sz = 0;
mu_stream_size (f_imap->string.stream, &sz);
lr->name = calloc (sz + 1, 1);
if (!lr->name)
status = ENOMEM;
else
mu_stream_read (f_imap->string.stream, lr->name, sz, 0, NULL);
mu_stream_truncate (f_imap->string.stream, 0);
f_imap->string.offset = 0;
f_imap->string.nleft = 0;
}
else
{
lr->name = strdup (tok);
if (!lr->name)
status = ENOMEM;
}
if (lr->separator)
{
size_t off;
char delim[2];
size_t n = 0;
delim[0] = lr->separator;
delim[1] = 0;
s = lr->name;
while (off = strcspn (s, delim), s[off])
{
n++;
off++;
s += off;
}
lr->level = n;
}
}
mu_argcv_free (argc, argv);
free (buffer);
if (f_imap->enum_fun)
f_imap->enum_stop = f_imap->enum_fun (f_imap->folder, lr,
f_imap->enum_data);
mu_list_append (f_imap->flist, lr);
return status;
}
/* Helping function to figure out the section name of the message: for example
a 2 part message with the first part being sub in two will be:
{1}, {1,1} {1,2} The first subpart of the message and its sub parts
{2} The second subpar of the message. */
char *
section_name (msg_imap_t msg_imap)
{
size_t sectionlen = 0;
char *section = strdup ("");
/* Build the section name, but it is in reverse. */
for (; msg_imap; msg_imap = msg_imap->parent)
{
if (msg_imap->part != 0)
{
char *tmp;
char part[64];
size_t partlen;
snprintf (part, sizeof part, "%lu", (unsigned long) msg_imap->part);
partlen = strlen (part);
tmp = realloc (section, sectionlen + partlen + 2);
if (tmp == NULL)
break;
section = tmp;
memset (section + sectionlen, '\0', partlen + 2);
if (sectionlen != 0)
strcat (section, ".");
strcat (section, part);
sectionlen = strlen (section);
}
}
/* Reverse the string. */
if (section)
{
char *begin, *last;
char c;
for (begin = section, last = section + sectionlen - 1; begin < last;
begin++, last--)
{
c = *begin;
*begin = *last;
*last = c;
}
}
return section;
}
/* We do not pay particular attention to the content of the bodystructure
but rather to the paremetized list layout to discover how many messages
the originial message is composed of. The information is later retrieve
when needed via the body[header.fields] command. Note that this function
is recursive. */
static int
imap_bodystructure0 (msg_imap_t msg_imap, char **ptr)
{
int paren = 0;
int no_arg = 0;
int status = 0;
int have_size = 0;
/* Skip space. */
while (**ptr == ' ')
(*ptr)++;
/* Pass lparen. */
if (**ptr == '(')
{
++(*ptr);
paren++;
no_arg++;
}
/* NOTE : this loop has side effects in strtol() and imap_string(), the
order of the if's are important. */
while (**ptr)
{
/* Skip the string argument. */
if (**ptr != '(' && **ptr != ')')
{
char *start = *ptr;
/* FIXME: set the command callback if EAGAIN to resume. */
status = imap_string (msg_imap->m_imap->f_imap, ptr);
if (status != 0)
return status;
if (start != *ptr)
no_arg = 0;
}
if (mu_isdigit ((unsigned)**ptr))
{
char *start = *ptr;
size_t size = strtoul (*ptr, ptr, 10);
if (start != *ptr)
{
if (!have_size && msg_imap && msg_imap->parent)
msg_imap->mu_message_size = size;
have_size = 1;
no_arg = 0;
}
}
if (**ptr == '(')
{
if (no_arg)
{
msg_imap_t new_part;
msg_imap_t *tmp;
tmp = realloc (msg_imap->parts,
((msg_imap->num_parts + 1) * sizeof (*tmp)));
if (tmp)
{
new_part = calloc (1, sizeof (*new_part));
if (new_part)
{
msg_imap->parts = tmp;
msg_imap->parts[msg_imap->num_parts] = new_part;
new_part->part = ++(msg_imap->num_parts);
new_part->parent = msg_imap;
new_part->num = msg_imap->num;
new_part->m_imap = msg_imap->m_imap;
new_part->flags = msg_imap->flags;
status = imap_bodystructure0 (new_part, ptr);
/* Jump up, the rparen been swallen already. */
continue;
}
else
{
status = ENOMEM;
free (tmp);
break;
}
}
else
{
status = ENOMEM;
break;
}
}
paren++;
}
if (**ptr == ')')
{
no_arg = 1;
paren--;
/* Did we reach the same number of close paren ? */
if (paren <= 0)
{
/* Swallow the rparen. */
(*ptr)++;
break;
}
}
if (**ptr == '\0')
break;
(*ptr)++;
}
return status;
}
static int
imap_bodystructure (f_imap_t f_imap, char **ptr)
{
return imap_bodystructure0 (f_imap->string.msg_imap, ptr);
}
/* The Format for a FLAG response is :
mailbox_data ::= "FLAGS" SPACE flag_list
flag_list ::= "(" #flag ")"
flag ::= "\Answered" / "\Flagged" / "\Deleted" /
"\Seen" / "\Draft" / flag_keyword / flag_extension
flag_extension ::= "\" atom
;; Future expansion. Client implementations
;; MUST accept flag_extension flags. Server
;; implementations MUST NOT generate
;; flag_extension flags except as defined by
;; future standard or standards-track
;; revisions of this specification.
flag_keyword ::= atom
S: * 14 FETCH (FLAGS (\Seen \Deleted))
S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
We assume that the '*' or the FLAGS keyword are strip.
FIXME: User flags are not take to account. */
static int
imap_fetch_flags (f_imap_t f_imap, char **ptr)
{
msg_imap_t msg_imap = f_imap->string.msg_imap;
if (msg_imap)
imap_flags (ptr, &msg_imap->flags);
return 0;
}
static int
imap_permanentflags (f_imap_t f_imap, char **ptr)
{
imap_flags (ptr, &f_imap->flags);
return 0;
}
static int
imap_flags (char **ptr, int *pflags)
{
char *start;
char *end;
int flags = 0;
/* Skip space. */
while (**ptr == ' ')
(*ptr)++;
/* Skip LPAREN. */
if (**ptr == '(')
(*ptr)++;
/* Go through the list and break on ')' */
do
{
/* Skip space before next word. */
while (**ptr == ' ')
(*ptr)++;
/* Save the beginning of the word. */
start = *ptr;
/* Get the next word boundary. */
while (**ptr && **ptr != ' ' && **ptr != ')')
++(*ptr);
/* Save the end for the mu_c_strcasecmp. */
end = *ptr;
/* Bail out. */
if (*start == '\0')
break;
/* Guess the flag. */
if (end == start)
flags |= MU_ATTRIBUTE_SEEN;
else
{
if (mu_c_strncasecmp (start, "\\Seen", end - start) == 0)
{
flags |= MU_ATTRIBUTE_READ;
}
else if (mu_c_strncasecmp (start, "\\Answered", end - start) == 0)
{
flags |= MU_ATTRIBUTE_ANSWERED;
}
else if (mu_c_strncasecmp (start, "\\Flagged", end - start) == 0)
{
flags |= MU_ATTRIBUTE_FLAGGED;
}
else if (mu_c_strncasecmp (start, "\\Deleted", end - start) == 0)
{
flags |= MU_ATTRIBUTE_DELETED;
}
else if (mu_c_strncasecmp (start, "\\Draft", end - start) == 0)
{
flags |= MU_ATTRIBUTE_DRAFT;
}
else if (mu_c_strncasecmp (start, "\\Recent", end - start))
flags |= MU_ATTRIBUTE_SEEN;
}
}
while (**ptr && **ptr != ')'); /* do {} */
/* Skip the last rparen. */
if (**ptr == ')')
(*ptr)++;
if (pflags)
*pflags = flags;
return 0;
}
static int
imap_body (f_imap_t f_imap, char **ptr)
{
int status;
/* Skip leading spaces. */
while (**ptr && **ptr == ' ')
(*ptr)++;
if (**ptr == '[')
{
char *sep = strchr (*ptr, ']');
(*ptr)++; /* Move past the '[' */
if (sep)
{
size_t len = sep - *ptr;
char *section = malloc (len + 1);
if (!section)
return ENOMEM;
strncpy (section, *ptr, len);
section[len] = '\0';
/* strupper. */
mu_strupper (section);
/* Set the string type to update the correct line count. */
/*if (!strstr (section, "FIELD"))*/
{
if (strstr (section, "MIME") || (strstr (section, "HEADER")))
{
f_imap->string.type = IMAP_HEADER;
}
else if (strstr (section, "TEXT") || len > 0)
{
f_imap->string.type = IMAP_BODY;
}
else if (len == 0) /* body[] */
{
f_imap->string.type = IMAP_MESSAGE;
}
}
free (section);
sep++; /* Move past the ']' */
*ptr = sep;
}
}
while (**ptr && **ptr == ' ')
(*ptr)++;
if (**ptr == '<')
{
char *sep = strchr (*ptr, '>');
if (sep)
{
sep++;
*ptr = sep;
}
}
status = imap_string (f_imap, ptr);
/* If the state scan. Catch it here. */
if (f_imap->state == IMAP_SCAN_ACK)
{
char *buffer;
mu_off_t total = 0;
if (f_imap->string.msg_imap && f_imap->string.msg_imap->fheader)
mu_header_destroy (&f_imap->string.msg_imap->fheader, NULL);
mu_stream_size (f_imap->string.stream, &total);
buffer = malloc (total + 1);
mu_stream_read (f_imap->string.stream, buffer, total, 0, NULL);
status = mu_header_create (&f_imap->string.msg_imap->fheader,
buffer, total, NULL);
free (buffer);
mu_stream_truncate (f_imap->string.stream, 0);
f_imap->string.offset = 0;
f_imap->string.nleft = 0;
}
return status;
}
static int
imap_internaldate (f_imap_t f_imap, char **ptr)
{
return imap_string (f_imap, ptr);
}
static int
imap_uid (f_imap_t f_imap, char **ptr)
{
char token[128];
imap_token (token, sizeof token, ptr);
if (f_imap->string.msg_imap)
f_imap->string.msg_imap->uid = strtoul (token, NULL, 10);
return 0;
}
static int
imap_rfc822 (f_imap_t f_imap, char **ptr)
{
int status;
f_imap->string.type = IMAP_MESSAGE;
status = imap_body (f_imap, ptr);
f_imap->string.type = 0;
return status;
}
static int
imap_rfc822_size (f_imap_t f_imap, char **ptr)
{
char token[128];
imap_token (token, sizeof token, ptr);
if (f_imap->string.msg_imap)
f_imap->string.msg_imap->mu_message_size = strtoul (token, NULL, 10);
return 0;
}
static int
imap_rfc822_header (f_imap_t f_imap, char **ptr)
{
int status;
f_imap->string.type = IMAP_HEADER;
status = imap_string (f_imap, ptr);
f_imap->string.type = 0;
return status;
}
static int
imap_rfc822_text (f_imap_t f_imap, char **ptr)
{
int status;
f_imap->string.type = IMAP_HEADER;
status = imap_string (f_imap, ptr);
f_imap->string.type = 0;
return status;
}
/* Parse imap unfortunately FETCH is use as response for many commands.
We just use a small set an ignore the other ones :
not use : ALL
use : BODY
not use : BODY[<section>]<<partial>>
use : BODY.PEEK[<section>]<<partial>>
not use : BODYSTRUCTURE
not use : ENVELOPE
not use : FAST
use : FLAGS
not use : FULL
use : INTERNALDATE
not use : RFC822
not use : RFC822.HEADER
use : RFC822.SIZE
not use : RFC822.TEXT
not use : UID
*/
static int
imap_fetch (f_imap_t f_imap)
{
char token[128];
size_t msgno = 0;
m_imap_t m_imap = f_imap->selected;
int status = 0;
char *sp = NULL;
/* We should have a mailbox selected. */
assert (m_imap != NULL);
/* Parse the FETCH respones to extract the pieces. */
sp = f_imap->buffer;
/* Skip untag '*'. */
imap_token (token, sizeof token, &sp);
/* Get msgno. */
imap_token (token, sizeof token, &sp);
msgno = strtol (token, NULL, 10);
/* Skip FETCH . */
imap_token (token, sizeof token, &sp);
/* It is actually possible, but higly unlikely that we do not have the
message yet, for example a "FETCH (FLAGS (\Recent))" notification
for a newly messsage. */
if (f_imap->string.msg_imap == NULL
|| f_imap->string.msg_imap->num != msgno)
{
/* Find the imap mesg struct. */
size_t i;
mu_message_t msg = NULL;
mu_mailbox_get_message (m_imap->mailbox, msgno, &msg);
for (i = 0; i < m_imap->imessages_count; i++)
{
if (m_imap->imessages[i] && m_imap->imessages[i]->num == msgno)
{
f_imap->string.msg_imap = m_imap->imessages[i];
break;
}
}
/* mu_message_destroy (&msg); */
}
while (*sp && *sp != ')')
{
/* Get the token. */
imap_token (token, sizeof token, &sp);
if (strncmp (token, "FLAGS", 5) == 0)
{
status = imap_fetch_flags (f_imap, &sp);
}
else if (mu_c_strcasecmp (token, "BODY") == 0)
{
if (*sp == '[')
status = imap_body (f_imap, &sp);
else
status = imap_bodystructure (f_imap, &sp);
}
else if (mu_c_strcasecmp (token, "BODYSTRUCTURE") == 0)
{
status = imap_bodystructure (f_imap, &sp);
}
else if (strncmp (token, "INTERNALDATE", 12) == 0)
{
status = imap_internaldate (f_imap, &sp);
}
else if (strncmp (token, "RFC822", 10) == 0)
{
if (*sp == '.')
{
sp++;
imap_token (token, sizeof token, &sp);
if (mu_c_strcasecmp (token, "SIZE") == 0)
{
status = imap_rfc822_size (f_imap, &sp);
}
else if (mu_c_strcasecmp (token, "TEXT") == 0)
{
status = imap_rfc822_text (f_imap, &sp);
}
else if (mu_c_strcasecmp (token, "HEADER") == 0)
{
status = imap_rfc822_header (f_imap, &sp);
}
/* else mu_error (_("not supported RFC822 option")); */
}
else
{
status = imap_rfc822 (f_imap, &sp);
}
}
else if (strncmp (token, "UID", 3) == 0)
{
status = imap_uid (f_imap, &sp);
}
/* else mu_error (_("not supported FETCH command")); */
}
return status;
}
static int
imap_search (f_imap_t f_imap MU_ARG_UNUSED)
{
/* Not implemented. No provision for this in the API, yet. */
return 0;
}
static int
imap_status (f_imap_t f_imap MU_ARG_UNUSED)
{
/* Not implemented. No provision for this in the API, yet. */
return 0;
}
static int
imap_expunge (f_imap_t f_imap MU_ARG_UNUSED, unsigned msgno MU_ARG_UNUSED)
{
/* We should not have this, since do not send the expunge, but rather
use SELECT/CLOSE. */
return 0;
}
/* This function will advance ptr to the next character that IMAP
recognise as special: " .()[]<>" and put the result in buf which
is of size len. */
static int
imap_token (char *buf, size_t len, char **ptr)
{
char *start = *ptr;
size_t i;
/* Skip leading space. */
while (**ptr && **ptr == ' ')
(*ptr)++;
/* Break the string by token, when we recognise Atoms we stop. */
for (i = 1; **ptr && i < len; (*ptr)++, buf++, i++)
{
if (**ptr == ' ' || **ptr == '.'
|| **ptr == '(' || **ptr == ')'
|| **ptr == '[' || **ptr == ']'
|| **ptr == '<' || **ptr == '>')
{
/* Advance. */
if (start == (*ptr))
(*ptr)++;
break;
}
*buf = **ptr;
}
*buf = '\0';
/* Skip trailing space. */
while (**ptr && **ptr == ' ')
(*ptr)++;
return *ptr - start;;
}
/* Checks to see if a mailbox name matches a pattern, treating
INBOX case insensitively, as required (INBOX is a special
name no matter what the case is).
*/
static int
imap_mailbox_name_match (const char* pattern, const char* mailbox)
{
if (mu_c_strcasecmp (pattern, "inbox") == 0)
{
return mu_c_strcasecmp (pattern, mailbox);
}
return fnmatch (pattern, mailbox, 0);
}
/* C99 says that a conforming implementations of snprintf () should return the
number of char that would have been call but many GNU/Linux && BSD
implementations return -1 on error. Worse QnX/Neutrino actually does not
put the terminal null char. So let's try to cope. */
int
imap_writeline (f_imap_t f_imap, const char *format, ...)
{
int len;
va_list ap;
int done = 1;
va_start(ap, format);
do
{
va_list aq;
va_copy(aq, ap);
len = vsnprintf (f_imap->buffer, f_imap->buflen, format, aq);
va_end(aq);
if (len < 0 || len >= (int)f_imap->buflen
|| !memchr (f_imap->buffer, '\0', len + 1))
{
f_imap->buflen *= 2;
f_imap->buffer = realloc (f_imap->buffer, f_imap->buflen);
if (f_imap->buffer == NULL)
return ENOMEM;
done = 0;
}
else
done = 1;
}
while (!done);
va_end(ap);
f_imap->ptr = f_imap->buffer + len;
if (DEBUG_SHOW_COMMAND)
fprintf (stderr, "%s", f_imap->buffer);
return 0;
}
/* Cover to send requests. */
int
imap_send (f_imap_t f_imap)
{
int status = 0;
if (f_imap->ptr > f_imap->buffer)
{
size_t len;
size_t n = 0;
len = f_imap->ptr - f_imap->buffer;
status = mu_stream_write (f_imap->folder->stream, f_imap->buffer, len,
0, &n);
if (status == 0)
{
memmove (f_imap->buffer, f_imap->buffer + n, len - n);
f_imap->ptr -= n;
}
}
else
f_imap->ptr = f_imap->buffer;
return status;
}
/* Read a complete line form the imap server. Transform CRLF to LF, put a null
in the buffer when done. Note f_imap->offset is not really of any use
but rather to keep the stream internal buffer scheme happy, so we have to
be in sync with the stream. */
int
imap_readline (f_imap_t f_imap)
{
size_t n = 0;
size_t total = f_imap->ptr - f_imap->buffer;
int status;
/* Must get a full line before bailing out. */
do
{
status = mu_stream_readline (f_imap->folder->stream, f_imap->buffer + total,
f_imap->buflen - total, f_imap->offset, &n);
if (status != 0)
return status;
/* The server went away: It maybe a timeout and some imap server
does not send the BYE. Consider like an error. */
if (n == 0)
return EIO;
total += n;
f_imap->offset += n;
f_imap->nl = memchr (f_imap->buffer, '\n', total);
if (f_imap->nl == NULL) /* Do we have a full line. */
{
/* Allocate a bigger buffer ? */
if (total >= f_imap->buflen -1)
{
f_imap->buflen *= 2;
f_imap->buffer = realloc (f_imap->buffer, f_imap->buflen + 1);
if (f_imap->buffer == NULL)
return ENOMEM;
}
}
f_imap->ptr = f_imap->buffer + total;
}
while (f_imap->nl == NULL);
/* Conversion \r\n --> \n\0 */
/* FIXME: This should be done transparently by the TCP stream */
if (f_imap->nl > f_imap->buffer && f_imap->nl[-1] == '\r')
{
*(f_imap->nl - 1) = '\n';
*(f_imap->nl) = '\0';
f_imap->ptr = f_imap->nl;
}
return 0;
}
/*
The parsing was inspired by this article form the BeNews channel: "BE
ENGINEERING INSIGHTS: IMAP for the Masses." By Adam Haberlach adam@be.com
The server responses are in three forms: status responses, server data,
and command continuation request, ...
An untagged response is indicated by the token "*" instead of a tag.
Untagged status responses indicate server greeting, or server status
that does not indicate the completion of a command (for example, an
impending system shutdown alert).
....
The client MUST be prepared to accept any response at all times.
Status responses are OK, NO, BAD, PREAUTH and BYE. OK, NO, and BAD
may be tagged or untagged. PREAUTH and BYE are always untagged.
Server Responses - Status Responses
BAD *|tag
BYE *
NO *|tag
OK *|tag
PREAUTH *
The code for status responses are
ALERT
BADCHARSET(IMAPV)
CAPABILITY(IMAPV)
NEWNAME
PARSE
PERMANENTFLAGS
READ-ONLY
READ-WRITE
TRYCREATE
UIDNEXT
UIDVALIDITY
UNSEEN
Server Responses - Server and Mailbox Status.
These responses are always untagged.
CAPABILITY *
EXISTS *
EXPUNGE *
FLAGS *
FETCH *
LIST *
LSUB *
RECENT *
SEARCH *
STATUS *
Server Responses - Command Continuation Request
+
*/
int
imap_parse (f_imap_t f_imap)
{
int done = 0;
int status = 0;
char empty[2];
char *buffer = NULL;
mu_folder_t folder = f_imap->folder;
/* We use that moronic hack to not check null for the tockenize strings. */
empty[0] = '\0';
empty[1] = '\0';
while (! done)
{
char *tag, *response, *remainder;
status = imap_readline (f_imap);
if (status != 0)
{
break;
}
/* Comment out to see all reading traffic. */
if (DEBUG_SHOW_RESPONSE)
mu_error ("\t\t%s", f_imap->buffer);
/* We do not want to step over f_imap->buffer since it can be use
further down the chain. */
if (buffer)
{
free (buffer);
buffer = NULL;
}
buffer = calloc ((f_imap->ptr - f_imap->buffer) + 1, 1);
memcpy (buffer, f_imap->buffer, (f_imap->ptr - f_imap->buffer));
/* Tokenize the string. */
{
char *sp = NULL;
tag = strtok_r (buffer, " ", &sp);
response = strtok_r (NULL, " ", &sp);
if (!response)
response = empty;
remainder = strtok_r (NULL, "\n", &sp);
if (!remainder)
remainder = empty;
}
if (!tag)
{
/* Just in case */
mu_error (_("no tag in response: %s %s"), response, remainder);
status = MU_ERR_FAILURE;
}
/* Is the response untagged ? */
else if (tag[0] == '*')
{
MU_DEBUG2 (folder->debug, MU_DEBUG_PROT, "* %s %s\n",
response, remainder);
/* Is it a Status Response. */
if (mu_c_strcasecmp (response, "OK") == 0)
{
/* Check for status response [code]. */
if (*remainder == '[')
{
char *cruft, *subtag;
char *sp = NULL, *sp1;
remainder++;
cruft = strtok_r (remainder, "]", &sp);
if (!cruft) cruft = empty;
subtag = strtok_r (cruft, " ", &sp1);
if (!subtag) subtag = empty;
if (mu_c_strcasecmp (subtag, "ALERT") == 0)
{
/* The human-readable text contains a special alert that
MUST be presented to the user in a fashion that calls
the user's attention to the message. */
mu_error (_("ALERT: %s"), (sp) ? sp : "");
}
else if (mu_c_strcasecmp (subtag, "BADCHARSET") == 0)
{
/* Optionally followed by a parenthesized list of
charsets. A SEARCH failed because the given charset
is not supported by this implementation. If the
optional list of charsets is given, this lists the
charsets that are supported by this implementation. */
mu_error (_("BAD CHARSET: %s"), (sp) ? sp : "");
}
else if (mu_c_strcasecmp (subtag, "CAPABILITY") == 0)
{
/* Followed by a list of capabilities. This can appear
in the initial OK or PREAUTH response to transmit an
initial capabilities list. This makes it unnecessary
for a client to send a separate CAPABILITY command if
it recognizes this response. */
parse_capa (f_imap, cruft);
}
else if (mu_c_strcasecmp (subtag, "NEWNAME") == 0)
{
/* Followed by a mailbox name and a new mailbox name. A
SELECT or EXAMINE failed because the target mailbox
name (which once existed) was renamed to the new
mailbox name. This is a hint to the client that the
operation can succeed if the SELECT or EXAMINE is
reissued with the new mailbox name. */
mu_error ("NEWNAME: %s", (sp) ? sp : "");
}
else if (mu_c_strcasecmp (subtag, "PARSE") == 0)
{
/* The human-readable text represents an error in
parsing the [RFC-822] header or [MIME-IMB] headers
of a message in the mailbox. */
mu_error ("PARSE: %s", (sp) ? sp : "");
}
else if (mu_c_strcasecmp (subtag, "PERMANENTFLAGS") == 0)
{
/* Followed by a parenthesized list of flags, indicates
which of the known flags that the client can change
permanently. Any flags that are in the FLAGS
untagged response, but not the PERMANENTFLAGS list,
can not be set permanently. If the client attempts
to STORE a flag that is not in the PERMANENTFLAGS
list, the server will either ignore the change or
store the state change for the remainder of the
current session only. The PERMANENTFLAGS list can
also include the special flag \*, which indicates
that it is possible to create new keywords by
attempting to store those flags in the mailbox. */
}
else if (mu_c_strcasecmp (subtag, "READ-ONLY") == 0)
{
/* The mailbox is selected read-only, or its access
while selected has changed from read-write to
read-only. */
}
else if (mu_c_strcasecmp (subtag, "READ-WRITE") == 0)
{
/* The mailbox is selected read-write, or its access
while selected has changed from read-only to
read-write. */
}
else if (mu_c_strcasecmp (subtag, "TRYCREATE") == 0)
{
/* An APPEND or COPY attempt is failing because the
target mailbox does not exist (as opposed to some
other reason). This is a hint to the client that
the operation can succeed if the mailbox is first
created by the CREATE command. */
mu_error ("TRYCREATE: %s", (sp) ? sp : "");
}
else if (mu_c_strcasecmp (subtag, "UIDNEXT") == 0)
{
/* Followed by a decimal number, indicates the next
unique identifier value. Refer to section 2.3.1.1
for more information. */
char *value = strtok_r (NULL, " ", &sp1);
if (value)
f_imap->selected->uidnext = strtol (value, NULL, 10);
}
else if (mu_c_strcasecmp (subtag, "UIDVALIDITY") == 0)
{
/* Followed by a decimal number, indicates the unique
identifier validity value. Refer to section 2.3.1.1
for more information. */
char *value = strtok_r (NULL, " ", &sp1);
if (value)
f_imap->selected->uidvalidity = strtol (value,
NULL, 10);
}
else if (mu_c_strcasecmp (subtag, "UNSEEN") == 0)
{
/* Followed by a decimal number, indicates the number of
the first message without the \Seen flag set. */
char *value = strtok_r (NULL, " ", &sp1);
if (value)
f_imap->selected->unseen = strtol (value, NULL, 10);
}
else
{
/* Additional response codes defined by particular
client or server implementations SHOULD be prefixed
with an "X" until they are added to a revision of
this protocol. Client implementations SHOULD ignore
response codes that they do not recognize. */
}
} /* End of code. */
else
{
/* Not sure why we would get an untagged ok...but we do... */
/* Still should we be verbose about is ? */
mu_error (_("untagged OK response: %s"), remainder);
}
}
else if (mu_c_strcasecmp (response, "NO") == 0)
{
/* This does not mean failure but rather a strong warning. */
mu_error (_("untagged NO response: %s"), remainder);
}
else if (mu_c_strcasecmp (response, "BAD") == 0)
{
/* We're dead, protocol/syntax error. */
mu_error (_("untagged BAD response: %s"), remainder);
}
else if (mu_c_strcasecmp (response, "PREAUTH") == 0)
{
/* Should we be dealing with this? */
}
else if (mu_c_strcasecmp (response, "BYE") == 0)
{
/* We should close the stream. This is not recoverable. */
done = 1;
mu_monitor_wrlock (f_imap->folder->monitor);
f_imap->isopen = 0;
f_imap->selected = NULL;
mu_monitor_unlock (f_imap->folder->monitor);
mu_stream_close (f_imap->folder->stream);
}
else if (mu_c_strcasecmp (response, "CAPABILITY") == 0)
{
parse_capa (f_imap, remainder);
}
else if (mu_c_strcasecmp (remainder, "EXISTS") == 0)
{
f_imap->selected->messages_count = strtol (response, NULL, 10);
}
else if (mu_c_strcasecmp (remainder, "EXPUNGE") == 0)
{
unsigned int msgno = strtol (response, NULL, 10);
status = imap_expunge (f_imap, msgno);
}
else if (mu_c_strncasecmp (remainder, "FETCH", 5) == 0)
{
status = imap_fetch (f_imap);
if (status != 0)
break;
}
else if (mu_c_strcasecmp (response, "FLAGS") == 0)
{
/* Flags define on the mailbox not a message flags. */
status = imap_permanentflags (f_imap, &remainder);
}
else if (mu_c_strcasecmp (response, "LIST") == 0)
{
status = imap_list (f_imap);
}
else if (mu_c_strcasecmp (response, "LSUB") == 0)
{
status = imap_list (f_imap);
}
else if (mu_c_strcasecmp (remainder, "RECENT") == 0)
{
f_imap->selected->recent = strtol (response, NULL, 10);
}
else if (mu_c_strcasecmp (response, "SEARCH") == 0)
{
status = imap_search (f_imap);
}
else if (mu_c_strcasecmp (response, "STATUS") == 0)
{
status = imap_status (f_imap);
}
else
{
/* Once again, check for something strange. */
mu_error (_("unknown untagged response: \"%s\" %s"),
response, remainder);
}
}
/* Continuation token ???. */
else if (tag[0] == '+')
{
done = 1;
}
else
{
/* Every transaction ends with a tagged response. */
done = 1;
if (mu_c_strcasecmp (response, "OK") == 0)
{
/* Cool we are doing ok. */
}
else if (mu_c_strcasecmp (response, "NO") == 0)
{
if (mu_c_strncasecmp (remainder, "LOGIN", 5) == 0)
{
mu_observable_t observable = NULL;
mu_folder_get_observable (f_imap->folder, &observable);
mu_observable_notify (observable, MU_EVT_FOLDER_AUTHORITY_FAILED,
NULL);
status = MU_ERR_AUTH_FAILURE;
}
else if (mu_c_strncasecmp (remainder, "LIST", 4) == 0)
status = MU_ERR_NOENT;
else
status = MU_ERR_FAILURE;
}
else /* NO and BAD */
{
status = EINVAL;
mu_error (_("NO or BAD tagged response: %s %s %s"),
tag, response, remainder);
}
}
f_imap->ptr = f_imap->buffer;
}
if (buffer)
free (buffer);
return status;
}
MU_IMAP_PRIO,
MU_IMAPS_SCHEME,
MU_RECORD_DEFAULT,
MU_URL_SCHEME | MU_URL_CRED | MU_URL_INET | MU_URL_PATH | MU_URL_PARAM,
MU_URL_HOST,
_mu_imaps_url_init, /* url entry. */
_mu_imaps_mailbox_init, /* Mailbox entry. */
NULL, /* Mailer entry. */
_mu_imap_folder_init, /* Folder entry. */
NULL, /* No need for a back pointer. */
NULL, /* _is_scheme method. */
NULL, /* _get_url method. */
NULL, /* _get_mailbox method. */
NULL, /* _get_mailer method. */
NULL /* _get_folder method. */
};
mu_record_t mu_imaps_record = &_imaps_record;
#else
#include <stdio.h>
#include <mailutils/sys/registrar.h>
mu_record_t mu_imap_record = NULL;
mu_record_t mu_imaps_record = NULL;
#endif /* ENABLE_IMAP */
#endif /* WITH_TLS */
......
......@@ -104,13 +104,15 @@ list_untagged_handler (mu_imap_t imap, mu_list_t resp, void *data)
}
elt = _mu_imap_list_at (resp, 2);
if (!(elt && elt->type == imap_eltype_string))
if (!elt)
return;
if (mu_c_strcasecmp (elt->v.string, "NIL") == 0)
if (_mu_imap_list_element_is_nil (elt))
{
rp->separator = 0;
rp->level = 0;
}
else if (elt->type != imap_eltype_string)
return;
else
{
rp->separator = elt->v.string[0];
......@@ -131,6 +133,9 @@ mu_imap_genlist (mu_imap_t imap, int lsub,
struct list_closure clos;
int rc;
if (!refname || !mboxname)
return EINVAL;
argv[0] = lsub ? "LSUB" : "LIST";
argv[1] = refname;
argv[2] = mboxname;
......
......@@ -22,6 +22,7 @@
#include <string.h>
#include <mailutils/errno.h>
#include <mailutils/stream.h>
#include <mailutils/secret.h>
#include <mailutils/sys/imap.h>
int
......@@ -77,3 +78,12 @@ mu_imap_login (mu_imap_t imap, const char *user, const char *pass)
return status;
}
int
mu_imap_login_secret (mu_imap_t imap, const char *user, mu_secret_t secret)
{
int rc = mu_imap_login (imap, user, mu_secret_password (secret));
mu_secret_password_unref (secret);
return rc;
}
......
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2003, 2004, 2005, 2006, 2007, 2009,
2010, 2011 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library. If not, see
<http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef ENABLE_IMAP
#include <errno.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#include <mailutils/address.h>
#include <mailutils/attribute.h>
#include <mailutils/body.h>
#include <mailutils/debug.h>
#include <mailutils/envelope.h>
#include <mailutils/error.h>
#include <mailutils/errno.h>
#include <mailutils/header.h>
#include <mailutils/message.h>
#include <mailutils/util.h>
#include <mailutils/observer.h>
#include <mailutils/property.h>
#include <mailutils/stream.h>
#include <mailutils/io.h>
#include <mailutils/sys/imap.h>
#include <mailutils/sys/mailbox.h>
#include <mailutils/sys/registrar.h>
#include <mailutils/sys/url.h>
#undef min
#define min(a,b) ((a) < (b) ? (a) : (b))
#define MU_IMAP_CACHE_HEADERS "Bcc Cc Content-Language Content-Transfer-Encoding Content-Type Date From In-Reply-To Message-ID Reference Reply-To Sender Subject To X-UIDL"
/* mu_mailbox_t API. */
static void mailbox_imap_destroy (mu_mailbox_t);
static int mailbox_imap_open (mu_mailbox_t, int);
static int mailbox_imap_close (mu_mailbox_t);
static int imap_uidvalidity (mu_mailbox_t, unsigned long *);
static int imap_uidnext (mu_mailbox_t, size_t *);
static int imap_expunge (mu_mailbox_t);
static int imap_get_message (mu_mailbox_t, size_t, mu_message_t *);
static int imap_messages_count (mu_mailbox_t, size_t *);
static int imap_messages_recent (mu_mailbox_t, size_t *);
static int imap_message_unseen (mu_mailbox_t, size_t *);
static int imap_scan (mu_mailbox_t, size_t, size_t *);
static int imap_scan0 (mu_mailbox_t, size_t, size_t *, int);
static int imap_is_updated (mu_mailbox_t);
static int imap_append_message (mu_mailbox_t, mu_message_t);
static int imap_append_message0 (mu_mailbox_t, mu_message_t);
static int imap_copy_message (mu_mailbox_t, mu_message_t);
/* mu_message_t API. */
static int imap_submessage_size (msg_imap_t, size_t *);
static int imap_message_size (mu_message_t, size_t *);
static int imap_message_lines (mu_message_t, size_t *);
static int imap_message_get_transport2 (mu_stream_t, mu_transport_t *pin,
mu_transport_t *pout);
static int imap_message_read (mu_stream_t , char *, size_t, mu_off_t, size_t *);
static int imap_message_uid (mu_message_t, size_t *);
/* mu_mime_t API. */
static int imap_is_multipart (mu_message_t, int *);
static int imap_get_num_parts (mu_message_t, size_t *);
static int imap_get_part (mu_message_t, size_t, mu_message_t *);
/* mu_envelope_t API */
static int imap_envelope_sender (mu_envelope_t, char *, size_t, size_t *);
static int imap_envelope_date (mu_envelope_t, char *, size_t, size_t *);
/* mu_attribute_t API */
static int imap_attr_get_flags (mu_attribute_t, int *);
static int imap_attr_set_flags (mu_attribute_t, int);
static int imap_attr_unset_flags (mu_attribute_t, int);
/* mu_header_t API. */
static int imap_header_read (mu_header_t, char*, size_t, mu_off_t, size_t *);
/* mu_body_t API. */
static int imap_body_read (mu_stream_t, char *, size_t, mu_off_t, size_t *);
static int imap_body_size (mu_body_t, size_t *);
static int imap_body_lines (mu_body_t, size_t *);
static int imap_body_get_transport2 (mu_stream_t, mu_transport_t *pin, mu_transport_t *pout);
/* Helpers. */
static int imap_get_transport2 (msg_imap_t msg_imap,
mu_transport_t *pin,
mu_transport_t *pout);
static int imap_get_message0 (msg_imap_t, mu_message_t *);
static int fetch_operation (f_imap_t, msg_imap_t, char *, size_t, size_t *);
static void free_subparts (msg_imap_t);
static int flags_to_string (char **, int);
static int delete_to_string (m_imap_t, char **);
static int is_same_folder (mu_mailbox_t, mu_message_t);
#define MBX_WRITABLE(mbx) ((mbx)->flags & (MU_STREAM_WRITE|MU_STREAM_RDWR|MU_STREAM_CREAT))
/* Initialize the concrete object mu_mailbox_t by overloading the function of the
structure. */
int
_mailbox_imap_and_imaps_init (mu_mailbox_t mailbox, int imaps)
{
int status;
m_imap_t m_imap;
if (!mailbox)
return EINVAL;
if (mailbox->folder == NULL)
return EINVAL;
m_imap = mailbox->data = calloc (1, sizeof (*m_imap));
if (m_imap == NULL)
return ENOMEM;
/* Retrieve the name of the mailbox from the URL. */
status = mu_url_aget_path (mailbox->url, &m_imap->name);
if (status == MU_ERR_NOENT)
{
m_imap->name = strdup ("INBOX");
if (!m_imap->name)
return ENOMEM;
}
else if (status)
return status;
/* Overload the functions. */
mailbox->_destroy = mailbox_imap_destroy;
mailbox->_open = mailbox_imap_open;
mailbox->_close = mailbox_imap_close;
/* Messages. */
mailbox->_get_message = imap_get_message;
mailbox->_append_message = imap_append_message;
mailbox->_messages_count = imap_messages_count;
mailbox->_messages_recent = imap_messages_recent;
mailbox->_message_unseen = imap_message_unseen;
mailbox->_expunge = imap_expunge;
mailbox->_uidvalidity = imap_uidvalidity;
mailbox->_uidnext = imap_uidnext;
mailbox->_scan = imap_scan;
mailbox->_is_updated = imap_is_updated;
/* Get the back pointer of the concrete folder. */
m_imap->f_imap = mailbox->folder->data;
m_imap->f_imap->imaps = imaps;
/* maibox back pointer. */
m_imap->mailbox = mailbox;
/* Set our properties. */
{
mu_property_t property = NULL;
mu_mailbox_get_property (mailbox, &property);
mu_property_set_value (property, "TYPE", "IMAP4", 1);
}
return 0;
}
int
_mailbox_imap_init (mu_mailbox_t mailbox)
{
return _mailbox_imap_and_imaps_init (mailbox, 0);
}
int
_mailbox_imaps_init (mu_mailbox_t mailbox)
{
return _mailbox_imap_and_imaps_init (mailbox, 1);
}
/* Recursive call to free all the subparts of a message. */
static void
free_subparts (msg_imap_t msg_imap)
{
size_t i;
for (i = 0; i < msg_imap->num_parts; i++)
{
if (msg_imap->parts[i])
free_subparts (msg_imap->parts[i]);
}
if (msg_imap->message)
mu_message_destroy (&(msg_imap->message), msg_imap);
if (msg_imap->parts)
free (msg_imap->parts);
if (msg_imap->fheader)
mu_header_destroy (&msg_imap->fheader, NULL);
if (msg_imap->internal_date)
free (msg_imap->internal_date);
free(msg_imap);
}
/* Give back all the resources. But it does not mean to shutdown the channel
this is done on the folder. */
static void
mailbox_imap_destroy (mu_mailbox_t mailbox)
{
if (mailbox->data)
{
m_imap_t m_imap = mailbox->data;
f_imap_t f_imap = m_imap->f_imap;
size_t i;
/* Deselect. */
if (m_imap != f_imap->selected)
f_imap->selected = NULL;
mu_monitor_wrlock (mailbox->monitor);
/* Destroy the imap messages and ressources associated to them. */
for (i = 0; i < m_imap->imessages_count; i++)
{
if (m_imap->imessages[i])
free_subparts (m_imap->imessages[i]);
}
if (m_imap->imessages)
free (m_imap->imessages);
if (m_imap->name)
free (m_imap->name);
free (m_imap);
mailbox->data = NULL;
mu_monitor_unlock (mailbox->monitor);
}
}
/* If the connection was not up it is open by the folder since the stream
socket is actually created by the folder. It is not necessary
to set select the mailbox right away, there are maybe on going operations.
But on any operation by a particular mailbox, it will be selected first. */
static int
mailbox_imap_open (mu_mailbox_t mailbox, int flags)
{
int status = 0;
m_imap_t m_imap = mailbox->data;
f_imap_t f_imap = m_imap->f_imap;
mu_folder_t folder = f_imap->folder;
mu_list_t folders = NULL;
size_t count;
/* m_imap must have been created during mailbox initialization. */
assert (mailbox->data);
assert (m_imap->name);
mailbox->flags = flags;
if ((status = mu_folder_open (mailbox->folder, flags)))
return status;
/* We might not have to SELECT the mailbox, but we need to know it
exists, and CREATE it if it doesn't, and CREATE is specified in
the flags.
*/
switch (m_imap->state)
{
case IMAP_NO_STATE:
m_imap->state = IMAP_LIST;
case IMAP_LIST:
status = mu_folder_list (folder, NULL, m_imap->name, 0, &folders);
if (status != 0)
{
if (status != EAGAIN && status != EINPROGRESS && status != EINTR)
m_imap->state = IMAP_NO_STATE;
return status;
}
m_imap->state = IMAP_NO_STATE;
status = mu_list_count (folders, &count);
mu_list_destroy (&folders);
if (status || count)
return 0;
if ((flags & MU_STREAM_CREAT) == 0)
return ENOENT;
m_imap->state = IMAP_CREATE;
case IMAP_CREATE:
switch (f_imap->state)
{
case IMAP_NO_STATE:
{
const char *path;
status = mu_url_sget_path (folder->url, &path);
if (status == MU_ERR_NOENT)
return 0;
else if (status)
return status;
status = imap_writeline (f_imap, "g%lu CREATE %s\r\n",
(unsigned long) f_imap->seq, path);
MU_DEBUG2 (folder->debug, MU_DEBUG_PROT, "g%lu CREATE %s\n",
(unsigned long) f_imap->seq, path);
f_imap->seq++;
if (status != 0)
{
m_imap->state = f_imap->state = IMAP_NO_STATE;
return status;
}
f_imap->state = IMAP_CREATE;
}
case IMAP_CREATE:
status = imap_send (f_imap);
if (status != 0)
{
if (status != EAGAIN && status != EINPROGRESS
&& status != EINTR)
m_imap->state = f_imap->state = IMAP_NO_STATE;
return status;
}
f_imap->state = IMAP_CREATE_ACK;
case IMAP_CREATE_ACK:
status = imap_parse (f_imap);
if (status != 0)
{
if (status == EINVAL)
status = EACCES;
if (status != EAGAIN && status != EINPROGRESS
&& status != EINTR)
m_imap->state = f_imap->state = IMAP_NO_STATE;
return status;
}
f_imap->state = IMAP_NO_STATE;
break;
default:
status = EINVAL;
break;
}
m_imap->state = IMAP_NO_STATE;
break;
default:
status = EINVAL;
break;
}
return status;
}
/* We can not close the folder in term of shuting down the connection but if
we were the selected mailbox we send the close and deselect ourself.
The CLOSE is also use to expunge instead of sending expunge. */
static int
mailbox_imap_close (mu_mailbox_t mailbox)
{
m_imap_t m_imap = mailbox->data;
f_imap_t f_imap = m_imap->f_imap;
int status = 0;
/* If we are not the selected mailbox, just close the stream. */
if (m_imap != f_imap->selected)
return mu_folder_close (mailbox->folder);
/* Select first. */
status = imap_messages_count (mailbox, NULL);
if (status != 0)
return status;
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%lu CLOSE\r\n",
(unsigned long) f_imap->seq++);
CHECK_ERROR (f_imap, status);
MU_DEBUG (mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_CLOSE;
case IMAP_CLOSE:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_CLOSE_ACK;
case IMAP_CLOSE_ACK:
{
size_t i;
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
mu_monitor_wrlock (mailbox->monitor);
/* Destroy the imap messages and ressources associated to them. */
for (i = 0; i < m_imap->imessages_count; i++)
{
if (m_imap->imessages[i])
free_subparts (m_imap->imessages[i]);
}
if (m_imap->imessages)
free (m_imap->imessages);
m_imap->imessages = NULL;
m_imap->imessages_count = 0;
m_imap->messages_count = 0;
m_imap->recent = 0;
m_imap->unseen = 0;
/* Clear the callback string structure. */
mu_stream_truncate (f_imap->string.stream, 0);
f_imap->string.offset = 0;
f_imap->string.nleft = 0;
f_imap->string.type = IMAP_NO_STATE;
f_imap->string.msg_imap = NULL;
mu_monitor_unlock (mailbox->monitor);
}
break;
default:
/* mu_error ("imap_close unknown state: reconnect\n");*/
break;
}
/* Deselect. */
f_imap->selected = NULL;
f_imap->state = IMAP_NO_STATE;
return mu_folder_close (mailbox->folder);
}
/* Construction of the mu_message_t, nothing else is done then this setup. To
clarify this is different from say mu_message_get_part(). This call is for the
mailbox and we are setting up the mu_message_t structure. */
static int
imap_get_message (mu_mailbox_t mailbox, size_t msgno, mu_message_t *pmsg)
{
m_imap_t m_imap = mailbox->data;
msg_imap_t msg_imap;
int status = 0;
if (pmsg == NULL)
return MU_ERR_OUT_PTR_NULL;
if (msgno == 0 || msgno > m_imap->messages_count)
return EINVAL;
/* Check to see if we have already this message. */
mu_monitor_rdlock (mailbox->monitor);
{
size_t i;
for (i = 0; i < m_imap->imessages_count; i++)
{
if (m_imap->imessages[i])
{
if (m_imap->imessages[i]->num == msgno)
{
*pmsg = m_imap->imessages[i]->message;
mu_monitor_unlock (mailbox->monitor);
return 0;
}
}
}
}
mu_monitor_unlock (mailbox->monitor);
/* Allocate a concrete imap message. */
msg_imap = calloc (1, sizeof *msg_imap);
if (msg_imap == NULL)
return ENOMEM;
/* Back pointer. */
msg_imap->m_imap = m_imap;
msg_imap->num = msgno;
status = imap_get_message0 (msg_imap, pmsg);
if (status == 0)
{
/* Add it to the list. */
mu_monitor_wrlock (mailbox->monitor);
{
msg_imap_t *m ;
m = realloc (m_imap->imessages,
(m_imap->imessages_count + 1) * sizeof *m);
if (m == NULL)
{
mu_message_destroy (pmsg, msg_imap);
mu_monitor_unlock (mailbox->monitor);
return ENOMEM;
}
m_imap->imessages = m;
m_imap->imessages[m_imap->imessages_count] = msg_imap;
m_imap->imessages_count++;
}
mu_monitor_unlock (mailbox->monitor);
msg_imap->message = *pmsg;
}
else
free (msg_imap);
return status;
}
/* Set all the mu_message_t functions and parts. */
static int
imap_get_message0 (msg_imap_t msg_imap, mu_message_t *pmsg)
{
int status = 0;
mu_message_t msg = NULL;
mu_mailbox_t mailbox = msg_imap->m_imap->mailbox;
/* Create the message and its stream. */
{
mu_stream_t stream = NULL;
if ((status = mu_message_create (&msg, msg_imap)) != 0
|| (status = mu_stream_create (&stream, mailbox->flags, msg)) != 0)
{
mu_stream_destroy (&stream, msg);
mu_message_destroy (&msg, msg_imap);
return status;
}
mu_stream_setbufsiz (stream, 128);
mu_stream_set_read (stream, imap_message_read, msg);
mu_stream_set_get_transport2 (stream, imap_message_get_transport2, msg);
mu_message_set_stream (msg, stream, msg_imap);
mu_message_set_size (msg, imap_message_size, msg_imap);
mu_message_set_lines (msg, imap_message_lines, msg_imap);
}
/* Create the header. */
{
mu_header_t header = NULL;
if ((status = mu_header_create (&header, NULL, 0, msg)) != 0)
{
mu_message_destroy (&msg, msg_imap);
return status;
}
mu_header_set_fill (header, imap_header_read, msg);
mu_message_set_header (msg, header, msg_imap);
}
/* Create the attribute. */
{
mu_attribute_t attribute;
status = mu_attribute_create (&attribute, msg);
if (status != 0)
{
mu_message_destroy (&msg, msg_imap);
return status;
}
mu_attribute_set_get_flags (attribute, imap_attr_get_flags, msg);
mu_attribute_set_set_flags (attribute, imap_attr_set_flags, msg);
mu_attribute_set_unset_flags (attribute, imap_attr_unset_flags, msg);
mu_message_set_attribute (msg, attribute, msg_imap);
}
/* Create the body and its stream. */
{
mu_body_t body = NULL;
mu_stream_t stream = NULL;
if ((status = mu_body_create (&body, msg)) != 0
|| (status = mu_stream_create (&stream, mailbox->flags, body)) != 0)
{
mu_body_destroy (&body, msg);
mu_stream_destroy (&stream, body);
mu_message_destroy (&msg, msg_imap);
return status;
}
mu_stream_setbufsiz (stream, 128);
mu_stream_set_read (stream, imap_body_read, body);
mu_stream_set_get_transport2 (stream, imap_body_get_transport2, body);
mu_body_set_size (body, imap_body_size, msg);
mu_body_set_lines (body, imap_body_lines, msg);
mu_body_set_stream (body, stream, msg);
mu_message_set_body (msg, body, msg_imap);
}
/* Set the envelope. */
{
mu_envelope_t envelope= NULL;
status = mu_envelope_create (&envelope, msg);
if (status != 0)
{
mu_message_destroy (&msg, msg_imap);
return status;
}
mu_envelope_set_sender (envelope, imap_envelope_sender, msg);
mu_envelope_set_date (envelope, imap_envelope_date, msg);
mu_message_set_envelope (msg, envelope, msg_imap);
}
/* Set the mime handling. */
mu_message_set_is_multipart (msg, imap_is_multipart, msg_imap);
mu_message_set_get_num_parts (msg, imap_get_num_parts, msg_imap);
mu_message_set_get_part (msg, imap_get_part, msg_imap);
/* Set the UID on the message. */
mu_message_set_uid (msg, imap_message_uid, msg_imap);
mu_message_set_mailbox (msg, mailbox, msg_imap);
/* We are done here. */
*pmsg = msg;
return 0;
}
static int
imap_message_unseen (mu_mailbox_t mailbox, size_t *punseen)
{
m_imap_t m_imap = mailbox->data;
*punseen = m_imap->unseen;
return 0;
}
static int
imap_messages_recent (mu_mailbox_t mailbox, size_t *precent)
{
m_imap_t m_imap = mailbox->data;
*precent = m_imap->recent;
return 0;
}
static int
imap_uidvalidity (mu_mailbox_t mailbox, unsigned long *puidvalidity)
{
m_imap_t m_imap = mailbox->data;
*puidvalidity = m_imap->uidvalidity;
return 0;
}
static int
imap_uidnext (mu_mailbox_t mailbox, size_t *puidnext)
{
m_imap_t m_imap = mailbox->data;
*puidnext = m_imap->uidnext;
return 0;
}
/* There is no explicit call to get the message count. The count is send on
a SELECT/EXAMINE command it is also sent async, meaning it will be piggy
back on other server response as an untag "EXIST" response. The
function is also use as a way to select mailbox by other functions. */
static int
imap_messages_count (mu_mailbox_t mailbox, size_t *pnum)
{
m_imap_t m_imap = mailbox->data;
f_imap_t f_imap = m_imap->f_imap;
int status = 0;
/* FIXME: It is debatable if we should reconnect when the connection
timeout or die. Probably for timeout client should ping i.e. send
a NOOP via imap_is_updated() function to keep the connection alive. */
status = mu_folder_open (mailbox->folder, mailbox->flags);
if (status != 0)
return status;
/* Are we already selected ? */
if (m_imap == (f_imap->selected))
{
if (pnum)
*pnum = m_imap->messages_count;
return 0;
}
/* Put the mailbox as selected. */
f_imap->selected = m_imap;
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%lu %s %s\r\n",
(unsigned long) f_imap->seq++,
MBX_WRITABLE(mailbox) ? "SELECT" : "EXAMINE",
m_imap->name);
CHECK_ERROR (f_imap, status);
MU_DEBUG (mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_SELECT;
case IMAP_SELECT:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_SELECT_ACK;
case IMAP_SELECT_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
break;
default:
/*mu_error ("imap_message_count unknown state: reconnect\n");*/
break;
}
if (pnum)
*pnum = m_imap->messages_count;
f_imap->state = IMAP_NO_STATE;
return status;
}
static int
imap_scan (mu_mailbox_t mailbox, size_t msgno, size_t *pcount)
{
return imap_scan0 (mailbox, msgno, pcount , 1);
}
/* Normally this function is called when an observer is trying to build
some sort of list/tree header as the scanning progresses. But doing
this for each message can be time consuming and inefficient. So we
bundle all requests into one and ask the server for everything:
"FETCH 1:*". The good side is that everything will be faster and we
do not do lots of small transcations, but rather a big one. The bad
side is that everything will be cached in the structure using a lot of
memory. */
static int
imap_scan0 (mu_mailbox_t mailbox, size_t msgno, size_t *pcount, int notif)
{
int status;
size_t i;
size_t count = 0;
m_imap_t m_imap = mailbox->data;
f_imap_t f_imap = m_imap->f_imap;
/* Selected. */
status = imap_messages_count (mailbox, &count);
if (pcount)
*pcount = count;
if (status != 0)
return status;
/* No need to scan, there is no messages. */
if (count == 0)
return 0;
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap,
"g%lu FETCH 1:* (FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (%s)])\r\n",
(unsigned long) f_imap->seq++,
MU_IMAP_CACHE_HEADERS);
CHECK_ERROR (f_imap, status);
MU_DEBUG (mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_SCAN;
case IMAP_SCAN:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_SCAN_ACK;
/* Clear the callback string structure. */
mu_stream_truncate (f_imap->string.stream, 0);
f_imap->string.offset = 0;
f_imap->string.nleft = 0;
f_imap->string.type = IMAP_NO_STATE;
f_imap->string.msg_imap = NULL;
case IMAP_SCAN_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
/* Clear the callback string structure. */
mu_stream_truncate (f_imap->string.stream, 0);
f_imap->string.offset = 0;
f_imap->string.nleft = 0;
f_imap->string.type = IMAP_NO_STATE;
f_imap->string.msg_imap = NULL;
break;
default:
/*mu_error ("imap_scan unknown state: reconnect\n");*/
return EINVAL;
}
f_imap->state = IMAP_NO_STATE;
/* Do not send notifications. */
if (!notif)
return 0;
/* If no callbacks bail out early. */
if (mailbox->observable == NULL)
return 0;
for (i = msgno; i <= count; i++)
{
size_t tmp = i;
if (mu_observable_notify (mailbox->observable, MU_EVT_MESSAGE_ADD,
&tmp) != 0)
break;
if ((i + 1) % 100 == 0)
mu_observable_notify (mailbox->observable, MU_EVT_MAILBOX_PROGRESS,
NULL);
}
return 0;
}
/* Send a NOOP and see if the count has changed. */
static int
imap_is_updated (mu_mailbox_t mailbox)
{
m_imap_t m_imap = mailbox->data;
size_t oldcount = m_imap->messages_count;
f_imap_t f_imap = m_imap->f_imap;
int status = 0;
/* Selected. */
status = imap_messages_count (mailbox, &oldcount);
if (status != 0)
return status;
/* Send a noop, and let imap piggy pack the information. */
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%lu NOOP\r\n",
(unsigned long) f_imap->seq++);
CHECK_ERROR (f_imap, status);
MU_DEBUG (mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_NOOP;
case IMAP_NOOP:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_NOOP_ACK;
case IMAP_NOOP_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
break;
default:
/*mu_error ("imap_noop unknown state: reconnect\n"); */
break;
}
f_imap->state = IMAP_NO_STATE;
return (oldcount == m_imap->messages_count);
}
static int
imap_expunge (mu_mailbox_t mailbox)
{
int status;
m_imap_t m_imap = mailbox->data;
f_imap_t f_imap = m_imap->f_imap;
if (!MBX_WRITABLE(mailbox))
return EACCES;
/* Select first. */
status = imap_messages_count (mailbox, NULL);
if (status != 0)
return status;
switch (f_imap->state)
{
case IMAP_NO_STATE:
{
char *set = NULL;
status = delete_to_string (m_imap, &set);
CHECK_ERROR (f_imap, status);
if (set == NULL || *set == '\0')
{
if (set)
free (set);
return 0;
}
status = imap_writeline (f_imap,
"g%lu STORE %s +FLAGS.SILENT (\\Deleted)\r\n",
(unsigned long) f_imap->seq++,
set);
free (set);
CHECK_ERROR (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_STORE;
}
/* Send DELETE. */
case IMAP_STORE:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_STORE_ACK;
case IMAP_STORE_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_NO_STATE;
case IMAP_EXPUNGE:
case IMAP_EXPUNGE_ACK:
status = imap_writeline (f_imap, "g%lu EXPUNGE\r\n",
(unsigned long) f_imap->seq++);
CHECK_ERROR (f_imap, status);
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
/* Rescan after expunging but do not trigger the observers. */
case IMAP_SCAN:
case IMAP_SCAN_ACK:
status = imap_scan0 (mailbox, 1, NULL, 0);
CHECK_EAGAIN (f_imap, status);
default:
/* mu_error ("imap_expunge: unknown state\n"); */
break;
}
return status;
}
/* FIXME: Not ___Nonblocking___ safe. */
/* DANGER: The mu_message_t object makes no guaranty about the size and the lines
that it returns, if its pointing to non-local file messages, so we
make a local copy. */
static int
imap_append_message (mu_mailbox_t mailbox, mu_message_t msg)
{
int status = 0;
m_imap_t m_imap = mailbox->data;
f_imap_t f_imap = m_imap->f_imap;
/* FIXME: It is debatable if we should reconnect when the connection
timeout or die. For timeout client should ping i.e. send
a NOOP via imap_is_updated() function to keep the connection alive. */
status = mu_folder_open (mailbox->folder, mailbox->flags);
if (status != 0)
return status;
/* FIXME: Can we append to self. */
/* Check to see if we are selected. If the message was not modified
and came from the same imap folder. use COPY.*/
if (f_imap->selected != m_imap && !mu_message_is_modified (msg)
&& is_same_folder (mailbox, msg))
return imap_copy_message (mailbox, msg);
/* copy the message to local disk by createing a floating message. */
{
mu_message_t message = NULL;
status = mu_message_create_copy(&message, msg);
if (status == 0)
status = imap_append_message0 (mailbox, message);
mu_message_destroy (&message, NULL);
}
return status;
}
/* Ok this mean that the message is coming from somewhere else. IMAP
is very susceptible on the size, example:
A003 APPEND saved-messages (\Seen) {310}
if the server does not get the right size advertise in the string literal
it will misbehave. Sine we are assuming that the message will be
in native file system format meaning ending with NEWLINE, we will have
to do the calculation. But what is worse; the value return
by mu_message_size () and mu_message_lines () are no mean exact but rather
a gross approximation for certain type of mailbox. So the sane
thing to do is to save the message in temporary file, this we say
we guarantee the size of the message. */
static int
imap_append_message0 (mu_mailbox_t mailbox, mu_message_t msg)
{
size_t total;
int status = 0;
m_imap_t m_imap = mailbox->data;
f_imap_t f_imap = m_imap->f_imap;
switch (f_imap->state)
{
case IMAP_NO_STATE:
{
size_t lines, size;
const char *path;
char *abuf = malloc (1);
/* Get the desired flags attribute. */
if (abuf == NULL)
return ENOMEM;
*abuf = '\0';
{
mu_attribute_t attribute = NULL;
int flags = 0;
mu_message_get_attribute (msg, &attribute);
mu_attribute_get_flags (attribute, &flags);
status = flags_to_string (&abuf, flags);
if (status != 0)
return status;
/* Put the surrounding parenthesis, wu-IMAP is sensible to this. */
{
char *tmp = calloc (strlen (abuf) + 3, 1);
if (tmp == NULL)
{
free (abuf);
return ENOMEM;
}
sprintf (tmp, "(%s)", abuf);
free (abuf);
abuf = tmp;
}
}
/* Get the mailbox filepath. */
status = mu_url_sget_path (mailbox->url, &path);
if (status == MU_ERR_NOENT)
path = "INBOX";
/* FIXME: we need to get the mu_envelope_date and use it.
currently it is ignored. */
/* Get the total size, assuming that it is in UNIX format. */
lines = size = 0;
mu_message_size (msg, &size);
mu_message_lines (msg, &lines);
total = size + lines;
status = imap_writeline (f_imap, "g%lu APPEND %s %s {%lu}\r\n",
(unsigned long) f_imap->seq++,
path,
abuf,
(unsigned long) (size + lines));
free (abuf);
CHECK_ERROR (f_imap, status);
MU_DEBUG (mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_APPEND;
}
case IMAP_APPEND:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_APPEND_CONT;
case IMAP_APPEND_CONT:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
/* If we did not receive the continuation token, it is an error
bail out. */
if (f_imap->buffer[0] != '+')
{
status = EACCES;
break;
}
f_imap->state = IMAP_APPEND_SEND;
case IMAP_APPEND_SEND:
{
mu_stream_t stream = NULL;
mu_off_t off = 0;
size_t n = 0;
char buffer[255];
mu_message_get_stream (msg, &stream);
while (mu_stream_readline (stream, buffer, sizeof buffer, off, &n) == 0
&& n > 0)
{
if (buffer[n - 1] == '\n')
{
buffer[n - 1] = '\0';
status = imap_writeline (f_imap, "%s\r\n", buffer);
}
else
imap_writeline (f_imap, "%s", buffer);
off += n;
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
}
f_imap->state = IMAP_APPEND_ACK;
}
/* !@#%$ UW-IMAP and Gimap server hack: both insist on the last line. */
imap_writeline (f_imap, "\r\n");
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
case IMAP_APPEND_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
default:
/* mu_error ("imap_append: unknown state\n"); */
break;
}
f_imap->state = IMAP_NO_STATE;
return status;
}
/* If the message is on the same server. Use the COPY command much more
efficient. */
static int
imap_copy_message (mu_mailbox_t mailbox, mu_message_t msg)
{
m_imap_t m_imap = mailbox->data;
f_imap_t f_imap = m_imap->f_imap;
msg_imap_t msg_imap = mu_message_get_owner (msg);
int status = 0;
/* FIXME: It is debatable if we should reconnect when the connection
timeout or die. For timeout client should ping i.e. send
a NOOP via imap_is_updated() function to keep the connection alive. */
status = mu_folder_open (mailbox->folder, mailbox->flags);
if (status != 0)
return status;
switch (f_imap->state)
{
case IMAP_NO_STATE:
{
const char *path;
/* Check for a valid mailbox name. */
status = mu_url_sget_path (mailbox->url, &path);
if (status == 0)
status = imap_writeline (f_imap, "g%lu COPY %lu %s\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num,
path);
CHECK_ERROR (f_imap, status);
MU_DEBUG (mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_COPY;
}
case IMAP_COPY:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_COPY_ACK;
case IMAP_COPY_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
MU_DEBUG (mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
f_imap->state = IMAP_NO_STATE;
return status;
}
/* Message read overload */
static int
imap_message_read (mu_stream_t stream, char *buffer, size_t buflen,
mu_off_t offset, size_t *plen)
{
mu_message_t msg = mu_stream_get_owner (stream);
msg_imap_t msg_imap = mu_message_get_owner (msg);
m_imap_t m_imap = msg_imap->m_imap;
f_imap_t f_imap = m_imap->f_imap;
char *oldbuf = NULL;
char newbuf[2];
int status;
/* This is so annoying, a buffer len of 1 is a killer. If you have for
example "\n" to retrieve from the server, IMAP will transform this to
"\r\n" and since you ask for only 1, the server will send '\r' only.
And ... '\r' will be stripped by (imap_readline()) the number of char
read will be 0 which means we're done .... sigh ... So we guard by at
least ask for 2 chars. */
if (buflen == 1)
{
oldbuf = buffer;
buffer = newbuf;
buflen = 2;
}
/* Start over. */
if (offset == 0)
msg_imap->mu_message_lines = 0;
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
/* Select first. */
if (f_imap->state == IMAP_NO_STATE)
{
char *section = NULL;
if (msg_imap->part)
section = section_name (msg_imap);
/* We have strip the \r, but the offset on the imap server is with that
octet(CFLF) so add it in the offset, it's the number of lines. */
status = imap_writeline (f_imap,
"g%lu FETCH %lu BODY.PEEK[%s]<%lu.%lu>\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num,
(section) ? section : "",
(unsigned long) (offset +
msg_imap->mu_message_lines),
(unsigned long) buflen);
if (section)
free (section);
CHECK_ERROR (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_FETCH;
}
status = fetch_operation (f_imap, msg_imap, buffer, buflen, plen);
if (oldbuf)
oldbuf[0] = buffer[0];
return status;
}
static int
imap_message_lines (mu_message_t msg, size_t *plines)
{
msg_imap_t msg_imap = mu_message_get_owner (msg);
if (plines && msg_imap)
{
if (msg_imap->mu_message_lines == 0)
*plines = msg_imap->body_lines + msg_imap->header_lines;
else
*plines = msg_imap->mu_message_lines;
}
return 0;
}
/* Sometimes a message is just a place container for other sub parts.
In those cases imap bodystructure does not set the mu_message_size aka
the mu_body_size. But we can calculate it since the mu_message_size
is the sum of its subparts. */
static int
imap_submessage_size (msg_imap_t msg_imap, size_t *psize)
{
if (psize)
{
*psize = 0;
if (msg_imap->mu_message_size == 0)
{
size_t i, size;
for (size = i = 0; i < msg_imap->num_parts; i++, size = 0)
{
if (msg_imap->parts[i])
imap_submessage_size (msg_imap->parts[i], &size);
*psize += size;
}
}
else
*psize = (msg_imap->mu_message_size + msg_imap->header_size)
- msg_imap->mu_message_lines;
}
return 0;
}
static int
imap_message_size (mu_message_t msg, size_t *psize)
{
msg_imap_t msg_imap = mu_message_get_owner (msg);
m_imap_t m_imap = msg_imap->m_imap;
f_imap_t f_imap = m_imap->f_imap;
int status = 0;;
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
/* If there is a parent it means it is a sub message, IMAP does not give
the full size of mime messages, so the mu_message_size retrieved from
doing a bodystructure represents rather the mu_body_size. */
if (msg_imap->parent)
return imap_submessage_size (msg_imap, psize);
if (msg_imap->mu_message_size == 0)
{
/* Select first. */
if (f_imap->state == IMAP_NO_STATE)
{
/* We strip the \r, but the offset/size on the imap server is with
that octet so add it in the offset, since it's the number of
lines. */
status = imap_writeline (f_imap,
"g%lu FETCH %lu RFC822.SIZE\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num);
CHECK_ERROR (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_FETCH;
}
status = fetch_operation (f_imap, msg_imap, 0, 0, 0);
}
if (status == 0)
{
if (psize)
*psize = msg_imap->mu_message_size - msg_imap->mu_message_lines;
}
return status;
}
static int
imap_message_uid (mu_message_t msg, size_t *puid)
{
msg_imap_t msg_imap = mu_message_get_owner (msg);
m_imap_t m_imap = msg_imap->m_imap;
f_imap_t f_imap = m_imap->f_imap;
int status;
if (puid)
return 0;
/* Select first. */
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
if (f_imap->state == IMAP_NO_STATE)
{
if (msg_imap->uid)
{
*puid = msg_imap->uid;
return 0;
}
status = imap_writeline (f_imap, "g%lu FETCH %lu UID\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num);
CHECK_ERROR (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_FETCH;
}
status = fetch_operation (f_imap, msg_imap, 0, 0, 0);
if (status != 0)
return status;
*puid = msg_imap->uid;
return 0;
}
static int
imap_message_get_transport2 (mu_stream_t stream, mu_transport_t *pin, mu_transport_t *pout)
{
mu_message_t msg = mu_stream_get_owner (stream);
msg_imap_t msg_imap = mu_message_get_owner (msg);
return imap_get_transport2 (msg_imap, pin, pout);
}
/* Mime. */
static int
imap_is_multipart (mu_message_t msg, int *ismulti)
{
msg_imap_t msg_imap = mu_message_get_owner (msg);
m_imap_t m_imap = msg_imap->m_imap;
f_imap_t f_imap = m_imap->f_imap;
int status;
/* Select first. */
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
if (f_imap->state == IMAP_NO_STATE)
{
if (msg_imap->num_parts || msg_imap->part)
{
if (ismulti)
*ismulti = (msg_imap->num_parts > 1);
return 0;
}
status = imap_writeline (f_imap,
"g%lu FETCH %lu BODYSTRUCTURE\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num);
CHECK_ERROR (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_FETCH;
}
status = fetch_operation (f_imap, msg_imap, 0, 0, 0);
if (status != 0)
return status;
if (ismulti)
*ismulti = (msg_imap->num_parts > 1);
return 0;
}
static int
imap_get_num_parts (mu_message_t msg, size_t *nparts)
{
msg_imap_t msg_imap = mu_message_get_owner (msg);
if (msg_imap)
{
if (msg_imap->num_parts == 0)
{
int status = imap_is_multipart (msg, NULL);
if (status != 0)
return status;
}
if (nparts)
*nparts = (msg_imap->num_parts == 0) ? 1 : msg_imap->num_parts;
}
return 0;
}
static int
imap_get_part (mu_message_t msg, size_t partno, mu_message_t *pmsg)
{
msg_imap_t msg_imap = mu_message_get_owner (msg);
int status = 0;
if (msg_imap->num_parts == 0)
{
status = imap_get_num_parts (msg, NULL);
if (status != 0)
return status;
}
if (partno <= msg_imap->num_parts)
{
if (msg_imap->parts[partno - 1]->message)
{
if (pmsg)
*pmsg = msg_imap->parts[partno - 1]->message;
}
else
{
mu_message_t message;
status = imap_get_message0 (msg_imap->parts[partno - 1], &message);
if (status == 0)
{
mu_header_t header;
mu_message_get_header (message, &header);
mu_message_set_stream (message, NULL, msg_imap->parts[partno - 1]);
/* mu_message_set_size (message, NULL, msg_imap->parts[partno - 1]); */
msg_imap->parts[partno - 1]->message = message;
if (pmsg)
*pmsg = message;
}
}
}
else
{
if (pmsg)
*pmsg = msg_imap->message;
}
return status;
}
/* Envelope. */
static int
imap_envelope_sender (mu_envelope_t envelope, char *buffer, size_t buflen,
size_t *plen)
{
mu_message_t msg = mu_envelope_get_owner (envelope);
mu_header_t header;
const char *sender;
int status;
mu_message_get_header (msg, &header);
status = mu_header_sget_value (header, MU_HEADER_SENDER, &sender);
if (status == EAGAIN)
return status;
else if (status != 0)
status = mu_header_sget_value (header, MU_HEADER_FROM, &sender);
if (status == 0)
{
const char *email = NULL;
size_t len;
mu_address_t address;
if (mu_address_create (&address, sender) == 0)
{
if (mu_address_sget_email (address, 1, &email) == 0)
len = mu_cpystr (buffer, email, buflen);
mu_address_destroy (&address);
}
if (!email)
return MU_ERR_NOENT;
if (plen)
*plen = len;
}
return status;
}
static int
imap_envelope_date (mu_envelope_t envelope, char *buffer, size_t buflen,
size_t *plen)
{
mu_message_t msg = mu_envelope_get_owner (envelope);
msg_imap_t msg_imap = mu_message_get_owner (msg);
m_imap_t m_imap = msg_imap->m_imap;
f_imap_t f_imap = m_imap->f_imap;
struct tm tm;
mu_timezone tz;
time_t now;
char datebuf[] = "mm-dd-yyyy hh:mm:ss +0000";
const char* date = datebuf;
const char** datep = &date;
/* reserve as much space as we need for internal-date */
int status;
/* Select first. */
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
if (msg_imap->internal_date == NULL)
{
if (f_imap->state == IMAP_NO_STATE)
{
status = imap_writeline (f_imap,
"g%lu FETCH %lu INTERNALDATE\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num);
CHECK_ERROR (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_FETCH;
}
status = fetch_operation (f_imap, msg_imap, datebuf,
sizeof datebuf, NULL);
if (status != 0)
return status;
msg_imap->internal_date = strdup (datebuf);
}
else
{
date = msg_imap->internal_date;
datep = &date;
}
if (mu_parse_imap_date_time(datep, &tm, &tz) != 0)
now = (time_t)-1;
else
now = mu_datetime_to_utc (&tm, &tz);
/* if the time was unparseable, or mktime() didn't like what we
parsed, use the calendar time. */
if (now == (time_t)-1)
{
time (&now);
tm = *gmtime (&now);
}
{
char tmpbuf[MU_ENVELOPE_DATE_LENGTH+1];
size_t n = mu_strftime (tmpbuf, sizeof tmpbuf,
MU_ENVELOPE_DATE_FORMAT, &tm);
n = mu_cpystr (buffer, tmpbuf, buflen);
if (plen)
*plen = n;
}
return 0;
}
/* Attributes. */
static int
imap_attr_get_flags (mu_attribute_t attribute, int *pflags)
{
mu_message_t msg = mu_attribute_get_owner (attribute);
msg_imap_t msg_imap = mu_message_get_owner (msg);
m_imap_t m_imap = msg_imap->m_imap;
f_imap_t f_imap = m_imap->f_imap;
int status = 0;
/* Select first. */
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
/* Did we retrieve it alread ? */
if (msg_imap->flags != 0)
{
if (pflags)
*pflags = msg_imap->flags;
return 0;
}
if (f_imap->state == IMAP_NO_STATE)
{
status = imap_writeline (f_imap, "g%lu FETCH %lu FLAGS\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num);
CHECK_ERROR (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_FETCH;
}
status = fetch_operation (f_imap, msg_imap, NULL, 0, NULL);
if (status == 0)
{
if (pflags)
*pflags = msg_imap->flags;
}
return status;
}
static int
imap_attr_set_flags (mu_attribute_t attribute, int flag)
{
mu_message_t msg = mu_attribute_get_owner (attribute);
msg_imap_t msg_imap = mu_message_get_owner (msg);
m_imap_t m_imap = msg_imap->m_imap;
f_imap_t f_imap = m_imap->f_imap;
int status = 0;
/* Select first. */
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
/* If already set don't bother. */
if (msg_imap->flags & flag)
return 0;
/* The delete FLAG is not pass yet but only on the expunge. */
if (flag & MU_ATTRIBUTE_DELETED)
{
msg_imap->flags |= MU_ATTRIBUTE_DELETED;
flag &= ~MU_ATTRIBUTE_DELETED;
}
if (f_imap->state == IMAP_NO_STATE)
{
char *abuf = malloc (1);
if (abuf == NULL)
return ENOMEM;
*abuf = '\0';
status = flags_to_string (&abuf, flag);
if (status != 0)
return status;
/* No flags to send?? */
if (*abuf == '\0')
{
free (abuf);
return 0;
}
status = imap_writeline (f_imap, "g%lu STORE %lu +FLAGS.SILENT (%s)\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num,
abuf);
free (abuf);
CHECK_ERROR (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
msg_imap->flags |= flag;
f_imap->state = IMAP_FETCH;
}
return fetch_operation (f_imap, msg_imap, NULL, 0, NULL);
}
static int
imap_attr_unset_flags (mu_attribute_t attribute, int flag)
{
mu_message_t msg = mu_attribute_get_owner (attribute);
msg_imap_t msg_imap = mu_message_get_owner (msg);
m_imap_t m_imap = msg_imap->m_imap;
f_imap_t f_imap = m_imap->f_imap;
int status = 0;
/* Select first. */
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
/* The delete FLAG is not pass yet but only on the expunge. */
if (flag & MU_ATTRIBUTE_DELETED)
{
msg_imap->flags &= ~MU_ATTRIBUTE_DELETED;
flag &= ~MU_ATTRIBUTE_DELETED;
}
if (f_imap->state == IMAP_NO_STATE)
{
char *abuf = malloc (1);
if (abuf == NULL)
return ENOMEM;
*abuf = '\0';
status = flags_to_string (&abuf, flag);
if (status != 0)
return status;
/* No flags to send?? */
if (*abuf == '\0')
{
free (abuf);
return 0;
}
status = imap_writeline (f_imap, "g%lu STORE %lu -FLAGS.SILENT (%s)\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num,
abuf);
free (abuf);
CHECK_ERROR (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
msg_imap->flags &= ~flag;
f_imap->state = IMAP_FETCH;
}
return fetch_operation (f_imap, msg_imap, NULL, 0, NULL);
}
/* Header. */
static int
imap_header_read (mu_header_t header, char *buffer,
size_t buflen, mu_off_t offset,
size_t *plen)
{
mu_message_t msg = mu_header_get_owner (header);
msg_imap_t msg_imap = mu_message_get_owner (msg);
m_imap_t m_imap = msg_imap->m_imap;
f_imap_t f_imap = m_imap->f_imap;
char *oldbuf = NULL;
char newbuf[2];
int status;
/* This is so annoying, a buffer len of 1 is a killer. If you have for
example "\n" to retrieve from the server, IMAP will transform this to
"\r\n" and since you ask for only 1, the server will send '\r' only.
And ... '\r' will be stripped by (imap_readline()) the number of char
read will be 0 which means we're done .... sigh ... So we guard by at
least ask for 2 chars. */
if (buflen == 1)
{
oldbuf = buffer;
buffer = newbuf;
buflen = 2;
}
/* Start over. */
if (offset == 0)
msg_imap->header_lines = 0;
/* Select first. */
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
if (f_imap->state == IMAP_NO_STATE)
{
/* We strip the \r, but the offset/size on the imap server is with that
octet so add it in the offset, since it's the number of lines. */
if (msg_imap->part)
{
char *section = section_name (msg_imap);
status = imap_writeline (f_imap,
"g%lu FETCH %lu BODY.PEEK[%s.MIME]<%lu.%lu>\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num,
(section) ? section : "",
(unsigned long) (offset +
msg_imap->header_lines),
(unsigned long) buflen);
if (section)
free (section);
}
else
status = imap_writeline (f_imap,
"g%lu FETCH %lu BODY.PEEK[HEADER]<%lu.%lu>\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num,
(unsigned long) (offset +
msg_imap->header_lines),
(unsigned long) buflen);
CHECK_ERROR (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_FETCH;
}
status = fetch_operation (f_imap, msg_imap, buffer, buflen, plen);
if (oldbuf)
oldbuf[0] = buffer[0];
return status;
}
/* Body. */
static int
imap_body_size (mu_body_t body, size_t *psize)
{
mu_message_t msg = mu_body_get_owner (body);
msg_imap_t msg_imap = mu_message_get_owner (msg);
if (psize && msg_imap)
{
/* If there is a parent it means it is a sub message, IMAP does not give
the full size of mime messages, so the mu_message_size was retrieve from
doing a bodystructure and represents rather the mu_body_size. */
if (msg_imap->parent)
{
*psize = msg_imap->mu_message_size - msg_imap->mu_message_lines;
}
else
{
if (msg_imap->body_size)
*psize = msg_imap->body_size;
else if (msg_imap->mu_message_size)
*psize = msg_imap->mu_message_size
- (msg_imap->header_size + msg_imap->header_lines);
else
*psize = 0;
}
}
return 0;
}
static int
imap_body_lines (mu_body_t body, size_t *plines)
{
mu_message_t msg = mu_body_get_owner (body);
msg_imap_t msg_imap = mu_message_get_owner (msg);
if (plines && msg_imap)
*plines = msg_imap->body_lines;
return 0;
}
/* FIXME: Send EISPIPE if trying to seek back. */
static int
imap_body_read (mu_stream_t stream, char *buffer, size_t buflen,
mu_off_t offset, size_t *plen)
{
mu_body_t body = mu_stream_get_owner (stream);
mu_message_t msg = mu_body_get_owner (body);
msg_imap_t msg_imap = mu_message_get_owner (msg);
m_imap_t m_imap = msg_imap->m_imap;
f_imap_t f_imap = m_imap->f_imap;
char *oldbuf = NULL;
char newbuf[2];
int status;
/* This is so annoying, a buffer len of 1 is a killer. If you have for
example "\n" to retrieve from the server, IMAP will transform this to
"\r\n" and since you ask for only 1, the server will send '\r' only.
And ... '\r' will be stripped by (imap_readline()) the number of char
read will be 0 which means we're done .... sigh ... So we guard by at
least ask for 2 chars. */
if (buflen == 1)
{
oldbuf = buffer;
buffer = newbuf;
buflen = 2;
}
/* Start over. */
if (offset == 0)
{
msg_imap->body_lines = 0;
msg_imap->body_size = 0;
}
/* Select first. */
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
if (f_imap->state == IMAP_NO_STATE)
{
/* We strip the \r, but the offset/size on the imap server is with the
octet, so add it since it's the number of lines. */
if (msg_imap->part)
{
char *section = section_name (msg_imap);
status = imap_writeline (f_imap,
"g%lu FETCH %lu BODY.PEEK[%s]<%lu.%lu>\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num,
(section) ? section: "",
(unsigned long) (offset +
msg_imap->body_lines),
(unsigned long) buflen);
if (section)
free (section);
}
else
status = imap_writeline (f_imap,
"g%lu FETCH %lu BODY.PEEK[TEXT]<%lu.%lu>\r\n",
(unsigned long) f_imap->seq++,
(unsigned long) msg_imap->num,
(unsigned long) (offset +
msg_imap->body_lines),
(unsigned long) buflen);
CHECK_ERROR (f_imap, status);
MU_DEBUG (m_imap->mailbox->debug, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_FETCH;
}
status = fetch_operation (f_imap, msg_imap, buffer, buflen, plen);
if (oldbuf)
oldbuf[0] = buffer[0];
return status;
}
static int
imap_body_get_transport2 (mu_stream_t stream, mu_transport_t *pin,
mu_transport_t *pout)
{
mu_body_t body = mu_stream_get_owner (stream);
mu_message_t msg = mu_body_get_owner (body);
msg_imap_t msg_imap = mu_message_get_owner (msg);
return imap_get_transport2 (msg_imap, pin, pout);
}
static int
imap_get_transport2 (msg_imap_t msg_imap, mu_transport_t *pin, mu_transport_t *pout)
{
if ( msg_imap
&& msg_imap->m_imap
&& msg_imap->m_imap->f_imap
&& msg_imap->m_imap->f_imap->folder)
return mu_stream_get_transport2 (msg_imap->m_imap->f_imap->folder->stream,
pin, pout);
return EINVAL;
}
/* Since so many operations are fetch, we regoup this into one function. */
static int
fetch_operation (f_imap_t f_imap, msg_imap_t msg_imap, char *buffer,
size_t buflen, size_t *plen)
{
int status = 0;
switch (f_imap->state)
{
case IMAP_FETCH:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
mu_stream_truncate (f_imap->string.stream, 0);
f_imap->string.offset = 0;
f_imap->string.nleft = 0;
f_imap->string.type = IMAP_NO_STATE;
f_imap->string.msg_imap = msg_imap;
f_imap->state = IMAP_FETCH_ACK;
case IMAP_FETCH_ACK:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
if (f_imap->selected)
MU_DEBUG (f_imap->selected->mailbox->debug, MU_DEBUG_PROT,
f_imap->buffer);
default:
break;
}
f_imap->state = IMAP_NO_STATE;
/* The server may have timeout any case connection is gone away. */
if (status == 0 && f_imap->isopen == 0 && f_imap->string.offset == 0)
status = MU_ERR_CONN_CLOSED;
if (buffer)
mu_stream_read (f_imap->string.stream, buffer, buflen, 0, plen);
else if (plen)
*plen = 0;
mu_stream_truncate (f_imap->string.stream, 0);
f_imap->string.offset = 0;
f_imap->string.nleft = 0;
f_imap->string.type = IMAP_NO_STATE;
f_imap->string.msg_imap = NULL;
return status;
}
/* Decide whether the message came from the same folder as the mailbox. */
static int
is_same_folder (mu_mailbox_t mailbox, mu_message_t msg)
{
mu_mailbox_t mbox = NULL;
mu_message_get_mailbox (msg, &mbox);
return (mbox != NULL && mbox->url != NULL
&& mu_url_is_same_scheme (mbox->url, mailbox->url)
&& mu_url_is_same_host (mbox->url, mailbox->url)
&& mu_url_is_same_port (mbox->url, mailbox->url));
}
/* Convert flag attribute to IMAP String attributes. */
static int
flags_to_string (char **pbuf, int flag)
{
char *abuf = *pbuf;
if (flag & MU_ATTRIBUTE_DELETED)
{
char *tmp = realloc (abuf, strlen (abuf) + strlen ("\\Deleted") + 2);
if (tmp == NULL)
{
free (abuf);
return ENOMEM;
}
abuf = tmp;
if (*abuf)
strcat (abuf, " ");
strcat (abuf, "\\Deleted");
}
if (flag & MU_ATTRIBUTE_READ)
{
char *tmp = realloc (abuf, strlen (abuf) + strlen ("\\Seen") + 2);
if (tmp == NULL)
{
free (abuf);
return ENOMEM;
}
abuf = tmp;
if (*abuf)
strcat (abuf, " ");
strcat (abuf, "\\Seen");
}
if (flag & MU_ATTRIBUTE_ANSWERED)
{
char *tmp = realloc (abuf, strlen (abuf) + strlen ("\\Answered") + 2);
if (tmp == NULL)
{
free (abuf);
return ENOMEM;
}
abuf = tmp;
if (*abuf)
strcat (abuf, " ");
strcat (abuf, "\\Answered");
}
if (flag & MU_ATTRIBUTE_DRAFT)
{
char *tmp = realloc (abuf, strlen (abuf) + strlen ("\\Draft") + 2);
if (tmp == NULL)
{
free (abuf);
return ENOMEM;
}
abuf = tmp;
if (*abuf)
strcat (abuf, " ");
strcat (abuf, "\\Draft");
}
if (flag & MU_ATTRIBUTE_FLAGGED)
{
char *tmp = realloc (abuf, strlen (abuf) + strlen ("\\Flagged") + 2);
if (tmp == NULL)
{
free (abuf);
return ENOMEM;
}
abuf = tmp;
if (*abuf)
strcat (abuf, " ");
strcat (abuf, "\\Flagged");
}
*pbuf = abuf;
return 0;
}
/* Convert a suite of number to IMAP message number. */
static int
add_number (char **pset, size_t start, size_t end)
{
char *buf = NULL;
char *set;
char *tmp;
size_t set_len = 0;
if (pset == NULL)
return 0;
set = *pset;
if (set)
set_len = strlen (set);
/* We had a previous seqence. */
if (start == 0)
/* nothing */;
else if (start != end)
mu_asprintf (&buf, "%lu:%lu",
(unsigned long) start, (unsigned long) end);
else
mu_asprintf (&buf, "%lu", (unsigned long) start);
if (set_len)
tmp = realloc (set, set_len + strlen (buf) + 2 /* null and comma */);
else
tmp = calloc (strlen (buf) + 1, 1);
if (tmp == NULL)
{
free (set);
free (buf);
return ENOMEM;
}
set = tmp;
/* If we had something add a comma separator. */
if (set_len)
strcat (set, ",");
strcat (set, buf);
free (buf);
*pset = set;
return 0;
}
static int
delete_to_string (m_imap_t m_imap, char **pset)
{
int status;
size_t i, prev = 0, is_range = 0;
size_t start = 0, cur = 0;
char *set = NULL;
/* Reformat the number for IMAP. */
for (i = 0; i < m_imap->imessages_count; ++i)
{
if (m_imap->imessages[i]
&& (m_imap->imessages[i]->flags & MU_ATTRIBUTE_DELETED))
{
cur = m_imap->imessages[i]->num;
/* The first number. */
if (start == 0)
{
start = prev = cur;
}
/* Is it a sequence? */
else if ((prev + 1) == cur)
{
prev = cur;
is_range = 1;
}
continue;
}
if (start)
{
status = add_number (&set, start, cur);
if (status != 0)
return status;
start = 0;
prev = 0;
cur = 0;
is_range = 0;
}
} /* for () */
if (start)
{
status = add_number (&set, start, cur);
if (status != 0)
return status;
}
*pset = set;
return 0;
}
#endif
......@@ -29,6 +29,9 @@ mu_imap_rename (mu_imap_t imap, const char *mailbox, const char *new_mailbox)
char const *argv[3];
static struct imap_command com;
if (!mailbox || !new_mailbox)
return EINVAL;
argv[0] = "RENAME";
argv[1] = mailbox;
argv[2] = new_mailbox;
......
......@@ -57,6 +57,7 @@ _mu_imap_response_list_create (mu_imap_t imap, mu_list_t *plist)
#define IS_LBRACE(p) ((p)[0] == '(')
#define IS_RBRACE(p) ((p)[0] == ')')
#define IS_NIL(p) (strcmp (p, "NIL") == 0)
static struct imap_list_element *
_new_imap_list_element (mu_imap_t imap, enum imap_eltype type)
......@@ -181,6 +182,7 @@ _parse_element (struct parsebuf *pb)
if (IS_RBRACE (tok))
{
parsebuf_gettok (pb);
elt = _new_imap_list_element (pb->pb_imap, imap_eltype_list);
if (!elt)
{
......@@ -203,6 +205,16 @@ _parse_element (struct parsebuf *pb)
parsebuf_seterr (pb, MU_ERR_PARSE);
return NULL;
}
else if (IS_NIL (tok))
{
elt = _new_imap_list_element (pb->pb_imap, imap_eltype_list);
if (!elt)
{
parsebuf_seterr (pb, ENOMEM);
return NULL;
}
elt->v.list = NULL;
}
else
{
char *s;
......@@ -255,6 +267,12 @@ _mu_imap_list_element_is_string (struct imap_list_element *elt,
return strcmp (elt->v.string, str) == 0;
}
int
_mu_imap_list_element_is_nil (struct imap_list_element *elt)
{
return elt->type == imap_eltype_list && mu_list_is_empty (elt->v.list);
}
struct imap_list_element *
_mu_imap_list_at (mu_list_t list, int idx)
{
......
......@@ -19,6 +19,7 @@
# include <config.h>
#endif
#include <errno.h>
#include <mailutils/imap.h>
#include <mailutils/sys/imap.h>
......@@ -28,6 +29,9 @@ mu_imap_subscribe (mu_imap_t imap, const char *mailbox)
char const *argv[2];
static struct imap_command com;
if (!mailbox)
return EINVAL;
argv[0] = "SUBSCRIBE";
argv[1] = mailbox;
......
## This file is part of GNU Mailutils.
## Copyright (C) 2003, 2005, 2006, 2007, 2010, 2011 Free Software
## Foundation, Inc.
##
## GNU Mailutils 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; either version 3, or (at
## your option) any later version.
##
## GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
noinst_PROGRAMS = \
imapfolder
INCLUDES = @MU_LIB_COMMON_INCLUDES@
LDADD = \
@MU_LIB_IMAP@\
@MU_LIB_AUTH@\
@MU_AUTHLIBS@\
@MU_LIB_MAILUTILS@
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2011 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library. If not, see
<http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <mailutils/mailutils.h>
struct command
{
char *verb;
int nargs;
char *args;
void (*handler) (mu_folder_t folder, char **argv);
};
static int
_print_list_entry (void *item, void *data)
{
struct mu_list_response *resp = item;
mu_printf ("%c%c %c %4d %s\n",
(resp->type & MU_FOLDER_ATTRIBUTE_DIRECTORY) ? 'd' : '-',
(resp->type & MU_FOLDER_ATTRIBUTE_FILE) ? 'f' : '-',
resp->separator ? resp->separator : ' ',
resp->level,
resp->name);
return 0;
}
static void
com_list (mu_folder_t folder, char **argv)
{
int rc;
mu_list_t list;
mu_printf ("listing %s %s\n", argv[0], argv[1]);
rc = mu_folder_list (folder, argv[0], argv[1], 0, &list);
if (rc)
mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_list", argv[0], rc);
else
{
mu_list_foreach (list, _print_list_entry, NULL);
mu_list_destroy (&list);
}
}
static void
com_lsub (mu_folder_t folder, char **argv)
{
int rc;
mu_list_t list;
mu_printf ("listing subscriptions for '%s' '%s'\n", argv[0], argv[1]);
rc = mu_folder_lsub (folder, argv[0], argv[1], &list);
if (rc)
mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_lsub", argv[0], rc);
else
{
mu_list_foreach (list, _print_list_entry, NULL);
mu_list_destroy (&list);
}
}
static void
com_delete (mu_folder_t folder, char **argv)
{
int rc;
mu_printf ("deleting %s\n", argv[0]);
rc = mu_folder_delete (folder, argv[0]);
if (rc)
mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_lsub", argv[0], rc);
else
mu_printf ("delete successful\n");
}
static void
com_rename (mu_folder_t folder, char **argv)
{
int rc;
mu_printf ("renaming %s to %s\n", argv[0], argv[1]);
rc = mu_folder_rename (folder, argv[0], argv[1]);
if (rc)
mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_rename", argv[0], rc);
else
mu_printf ("rename successful\n");
}
static void
com_subscribe (mu_folder_t folder, char **argv)
{
int rc;
mu_printf ("subscribing %s\n", argv[0]);
rc = mu_folder_subscribe (folder, argv[0]);
if (rc)
mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_subscribe", argv[0], rc);
else
mu_printf ("subscribe successful\n");
}
static void
com_unsubscribe (mu_folder_t folder, char **argv)
{
int rc;
mu_printf ("unsubscribing %s\n", argv[0]);
rc = mu_folder_unsubscribe (folder, argv[0]);
if (rc)
mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_unsubscribe", argv[0], rc);
else
mu_printf ("unsubscribe successful\n");
}
static struct command comtab[] = {
{ "list", 2, "REF MBX", com_list },
{ "lsub", 2, "REF MBX", com_lsub },
{ "delete", 1, "MBX", com_delete },
{ "rename", 2, "OLD NEW", com_rename },
{ "delete", 1, "MBOX", com_delete },
{ "subscribe", 1, "MBX", com_subscribe },
{ "unsubscribe", 1, "MBX", com_unsubscribe },
{ NULL }
};
static struct command *
find_command (const char *name)
{
struct command *cp;
for (cp = comtab; cp->verb; cp++)
if (strcmp (cp->verb, name) == 0)
return cp;
return NULL;
}
static void
usage ()
{
struct command *cp;
mu_printf (
"usage: %s [debug=SPEC] url=URL OP ARG [ARG...] [OP ARG [ARG...]...]\n",
mu_program_name);
mu_printf ("OPerations and corresponding ARGuments are:\n");
for (cp = comtab; cp->verb; cp++)
mu_printf (" %s %s\n", cp->verb, cp->args);
}
int
main (int argc, char **argv)
{
int i;
int rc;
mu_folder_t folder;
char *fname = NULL;
mu_set_program_name (argv[0]);
mu_registrar_record (mu_imap_record);
mu_registrar_record (mu_imaps_record);
if (argc == 1)
{
usage ();
exit (0);
}
for (i = 1; i < argc; i++)
{
if (strncmp (argv[i], "debug=", 6) == 0)
mu_debug_parse_spec (argv[i] + 6);
else if (strncmp (argv[i], "url=", 4) == 0)
fname = argv[i] + 4;
else
break;
}
if (!fname)
{
mu_error ("URL not specified");
exit (1);
}
rc = mu_folder_create (&folder, fname);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_create", fname, rc);
return 1;
}
rc = mu_folder_open (folder, MU_STREAM_READ);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_open", fname, rc);
return 1;
}
while (i < argc)
{
char *comargs[2];
struct command *cmd;
cmd = find_command (argv[i]);
if (!cmd)
{
mu_error ("unknown command %s\n", argv[i]);
break;
}
i++;
if (i + cmd->nargs > argc)
{
mu_error ("not enough arguments for %s", cmd->verb);
break;
}
memcpy (comargs, argv + i, cmd->nargs * sizeof (comargs[0]));
i += cmd->nargs;
cmd->handler (folder, comargs);
}
mu_folder_close (folder);
mu_folder_destroy (&folder);
return 0;
}
......@@ -19,6 +19,7 @@
# include <config.h>
#endif
#include <errno.h>
#include <mailutils/imap.h>
#include <mailutils/sys/imap.h>
......@@ -28,6 +29,9 @@ mu_imap_unsubscribe (mu_imap_t imap, const char *mailbox)
char const *argv[2];
static struct imap_command com;
if (!mailbox)
return EINVAL;
argv[0] = "UNSUBSCRIBE";
argv[1] = mailbox;
......
......@@ -20,18 +20,15 @@
# include <config.h>
#endif
#ifdef ENABLE_IMAP
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <string.h>
#include <mailutils/sys/registrar.h>
#include <mailutils/sys/url.h>
#include <mailutils/sys/imap.h>
static void url_imap_destroy (mu_url_t url);
......@@ -47,7 +44,7 @@ url_imap_destroy (mu_url_t url MU_ARG_UNUSED)
*/
int
_url_imap_init (mu_url_t url)
_mu_imap_url_init (mu_url_t url)
{
if (url->port == 0)
url->port = MU_IMAP_PORT;
......@@ -78,7 +75,7 @@ _url_imap_init (mu_url_t url)
*/
int
_url_imaps_init (mu_url_t url)
_mu_imaps_url_init (mu_url_t url)
{
if (url->port == 0)
url->port = MU_IMAPS_PORT;
......@@ -102,4 +99,3 @@ _url_imaps_init (mu_url_t url)
return 0;
}
#endif /* ENABLE_IMAP */
......
......@@ -160,7 +160,7 @@ imap_prompt_env ()
/* Callbacks */
static void
imap_popauth_callback (void *data, int code, size_t sdat, void *pdat)
imap_preauth_callback (void *data, int code, size_t sdat, void *pdat)
{
const char *text = pdat;
if (text)
......@@ -425,7 +425,7 @@ com_connect (int argc, char **argv)
/* Set callbacks */
mu_imap_register_callback_function (imap, MU_IMAP_CB_PREAUTH,
imap_popauth_callback,
imap_preauth_callback,
NULL);
mu_imap_register_callback_function (imap, MU_IMAP_CB_BYE,
imap_bye_callback,
......