Commit 11bc148b 11bc148b1877b7c692f28d90847bc74737068416 by Sergey Poznyakoff

'Moved from ../'

1 parent 887e7b3b
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef ENABLE_IMAP
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <fnmatch.h>
#ifdef HAVE_ALLOCA_H
# include <alloca.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <imap0.h>
#include <url0.h>
#include <mailutils/auth.h>
#include <mailutils/attribute.h>
#include <mailutils/debug.h>
#include <mailutils/error.h>
#include <mailutils/header.h>
#include <mailutils/observer.h>
#include <mailutils/stream.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
/* Variable use for the registrar. */
static struct _record _imap_record =
{
MU_IMAP_SCHEME,
_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. */
record_t imap_record = &_imap_record;
#ifndef HAVE_STRTOK_R
char *strtok_r __P ((char *, const char *, char **));
#endif
/* Concrete folder_t IMAP implementation. */
static int folder_imap_open __P ((folder_t, int));
static int folder_imap_close __P ((folder_t));
static void folder_imap_destroy __P ((folder_t));
static int folder_imap_delete __P ((folder_t, const char *));
static int folder_imap_list __P ((folder_t, const char *, const char *,
struct folder_list *));
static int folder_imap_lsub __P ((folder_t, const char *, const char *,
struct folder_list *));
static int folder_imap_rename __P ((folder_t, const char *,
const char *));
static int folder_imap_subscribe __P ((folder_t, const char *));
static int folder_imap_unsubscribe __P ((folder_t, const char *));
static int folder_imap_get_authority __P ((folder_t, authority_t *));
static int authenticate_imap_login __P ((authority_t));
static int authenticate_imap_sasl_anon __P ((authority_t));
/* FETCH */
static int imap_fetch __P ((f_imap_t));
static int imap_rfc822 __P ((f_imap_t, char **));
static int imap_rfc822_size __P ((f_imap_t, char **));
static int imap_rfc822_header __P ((f_imap_t, char **));
static int imap_rfc822_text __P ((f_imap_t, char **));
static int imap_fetch_flags __P ((f_imap_t, char **));
static int imap_permanentflags __P ((f_imap_t, char **));
static int imap_flags __P ((char **, int *));
static int imap_bodystructure __P ((f_imap_t, char **));
static int imap_body __P ((f_imap_t, char **));
static int imap_internaldate __P ((f_imap_t, char **));
static int imap_uid __P ((f_imap_t, char **));
static int imap_status __P ((f_imap_t));
static int imap_expunge __P ((f_imap_t, unsigned int));
static int imap_search __P ((f_imap_t));
/* String. */
static int imap_literal_string __P ((f_imap_t, char **));
static int imap_string __P ((f_imap_t, char **));
static int imap_quoted_string __P ((f_imap_t, char **));
static int imap_mailbox_name_match __P ((const char* pattern, const char* mailbox));
static int imap_token __P ((char *, size_t, char **));
/* Initialize the concrete IMAP mailbox: overload the folder functions */
int
_folder_imap_init (folder_t folder)
{
int status;
f_imap_t f_imap;
/* 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;
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 (folder_t folder)
{
if (folder->data)
{
f_imap_t f_imap = folder->data;
if (f_imap->buffer)
free (f_imap->buffer);
if (f_imap->capa)
free (f_imap->capa);
free (f_imap);
folder->data = NULL;
}
}
static int
folder_imap_get_authority (folder_t folder, authority_t *pauth)
{
int status = 0;
if (folder->authority == NULL)
{
/* assert (folder->url); */
if (folder->url == NULL)
return EINVAL;
if (folder->url->auth == NULL
|| strcasecmp (folder->url->auth, "*") == 0)
{
status = authority_create (&folder->authority, NULL, folder);
authority_set_authenticate (folder->authority,
authenticate_imap_login, folder);
}
else if (strcasecmp (folder->url->auth, "anon") == 0)
{
status = authority_create (&folder->authority, NULL, folder);
authority_set_authenticate (folder->authority,
authenticate_imap_sasl_anon, folder);
}
else
{
/* Not a supported authentication mechanism. */
status = ENOSYS;
}
}
if (pauth)
*pauth = folder->authority;
return status;
}
/* Simple User/pass authentication for imap. */
static int
authenticate_imap_login (authority_t auth)
{
folder_t folder = authority_get_owner (auth);
f_imap_t f_imap = folder->data;
ticket_t ticket;
int status = 0;
switch (f_imap->state)
{
case IMAP_AUTH:
{
/* Grab the User and Passwd information. */
size_t n = 0;
authority_get_ticket (auth, &ticket);
if (f_imap->user)
free (f_imap->user);
if (f_imap->passwd)
free (f_imap->passwd);
/* Was it in the URL? */
status = url_get_user (folder->url, NULL, 0, &n);
if (status != 0 || n == 0)
ticket_pop (ticket, folder->url, "Imap User: ", &f_imap->user);
else
{
f_imap->user = calloc (1, n + 1);
url_get_user (folder->url, f_imap->user, n + 1, NULL);
}
/* Was it in the URL? */
status = url_get_passwd (folder->url, NULL, 0, &n);
if (status != 0 || n == 0)
ticket_pop (ticket, folder->url, "Imap Passwd: ", &f_imap->passwd);
else
{
f_imap->passwd = calloc (1, n + 1);
url_get_passwd (folder->url, f_imap->passwd, n + 1, NULL);
}
if (f_imap->user == NULL || f_imap->passwd == NULL)
{
CHECK_ERROR_CLOSE (folder, f_imap, EINVAL);
}
status = imap_writeline (f_imap, "g%u LOGIN %s %s\r\n",
f_imap->seq, f_imap->user, f_imap->passwd);
CHECK_ERROR_CLOSE(folder, f_imap, status);
FOLDER_DEBUG2 (folder, MU_DEBUG_PROT, "g%u LOGIN %s *\n",
f_imap->seq, f_imap->user);
f_imap->seq++;
free (f_imap->user);
f_imap->user = NULL;
/* We have to nuke the passwd. */
memset (f_imap->passwd, '\0', strlen (f_imap->passwd));
free (f_imap->passwd);
f_imap->passwd = NULL;
f_imap->state = IMAP_LOGIN;
}
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);
CHECK_EAGAIN (f_imap, status);
FOLDER_DEBUG0 (folder, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_AUTH_DONE;
default:
break; /* We're outta here. */
}
CLEAR_STATE (f_imap);
return 0;
}
/*
The anonymous SASL mechanism is defined in rfc2245.txt as a single
message from client to server:
message = [email / token]
So the message is optional.
The command is:
C: <tag> authenticate anonymous
The server responds with a request for continuation data (the "message"
in the SASL syntax). We respond with no data, which is legal.
S: +
C:
The server should then respond with OK on success, or else a failure
code (NO or BAD).
If OK, then we are authenticated!
So, states are:
AUTH_ANON_REQ
> g%u AUTHENTICATE ANONYMOUS
AUTH_ANON_WAIT_CONT
< +
AUTH_ANON_MSG
>
AUTH_ANON_WAIT_RESP
< NO/BAD/OK
*/
static int
authenticate_imap_sasl_anon (authority_t auth)
{
folder_t folder = authority_get_owner (auth);
f_imap_t f_imap = folder->data;
int status = 0;
assert (f_imap->state == IMAP_AUTH);
switch (f_imap->auth_state)
{
case IMAP_AUTH_ANON_REQ_WRITE:
{
FOLDER_DEBUG1 (folder, MU_DEBUG_PROT, "g%u AUTHENTICATE ANONYMOUS\n",
f_imap->seq);
status = imap_writeline (f_imap, "g%u AUTHENTICATE ANONYMOUS\r\n",
f_imap->seq);
f_imap->seq++;
CHECK_ERROR_CLOSE (folder, f_imap, status);
f_imap->state = IMAP_AUTH_ANON_REQ_SEND;
}
case IMAP_AUTH_ANON_REQ_SEND:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_AUTH_ANON_WAIT_CONT;
case IMAP_AUTH_ANON_WAIT_CONT:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
FOLDER_DEBUG0 (folder, MU_DEBUG_PROT, f_imap->buffer);
if (strncmp ("+", f_imap->buffer, 2) == 0)
{
f_imap->state = IMAP_AUTH_ANON_MSG;
}
else
{
/* Something is wrong! */
}
f_imap->state = IMAP_AUTH_ANON_MSG;
case IMAP_AUTH_ANON_MSG:
FOLDER_DEBUG0 (folder, MU_DEBUG_PROT, "\n");
status = imap_writeline (f_imap, "\r\n");
CHECK_ERROR_CLOSE (folder, f_imap, status);
f_imap->state = IMAP_AUTH_ANON_MSG_SEND;
case IMAP_AUTH_ANON_MSG_SEND:
status = imap_send (f_imap);
CHECK_EAGAIN (f_imap, status);
f_imap->state = IMAP_AUTH_ANON_WAIT_RESP;
case IMAP_AUTH_ANON_WAIT_RESP:
status = imap_parse (f_imap);
CHECK_EAGAIN (f_imap, status);
FOLDER_DEBUG0 (folder, MU_DEBUG_PROT, f_imap->buffer);
default:
break; /* We're outta here. */
}
CLEAR_STATE (f_imap);
return 0;
}
/* Create/Open the stream for IMAP. */
static int
folder_imap_open (folder_t folder, int flags)
{
f_imap_t f_imap = folder->data;
char *host;
long port = 143; /* default imap port. */
int status = 0;
size_t len = 0;
/* If we are already open for business, noop. */
monitor_wrlock (folder->monitor);
if (f_imap->isopen)
{
monitor_unlock (folder->monitor);
return 0;
}
monitor_unlock (folder->monitor);
/* Fetch the server name and the port in the url_t. */
status = url_get_host (folder->url, NULL, 0, &len);
if (status != 0)
return status;
host = alloca (len + 1);
url_get_host (folder->url, host, len + 1, NULL);
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 = memory_stream_create (&f_imap->string.stream, NULL, MU_STREAM_RDWR);
CHECK_ERROR (f_imap, status);
stream_open (f_imap->string.stream);
}
else
{
/* Clear from any residue. */
memset (f_imap->buffer, '\0', f_imap->buflen);
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 = tcp_stream_create (&folder->stream, host, port, folder->flags);
CHECK_ERROR (f_imap, status);
/* Ask for the stream internal buffering mechanism scheme. */
stream_setbufsiz (folder->stream, BUFSIZ);
}
else
stream_close (folder->stream);
FOLDER_DEBUG2 (folder, MU_DEBUG_PROT, "imap_open (%s:%d)\n", host, port);
f_imap->state = IMAP_OPEN_CONNECTION;
case IMAP_OPEN_CONNECTION:
/* Establish the connection. */
status = 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;
FOLDER_DEBUG0 (folder, MU_DEBUG_PROT, f_imap->buffer);
/* Are they open for business ? The server send an untag response
for greeting. Thenically it can be OK/PREAUTH/BYE. The BYE is
the one that we do not want, server being unfriendly. */
if (strncasecmp (f_imap->buffer, "* PREAUTH", 9) == 0)
{
f_imap->state = IMAP_AUTH_DONE;
}
else
{
if (strncasecmp (f_imap->buffer, "* OK", 4) != 0)
CHECK_ERROR_CLOSE (folder, f_imap, EACCES);
f_imap->state = IMAP_AUTH;
}
}
case IMAP_AUTH:
case IMAP_LOGIN:
case IMAP_LOGIN_ACK:
assert (folder->authority);
{
status = authority_authenticate (folder->authority);
CHECK_EAGAIN (f_imap, status);
}
case IMAP_AUTH_DONE:
default:
break;
}
f_imap->state = IMAP_NO_STATE;
monitor_wrlock (folder->monitor);
f_imap->isopen++;
monitor_unlock (folder->monitor);
return 0;
}
/* Shutdown the connection. */
static int
folder_imap_close (folder_t folder)
{
f_imap_t f_imap = folder->data;
int status = 0;
monitor_wrlock (folder->monitor);
f_imap->isopen--;
if (f_imap->isopen)
{
monitor_unlock (folder->monitor);
return 0;
}
monitor_unlock (folder->monitor);
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%u LOGOUT\r\n", f_imap->seq++);
CHECK_ERROR (f_imap, status);
FOLDER_DEBUG0 (folder, 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);
FOLDER_DEBUG0 (folder, MU_DEBUG_PROT, f_imap->buffer);
/* This is done when we received the BYE in the parser code. */
/* 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 (folder_t folder, const char *name)
{
f_imap_t f_imap = folder->data;
int status = 0;
if (name == NULL)
return EINVAL;
status = folder_open (folder, folder->flags);
if (status != 0)
return status;
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%u DELETE %s\r\n", f_imap->seq++,
name);
CHECK_ERROR (f_imap, status);
FOLDER_DEBUG0 (folder, 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);
FOLDER_DEBUG0 (folder, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
f_imap->state = IMAP_NO_STATE;
return status;
}
/* Since mailutils API does not offer recursive listing. There is no need
to follow IMAP "bizarre" recursive rules. The use of '%' is sufficient. So
the approach is everywhere there is a regex in the path we change that
branch for '%' and do the matching ourself with fnmatch(). */
static int
folder_imap_list (folder_t folder, const char *ref, const char *name,
struct folder_list *pflist)
{
f_imap_t f_imap = folder->data;
int status = 0;
char *path = NULL;
/* NOOP. */
if (pflist == NULL)
return EINVAL;
status = folder_open (folder, folder->flags);
if (status != 0)
return status;
if (ref == NULL)
ref = "";
if (name == NULL)
name = "";
path = strdup ("");
if (path == NULL)
return ENOMEM;
/* We break the string to pieces and change the occurences of "*?[" for
the imap magic "%" for expansion. Then reassemble the string:
"/home/?/Mail/a*lain*" --> "/usr/%/Mail/%". */
{
int done = 0;
size_t i;
char **node = NULL;
size_t nodelen = 0;
const char *p = name;
/* Disassemble. */
while (!done && *p)
{
char **n;
n = realloc (node, (nodelen + 1) * sizeof (*node));
if (n == NULL)
break;
node = n;
if (*p == '/')
{
node[nodelen] = strdup ("/");
p++;
}
else
{
const char *s = strchr (p, '/');
if (s)
{
node[nodelen] = calloc (s - p + 1, 1);
if (node[nodelen])
memcpy (node[nodelen], p, s - p);
p = s;
}
else
{
node[nodelen] = strdup (p);
done = 1;
}
if (node[nodelen] && strpbrk (node[nodelen], "*?["))
{
free (node[nodelen]);
node[nodelen] = strdup ("%");
}
}
nodelen++;
if (done)
break;
}
/* Reassemble. */
for (i = 0; i < nodelen; i++)
{
if (node[i])
{
char *pth;
pth = realloc (path, strlen (path) + strlen (node[i]) + 1);
if (pth)
{
path = pth;
strcat (path, node[i]);
}
free (node[i]);
}
}
if (node)
free (node);
}
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%u LIST \"%s\" \"%s\"\r\n",
f_imap->seq++, ref, path);
free (path);
CHECK_ERROR (f_imap, status);
FOLDER_DEBUG0 (folder, 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);
FOLDER_DEBUG0 (folder, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
/* Build the folder list. */
if (f_imap->flist.num > 0)
{
struct list_response **plist = NULL;
size_t num = f_imap->flist.num;
size_t j = 0;
plist = calloc (num, sizeof (*plist));
if (plist)
{
size_t i;
for (i = 0; i < num; i++)
{
struct list_response *lr = f_imap->flist.element[i];
if (imap_mailbox_name_match (name, lr->name) == 0)
{
/*
FOLDER_DEBUG2(folder, MU_DEBUG_TRACE,
"fnmatch against %s: %s - match!\n", name, lr->name);
*/
plist[i] = calloc (1, sizeof (**plist));
if (plist[i] == NULL
|| (plist[i]->name = strdup (lr->name)) == NULL)
{
break;
}
plist[i]->type = lr->type;
plist[i]->separator = lr->separator;
j++;
}
/*
else
FOLDER_DEBUG2(folder, MU_DEBUG_TRACE,
"fnmatch against %s: %s - no match!\n", name, lr->name);
*/
}
}
pflist->element = plist;
pflist->num = j;
}
folder_list_destroy (&(f_imap->flist));
f_imap->state = IMAP_NO_STATE;
return status;
}
static int
folder_imap_lsub (folder_t folder, const char *ref, const char *name,
struct folder_list *pflist)
{
f_imap_t f_imap = folder->data;
int status = 0;
/* NOOP. */
if (pflist == NULL)
return EINVAL;
status = folder_open (folder, folder->flags);
if (status != 0)
return status;
if (ref == NULL) ref = "";
if (name == NULL) name = "";
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%u LSUB \"%s\" \"%s\"\r\n",
f_imap->seq++, ref, name);
CHECK_ERROR (f_imap, status);
FOLDER_DEBUG0 (folder, 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);
FOLDER_DEBUG0 (folder, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
/* Build the folder list. */
if (f_imap->flist.num > 0)
{
struct list_response **plist = NULL;
size_t num = f_imap->flist.num;
size_t j = 0;
plist = calloc (num, sizeof (*plist));
if (plist)
{
size_t i;
for (i = 0; i < num; i++)
{
struct list_response *lr = f_imap->flist.element[i];
/* printf ("%s --> %s\n", lr->name, name); */
plist[i] = calloc (1, sizeof (**plist));
if (plist[i] == NULL
|| (plist[i]->name = strdup (lr->name)) == NULL)
{
break;
}
plist[i]->type = lr->type;
plist[i]->separator = lr->separator;
j++;
}
}
pflist->element = plist;
pflist->num = j;
folder_list_destroy (&(f_imap->flist));
}
f_imap->state = IMAP_NO_STATE;
f_imap->state = IMAP_NO_STATE;
return 0;
}
static int
folder_imap_rename (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 = folder_open (folder, folder->flags);
if (status != 0)
return status;
switch (f_imap->state)
{
case IMAP_NO_STATE:
status = imap_writeline (f_imap, "g%u RENAME %s %s\r\n",
f_imap->seq++, oldpath, newpath);
CHECK_ERROR (f_imap, status);
FOLDER_DEBUG0 (folder, 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);
FOLDER_DEBUG0 (folder, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
f_imap->state = IMAP_NO_STATE;
return status;
}
static int
folder_imap_subscribe (folder_t folder, const char *name)
{
f_imap_t f_imap = folder->data;
int status = 0;
status = 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%u SUBSCRIBE %s\r\n",
f_imap->seq++, name);
CHECK_ERROR (f_imap, status);
FOLDER_DEBUG0 (folder, 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);
FOLDER_DEBUG0 (folder, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
f_imap->state = IMAP_NO_STATE;
return status;
}
static int
folder_imap_unsubscribe (folder_t folder, const char *name)
{
f_imap_t f_imap = folder->data;
int status = 0;
status = 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%u UNSUBSCRIBE %s\r\n",
f_imap->seq++, name);
CHECK_ERROR (f_imap, status);
FOLDER_DEBUG0 (folder, 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);
FOLDER_DEBUG0 (folder, 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;
/* 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--;
}
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->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;
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;
}
/* 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:
/* Problem. */
status = 1;
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 list_response **plr;
struct list_response *lr;
int status = 0;
buffer = alloca (len);
memcpy (buffer, f_imap->buffer, len);
buffer[len] = '\0';
plr = realloc (f_imap->flist.element,
(f_imap->flist.num + 1) * sizeof (*plr));
if (plr == NULL)
return ENOMEM;
f_imap->flist.element = plr;
lr = plr[f_imap->flist.num] = calloc (1, sizeof (*lr));
if (lr == NULL)
return ENOMEM;
(f_imap->flist.num)++;
/* 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 (strcasecmp (tok, "\\Noselect") == 0)
{
lr->type |= MU_FOLDER_ATTRIBUTE_DIRECTORY;
}
else if (strcasecmp (tok, "\\Marked") == 0)
{
}
else if (strcasecmp (tok, "\\Unmarked") == 0)
{
}
else if (strcasecmp (tok, "\\Noinferiors") == 0)
{
lr->type |= MU_FOLDER_ATTRIBUTE_FILE;
}
else
{
lr->type |= MU_FOLDER_ATTRIBUTE_DIRECTORY;
}
p = NULL;
}
}
/* Hiearchy delimeter. */
tok = strtok_r (NULL, " ", &sp);
if (tok && strlen (tok) > 2 && strcasecmp (tok, "NIL"))
lr->separator = tok[1];
/* The path. */
tok = strtok_r (NULL, " ", &sp);
if (tok)
{
char *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)
{
off_t sz = 0;
stream_size (f_imap->string.stream, &sz);
lr->name = calloc (sz + 1, 1);
if (!lr->name)
status = ENOMEM;
else
stream_read (f_imap->string.stream, lr->name, sz, 0, NULL);
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;
}
}
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 (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->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 strcasecmp. */
end = *ptr;
/* Bail out. */
if (*start == '\0')
break;
/* Guess the flag. */
if (end == start)
flags |= MU_ATTRIBUTE_SEEN;
else
{
if (strncasecmp (start, "\\Seen", end - start) == 0)
{
flags |= MU_ATTRIBUTE_READ;
}
else if (strncasecmp (start, "\\Answered", end - start) == 0)
{
flags |= MU_ATTRIBUTE_ANSWERED;
}
else if (strncasecmp (start, "\\Flagged", end - start) == 0)
{
flags |= MU_ATTRIBUTE_FLAGGED;
}
else if (strncasecmp (start, "\\Deleted", end - start) == 0)
{
flags |= MU_ATTRIBUTE_DELETED;
}
else if (strncasecmp (start, "\\Draft", end - start) == 0)
{
flags |= MU_ATTRIBUTE_DRAFT;
}
else if (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 pass the '[' */
if (sep)
{
size_t len = sep - *ptr;
char *section = alloca (len + 1);
char *p = section;
strncpy (section, *ptr, len);
section[len] = '\0';
/* strupper. */
for (; *p; p++) if (isupper((unsigned)*p)) *p = toupper ((unsigned)*p);
/* 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;
}
}
sep++; /* Move pass 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;
off_t total = 0;
if (f_imap->string.msg_imap && f_imap->string.msg_imap->fheader)
header_destroy (&f_imap->string.msg_imap->fheader, NULL);
stream_size (f_imap->string.stream, &total);
buffer = malloc (total + 1);
stream_read (f_imap->string.stream, buffer, total, 0, NULL);
status = header_create (&f_imap->string.msg_imap->fheader,
buffer, total, NULL);
free (buffer);
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->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;
message_t msg = NULL;
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;
}
}
/* 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 (strcasecmp (token, "BODY") == 0)
{
if (*sp == '[')
status = imap_body (f_imap, &sp);
else
status = imap_bodystructure (f_imap, &sp);
}
else if (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 (strcasecmp (token, "SIZE") == 0)
{
status = imap_rfc822_size (f_imap, &sp);
}
else if (strcasecmp (token, "TEXT") == 0)
{
status = imap_rfc822_text (f_imap, &sp);
}
else if (strcasecmp (token, "HEADER") == 0)
{
status = imap_rfc822_header (f_imap, &sp);
}
/* else mu_error ("not supported RFC822 option\n"); */
}
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\n"); */
}
return status;
}
static int
imap_search (f_imap_t f_imap)
{
(void)f_imap;
/* Not implemented. No provision for this in the API, yet. */
return 0;
}
static int
imap_status (f_imap_t f_imap)
{
(void)f_imap;
/* Not implemented. No provision for this in the API, yet. */
return 0;
}
static int
imap_expunge (f_imap_t f_imap, unsigned msgno)
{
(void)f_imap; (void)msgno;
/* 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(strcasecmp(pattern, "inbox") == 0)
{
return 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
{
len = vsnprintf (f_imap->buffer, f_imap->buflen - 1, format, ap);
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 = 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 = 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 */
if (f_imap->nl > f_imap->buffer)
{
*(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;
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;
}
/* Is the response untagged ? */
if (tag && tag[0] == '*')
{
FOLDER_DEBUG2(folder, MU_DEBUG_PROT, "* %s %s\n",
response, remainder);
/* Is it a Status Response. */
if (strcasecmp (response, "OK") == 0)
{
/* Check for status response [code]. */
if (*remainder == '[')
{
char *cruft, *subtag;
char *sp = NULL;
remainder++;
cruft = strtok_r (remainder, "]", &sp);
if (!cruft) cruft = empty;
subtag = strtok_r (cruft, " ", &sp);
if (!subtag) subtag = empty;
if (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\n", (sp) ? sp : "");
}
else if (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 ("BADCHARSET: %s\n", (sp) ? sp : "");
}
else if (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. */
if (f_imap->capa)
free (f_imap->capa);
f_imap->capa = strdup (cruft);
}
else if (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\n", (sp) ? sp : "");
}
else if (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\n", (sp) ? sp : "");
}
else if (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 (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 (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 (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\n", (sp) ? sp : "");
}
else if (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, " ", &sp);
f_imap->selected->uidnext = strtol (value, NULL, 10);
}
else if (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, " ", &sp);
f_imap->selected->uidvalidity = strtol (value, NULL, 10);
}
else if (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, " ", &sp);
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: %s\n", remainder);
}
}
else if (strcasecmp (response, "NO") == 0)
{
/* This does not mean failure but rather a strong warning. */
mu_error ("Untagged NO: %s\n", remainder);
}
else if (strcasecmp (response, "BAD") == 0)
{
/* We're dead, protocol/syntax error. */
mu_error ("Untagged BAD: %s\n", remainder);
}
else if (strcasecmp (response, "PREAUTH") == 0)
{
/* Should we be dealing with this? */
}
else if (strcasecmp (response, "BYE") == 0)
{
/* We should close the stream. This is not recoverable. */
done = 1;
monitor_wrlock (f_imap->folder->monitor);
f_imap->isopen = 0;
f_imap->selected = NULL;
monitor_unlock (f_imap->folder->monitor);
stream_close (f_imap->folder->stream);
}
else if (strcasecmp (response, "CAPABILITY") == 0)
{
if (f_imap->capa)
free (f_imap->capa);
f_imap->capa = strdup (remainder);
}
else if (strcasecmp (remainder, "EXISTS") == 0)
{
f_imap->selected->messages_count = strtol (response, NULL, 10);
}
else if (strcasecmp (remainder, "EXPUNGE") == 0)
{
unsigned int msgno = strtol (response, NULL, 10);
status = imap_expunge (f_imap, msgno);
}
else if (strncasecmp (remainder, "FETCH", 5) == 0)
{
status = imap_fetch (f_imap);
if (status != 0)
break;
}
else if (strcasecmp (response, "FLAGS") == 0)
{
/* Flags define on the mailbox not a message flags. */
status = imap_permanentflags (f_imap, &remainder);
}
else if (strcasecmp (response, "LIST") == 0)
{
status = imap_list (f_imap);
}
else if (strcasecmp (response, "LSUB") == 0)
{
status = imap_list (f_imap);
}
else if (strcasecmp (remainder, "RECENT") == 0)
{
f_imap->selected->recent = strtol (response, NULL, 10);
}
else if (strcasecmp (response, "SEARCH") == 0)
{
status = imap_search (f_imap);
}
else if (strcasecmp (response, "STATUS") == 0)
{
status = imap_status (f_imap);
}
else
{
/* Once again, check for something strange. */
mu_error ("unknown untagged response: \"%s\" %s\n",
response, remainder);
}
}
/* Continuation token ???. */
else if (tag && tag[0] == '+')
{
done = 1;
}
else
{
/* Every transaction ends with a tagged response. */
done = 1;
if (strcasecmp (response, "OK") == 0)
{
/* Cool we are doing ok. */
}
else /* NO and BAD */
{
if (strncasecmp (remainder, "LOGIN", 5) == 0)
{
observable_t observable = NULL;
folder_get_observable (f_imap->folder, &observable);
observable_notify (observable, MU_EVT_AUTHORITY_FAILED);
}
mu_error ("NO/Bad Tagged: %s %s\n", response, remainder);
status = EINVAL;
}
}
f_imap->ptr = f_imap->buffer;
}
if (buffer)
free (buffer);
return status;
}
#else
#include <stdio.h>
#include <registrar0.h>
record_t imap_record = NULL;
#endif
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#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/header.h>
#include <mailutils/message.h>
#include <mailutils/mutil.h>
#include <mailutils/observer.h>
#include <mailutils/property.h>
#include <mailutils/stream.h>
#include <imap0.h>
#include <mailbox0.h>
#include <registrar0.h>
#include <url0.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"
/* mailbox_t API. */
static void mailbox_imap_destroy __P ((mailbox_t));
static int mailbox_imap_open __P ((mailbox_t, int));
static int mailbox_imap_close __P ((mailbox_t));
static int imap_uidvalidity __P ((mailbox_t, unsigned long *));
static int imap_uidnext __P ((mailbox_t, size_t *));
static int imap_expunge __P ((mailbox_t));
static int imap_get_message __P ((mailbox_t, size_t, message_t *));
static int imap_messages_count __P ((mailbox_t, size_t *));
static int imap_messages_recent __P ((mailbox_t, size_t *));
static int imap_message_unseen __P ((mailbox_t, size_t *));
static int imap_scan __P ((mailbox_t, size_t, size_t *));
static int imap_scan0 __P ((mailbox_t, size_t, size_t *, int));
static int imap_is_updated __P ((mailbox_t));
static int imap_append_message __P ((mailbox_t, message_t));
static int imap_append_message0 __P ((mailbox_t, message_t));
static int imap_copy_message __P ((mailbox_t, message_t));
/* message_t API. */
static int imap_submessage_size __P ((msg_imap_t, size_t *));
static int imap_message_size __P ((message_t, size_t *));
static int imap_message_lines __P ((message_t, size_t *));
static int imap_message_fd __P ((stream_t, int *));
static int imap_message_read __P ((stream_t , char *, size_t, off_t, size_t *));
static int imap_message_uid __P ((message_t, size_t *));
/* mime_t API. */
static int imap_is_multipart __P ((message_t, int *));
static int imap_get_num_parts __P ((message_t, size_t *));
static int imap_get_part __P ((message_t, size_t, message_t *));
/* envelope_t API */
static int imap_envelope_sender __P ((envelope_t, char *, size_t, size_t *));
static int imap_envelope_date __P ((envelope_t, char *, size_t, size_t *));
/* attribute_t API */
static int imap_attr_get_flags __P ((attribute_t, int *));
static int imap_attr_set_flags __P ((attribute_t, int));
static int imap_attr_unset_flags __P ((attribute_t, int));
/* header_t API. */
static int imap_header_read __P ((header_t, char*, size_t, off_t, size_t *));
static int imap_header_get_value __P ((header_t, const char*, char *, size_t, size_t *));
static int imap_header_get_fvalue __P ((header_t, const char*, char *, size_t, size_t *));
/* body_t API. */
static int imap_body_read __P ((stream_t, char *, size_t, off_t,
size_t *));
static int imap_body_size __P ((body_t, size_t *));
static int imap_body_lines __P ((body_t, size_t *));
static int imap_body_fd __P ((stream_t, int *));
/* Helpers. */
static int imap_get_fd __P ((msg_imap_t, int *));
static int imap_get_message0 __P ((msg_imap_t, message_t *));
static int fetch_operation __P ((f_imap_t, msg_imap_t, char *, size_t, size_t *));
static void free_subparts __P ((msg_imap_t));
static int flags_to_string __P ((char **, int));
static int delete_to_string __P ((m_imap_t, char **));
static int is_same_folder __P ((mailbox_t, message_t));
/* Initialize the concrete object mailbox_t by overloading the function of the
structure. */
int
_mailbox_imap_init (mailbox_t mailbox)
{
m_imap_t m_imap;
size_t name_len = 0;
folder_t folder = NULL;
assert(mailbox);
folder = mailbox->folder;
m_imap = mailbox->data = calloc (1, sizeof (*m_imap));
if (m_imap == NULL)
return ENOMEM;
/* Retrieve the name of the mailbox from the URL. */
url_get_path (mailbox->url, NULL, 0, &name_len);
if (name_len == 0)
{
/* name "INBOX" is the default. */
m_imap->name = calloc (6, sizeof (char));
strcpy (m_imap->name, "INBOX");
}
else
{
m_imap->name = calloc (name_len + 1, sizeof (char));
url_get_path (mailbox->url, m_imap->name, name_len + 1, NULL);
}
/* 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. */
if (mailbox->folder)
m_imap->f_imap = mailbox->folder->data;
/* maibox back pointer. */
m_imap->mailbox = mailbox;
/* Set our properties. */
{
property_t property = NULL;
mailbox_get_property (mailbox, &property);
property_set_value (property, "TYPE", "IMAP4", 1);
}
return 0;
}
/* 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)
message_destroy (&(msg_imap->message), msg_imap);
if (msg_imap->parts)
free (msg_imap->parts);
if (msg_imap->fheader)
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 (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;
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;
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 (mailbox_t mailbox, int flags)
{
int status = 0;
m_imap_t m_imap = mailbox->data;
f_imap_t f_imap = m_imap->f_imap;
folder_t folder = f_imap->folder;
struct folder_list folders = { 0, 0 };
/* m_imap must have been created during mailbox initialization. */
assert (mailbox->data);
assert (m_imap->name);
mailbox->flags = flags;
if ((status = 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 = folder_list (folder, NULL, m_imap->name, &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;
if (folders.num)
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:
{
char *path;
size_t len;
url_get_path (folder->url, NULL, 0, &len);
if (len == 0)
return 0;
path = calloc (len + 1, sizeof (*path));
if (path == NULL)
return ENOMEM;
url_get_path (folder->url, path, len + 1, NULL);
status = imap_writeline (f_imap, "g%u CREATE %s\r\n",
f_imap->seq, path);
MAILBOX_DEBUG2 (folder, MU_DEBUG_PROT, "g%u CREATE %s\n",
f_imap->seq, path);
f_imap->seq++;
free (path);
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;
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 (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 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%d CLOSE\r\n", f_imap->seq++);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (mailbox, 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);
MAILBOX_DEBUG0 (mailbox, MU_DEBUG_PROT, f_imap->buffer);
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. */
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;
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 folder_close (mailbox->folder);
}
/* Construction of the message_t, nothing else is done then this setup. To
clarify this is different from say message_get_part(). This call is for the
mailbox and we are setting up the message_t structure. */
static int
imap_get_message (mailbox_t mailbox, size_t msgno, message_t *pmsg)
{
m_imap_t m_imap = mailbox->data;
msg_imap_t msg_imap;
int status = 0;
if (pmsg == NULL || msgno == 0 || msgno > m_imap->messages_count)
return EINVAL;
/* Check to see if we have already this message. */
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;
monitor_unlock (mailbox->monitor);
return 0;
}
}
}
}
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. */
monitor_wrlock (mailbox->monitor);
{
msg_imap_t *m ;
m = realloc (m_imap->imessages,
(m_imap->imessages_count + 1) * sizeof *m);
if (m == NULL)
{
message_destroy (pmsg, msg_imap);
monitor_unlock (mailbox->monitor);
return ENOMEM;
}
m_imap->imessages = m;
m_imap->imessages[m_imap->imessages_count] = msg_imap;
m_imap->imessages_count++;
}
monitor_unlock (mailbox->monitor);
msg_imap->message = *pmsg;
}
else
free (msg_imap);
return status;
}
/* Set all the message_t functions and parts. */
static int
imap_get_message0 (msg_imap_t msg_imap, message_t *pmsg)
{
int status = 0;
message_t msg = NULL;
mailbox_t mailbox = msg_imap->m_imap->mailbox;
/* Create the message and its stream. */
{
stream_t stream = NULL;
if ((status = message_create (&msg, msg_imap)) != 0
|| (status = stream_create (&stream, mailbox->flags, msg)) != 0)
{
stream_destroy (&stream, msg);
message_destroy (&msg, msg_imap);
return status;
}
stream_setbufsiz (stream, 128);
stream_set_read (stream, imap_message_read, msg);
stream_set_fd (stream, imap_message_fd, msg);
message_set_stream (msg, stream, msg_imap);
message_set_size (msg, imap_message_size, msg_imap);
message_set_lines (msg, imap_message_lines, msg_imap);
}
/* Create the header. */
{
header_t header = NULL;
if ((status = header_create (&header, NULL, 0, msg)) != 0)
{
message_destroy (&msg, msg_imap);
return status;
}
header_set_fill (header, imap_header_read, msg);
header_set_get_value (header, imap_header_get_value, msg);
header_set_get_fvalue (header, imap_header_get_fvalue, msg);
message_set_header (msg, header, msg_imap);
}
/* Create the attribute. */
{
attribute_t attribute;
status = attribute_create (&attribute, msg);
if (status != 0)
{
message_destroy (&msg, msg_imap);
return status;
}
attribute_set_get_flags (attribute, imap_attr_get_flags, msg);
attribute_set_set_flags (attribute, imap_attr_set_flags, msg);
attribute_set_unset_flags (attribute, imap_attr_unset_flags, msg);
message_set_attribute (msg, attribute, msg_imap);
}
/* Create the body and its stream. */
{
body_t body = NULL;
stream_t stream = NULL;
if ((status = body_create (&body, msg)) != 0
|| (status = stream_create (&stream, mailbox->flags, body)) != 0)
{
body_destroy (&body, msg);
stream_destroy (&stream, body);
message_destroy (&msg, msg_imap);
return status;
}
stream_setbufsiz (stream, 128);
stream_set_read (stream, imap_body_read, body);
stream_set_fd (stream, imap_body_fd, body);
body_set_size (body, imap_body_size, msg);
body_set_lines (body, imap_body_lines, msg);
body_set_stream (body, stream, msg);
message_set_body (msg, body, msg_imap);
}
/* Set the envelope. */
{
envelope_t envelope= NULL;
status = envelope_create (&envelope, msg);
if (status != 0)
{
message_destroy (&msg, msg_imap);
return status;
}
envelope_set_sender (envelope, imap_envelope_sender, msg);
envelope_set_date (envelope, imap_envelope_date, msg);
message_set_envelope (msg, envelope, msg_imap);
}
/* Set the mime handling. */
message_set_is_multipart (msg, imap_is_multipart, msg_imap);
message_set_get_num_parts (msg, imap_get_num_parts, msg_imap);
message_set_get_part (msg, imap_get_part, msg_imap);
/* Set the UID on the message. */
message_set_uid (msg, imap_message_uid, msg_imap);
message_set_mailbox (msg, mailbox, msg_imap);
/* We are done here. */
*pmsg = msg;
return 0;
}
static int
imap_message_unseen (mailbox_t mailbox, size_t *punseen)
{
m_imap_t m_imap = mailbox->data;
*punseen = m_imap->unseen;
return 0;
}
static int
imap_messages_recent (mailbox_t mailbox, size_t *precent)
{
m_imap_t m_imap = mailbox->data;
*precent = m_imap->recent;
return 0;
}
static int
imap_uidvalidity (mailbox_t mailbox, unsigned long *puidvalidity)
{
m_imap_t m_imap = mailbox->data;
*puidvalidity = m_imap->uidvalidity;
return 0;
}
static int
imap_uidnext (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 (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 = 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%d SELECT %s\r\n",
f_imap->seq++, m_imap->name);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (mailbox, 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);
MAILBOX_DEBUG0 (mailbox, 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 (mailbox_t mailbox, size_t msgno, size_t *pcount)
{
return imap_scan0 (mailbox, msgno, pcount , 1);
}
/* Usually when this function is call it is because there is an oberver
attach an the client is try to build some sort of list/tree header
as the scanning progress. But doing this for each message can be
time consuming and inefficient. So we bundle all the request
into one and ask the server for everything "FETCH 1:*". The good
side is that everything will be faster and we do not do lot of small
transcation but rather a big one. The bad thing is that every thing
will be cache in the structure using a lot of memory. */
static int
imap_scan0 (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%d FETCH 1:* (FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (%s)])\r\n",
f_imap->seq++, MU_IMAP_CACHE_HEADERS);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (mailbox, 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. */
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);
MAILBOX_DEBUG0 (mailbox, MU_DEBUG_PROT, f_imap->buffer);
/* Clear the callback string structure. */
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++)
{
if (observable_notify (mailbox->observable, MU_EVT_MESSAGE_ADD) != 0)
break;
if (((i + 1) % 100) == 0)
{
observable_notify (mailbox->observable, MU_EVT_MAILBOX_PROGRESS);
}
}
return 0;
}
/* Send a NOOP and see if the count has changed. */
static int
imap_is_updated (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%d NOOP\r\n", f_imap->seq++);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (mailbox, 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);
MAILBOX_DEBUG0 (mailbox, 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);
}
/* It is only here that the Deleted flags are sent. Expunge is not
call rather the mailbox is close explicitely, letting the server
do the expunge without sending the notifications. It's faster. */
static int
imap_expunge (mailbox_t mailbox)
{
int status;
m_imap_t m_imap = mailbox->data;
f_imap_t f_imap = m_imap->f_imap;
/* 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%d STORE %s +FLAGS.SILENT (\\Deleted)\r\n",
f_imap->seq++, set);
free (set);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, 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);
MAILBOX_DEBUG0 (m_imap->mailbox, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_NO_STATE;
/* We are not sending EXPUNGE, rather we close the mailbox
which will purge. */
case IMAP_CLOSE:
case IMAP_CLOSE_ACK:
status = mailbox_imap_close (mailbox);
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: unknow state\n"); */
break;
}
return status;
}
/* FIXME: Not ___Nonblocking___ safe. */
/* DANGER: The 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 (mailbox_t mailbox, 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 = 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 && !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. */
{
message_t message = NULL;
status = message_create_copy(&message, msg);
if (status == 0)
status = imap_append_message0 (mailbox, message);
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 message_size () and 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 (mailbox_t mailbox, 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;
char *path;
char *abuf = malloc (1);
/* Get the desired flags attribute. */
if (abuf == NULL)
return ENOMEM;
*abuf = '\0';
{
attribute_t attribute = NULL;
int flags = 0;
message_get_attribute (msg, &attribute);
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. */
{
size_t n = 0;
url_get_path (mailbox->url, NULL, 0, &n);
if (n == 0)
{
if (!(path = strdup ("INBOX")))
{
free (abuf);
return ENOMEM;
}
}
else
{
path = calloc (n + 1, sizeof (*path));
if (path == NULL)
{
free (abuf);
return ENOMEM;
}
url_get_path (mailbox->url, path, n + 1, NULL);
}
}
/* FIXME: we need to get the envelope_date and use it.
currently it is ignored. */
/* Get the total size, assuming that it is in UNIX format. */
lines = size = 0;
message_size (msg, &size);
message_lines (msg, &lines);
total = size + lines;
status = imap_writeline (f_imap, "g%d APPEND %s %s {%d}\r\n",
f_imap->seq++, path, abuf, size + lines);
free (abuf);
free (path);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (mailbox, 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);
MAILBOX_DEBUG0 (mailbox, 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:
{
stream_t stream = NULL;
off_t off = 0;
size_t n = 0;
char buffer[255];
message_get_stream (msg, &stream);
while (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 server hack: insists on the last line. */
imap_writeline (f_imap, "\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);
MAILBOX_DEBUG0 (m_imap->mailbox, 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 (mailbox_t mailbox, message_t msg)
{
m_imap_t m_imap = mailbox->data;
f_imap_t f_imap = m_imap->f_imap;
msg_imap_t msg_imap = 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 = folder_open (mailbox->folder, mailbox->flags);
if (status != 0)
return status;
switch (f_imap->state)
{
case IMAP_NO_STATE:
{
char *path;
size_t n = 0;
/* Check for a valid mailbox name. */
url_get_path (mailbox->url, NULL, 0, &n);
if (n == 0)
return EINVAL;
path = calloc (n + 1, sizeof (*path));
if (path == NULL)
return ENOMEM;
url_get_path (mailbox->url, path, n + 1, NULL);
status = imap_writeline (f_imap, "g%d COPY %d %s\r\n", f_imap->seq++,
msg_imap->num, path);
free (path);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (mailbox, 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);
MAILBOX_DEBUG0 (mailbox, MU_DEBUG_PROT, f_imap->buffer);
default:
break;
}
f_imap->state = IMAP_NO_STATE;
return status;
}
/* Message read overload */
static int
imap_message_read (stream_t stream, char *buffer, size_t buflen,
off_t offset, size_t *plen)
{
message_t msg = stream_get_owner (stream);
msg_imap_t msg_imap = 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->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%d FETCH %d BODY.PEEK[%s]<%d.%d>\r\n",
f_imap->seq++, msg_imap->num,
(section) ? section : "",
offset + msg_imap->message_lines, buflen);
if (section)
free (section);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, 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 (message_t msg, size_t *plines)
{
msg_imap_t msg_imap = message_get_owner (msg);
if (plines && msg_imap)
{
if (msg_imap->message_lines == 0)
*plines = msg_imap->body_lines + msg_imap->header_lines;
else
*plines = msg_imap->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 message_size aka
the body_size. But we can calculate it since the 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->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->message_size + msg_imap->header_size)
- msg_imap->message_lines;
}
return 0;
}
static int
imap_message_size (message_t msg, size_t *psize)
{
msg_imap_t msg_imap = 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 message_size retrieved from
doing a bodystructure represents rather the body_size. */
if (msg_imap->parent)
return imap_submessage_size (msg_imap, psize);
if (msg_imap->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%d FETCH %d RFC822.SIZE\r\n",
f_imap->seq++, msg_imap->num);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, 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->message_size - msg_imap->message_lines;
}
return status;
}
static int
imap_message_uid (message_t msg, size_t *puid)
{
msg_imap_t msg_imap = 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%d FETCH %d UID\r\n",
f_imap->seq++, msg_imap->num);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, 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_fd (stream_t stream, int * pfd)
{
message_t msg = stream_get_owner (stream);
msg_imap_t msg_imap = message_get_owner (msg);
return imap_get_fd (msg_imap, pfd);
}
/* Mime. */
static int
imap_is_multipart (message_t msg, int *ismulti)
{
msg_imap_t msg_imap = 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%d FETCH %d BODYSTRUCTURE\r\n",
f_imap->seq++, msg_imap->num);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, 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 (message_t msg, size_t *nparts)
{
msg_imap_t msg_imap = 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 (message_t msg, size_t partno, message_t *pmsg)
{
msg_imap_t msg_imap = 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
{
message_t message;
status = imap_get_message0 (msg_imap->parts[partno - 1], &message);
if (status == 0)
{
header_t header;
message_get_header (message, &header);
header_set_get_value (header, NULL, message);
message_set_stream (message, NULL, msg_imap->parts[partno - 1]);
/* 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 (envelope_t envelope, char *buffer, size_t buflen,
size_t *plen)
{
message_t msg = envelope_get_owner (envelope);
header_t header;
int status;
if (buflen == 0)
return 0;
message_get_header (msg, &header);
status = header_get_value (header, MU_HEADER_SENDER, buffer, buflen, plen);
if (status == EAGAIN)
return status;
else if (status != 0)
status = header_get_value (header, MU_HEADER_FROM, buffer, buflen, plen);
if (status == 0)
{
address_t address;
if (address_create (&address, buffer) == 0)
{
address_get_email (address, 1, buffer, buflen, plen);
address_destroy (&address);
}
}
else if (status != EAGAIN)
{
strncpy (buffer, "Unknown", buflen)[buflen - 1] = '0';
if (plen)
*plen = strlen (buffer);
}
return status;
}
static int
imap_envelope_date (envelope_t envelope, char *buffer, size_t buflen,
size_t *plen)
{
message_t msg = envelope_get_owner (envelope);
msg_imap_t msg_imap = 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%d FETCH %d INTERNALDATE\r\n",
f_imap->seq++, msg_imap->num);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, 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_tm2time (&tm, &tz);
/* if the time was unparseable, or mktime() didn't like what we
parsed, use the calendar time. */
if (now == (time_t)-1)
{
struct tm* gmt;
time(&now);
gmt = gmtime(&now);
tm = *gmt;
}
{
int len = strftime (buffer, buflen, " %a %b %d %H:%M:%S %Y", &tm);
/* FIXME: I don't know what strftime does if the buflen is too
short, or it fails. Assuming that it won't fail, this is my guess
as to the right thing.
I think if the buffer is too short, it will fill it as much
as it can, and nul terminate it. But I'll terminate it anyhow.
*/
if(len == 0)
{
len = buflen - 1;
buffer[len] = 0;
}
if(plen)
*plen = len;
}
return 0;
}
/* Attributes. */
static int
imap_attr_get_flags (attribute_t attribute, int *pflags)
{
message_t msg = attribute_get_owner (attribute);
msg_imap_t msg_imap = 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%d FETCH %d FLAGS\r\n",
f_imap->seq++, msg_imap->num);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, 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 (attribute_t attribute, int flag)
{
message_t msg = attribute_get_owner (attribute);
msg_imap_t msg_imap = 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%d STORE %d +FLAGS.SILENT (%s)\r\n",
f_imap->seq++, msg_imap->num, abuf);
free (abuf);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, 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 (attribute_t attribute, int flag)
{
message_t msg = attribute_get_owner (attribute);
msg_imap_t msg_imap = 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%d STORE %d -FLAGS.SILENT (%s)\r\n",
f_imap->seq++, msg_imap->num, abuf);
free (abuf);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, 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_get_value (header_t header, const char *field, char * buffer,
size_t buflen, size_t *plen)
{
message_t msg = header_get_owner (header);
msg_imap_t msg_imap = message_get_owner (msg);
m_imap_t m_imap = msg_imap->m_imap;
f_imap_t f_imap = m_imap->f_imap;
int status;
size_t len = 0;
char *value;
/* Select first. */
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
/* Hack, if buffer == NULL they want to know how big is the field value,
Unfortunately IMAP does not say, so we take a guess hoping that the
value will not be over 1024. */
if (buffer == NULL || buflen == 0)
len = 1024;
else
len = strlen (field) + buflen + 4;
if (f_imap->state == IMAP_NO_STATE)
{
/* Select first. */
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
status = imap_writeline (f_imap,
"g%d FETCH %d BODY.PEEK[HEADER.FIELDS (%s)]<0.%d>\r\n",
f_imap->seq++, msg_imap->num, field, len);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_FETCH;
}
value = calloc (len, sizeof (*value));
status = fetch_operation (f_imap, msg_imap, value, len, &len);
if (status == 0)
{
char *colon;
/* The field-matching is case-insensitive. In all cases, the
delimiting newline between the header and the body is always
included. Nuke it */
if (len)
value[len - 1] = '\0';
/* Move pass the field-name. */
colon = strchr (value, ':');
if (colon)
{
colon++;
if (*colon == ' ')
colon++;
}
else
colon = value;
while (*colon && colon[strlen (colon) - 1] == '\n')
colon[strlen (colon) - 1] = '\0';
if (buffer && buflen)
{
strncpy (buffer, colon, buflen);
buffer[buflen - 1] = '\0';
}
len = strlen (buffer);
if (plen)
*plen = len;
if (len == 0)
status = ENOENT;
}
free (value);
return status;
}
static int
imap_header_get_fvalue (header_t header, const char *field, char * buffer,
size_t buflen, size_t *plen)
{
message_t msg = header_get_owner (header);
msg_imap_t msg_imap = message_get_owner (msg);
m_imap_t m_imap = msg_imap->m_imap;
f_imap_t f_imap = m_imap->f_imap;
int status;
size_t len = 0;
char *value;
/* Select first. */
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
/* Do we all ready have the headers. */
if (msg_imap->fheader)
return header_get_value (msg_imap->fheader, field, buffer, buflen, plen);
/* We are caching the must use headers. */
if (f_imap->state == IMAP_NO_STATE)
{
/* Select first. */
status = imap_messages_count (m_imap->mailbox, NULL);
if (status != 0)
return status;
status = imap_writeline (f_imap,
"g%d FETCH %d (FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (%s)])\r\n",
f_imap->seq++, msg_imap->num,
MU_IMAP_CACHE_HEADERS);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, MU_DEBUG_PROT, f_imap->buffer);
f_imap->state = IMAP_FETCH;
}
/* Should be enough for our needs. */
len = 2048;
value = calloc (len, sizeof *value);
status = fetch_operation (f_imap, msg_imap, value, len, &len);
if (status == 0)
{
status = header_create (&msg_imap->fheader, value, len, NULL);
if (status == 0)
status = header_get_value (msg_imap->fheader, field, buffer,
buflen, plen);
}
free (value);
return status;
}
static int
imap_header_read (header_t header, char *buffer, size_t buflen, off_t offset,
size_t *plen)
{
message_t msg = header_get_owner (header);
msg_imap_t msg_imap = 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%d FETCH %d BODY.PEEK[%s.MIME]<%d.%d>\r\n",
f_imap->seq++, msg_imap->num,
(section) ? section : "",
offset + msg_imap->header_lines, buflen);
if (section)
free (section);
}
else
status = imap_writeline (f_imap,
"g%d FETCH %d BODY.PEEK[HEADER]<%d.%d>\r\n",
f_imap->seq++, msg_imap->num,
offset + msg_imap->header_lines, buflen);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, 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 (body_t body, size_t *psize)
{
message_t msg = body_get_owner (body);
msg_imap_t msg_imap = 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 message_size was retrieve from
doing a bodystructure and represents rather the body_size. */
if (msg_imap->parent)
{
*psize = msg_imap->message_size - msg_imap->message_lines;
}
else
{
if (msg_imap->body_size)
*psize = msg_imap->body_size;
else if (msg_imap->message_size)
*psize = msg_imap->message_size
- (msg_imap->header_size + msg_imap->header_lines);
else
*psize = 0;
}
}
return 0;
}
static int
imap_body_lines (body_t body, size_t *plines)
{
message_t msg = body_get_owner (body);
msg_imap_t msg_imap = 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 (stream_t stream, char *buffer, size_t buflen, off_t offset,
size_t *plen)
{
body_t body = stream_get_owner (stream);
message_t msg = body_get_owner (body);
msg_imap_t msg_imap = 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%d FETCH %d BODY.PEEK[%s]<%d.%d>\r\n",
f_imap->seq++, msg_imap->num,
(section) ? section: "",
offset + msg_imap->body_lines, buflen);
if (section)
free (section);
}
else
status = imap_writeline (f_imap,
"g%d FETCH %d BODY.PEEK[TEXT]<%d.%d>\r\n",
f_imap->seq++, msg_imap->num,
offset + msg_imap->body_lines, buflen);
CHECK_ERROR (f_imap, status);
MAILBOX_DEBUG0 (m_imap->mailbox, 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_fd (stream_t stream, int *pfd)
{
body_t body = stream_get_owner (stream);
message_t msg = body_get_owner (body);
msg_imap_t msg_imap = message_get_owner (msg);
return imap_get_fd (msg_imap, pfd);
}
static int
imap_get_fd (msg_imap_t msg_imap, int *pfd)
{
if ( msg_imap
&& msg_imap->m_imap
&& msg_imap->m_imap->f_imap
&& msg_imap->m_imap->f_imap->folder)
return stream_get_fd (msg_imap->m_imap->f_imap->folder->stream, pfd);
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);
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)
MAILBOX_DEBUG0 (f_imap->selected->mailbox, 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 = EBADF;
if (buffer)
stream_read (f_imap->string.stream, buffer, buflen, 0, plen);
else if (plen)
*plen = 0;
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 (mailbox_t mailbox, message_t msg)
{
mailbox_t mbox = NULL;
message_get_mailbox (msg, &mbox);
return (mbox != NULL && mbox->url != NULL
&& url_is_same_scheme (mbox->url, mailbox->url)
&& url_is_same_host (mbox->url, mailbox->url)
&& 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[128];
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)
*buf = '\0';
else if (start != end)
snprintf (buf, sizeof buf, "%lu:%lu",
(unsigned long) start,
(unsigned long) end);
else
snprintf (buf, sizeof 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);
return ENOMEM;
}
set = tmp;
/* If we had something add a comma separator. */
if (set_len)
strcat (set, ",");
strcat (set, 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 () */
status = add_number (&set, start, cur);
if (status != 0)
return status;
*pset = set;
return 0;
}
#endif
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifdef HAVE_CONFIG_H
# 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 <registrar0.h>
#include <url0.h>
static void url_imap_destroy (url_t url);
static void
url_imap_destroy (url_t url)
{
(void)url;
}
/*
IMAP URL:
imap://[<user>[;AUTH=<auth>]@]<host>[/<mailbox>]
else
imap://[<user>[:<pass>]@]<host>[/<mailbox>]
*/
int
_url_imap_init (url_t url)
{
int status = 0;
url->_destroy = url_imap_destroy;
status = url_parse (url);
if (status)
return status;
if(!url->host || url->query)
return EINVAL;
/* is it pop? */
if (strcmp ("imap", url->scheme) != 0)
return EINVAL;
/* fill in default port, if necesary */
if (url->port == 0)
url->port = MU_IMAP_PORT;
/* fill in default auth, if necessary */
if (!url->auth)
{
url->auth = malloc (1 + 1);
if (!url->auth)
return ENOMEM;
url->auth[0] = '*';
url->auth[1] = '\0';
}
return status;
}
#endif
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2002, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/* First draft by Jeff Bailey based on mbox by Alain Magloire */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
#ifdef WITH_PTHREAD
# ifdef HAVE_PTHREAD_H
# define _XOPEN_SOURCE 500
# include <pthread.h>
# endif
#endif
#ifdef HAVE_ALLOCA_H
# include <alloca.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <mailbox0.h>
#include <registrar0.h>
#include <mailutils/address.h>
#include <mailutils/attribute.h>
#include <mailutils/body.h>
#include <mailutils/debug.h>
#include <mailutils/envelope.h>
#include <mailutils/errno.h>
#include <mailutils/error.h>
#include <mailutils/header.h>
#include <mailutils/locker.h>
#include <mailutils/message.h>
#include <mailutils/mutil.h>
#include <mailutils/observer.h>
#include <mailutils/property.h>
#include <mailutils/stream.h>
#include <mailutils/url.h>
/* Mailbox concrete implementation. */
static int maildir_open __P ((mailbox_t, int));
static int maildir_close __P ((mailbox_t));
static void maildir_destroy __P ((mailbox_t));
static int maildir_get_message __P ((mailbox_t, size_t, message_t *));
/* static int maildir_get_message_by_uid __P ((mailbox_t, size_t, message_t *)); */
static int maildir_append_message __P ((mailbox_t, message_t));
static int maildir_messages_count __P ((mailbox_t, size_t *));
static int maildir_messages_recent __P ((mailbox_t, size_t *));
static int maildir_message_unseen __P ((mailbox_t, size_t *));
static int maildir_expunge __P ((mailbox_t));
static int maildir_save_attributes __P ((mailbox_t));
static int maildir_uidvalidity __P ((mailbox_t, unsigned long *));
static int maildir_uidnext __P ((mailbox_t, size_t *));
static int maildir_scan __P ((mailbox_t, size_t, size_t *));
static int maildir_is_updated __P ((mailbox_t));
static int maildir_get_size __P ((mailbox_t, off_t *));
int
_mailbox_maildir_init (mailbox_t mailbox)
{
if (mailbox == NULL)
return EINVAL;
/* Overloading the defaults. */
mailbox->_destroy = maildir_destroy;
mailbox->_open = maildir_open;
mailbox->_close = maildir_close;
/* Overloading of the entire mailbox object methods. */
mailbox->_get_message = maildir_get_message;
mailbox->_append_message = maildir_append_message;
mailbox->_messages_count = maildir_messages_count;
mailbox->_messages_recent = maildir_messages_recent;
mailbox->_message_unseen = maildir_message_unseen;
mailbox->_expunge = maildir_expunge;
mailbox->_save_attributes = maildir_save_attributes;
mailbox->_uidvalidity = maildir_uidvalidity;
mailbox->_uidnext = maildir_uidnext;
mailbox->_scan = maildir_scan;
mailbox->_is_updated = maildir_is_updated;
mailbox->_get_size = maildir_get_size;
return 0; /* okdoke */
}
/* Destruct maildir setup */
static void
maildir_destroy (mailbox_t mailbox)
{
return;
}
/* Open the file. For MU_STREAM_READ, the code tries mmap() first and fall
back to normal file. */
static int
maildir_open (mailbox_t mailbox, int flags)
{
return -1;
}
static int
maildir_close (mailbox_t mailbox)
{
return -1;
}
/* Cover function that call the real thing, maildir_scan(), with
notification set. */
static int
maildir_scan (mailbox_t mailbox, size_t msgno, size_t *pcount)
{
return 0;
}
/* FIXME: How to handle a shrink ? meaning, the &^$^@%#@^& user start two
browsers and deleted emails in one session. My views is that we should
scream bloody murder and hunt them with a machette. But for now just play
dumb, but maybe the best approach is to pack our things and leave
.i.e exit()/abort(). */
static int
maildir_is_updated (mailbox_t mailbox)
{
return -1;
}
static int
maildir_expunge (mailbox_t mailbox)
{
return -1;
}
static int
maildir_get_size (mailbox_t mailbox, off_t *psize)
{
return -1;
}
static int
maildir_save_attributes (mailbox_t mailbox)
{
return -1;
}
static int
maildir_get_message (mailbox_t mailbox, size_t msgno, message_t *pmsg)
{
return -1;
}
static int
maildir_append_message (mailbox_t mailbox, message_t msg)
{
return -1;
}
static int
maildir_messages_count (mailbox_t mailbox, size_t *pcount)
{
return -1;
}
/* A "recent" message is the one not marked with MU_ATTRIBUTE_SEEN
('O' in the Status header), i.e. a message that is first seen
by the current session (see attributes.h) */
static int
maildir_messages_recent (mailbox_t mailbox, size_t *pcount)
{
return -1;
}
/* An "unseen" message is the one that has not been read yet */
static int
maildir_message_unseen (mailbox_t mailbox, size_t *pmsgno)
{
return -1;
}
static int
maildir_uidvalidity (mailbox_t mailbox, unsigned long *puidvalidity)
{
return -1;
}
static int
maildir_uidnext (mailbox_t mailbox, size_t *puidnext)
{
return -1;
}
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <errno.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <glob.h>
#include <fnmatch.h>
#include <stdio.h>
#include <stdlib.h>
#include <folder0.h>
#include <registrar0.h>
#include <mailutils/auth.h>
#include <mailutils/url.h>
#include <mailutils/stream.h>
/* We export url parsing and the initialisation of
the mailbox, via the register entry/record. */
static struct _record _mbox_record =
{
MU_MBOX_SCHEME,
_url_mbox_init, /* Mailbox init. */
_mailbox_mbox_init, /* Mailbox init. */
NULL, /* Mailer init. */
_folder_mbox_init, /* Folder init. */
NULL, /* No need for an back pointer. */
NULL, /* _is_scheme method. */
NULL, /* _get_url method. */
NULL, /* _get_mailbox method. */
NULL, /* _get_mailer method. */
NULL /* _get_folder method. */
};
record_t mbox_record = &_mbox_record;
static struct _record _file_record =
{
MU_FILE_SCHEME,
_url_file_init, /* Mailbox init. */
_mailbox_file_init, /* Mailbox init. */
NULL, /* Mailer init. */
_folder_mbox_init, /* Folder init. */
NULL, /* No need for an owner. */
NULL, /* _is_scheme method. */
NULL, /* _get_url method. */
NULL, /* _get_mailbox method. */
NULL, /* _get_mailer method. */
NULL /* _get_folder method. */
};
record_t file_record = &_file_record;
static struct _record _path_record =
{
MU_PATH_SCHEME,
_url_path_init, /* Mailbox init. */
_mailbox_file_init, /* Mailbox init. */
NULL, /* Mailer init. */
_folder_mbox_init, /* Folder init. */
NULL, /* No need for an owner. */
NULL, /* is_scheme method. */
NULL, /* get_url method. */
NULL, /* get_mailbox method. */
NULL, /* get_mailer method. */
NULL /* get_folder method. */
};
record_t path_record = &_path_record;
/* lsub/subscribe/unsubscribe are not needed. */
static void folder_mbox_destroy __P ((folder_t));
static int folder_mbox_open __P ((folder_t, int));
static int folder_mbox_close __P ((folder_t));
static int folder_mbox_delete __P ((folder_t, const char *));
static int folder_mbox_rename __P ((folder_t , const char *,
const char *));
static int folder_mbox_list __P ((folder_t, const char *, const char *,
struct folder_list *));
static int folder_mbox_subscribe __P ((folder_t, const char *));
static int folder_mbox_unsubscribe __P ((folder_t, const char *));
static int folder_mbox_lsub __P ((folder_t, const char *, const char *,
struct folder_list *));
static char *get_pathname __P ((const char *, const char *));
static int folder_mbox_get_authority __P ((folder_t folder,
authority_t * pauth));
struct _fmbox
{
char *dirname;
char **subscribe;
size_t sublen;
};
typedef struct _fmbox *fmbox_t;
int
_folder_mbox_init (folder_t folder)
{
fmbox_t dfolder;
size_t name_len = 0;
int status = 0;
/* We create an authority so the API is uniform across the mailbox
types. */
status = folder_mbox_get_authority (folder, NULL);
if (status != 0)
return status;
dfolder = folder->data = calloc (1, sizeof (*dfolder));
if (dfolder == NULL)
return ENOMEM;
url_get_path (folder->url, NULL, 0, &name_len);
dfolder->dirname = calloc (name_len + 1, sizeof (char));
if (dfolder->dirname == NULL)
{
free (dfolder);
folder->data = NULL;
return ENOMEM;
}
url_get_path (folder->url, dfolder->dirname, name_len + 1, NULL);
folder->_destroy = folder_mbox_destroy;
folder->_open = folder_mbox_open;
folder->_close = folder_mbox_close;
folder->_list = folder_mbox_list;
folder->_lsub = folder_mbox_lsub;
folder->_subscribe = folder_mbox_subscribe;
folder->_unsubscribe = folder_mbox_unsubscribe;
folder->_delete = folder_mbox_delete;
folder->_rename = folder_mbox_rename;
return 0;
}
void
folder_mbox_destroy (folder_t folder)
{
if (folder->data)
{
fmbox_t fmbox = folder->data;
if (fmbox->dirname)
free (fmbox->dirname);
if (fmbox->subscribe)
free (fmbox->subscribe);
free (folder->data);
folder->data = NULL;
}
}
/* Noop. */
static int
folder_mbox_open (folder_t folder, int flags)
{
fmbox_t fmbox = folder->data;
if (flags & MU_STREAM_CREAT)
{
return (mkdir (fmbox->dirname, S_IRWXU) == 0) ? 0 : errno;
}
(void)(flags);
return 0;
}
/* Noop. */
static int
folder_mbox_close (folder_t folder)
{
(void)(folder);
return 0;
}
static int
folder_mbox_delete (folder_t folder, const char *filename)
{
fmbox_t fmbox = folder->data;
if (filename)
{
int status = 0;
char *pathname = get_pathname (fmbox->dirname, filename);
if (pathname)
{
if (remove (pathname) != 0)
status = errno;
free (pathname);
}
else
status = ENOMEM;
return status;
}
return EINVAL;
}
static int
folder_mbox_rename (folder_t folder, const char *oldpath, const char *newpath)
{
fmbox_t fmbox = folder->data;
if (oldpath && newpath)
{
int status = 0;
char *pathold = get_pathname (fmbox->dirname, oldpath);
if (pathold)
{
char *pathnew = get_pathname (fmbox->dirname, newpath);
if (pathnew)
{
if (rename (pathold, pathnew) != 0)
status = errno;
free (pathnew);
}
else
status = ENOMEM;
free (pathold);
}
else
status = ENOMEM;
return status;
}
return EINVAL;
}
/* The listing is not recursif and we use glob() some expansion for us.
Unfortunately glob() does not expand the '~'. We also return
The full pathname so it can be use to create other folders. */
static int
folder_mbox_list (folder_t folder, const char *dirname, const char *pattern,
struct folder_list *pflist)
{
fmbox_t fmbox = folder->data;
char *pathname = NULL;
int status;
size_t num = 0;
glob_t gl;
if (dirname == NULL || dirname[0] == '\0')
dirname = (const char *)fmbox->dirname;
pathname = get_pathname (dirname, pattern);
if (pathname)
{
memset(&gl, 0, sizeof(gl));
status = glob (pathname, 0, NULL, &gl);
free (pathname);
num = gl.gl_pathc;
}
else
status = ENOMEM;
/* Build the folder list from glob. */
if (status == 0)
{
if (pflist)
{
struct list_response **plist;
plist = calloc (num, sizeof (*plist));
if (plist)
{
size_t i;
struct stat stbuf;
for (i = 0; i < num; i++)
{
plist[i] = calloc (1, sizeof (**plist));
if (plist[i] == NULL
|| (plist[i]->name = strdup (gl.gl_pathv[i])) == NULL)
{
num = i;
break;
}
if (stat (gl.gl_pathv[i], &stbuf) == 0)
{
if (S_ISDIR(stbuf.st_mode))
plist[i]->type = MU_FOLDER_ATTRIBUTE_DIRECTORY;
if (S_ISREG(stbuf.st_mode))
plist[i]->type = MU_FOLDER_ATTRIBUTE_FILE;
}
plist[i]->separator = '/';
}
}
pflist->element = plist;
pflist->num = num;
}
globfree (&gl);
}
else
{
status = (status == GLOB_NOSPACE) ? ENOMEM : EINVAL;
}
return status;
}
static int
folder_mbox_lsub (folder_t folder, const char *ref, const char *name,
struct folder_list *pflist)
{
fmbox_t fmbox = folder->data;
size_t j = 0;
if (pflist == NULL)
return EINVAL;
(void)ref;
if (name == NULL || *name == '\0')
name = "*";
if (fmbox->sublen > 0)
{
struct list_response **plist;
size_t i;
plist = calloc (fmbox->sublen, sizeof (*plist));
for (i = 0; i < fmbox->sublen; i++)
{
if (fmbox->subscribe[i]
&& fnmatch (name, fmbox->subscribe[i], 0) == 0)
{
plist[i] = calloc (1, sizeof (**plist));
if (plist[i] == NULL
|| (plist[i]->name = strdup (fmbox->subscribe[i])) == NULL)
break;
plist[i]->type = MU_FOLDER_ATTRIBUTE_FILE;
plist[i]->separator = '/';
j++;
}
}
pflist->element = plist;
}
pflist->num = j;
return 0;
}
static int
folder_mbox_subscribe (folder_t folder, const char *name)
{
fmbox_t fmbox = folder->data;
char **tmp;
size_t i;
for (i = 0; i < fmbox->sublen; i++)
{
if (fmbox->subscribe[i] && strcmp (fmbox->subscribe[i], name) == 0)
return 0;
}
tmp = realloc (fmbox->subscribe, (fmbox->sublen + 1) * sizeof (*tmp));
if (tmp == NULL)
return ENOMEM;
fmbox->subscribe = tmp;
fmbox->subscribe[fmbox->sublen] = strdup (name);
if (fmbox->subscribe[fmbox->sublen] == NULL)
return ENOMEM;
fmbox->sublen++;
return 0;
}
static int
folder_mbox_unsubscribe (folder_t folder, const char *name)
{
fmbox_t fmbox = folder->data;
size_t i;
for (i = 0; i < fmbox->sublen; i++)
{
if (fmbox->subscribe[i] && strcmp (fmbox->subscribe[i], name) == 0)
{
free (fmbox->subscribe[i]);
fmbox->subscribe[i] = NULL;
return 0;
}
}
return ENOENT;
}
static char *
get_pathname (const char *dirname, const char *basename)
{
char *pathname = NULL;
/* null basename gives dirname. */
if (basename == NULL)
pathname = (dirname) ? strdup (dirname) : strdup (".");
/* Absolute. */
else if (basename[0] == '/')
pathname = strdup (basename);
/* Relative. */
else
{
size_t len = strlen (basename);
pathname = calloc (strlen (dirname) + len + 2, sizeof (char));
if (pathname)
sprintf (pathname, "%s/%s", dirname, basename);
}
return pathname;
}
static int
folder_mbox_get_authority (folder_t folder, authority_t *pauth)
{
int status = 0;
if (folder->authority == NULL)
{
status = authority_create_null (&folder->authority, folder);
}
if (!status && pauth)
*pauth = folder->authority;
return status;
}
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/* First draft by Alain Magloire */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <mbox0.h>
#define ATTRIBUTE_IS_DELETED(flag) (flag & MU_ATTRIBUTE_DELETED)
#define ATTRIBUTE_IS_EQUAL(flag1, flag2) (flag1 == flag2)
static void mbox_destroy (mailbox_t);
/* Below are the headers field-names that we are caching for speed, it is
more or less the list of headers in ENVELOPE command from IMAP.
NOTE: These are indexed with H_.* macros defined in mbox0.h. Keep them
in sync if you add or remove something. */
const char *fhdr_table[HDRSIZE] =
{
"Bcc",
"Cc",
"Content-Language",
"Content-Transfer-Encoding",
"Content-Type",
"Date",
"From",
"In-Reply-To",
"Message-ID",
"Reference",
"Reply-To",
"Sender",
"Subject",
"To",
"X-UIDL"
};
/* Mailbox concrete implementation. */
static int mbox_open __P ((mailbox_t, int));
static int mbox_close __P ((mailbox_t));
static int mbox_get_message __P ((mailbox_t, size_t, message_t *));
/* static int mbox_get_message_by_uid __P ((mailbox_t, size_t, message_t *)); */
static int mbox_append_message __P ((mailbox_t, message_t));
static int mbox_messages_count __P ((mailbox_t, size_t *));
static int mbox_messages_recent __P ((mailbox_t, size_t *));
static int mbox_message_unseen __P ((mailbox_t, size_t *));
static int mbox_expunge0 __P ((mailbox_t, int));
static int mbox_expunge __P ((mailbox_t));
static int mbox_save_attributes __P ((mailbox_t));
static int mbox_uidvalidity __P ((mailbox_t, unsigned long *));
static int mbox_uidnext __P ((mailbox_t, size_t *));
static int mbox_scan __P ((mailbox_t, size_t, size_t *));
static int mbox_is_updated __P ((mailbox_t));
static int mbox_get_size __P ((mailbox_t, off_t *));
/* private stuff */
static int mbox_append_message0 __P ((mailbox_t, message_t, off_t *,
int, int));
static int mbox_message_uid __P ((message_t, size_t *));
static int mbox_header_fill __P ((header_t, char *, size_t, off_t,
size_t *));
static int mbox_get_body_fd __P ((stream_t, int *));
static int mbox_get_fd __P ((mbox_message_t, int *));
static int mbox_get_attr_flags __P ((attribute_t, int *));
static int mbox_set_attr_flags __P ((attribute_t, int));
static int mbox_unset_attr_flags __P ((attribute_t, int));
static int mbox_body_read __P ((stream_t, char *, size_t, off_t,
size_t *));
static int mbox_body_readline __P ((stream_t, char *, size_t, off_t,
size_t *));
static int mbox_readstream __P ((mbox_message_t, char *, size_t,
off_t, size_t *, int, off_t,
off_t));
static int mbox_stream_size __P((stream_t stream, off_t *psize));
static int mbox_header_size __P ((header_t, size_t *));
static int mbox_header_lines __P ((header_t, size_t *));
static int mbox_body_size __P ((body_t, size_t *));
static int mbox_body_lines __P ((body_t, size_t *));
static int mbox_envelope_sender __P ((envelope_t, char *, size_t,
size_t *));
static int mbox_envelope_date __P ((envelope_t, char *, size_t,
size_t *));
static int mbox_tmpfile __P ((mailbox_t, char **pbox));
/* Allocate the mbox_data_t struct(concrete mailbox), but don't do any
parsing on the name or even test for existence. However we do strip any
leading "mbox:" part of the name, this is suppose to be the
protocol/scheme name. */
int
_mailbox_mbox_init (mailbox_t mailbox)
{
mbox_data_t mud;
size_t name_len;
if (mailbox == NULL)
return EINVAL;
/* Allocate specific mbox data. */
mud = mailbox->data = calloc (1, sizeof (*mud));
if (mailbox->data == NULL)
return ENOMEM;
/* Back pointer. */
mud->mailbox = mailbox;
/* Copy the name:
We do not do any further interpretation after the scheme "mbox:"
Because for example on distributed system like QnX4 a file name is
//390/etc/passwd. So the best approach is to let the OS handle it
for example if we receive: "mbox:///var/mail/alain" the mailbox name
will be "///var/mail/alain", we let open() do the right thing.
So it will let things like this "mbox://390/var/mail/alain" where
the "//" _is_ part of the filename, pass correctely. */
url_get_path (mailbox->url, NULL, 0, &name_len);
mud->name = calloc (name_len + 1, sizeof (char));
if (mud->name == NULL)
{
free (mud);
mailbox->data = NULL;
return ENOMEM;
}
url_get_path (mailbox->url, mud->name, name_len + 1, NULL);
mud->state = MBOX_NO_STATE;
/* Overloading the defaults. */
mailbox->_destroy = mbox_destroy;
mailbox->_open = mbox_open;
mailbox->_close = mbox_close;
/* Overloading of the entire mailbox object methods. */
mailbox->_get_message = mbox_get_message;
mailbox->_append_message = mbox_append_message;
mailbox->_messages_count = mbox_messages_count;
mailbox->_messages_recent = mbox_messages_recent;
mailbox->_message_unseen = mbox_message_unseen;
mailbox->_expunge = mbox_expunge;
mailbox->_save_attributes = mbox_save_attributes;
mailbox->_uidvalidity = mbox_uidvalidity;
mailbox->_uidnext = mbox_uidnext;
mailbox->_scan = mbox_scan;
mailbox->_is_updated = mbox_is_updated;
mailbox->_get_size = mbox_get_size;
/* Set our properties. */
{
property_t property = NULL;
mailbox_get_property (mailbox, &property);
property_set_value (property, "TYPE", "MBOX", 1);
}
MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE, "mbox_init(%s)\n", mud->name);
return 0; /* okdoke */
}
/* Free all ressources associated with Unix concrete mailbox. */
static void
mbox_destroy (mailbox_t mailbox)
{
if (mailbox->data)
{
size_t i;
mbox_data_t mud = mailbox->data;
MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE,
"mbox_destroy (%s)\n", mud->name);
monitor_wrlock (mailbox->monitor);
for (i = 0; i < mud->umessages_count; i++)
{
mbox_message_t mum = mud->umessages[i];
if (mum)
{
size_t j;
message_destroy (&(mum->message), mum);
for (j = 0; j < HDRSIZE; j++)
if (mum->fhdr[j])
free (mum->fhdr[j]);
free (mum);
}
}
if (mud->umessages)
free (mud->umessages);
if (mud->name)
free (mud->name);
free (mud);
mailbox->data = NULL;
monitor_unlock (mailbox->monitor);
}
}
/* Open the file. For MU_STREAM_READ, the code tries mmap() first and fall
back to normal file. */
static int
mbox_open (mailbox_t mailbox, int flags)
{
mbox_data_t mud = mailbox->data;
int status = 0;
if (mud == NULL)
return EINVAL;
mailbox->flags = flags;
/* Get a stream. */
if (mailbox->stream == NULL)
{
/* We do not try to mmap for CREAT or APPEND, it is not supported. */
status = (flags & MU_STREAM_CREAT)
|| (mailbox->flags & MU_STREAM_APPEND);
/* Try to mmap () the file first. */
if (status == 0)
{
status = mapfile_stream_create (&mailbox->stream, mud->name, mailbox->flags);
if (status == 0)
{
status = stream_open (mailbox->stream);
}
}
/* Fall back to normal file if mmap() failed. */
if (status != 0)
{
status = file_stream_create (&mailbox->stream, mud->name, mailbox->flags);
if (status != 0)
return status;
status = stream_open (mailbox->stream);
}
/* All failed, bail out. */
if (status != 0)
{
stream_destroy (&mailbox->stream, NULL);
return status;
}
/* Even on top, of normal FILE *, lets agressively cache. But this
may not be suitable for system tight on memory. */
stream_setbufsiz (mailbox->stream, BUFSIZ);
}
else
{
status = stream_open (mailbox->stream);
if (status != 0)
return status;
}
MAILBOX_DEBUG2 (mailbox, MU_DEBUG_TRACE, "mbox_open(%s, 0x%x)\n",
mud->name, mailbox->flags);
if (mailbox->locker == NULL)
status = locker_create (&(mailbox->locker), mud->name, 0);
return status;
}
static int
mbox_close (mailbox_t mailbox)
{
mbox_data_t mud = mailbox->data;
/* size_t i; */
if (mud == NULL)
return EINVAL;
MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE, "mbox_close(%s)\n", mud->name);
/* Make sure that we do not hold any file locking. */
locker_unlock (mailbox->locker);
#if 0
/* RFC: I'm not sure on the right approach especially if the client is
working in disconnected mode, where it can mailbox_close/mailbox_open
for each request, maybe we should keep them for a while. */
monitor_wrlock (mailbox->monitor);
/* Before closing we need to remove all the messages
- to reclaim the memory
- to prepare for another scan. */
for (i = 0; i < mud->umessages_count; i++)
{
mbox_message_t mum = mud->umessages[i];
/* Destroy the attach messages. */
if (mum)
{
size_t j;
message_destroy (&(mum->message), mum);
for (j = 0; j < HDRSIZE; j++)
if (mum->fhdr[j])
free (mum->fhdr[j]);
free (mum);
}
}
if (mud->umessages)
free (mud->umessages);
mud->umessages = NULL;
mud->messages_count = mud->umessages_count = 0;
mud->size = 0;
mud->uidvalidity = 0;
mud->uidnext = 0;
monitor_unlock (mailbox->monitor);
#endif
return stream_close (mailbox->stream);
}
/* Cover function that call the real thing, mbox_scan(), with
notification set. */
static int
mbox_scan (mailbox_t mailbox, size_t msgno, size_t *pcount)
{
size_t i;
mbox_data_t mud = mailbox->data;
MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE, "mbox_scan(%s)\n", mud->name);
if (! mbox_is_updated (mailbox))
return mbox_scan0 (mailbox, msgno, pcount, 1);
/* Since the mailbox is already updated fake the scan. */
if (msgno > 0)
msgno--; /* The fist message is number "1", decremente for the C array. */
for (i = msgno; i < mud->messages_count; i++)
{
if (observable_notify (mailbox->observable, MU_EVT_MESSAGE_ADD) != 0)
break;
if (((i +1) % 50) == 0)
{
observable_notify (mailbox->observable, MU_EVT_MAILBOX_PROGRESS);
}
}
return 0;
}
/* FIXME: How to handle a shrink ? meaning, the &^$^@%#@^& user start two
browsers and deleted emails in one session. My views is that we should
scream bloody murder and hunt them with a machette. But for now just play
dumb, but maybe the best approach is to pack our things and leave
.i.e exit()/abort(). */
static int
mbox_is_updated (mailbox_t mailbox)
{
off_t size = 0;
mbox_data_t mud = mailbox->data;
if (stream_size (mailbox->stream, &size) != 0)
return 0;
if (size < mud->size)
{
observable_notify (mailbox->observable, MU_EVT_MAILBOX_CORRUPT);
/* And be verbose. ? */
mu_error ("* BAD : Mailbox corrupted, shrank size\n");
/* FIXME: should I crash. */
return 0;
}
return (mud->size == size);
}
/* Try to create an uniq file, we no race conditions. */
static int
mbox_tmpfile (mailbox_t mailbox, char **pbox)
{
const char *tmpdir;
int fd;
const char *basename;
mbox_data_t mud = mailbox->data;
/* P_tmpdir should be in <stdio.h>. */
#ifndef P_tmpdir
# define P_tmpdir "/tmp"
#endif
basename = strrchr (mud->name, '/');
if (basename)
basename++;
else
basename = mud->name;
tmpdir = getenv ("TMPDIR") ? getenv ("TMPDIR") : P_tmpdir;
/* (separator + null) == 2 + XXXXXX == 6 + ... */
*pbox = calloc (strlen (tmpdir) + /* '/' */ 1 + /*strlen ("MU_")*/ 3 +
strlen (basename) + /*strlen ("_XXXXXX")*/ 7 + /*null*/1,
sizeof (**pbox));
if (*pbox == NULL)
return -1;
sprintf (*pbox, "%s/MU_%s_XXXXXX", tmpdir, basename);
#ifdef HAVE_MKSTEMP
fd = mkstemp (*pbox);
#else
/* Create the file. It must not exist. If it does exist, fail. */
if (mktemp (*pbox))
{
fd = open (*pbox, O_RDWR|O_CREAT|O_EXCL, 0600);
}
else
{
free (*pbox);
fd = -1;
}
#endif
return fd;
}
/* For the expunge bits we took a very cautionnary approach, meaning
we create a temporary mailbox in the tmpdir copy all the message not mark
deleted(Actually we copy all the message that may have been modified
i.e new header values set; UIDL or UID or etc ....
and skip the deleted ones, truncate the real mailbox to the desired size
and overwrite with the tmp mailbox. The approach to do everyting
in core is tempting but require
- to much memory, it is not rare nowadays to have 30 Megs mailbox,
- also there is danger for filesystems with quotas,
- if we run out of disk space everything is lost.
- or some program may not respect the advisory lock and decide to append
a new message while your expunging etc ...
The real downside to the approach is that when things go wrong
the temporary file may be left in /tmp, which is not all that bad
because at least, we have something to recuperate when failure. */
static int
mbox_expunge0 (mailbox_t mailbox, int remove_deleted)
{
mbox_data_t mud = mailbox->data;
mbox_message_t mum;
int status = 0;
sigset_t signalset;
int tempfile;
size_t i, j, dirty; /* dirty will indicate the first modified message. */
off_t marker = 0; /* marker will be the position to truncate. */
off_t total = 0;
char *tmpmboxname = NULL;
mailbox_t tmpmailbox = NULL;
size_t save_imapbase = 0; /* uidvalidity is save in the first message. */
#ifdef WITH_PTHREAD
int state;
#endif
if (mud == NULL)
return EINVAL;
MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE, "mbox_expunge (%s)\n", mud->name);
/* Noop. */
if (mud->messages_count == 0)
return 0;
/* Find the first dirty(modified) message. */
for (dirty = 0; dirty < mud->messages_count; dirty++)
{
mum = mud->umessages[dirty];
/* Message may have been tampered, break here. */
if ((mum->attr_flags & MU_ATTRIBUTE_MODIFIED) ||
(mum->attr_flags & MU_ATTRIBUTE_DELETED) ||
(mum->message && message_is_modified (mum->message)))
break;
}
/* Did something change ? */
if (dirty == mud->messages_count)
return 0; /* Nothing change, bail out. */
/* Create a temporary file. */
tempfile = mbox_tmpfile (mailbox, &tmpmboxname);
if (tempfile == -1)
{
if (tmpmboxname)
free (tmpmboxname);
mu_error ("Failed to create temporary file when expunging.\n");
return errno;
}
/* This is redundant, we go to the loop again. But it's more secure here
since we don't want to be disturb when expunging. Destroy all the
messages mark for deletion. */
if (remove_deleted)
{
for (j = 0; j < mud->messages_count; j++)
{
mum = mud->umessages[j];
if (mum && mum->message && ATTRIBUTE_IS_DELETED (mum->attr_flags))
message_destroy (&(mum->message), mum);
}
}
/* Create temporary mailbox_t. */
{
mbox_data_t tmp_mud;
char *m = alloca (5 + strlen (tmpmboxname) + 1);
/* Try via the mbox: protocol. */
sprintf (m, "mbox:%s", tmpmboxname);
status = mailbox_create (&tmpmailbox, m);
if (status != 0)
{
/* Do not give up just yet, maybe they register the path_record. */
sprintf (m, "%s", tmpmboxname);
status = mailbox_create (&tmpmailbox, m);
if (status != 0)
{
/* Ok give up. */
close (tempfile);
remove (tmpmboxname);
free (tmpmboxname);
return status;
}
}
/* Must be flag CREATE if not the mailbox_open will try to mmap()
the file. */
status = mailbox_open (tmpmailbox, MU_STREAM_CREAT | MU_STREAM_RDWR);
if (status != 0)
{
close (tempfile);
remove (tmpmboxname);
free (tmpmboxname);
return status;
}
close (tempfile); /* This one is useless the mailbox have its own. */
tmp_mud = tmpmailbox->data;
/* May need when appending. */
tmp_mud->uidvalidity = mud->uidvalidity;
tmp_mud->uidnext = mud->uidnext;
}
/* Get the File lock. */
if ((status = locker_lock (mailbox->locker)) != 0)
{
mailbox_close (tmpmailbox);
mailbox_destroy (&tmpmailbox);
remove (tmpmboxname);
free (tmpmboxname);
mu_error ("Failed to grab the lock: %s\n", mu_strerror(status));
return status;
}
/* Critical section, we can not allowed signal here. */
#ifdef WITH_PTHREAD
pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &state);
#endif
sigemptyset (&signalset);
sigaddset (&signalset, SIGTERM);
sigaddset (&signalset, SIGHUP);
sigaddset (&signalset, SIGTSTP);
sigaddset (&signalset, SIGINT);
sigaddset (&signalset, SIGWINCH);
sigprocmask (SIG_BLOCK, &signalset, 0);
/* Set the marker position. */
marker = mud->umessages[dirty]->header_from;
total = 0;
/* Copy to the temporary mailbox emails not mark deleted. */
for (i = dirty; i < mud->messages_count; i++)
{
mum = mud->umessages[i];
/* Skip it, if mark for deletion. */
if (remove_deleted && ATTRIBUTE_IS_DELETED (mum->attr_flags))
{
/* We save the uidvalidity in the first message, if it is being
deleted we need to move the uidvalidity to the first available
(non-deleted) message. */
if (i == save_imapbase)
{
save_imapbase = i + 1;
if (save_imapbase < mud->messages_count)
(mud->umessages[save_imapbase])->attr_flags |= MU_ATTRIBUTE_MODIFIED;
}
continue;
}
/* Do the expensive mbox_append_message0() only if mark dirty. */
if ((mum->attr_flags & MU_ATTRIBUTE_MODIFIED) ||
(mum->message && message_is_modified (mum->message)))
{
/* The message was not instanciated, probably the dirty flag was
set by mbox_scan(), create one here. */
if (mum->message == 0)
{
message_t msg;
status = mbox_get_message (mailbox, i + 1, &msg);
if (status != 0)
{
mu_error ("Error expunge:%d: %s", __LINE__,
mu_strerror (status));
goto bailout0;
}
}
status = mbox_append_message0 (tmpmailbox, mum->message,
&total, 1, (i == save_imapbase));
if (status != 0)
{
mu_error ("Error expunge:%d: %s", __LINE__,
mu_strerror (status));
goto bailout0;
}
/* Clear the dirty bits. */
mum->attr_flags &= ~MU_ATTRIBUTE_MODIFIED;
message_clear_modified (mum->message);
}
else
{
/* Nothing changed copy the message straight. */
char buffer [1024];
size_t n;
off_t offset = mum->header_from;
size_t len = mum->body_end - mum->header_from;
while (len > 0)
{
n = (len < sizeof (buffer)) ? len : sizeof (buffer);
if ((status = stream_read (mailbox->stream, buffer, n, offset,
&n) != 0)
|| (status = stream_write (tmpmailbox->stream, buffer, n,
total, &n) != 0))
{
mu_error ("Error expunge:%d: %s", __LINE__,
mu_strerror (status));
goto bailout0;
}
len -= n;
total += n;
offset += n;
}
/* Add the newline separator. */
status = stream_write (tmpmailbox->stream, "\n", 1, total, &n);
if (status != 0)
{
mu_error ("Error expunge:%d: %s", __LINE__,
mu_strerror (status));
goto bailout0;
}
total++;
}
} /* for (;;) */
/* Caution: before ftruncate()ing the file see
- if we've receive new mails. Some programs may not respect the lock,
- or the lock was held for too long.
- The mailbox may not have been properly updated before expunging. */
{
off_t size = 0;
if (stream_size (mailbox->stream, &size) == 0)
{
off_t len = size - mud->size;
off_t offset = mud->size;
char buffer [1024];
size_t n = 0;
if (len > 0 )
{
while ((status = stream_read (mailbox->stream, buffer,
sizeof (buffer), offset, &n)) == 0
&& n > 0)
{
status = stream_write (tmpmailbox->stream, buffer, n,
total, &n);
if (status != 0)
{
mu_error ("Error expunge:%d: %s", __LINE__,
mu_strerror (status));
goto bailout0;
}
total += n;
offset += n;
}
}
else if (len < 0)
{
/* Corrupted mailbox. */
mu_error ("Error expunge:%d: %s", __LINE__,
mu_strerror (status));
goto bailout0;
}
}
} /* End of precaution. */
/* Seek and rewrite it. */
if (total > 0)
{
char buffer [1024];
size_t n = 0;
off_t off = 0;
off_t offset = marker;
while ((status = stream_read (tmpmailbox->stream, buffer,
sizeof (buffer), off, &n)) == 0
&& n > 0)
{
status = stream_write (mailbox->stream, buffer, n, offset, &n);
if (status != 0)
{
mu_error ("Error expunge:%d: %s\n", __LINE__,
mu_strerror (status));
goto bailout;
}
off += n;
offset += n;
}
}
/* Flush/truncation. Need to flush before truncate. */
stream_flush (mailbox->stream);
status = stream_truncate (mailbox->stream, total + marker);
if (status != 0)
{
mu_error ("Error expunging:%d: %s\n", __LINE__,
mu_strerror (status));
goto bailout;
}
/* Don't remove the tmp mbox in case of errors, when writing back. */
bailout0:
remove (tmpmboxname);
bailout:
free (tmpmboxname);
/* Release the File lock. */
locker_unlock (mailbox->locker);
mailbox_close (tmpmailbox);
mailbox_destroy (&tmpmailbox);
/* Reenable signal. */
#ifdef WITH_PTHREAD
pthread_setcancelstate (state, &state);
#endif
sigprocmask (SIG_UNBLOCK, &signalset, 0);
/* We need to readjust the pointers.
It is a little hairy because we need to keep the message pointers alive
So we are going through the array and "defragmentize". For example
in (1 2 3 4) if 2 was deleted we need to move 3 4 up by one etc ..
*/
if (status == 0)
{
size_t dlast;
monitor_wrlock (mailbox->monitor);
for (j = dirty, dlast = mud->messages_count - 1;
j <= dlast; j++)
{
/* Clear all the references, any attach messages been already
destroy above. */
mum = mud->umessages[j];
if (remove_deleted && ATTRIBUTE_IS_DELETED (mum->attr_flags))
{
if ((j + 1) <= dlast)
{
/* Move all the pointers up. So the message pointer
part of mum will be at the right position. */
memmove (mud->umessages + j, mud->umessages + j + 1,
(dlast - j) * sizeof (mum));
#if 0
mum->header_from = mum->header_from_end = 0;
mum->body = mum->body_end = 0;
mum->header_lines = mum->body_lines = 0;
#endif
for (i = 0; i < HDRSIZE; i++)
if (mum->fhdr[i])
{
free (mum->fhdr[i]);
mum->fhdr[i] = NULL;
}
memset (mum, 0, sizeof (*mum));
/* We are not free()ing the useless mum, but instead
we put it back in the pool, to be reuse. */
mud->umessages[dlast] = mum;
dlast--;
/* Set mum to the new value after the memmove so it
gets cleared to. */
mum = mud->umessages[j];
}
else
{
for (i = 0; i < HDRSIZE; i++)
if (mum->fhdr[i])
{
free (mum->fhdr[i]);
mum->fhdr[i] = NULL;
}
memset (mum, 0, sizeof (*mum));
}
}
mum->header_from = mum->header_from_end = 0;
mum->body = mum->body_end = 0;
mum->header_lines = mum->body_lines = 0;
for (i = 0; i < HDRSIZE; i++)
if (mum->fhdr[i])
{
free (mum->fhdr[i]);
mum->fhdr[i] = NULL;
}
}
monitor_unlock (mailbox->monitor);
/* This is should reset the messages_count, the last argument 0 means
not to send event notification. */
mbox_scan0 (mailbox, dirty, NULL, 0);
}
return status;
}
static int
mbox_expunge (mailbox_t mailbox)
{
return mbox_expunge0 (mailbox, 1);
}
static int
mbox_save_attributes (mailbox_t mailbox)
{
return mbox_expunge0 (mailbox, 0);
}
static int
mbox_message_uid (message_t msg, size_t *puid)
{
mbox_message_t mum = message_get_owner (msg);
if (puid)
*puid = mum->uid;
return 0;
}
static int
mbox_get_body_fd (stream_t is, int *pfd)
{
body_t body = stream_get_owner (is);
message_t msg = body_get_owner (body);
mbox_message_t mum = message_get_owner (msg);
return mbox_get_fd (mum, pfd);
}
static int
mbox_get_fd (mbox_message_t mum, int *pfd)
{
int status;
if (mum == NULL)
return EINVAL;
status = stream_get_fd (mum->mud->mailbox->stream, pfd);
return status;
}
static int
mbox_get_attr_flags (attribute_t attr, int *pflags)
{
message_t msg = attribute_get_owner (attr);
mbox_message_t mum = message_get_owner (msg);
if (mum == NULL)
return EINVAL;
if (pflags)
*pflags = mum->attr_flags;
return 0;
}
static int
mbox_set_attr_flags (attribute_t attr, int flags)
{
message_t msg = attribute_get_owner (attr);
mbox_message_t mum = message_get_owner (msg);
if (mum == NULL)
return EINVAL;
mum->attr_flags |= flags;
return 0;
}
static int
mbox_unset_attr_flags (attribute_t attr, int flags)
{
message_t msg = attribute_get_owner (attr);
mbox_message_t mum = message_get_owner (msg);
if (mum == NULL)
return EINVAL;
mum->attr_flags &= ~flags;
return 0;
}
static int
mbox_body_readline (stream_t is, char *buffer, size_t buflen,
off_t off, size_t *pnread)
{
body_t body = stream_get_owner (is);
message_t msg = body_get_owner (body);
mbox_message_t mum = message_get_owner (msg);
return mbox_readstream (mum, buffer, buflen, off, pnread, 1,
mum->body, mum->body_end);
}
static int
mbox_body_read (stream_t is, char *buffer, size_t buflen,
off_t off, size_t *pnread)
{
body_t body = stream_get_owner (is);
message_t msg = body_get_owner (body);
mbox_message_t mum = message_get_owner (msg);
return mbox_readstream (mum, buffer, buflen, off, pnread, 0,
mum->body, mum->body_end);
}
static int
mbox_readstream (mbox_message_t mum, char *buffer, size_t buflen,
off_t off, size_t *pnread, int isreadline,
off_t start, off_t end)
{
size_t nread = 0;
int status = 0;
if (buffer == NULL || buflen == 0)
{
if (pnread)
*pnread = nread;
return 0;
}
monitor_rdlock (mum->mud->mailbox->monitor);
#ifdef WITH_PTHREAD
/* read() is cancellation point since we're doing a potentially
long operation. Lets make sure we clean the state. */
pthread_cleanup_push (mbox_cleanup, (void *)mum->mud->mailbox);
#endif
{
off_t ln = end - (start + off);
if (ln > 0)
{
/* Position the file pointer and the buffer. */
nread = ((size_t)ln < buflen) ? (size_t)ln : buflen;
if (isreadline)
status = stream_readline (mum->mud->mailbox->stream, buffer, buflen,
start + off, &nread);
else
status = stream_read (mum->mud->mailbox->stream, buffer, nread,
start + off, &nread);
}
}
monitor_unlock (mum->mud->mailbox->monitor);
#ifdef WITH_PTHREAD
pthread_cleanup_pop (0);
#endif
if (pnread)
*pnread = nread;
return status;
}
static int
mbox_header_fill (header_t header, char *buffer, size_t len,
off_t off, size_t *pnread)
{
message_t msg = header_get_owner (header);
mbox_message_t mum = message_get_owner (msg);
size_t j;
/* Since we are filling the header there is no need for the cache headers
discard them. */
for (j = 0; j < HDRSIZE; j++)
{
if (mum->fhdr[j])
{
free (mum->fhdr[j]);
mum->fhdr[j] = NULL;
}
}
return mbox_readstream (mum, buffer, len, off, pnread, 0,
mum->header_from_end, mum->body);
}
static int
mbox_header_get_fvalue (header_t header, const char *name, char *buffer,
size_t buflen, size_t *pnread)
{
size_t i, fv_len = 0;
message_t msg = header_get_owner (header);
mbox_message_t mum = message_get_owner (msg);
int err = ENOENT;
for (i = 0; i < HDRSIZE; i++)
{
if (*name == *(fhdr_table[i]) && strcasecmp (fhdr_table[i], name) == 0)
{
if (mum->fhdr[i])
{
fv_len = strlen (mum->fhdr[i]);
if (buffer && buflen > 0)
{
/* For the null. */
buflen--;
fv_len = (fv_len < buflen) ? fv_len : buflen;
memcpy (buffer, mum->fhdr[i], fv_len);
buffer[fv_len] = '\0';
}
err = 0;
}
else
err = ENOENT;
break;
}
}
if (pnread)
*pnread = fv_len;
return err;
}
static int
mbox_header_size (header_t header, size_t *psize)
{
message_t msg = header_get_owner (header);
mbox_message_t mum = message_get_owner (msg);
if (mum == NULL)
return EINVAL;
if (psize)
*psize = mum->body - mum->header_from_end;
return 0;
}
static int
mbox_header_lines (header_t header, size_t *plines)
{
message_t msg = header_get_owner (header);
mbox_message_t mum = message_get_owner (msg);
if (mum == NULL)
return EINVAL;
if (plines)
*plines = mum->header_lines;
return 0;
}
static int
mbox_body_size (body_t body, size_t *psize)
{
message_t msg = body_get_owner (body);
mbox_message_t mum = message_get_owner (msg);
if (mum == NULL)
return EINVAL;
if (psize)
*psize = mum->body_end - mum->body;
return 0;
}
static int
mbox_stream_size (stream_t stream, off_t *psize)
{
body_t body = stream_get_owner (stream);
return mbox_body_size (body, (size_t*) psize);
}
static int
mbox_body_lines (body_t body, size_t *plines)
{
message_t msg = body_get_owner (body);
mbox_message_t mum = message_get_owner (msg);
if (mum == NULL)
return EINVAL;
if (plines)
*plines = mum->body_lines;
return 0;
}
static int
mbox_envelope_date (envelope_t envelope, char *buf, size_t len,
size_t *pnwrite)
{
message_t msg = envelope_get_owner (envelope);
mbox_message_t mum = message_get_owner (msg);
size_t n = 0;
int status;
char buffer[512];
char *s;
if (mum == NULL)
return EINVAL;
status = stream_readline (mum->mud->mailbox->stream, buffer, sizeof(buffer),
mum->header_from, &n);
if (status != 0)
{
if (pnwrite)
*pnwrite = 0;
return status;
}
/* Format: "From [sender] [date]" */
/* strlen ("From ") == 5 */
if (n > 5 && (s = strchr (buffer + 5, ' ')) != NULL)
{
if (buf && len > 0)
{
len--; /* Leave space for the null. */
strncpy (buf, s + 1, len)[len] = '\0';
len = strlen (buf);
}
else
len = strlen (s + 1);
}
else
len = 0;
if (pnwrite)
*pnwrite = len;
return 0;
}
static int
mbox_envelope_sender (envelope_t envelope, char *buf, size_t len,
size_t *pnwrite)
{
message_t msg = envelope_get_owner (envelope);
mbox_message_t mum = message_get_owner (msg);
size_t n = 0;
int status;
char buffer[512];
char *s;
if (mum == NULL)
return EINVAL;
status = stream_readline (mum->mud->mailbox->stream, buffer, sizeof(buffer),
mum->header_from, &n);
if (status != 0)
{
if (pnwrite)
*pnwrite = 0;
return status;
}
/* Format: "From [sender] [date]" */
/* strlen ("From ") == 5 */
if (n > 5 && (s = strchr (buffer + 5, ' ')) != NULL)
{
/* Put a NULL to isolate the sender string, make a C string. */
*s = '\0';
if (buf && len > 0)
{
len--; /* leave space for the null */
strncpy (buf, buffer + 5, len)[len] = '\0';
len = strlen (buf);
}
else
len = strlen (buffer + 5);
}
else
len = 0;
if (pnwrite)
*pnwrite = len;
return 0;
}
static int
mbox_get_message (mailbox_t mailbox, size_t msgno, message_t *pmsg)
{
int status;
mbox_data_t mud = mailbox->data;
mbox_message_t mum;
message_t msg = NULL;
/* Sanity checks. */
if (pmsg == NULL || mud == NULL)
return EINVAL;
/* If we did not start a scanning yet do it now. */
if (mud->messages_count == 0)
{
status = mbox_scan0 (mailbox, 1, NULL, 0);
if (status != 0)
return status;
}
/* Second sanity: check the message number. */
if (!(mud->messages_count > 0
&& msgno > 0
&& msgno <= mud->messages_count))
return EINVAL;
mum = mud->umessages[msgno - 1];
/* Check if we already have it. */
if (mum->message)
{
if (pmsg)
*pmsg = mum->message;
return 0;
}
MAILBOX_DEBUG2 (mailbox, MU_DEBUG_TRACE, "mbox_get_message(%s, %d)\n",
mud->name, msgno);
/* Get an empty message struct. */
status = message_create (&msg, mum);
if (status != 0)
return status;
/* Set the header. */
{
header_t header = NULL;
status = header_create (&header, NULL, 0, msg);
if (status != 0)
{
message_destroy (&msg, mum);
return status;
}
header_set_fill (header, mbox_header_fill, msg);
header_set_get_fvalue (header, mbox_header_get_fvalue, msg);
header_set_size (header, mbox_header_size, msg);
header_set_lines (header, mbox_header_lines, msg);
message_set_header (msg, header, mum);
}
/* Set the attribute. */
{
attribute_t attribute;
status = attribute_create (&attribute, msg);
if (status != 0)
{
message_destroy (&msg, mum);
return status;
}
attribute_set_get_flags (attribute, mbox_get_attr_flags, msg);
attribute_set_set_flags (attribute, mbox_set_attr_flags, msg);
attribute_set_unset_flags (attribute, mbox_unset_attr_flags, msg);
message_set_attribute (msg, attribute, mum);
}
/* Prepare the body. */
{
body_t body = NULL;
stream_t stream = NULL;
if ((status = body_create (&body, msg)) != 0
|| (status = stream_create (&stream,
mailbox->flags | MU_STREAM_SEEKABLE,
body)) != 0)
{
body_destroy (&body, msg);
stream_destroy (&stream, body);
message_destroy (&msg, mum);
return status;
}
stream_set_read (stream, mbox_body_read, body);
stream_set_readline (stream, mbox_body_readline, body);
stream_set_fd (stream, mbox_get_body_fd, body);
stream_set_size (stream, mbox_stream_size, body);
body_set_stream (body, stream, msg);
body_set_size (body, mbox_body_size, msg);
body_set_lines (body, mbox_body_lines, msg);
message_set_body (msg, body, mum);
}
/* Set the envelope. */
{
envelope_t envelope= NULL;
status = envelope_create (&envelope, msg);
if (status != 0)
{
message_destroy (&msg, mum);
return status;
}
envelope_set_sender (envelope, mbox_envelope_sender, msg);
envelope_set_date (envelope, mbox_envelope_date, msg);
message_set_envelope (msg, envelope, mum);
}
/* Set the UID. */
message_set_uid (msg, mbox_message_uid, mum);
/* Attach the message to the mailbox mbox data. */
mum->message = msg;
message_set_mailbox (msg, mailbox, mum);
*pmsg = msg;
return 0;
}
static int
mbox_append_message (mailbox_t mailbox, message_t msg)
{
int status = 0;
mbox_data_t mud = mailbox->data;
if (msg == NULL || mud == NULL)
return EINVAL;
MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE, "mbox_append_message (%s)\n",
mud->name);
switch (mud->state)
{
case MBOX_NO_STATE:
if ((status = locker_lock (mailbox->locker)) != 0)
{
MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE,
"mbox_append_message: %s\n",
mu_strerror(status));
return status;
}
default:
{
off_t size;
/* Move to the end of the file, not necesary if _APPEND mode. */
if ((status = stream_size (mailbox->stream, &size)) != 0
|| (status = mbox_append_message0 (mailbox, msg, &size, 0, 0)) != 0)
{
if (status != EAGAIN)
locker_unlock (mailbox->locker);
return status;
}
}
}
locker_unlock (mailbox->locker);
return 0;
}
/* FIXME: We need to escape body line that begins with "From ", this
will required to read the body by line instead of by chuncks hurting
perfomance big time when expunging. But should not this be the
responsability of the client ? */
static int
mbox_append_message0 (mailbox_t mailbox, message_t msg, off_t *psize,
int is_expunging, int first)
{
mbox_data_t mud = mailbox->data;
int status = 0;
size_t n = 0;
char nl = '\n';
switch (mud->state)
{
case MBOX_NO_STATE:
/* Allocate memory for the sender/date buffers. */
mud->sender = calloc (128, sizeof (char));
if (mud->sender == NULL)
return ENOMEM;
mud->date = calloc (128, sizeof (char));
if (mud->date == NULL)
{
free (mud->sender);
mud->sender = NULL;
return ENOMEM;
}
mud->off = 0;
mud->state = MBOX_STATE_APPEND_SENDER;
case MBOX_STATE_APPEND_SENDER:
/* Generate the sender for the "From " separator. */
{
char *s;
size_t len = 0;
envelope_t envelope = NULL;
message_get_envelope (msg, &envelope);
status = envelope_sender (envelope, mud->sender, 128, &len);
if (status != 0)
{
if (status != EAGAIN)
{
free (mud->sender);
free (mud->date);
mud->date = mud->sender = NULL;
mud->state = MBOX_NO_STATE;
}
return status;
}
/* Nuke trailing newline. */
s = memchr (mud->sender, nl, len);
if (s)
*s = '\0';
mud->state = MBOX_STATE_APPEND_DATE;
}
case MBOX_STATE_APPEND_DATE:
/* Generate a date for the "From " separator. */
{
char *s;
size_t len = 0;
envelope_t envelope = NULL;
char buffer[1024];
message_get_envelope (msg, &envelope);
status = envelope_date (envelope, mud->date, 128, &len);
if (status != 0)
{
if (status != EAGAIN)
{
free (mud->sender);
free (mud->date);
mud->date = mud->sender = NULL;
mud->state = MBOX_NO_STATE;
}
return status;
}
/* Nuke trailing newline. */
s = memchr (mud->date, nl, len);
if (s)
*s = '\0';
/* Write the separator to the mailbox. */
n = snprintf (buffer, sizeof (buffer), "From %s %s",
mud->sender, mud->date);
stream_write (mailbox->stream, buffer, n, *psize, &n);
*psize += n;
/* Add the newline, the above may be truncated. */
stream_write (mailbox->stream, &nl , 1, *psize, &n);
*psize += n;
free (mud->sender);
free (mud->date);
mud->sender = mud->date = NULL;
/* If we are not expunging get the message in one block via the stream
message instead of the header/body. This is good for POP where
there is no separation between header and body(RETR). */
if (! is_expunging)
{
mud->state = MBOX_STATE_APPEND_MESSAGE;
break;
}
mud->state = MBOX_STATE_APPEND_HEADER;
}
case MBOX_STATE_APPEND_HEADER:
/* Append the Header. */
{
char buffer[1024];
size_t nread = 0;
stream_t is = NULL;
header_t header = NULL;
message_get_header (msg, &header);
header_get_stream (header, &is);
do
{
status = stream_readline (is, buffer, sizeof (buffer), mud->off,
&nread);
if (status != 0)
{
if (status != EAGAIN)
{
mud->state = MBOX_NO_STATE;
mud->off = 0;
}
return status;
}
mud->off += nread;
if (*buffer == '\n')
break;
/* We do not copy the Status since it is rewritten by the
attribute code below. Ditto for X-UID and X-IMAPBase.
FIXME:
- We have a problem here the header may not fit the buffer.
- Should we skip the IMAP "X-Status"? */
if ((strncasecmp (buffer, "Status", 6) == 0)
|| (strncasecmp (buffer, "X-IMAPbase", 10) == 0)
/* FIXME: isn't the length of "X-UID" 5, not 4? And
this will match X-UID and X-UIDL, is this intended? */
|| (strncasecmp (buffer, "X-UID", 4) == 0
&& (buffer[5] == ':' || buffer[5] == ' '
|| buffer[5] == '\t')))
continue;
status = stream_write (mailbox->stream, buffer, nread,
*psize, &n);
if (status != 0)
break;
*psize += n;
}
while (nread > 0);
mud->off = 0;
/* Rewrite the X-IMAPbase marker. */
if (first && is_expunging)
{
n = sprintf (buffer, "X-IMAPbase: %lu %u\n",
(unsigned long) mud->uidvalidity,
(unsigned) mud->uidnext);
stream_write (mailbox->stream, buffer, n, *psize, &n);
*psize += n;
}
mud->state = MBOX_STATE_APPEND_ATTRIBUTE;
}
case MBOX_STATE_APPEND_ATTRIBUTE:
/* Put the new attributes. */
{
char abuf[64];
size_t na = 0;
attribute_t attr = NULL;
abuf[0] = '\0';
message_get_attribute (msg, &attr);
attribute_to_string (attr, abuf, sizeof(abuf), &na);
status = stream_write (mailbox->stream, abuf, na, *psize, &n);
if (status != 0)
break;
*psize += n;
mud->state = MBOX_STATE_APPEND_UID;
}
case MBOX_STATE_APPEND_UID:
/* The new X-UID. */
{
char suid[64];
size_t uid = 0;
if (is_expunging)
{
status = message_get_uid (msg, &uid);
if (status == EAGAIN)
return status;
}
else
uid = mud->uidnext++;
if (status == 0 || uid != 0)
{
n = sprintf (suid, "X-UID: %u\n", (unsigned) uid);
/* Put the UID. */
status = stream_write (mailbox->stream, suid, n, *psize, &n);
if (status != 0)
break;
*psize += n;
}
/* New line separator of the Header. */
status = stream_write (mailbox->stream, &nl , 1, *psize, &n);
if (status != 0)
break;
*psize += n;
mud->state = MBOX_STATE_APPEND_BODY;
}
case MBOX_STATE_APPEND_BODY:
/* Append the Body. */
{
char buffer[1024];
size_t nread = 0;
stream_t is = NULL;
body_t body = NULL;
message_get_body (msg, &body);
body_get_stream (body, &is);
do
{
status = stream_read (is, buffer, sizeof (buffer), mud->off,
&nread);
if (status != 0)
{
if (status != EAGAIN)
{
mud->state = MBOX_NO_STATE;
mud->off = 0;
}
return status;
}
mud->off += nread;
status = stream_write (mailbox->stream, buffer, nread, *psize, &n);
if (status != 0)
break;
*psize += n;
}
while (nread > 0);
mud->off = 0;
n = 0;
stream_write (mailbox->stream, &nl, 1, *psize, &n);
*psize += n;
}
default:
break;
}
/* If not expunging we are taking the stream message. */
if (!is_expunging)
{
switch (mud->state)
{
case MBOX_STATE_APPEND_MESSAGE:
{
/* Append the Message. */
char buffer[1024];
size_t nread = 0;
stream_t is = NULL;
message_get_stream (msg, &is);
do
{
status = stream_read (is, buffer, sizeof (buffer), mud->off,
&nread);
if (status != 0)
{
if (status != EAGAIN)
{
mud->state = MBOX_NO_STATE;
mud->off = 0;
}
stream_flush (mailbox->stream);
return status;
}
stream_write (mailbox->stream, buffer, nread, *psize, &n);
mud->off += nread;
*psize += n;
}
while (nread > 0);
n = 0;
stream_write (mailbox->stream, &nl, 1, *psize, &n);
*psize += n;
}
default:
break;
}
} /* is_expunging */
mud->state = MBOX_NO_STATE;
stream_flush (mailbox->stream);
return status;
}
static int
mbox_get_size (mailbox_t mailbox, off_t *psize)
{
off_t size;
int status;
/* Maybe was not open yet ?? */
status = stream_size (mailbox->stream, &size);
if (status != 0)
return status;
if (psize)
*psize = size;
return 0;
}
static int
mbox_messages_count (mailbox_t mailbox, size_t *pcount)
{
mbox_data_t mud = mailbox->data;
if (mud == NULL)
return EINVAL;
if (! mbox_is_updated (mailbox))
return mbox_scan0 (mailbox, mud->messages_count, pcount, 0);
if (pcount)
*pcount = mud->messages_count;
return 0;
}
/* A "recent" message is the one not marked with MU_ATTRIBUTE_SEEN
('O' in the Status header), i.e. a message that is first seen
by the current session (see attributes.h) */
static int
mbox_messages_recent (mailbox_t mailbox, size_t *pcount)
{
mbox_data_t mud = mailbox->data;
mbox_message_t mum;
size_t j, recent;
/* If we did not start a scanning yet do it now. */
if (mud->messages_count == 0)
{
int status = mbox_scan0 (mailbox, 1, NULL, 0);
if (status != 0)
return status;
}
for (recent = j = 0; j < mud->messages_count; j++)
{
mum = mud->umessages[j];
if (mum && MU_ATTRIBUTE_IS_UNSEEN(mum->attr_flags))
recent++;
}
*pcount = recent;
return 0;
}
/* An "unseen" message is the one that has not been read yet */
static int
mbox_message_unseen (mailbox_t mailbox, size_t *pmsgno)
{
mbox_data_t mud = mailbox->data;
mbox_message_t mum;
size_t j, unseen;
/* If we did not start a scanning yet do it now. */
if (mud->messages_count == 0)
{
int status = mbox_scan0 (mailbox, 1, NULL, 0);
if (status != 0)
return status;
}
for (unseen = j = 0; j < mud->messages_count; j++)
{
mum = mud->umessages[j];
if (mum && MU_ATTRIBUTE_IS_UNREAD(mum->attr_flags))
{
unseen = j + 1;
break;
}
}
*pmsgno = unseen;
return 0;
}
static int
mbox_uidvalidity (mailbox_t mailbox, unsigned long *puidvalidity)
{
mbox_data_t mud = mailbox->data;
int status = mbox_messages_count (mailbox, NULL);
if (status != 0)
return status;
/* If we did not start a scanning yet do it now. */
if (mud->messages_count == 0)
{
status = mbox_scan0 (mailbox, 1, NULL, 0);
if (status != 0)
return status;
}
if (puidvalidity)
*puidvalidity = mud->uidvalidity;
return 0;
}
static int
mbox_uidnext (mailbox_t mailbox, size_t *puidnext)
{
mbox_data_t mud = mailbox->data;
int status = mbox_messages_count (mailbox, NULL);
if (status != 0)
return status;
/* If we did not start a scanning yet do it now. */
if (mud->messages_count == 0)
{
status = mbox_scan0 (mailbox, 1, NULL, 0);
if (status != 0)
return status;
}
if (puidnext)
*puidnext = mud->uidnext;
return 0;
}
#ifdef WITH_PTHREAD
void
mbox_cleanup (void *arg)
{
mailbox_t mailbox = arg;
monitor_unlock (mailbox->monitor);
locker_unlock (mailbox->locker);
}
#endif
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/* Mailbox Parsing. */
/* Credits to the c-client and its Authors
* The notorius c-client VALID() macro, was written by Mark Crispin.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef WITH_PTHREAD
# ifdef HAVE_PTHREAD_H
# define _XOPEN_SOURCE 500
# include <pthread.h>
# endif
#endif
#include <stdlib.h>
#include <mbox0.h>
/* Parsing.
The approach is to detect the "From " as start of a new message, give the
position of the header and scan until "\n" then set header_end, set body
position, scan until we it another "From " and set body_end.
************************************
This is a classic case of premature optimisation being the root of all
Evil(Donald E. Knuth). But I'm under "pressure" ;-) to come with
something "faster". I think it's wastefull * to spend time to gain a few
seconds on 30Megs mailboxes ... but then again ... in computer time, 60
seconds, is eternity. If they use the event notification stuff to get
some headers/messages early ... it's like pissing in the wind(sorry don't
have the english equivalent). The worst is progress_bar it should be ...
&*($^ nuke. For the events, we have to remove the *.LCK file, release the
locks, flush the stream save the pointers etc ... hurry and wait...
I this point I'm pretty much ranting. */
/* From the C-Client, part of pine */
/* You are not expected to understand this macro, but read the next page if
* you are not faint of heart.
*
* Known formats to the VALID macro are:
* From user Wed Dec 2 05:53 1992
* BSD From user Wed Dec 2 05:53:22 1992
* SysV From user Wed Dec 2 05:53 PST 1992
* rn From user Wed Dec 2 05:53:22 PST 1992
* From user Wed Dec 2 05:53 -0700 1992
* From user Wed Dec 2 05:53:22 -0700 1992
* From user Wed Dec 2 05:53 1992 PST
* From user Wed Dec 2 05:53:22 1992 PST
* From user Wed Dec 2 05:53 1992 -0700
* Solaris From user Wed Dec 2 05:53:22 1992 -0700
*
* Plus all of the above with `` remote from xxx'' after it. Thank you very
* much, smail and Solaris, for making my life considerably more complicated.
*/
/*
* What? You want to understand the VALID macro anyway? Alright, since you
* insist. Actually, it isn't really all that difficult, provided that you
* take it step by step.
*
* Line 1 Initializes the return ti value to failure (0);
* Lines 2-3 Validates that the 1st-5th characters are ``From ''.
* Lines 4-6 Validates that there is an end of line and points x at it.
* Lines 7-14 First checks to see if the line is at least 41 characters long
.
* If so, it scans backwards to find the rightmost space. From
* that point, it scans backwards to see if the string matches
* `` remote from''. If so, it sets x to point to the space at
* the start of the string.
* Line 15 Makes sure that there are at least 27 characters in the line.
* Lines 16-21 Checks if the date/time ends with the year (there is a space
* five characters back). If there is a colon three characters
* further back, there is no timezone field, so zn is set to 0
* and ti is set in front of the year. Otherwise, there must
* either to be a space four characters back for a three-letter
* timezone, or a space six characters back followed by a + or -
* for a numeric timezone; in either case, zn and ti become the
* offset of the space immediately before it.
* Lines 22-24 Are the failure case for line 14. If there is a space four
* characters back, it is a three-letter timezone; there must be
a
* space for the year nine characters back. zn is the zone
* offset; ti is the offset of the space.
* Lines 25-28 Are the failure case for line 20. If there is a space six
* characters back, it is a numeric timezone; there must be a
* space eleven characters back and a + or - five characters back
.
* zn is the zone offset; ti is the offset of the space.
* Line 29-32 If ti is valid, make sure that the string before ti is of the
* form www mmm dd hh:mm or www mmm dd hh:mm:ss, otherwise
* invalidate ti. There must be a colon three characters back
* and a space six or nine characters back (depending upon
* whether or not the character six characters back is a colon).
* There must be a space three characters further back (in front
* of the day), one seven characters back (in front of the month)
,
* and one eleven characters back (in front of the day of week).
* ti is set to be the offset of the space before the time.
*
* Why a macro? It gets invoked a *lot* in a tight loop. On some of the
* newer pipelined machines it is faster being open-coded than it would be if
* subroutines are called.
*
* Why does it scan backwards from the end of the line, instead of doing the
* much easier forward scan? There is no deterministic way to parse the
* ``user'' field, because it may contain unquoted spaces! Yes, I tested it t
o
* see if unquoted spaces were possible. They are, and I've encountered enoug
h
* evil mail to be totally unwilling to trust that ``it will never happen''.
*/
#define VALID(s,x,ti,zn) { \
ti = 0; \
if ((*s == 'F') && (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') && \
(s[4] == ' ')) { \
for (x = s + 5; *x && *x != '\n'; x++); \
if (x) { \
if (x - s >= 41) { \
for (zn = -1; x[zn] != ' '; zn--); \
if ((x[zn-1] == 'm') && (x[zn-2] == 'o') && (x[zn-3] == 'r') && \
(x[zn-4] == 'f') && (x[zn-5] == ' ') && (x[zn-6] == 'e') && \
(x[zn-7] == 't') && (x[zn-8] == 'o') && (x[zn-9] == 'm') && \
(x[zn-10] == 'e') && (x[zn-11] == 'r') && (x[zn-12] == ' '))\
x += zn - 12; \
} \
if (x - s >= 27) { \
if (x[-5] == ' ') { \
if (x[-8] == ':') zn = 0,ti = -5; \
else if (x[-9] == ' ') ti = zn = -9; \
else if ((x[-11] == ' ') && ((x[-10]=='+') || (x[-10]=='-'))) \
ti = zn = -11; \
} \
else if (x[-4] == ' ') { \
if (x[-9] == ' ') zn = -4,ti = -9; \
} \
else if (x[-6] == ' ') { \
if ((x[-11] == ' ') && ((x[-5] == '+') || (x[-5] == '-'))) \
zn = -6,ti = -11; \
} \
if (ti && !((x[ti - 3] == ':') && \
(x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') && \
(x[ti - 3] == ' ') && (x[ti - 7] == ' ') && \
(x[ti - 11] == ' '))) ti = 0; \
} \
} \
} \
}
#define ATTRIBUTE_SET(buf,mum,c0,c1,type) \
do \
{ \
char *s; \
for (s = (buf) + 7; *s; s++) \
{ \
if (*s == c0 || *s == c1) \
{ \
(mum)->attr_flags |= (type); \
break; \
} \
} \
} while (0)
#define ISBCC(buf) (\
(buf[0] == 'B' || buf[0] == 'b') \
&& (buf[1] == 'C' || buf[1] == 'c') \
&& (buf[2] == 'C' || buf[2] == 'c') \
&& (buf[3] == ':' || buf[3] == ' ' || buf[3] == '\t'))
#define ISCC(buf) (\
(buf[0] == 'C' || buf[0] == 'c') \
&& (buf[1] == 'C' || buf[1] == 'c') \
&& (buf[2] == ':' || buf[2] == ' ' || buf[2] == '\t'))
#define ISCONTENT_LANGUAGE(buf) (\
(buf[0] == 'C' || buf[0] == 'c') \
&& (buf[1] == 'O' || buf[1] == 'o') \
&& (buf[2] == 'N' || buf[2] == 'n') \
&& (buf[3] == 'T' || buf[3] == 't') \
&& (buf[4] == 'E' || buf[4] == 'e') \
&& (buf[5] == 'N' || buf[5] == 'n') \
&& (buf[6] == 'T' || buf[6] == 't') \
&& (buf[7] == '-') \
&& (buf[8] == 'L' || buf[8] == 'l') \
&& (buf[9] == 'A' || buf[9] == 'a') \
&& (buf[10] == 'N' || buf[10] == 'n') \
&& (buf[11] == 'G' || buf[11] == 'g') \
&& (buf[12] == 'U' || buf[12] == 'u') \
&& (buf[13] == 'A' || buf[13] == 'a') \
&& (buf[14] == 'G' || buf[14] == 'g') \
&& (buf[15] == 'E' || buf[15] == 'e') \
&& (buf[16] == ':' || buf[16] == ' ' || buf[16] == '\t'))
#define ISCONTENT_TRANSFER_ENCODING(buf) (\
(buf[0] == 'C' || buf[0] == 'c') \
&& (buf[1] == 'O' || buf[1] == 'o') \
&& (buf[2] == 'N' || buf[2] == 'n') \
&& (buf[3] == 'T' || buf[3] == 't') \
&& (buf[4] == 'E' || buf[4] == 'e') \
&& (buf[5] == 'N' || buf[5] == 'n') \
&& (buf[6] == 'T' || buf[6] == 't') \
&& (buf[7] == '-') \
&& (buf[8] == 'T' || buf[8] == 't') \
&& (buf[9] == 'R' || buf[9] == 'r') \
&& (buf[10] == 'A' || buf[10] == 'a') \
&& (buf[11] == 'N' || buf[11] == 'n') \
&& (buf[12] == 'S' || buf[12] == 's') \
&& (buf[13] == 'F' || buf[13] == 'f') \
&& (buf[14] == 'E' || buf[14] == 'e') \
&& (buf[15] == 'R' || buf[15] == 'r') \
&& (buf[16] == '-') \
&& (buf[17] == 'E' || buf[17] == 'e') \
&& (buf[18] == 'N' || buf[18] == 'n') \
&& (buf[19] == 'C' || buf[19] == 'c') \
&& (buf[20] == 'O' || buf[20] == 'o') \
&& (buf[21] == 'D' || buf[21] == 'd') \
&& (buf[22] == 'I' || buf[22] == 'i') \
&& (buf[23] == 'N' || buf[23] == 'n') \
&& (buf[24] == 'G' || buf[24] == 'g') \
&& (buf[25] == ':' || buf[25] == ' ' || buf[25] == '\t'))
#define ISCONTENT_TYPE(buf) (\
(buf[0] == 'C' || buf[0] == 'c') \
&& (buf[1] == 'O' || buf[1] == 'o') \
&& (buf[2] == 'N' || buf[2] == 'n') \
&& (buf[3] == 'T' || buf[3] == 't') \
&& (buf[4] == 'E' || buf[4] == 'e') \
&& (buf[5] == 'N' || buf[5] == 'n') \
&& (buf[6] == 'T' || buf[6] == 't') \
&& (buf[7] == '-') \
&& (buf[8] == 'T' || buf[8] == 't') \
&& (buf[9] == 'Y' || buf[9] == 'y') \
&& (buf[10] == 'P' || buf[10] == 'p') \
&& (buf[11] == 'E' || buf[11] == 'e') \
&& (buf[12] == ':' || buf[12] == ' ' || buf[12] == '\t'))
#define ISDATE(buf) (\
(buf[0] == 'D' || buf[0] == 'd') \
&& (buf[1] == 'A' || buf[1] == 'a') \
&& (buf[2] == 'T' || buf[2] == 't') \
&& (buf[3] == 'E' || buf[3] == 'e') \
&& (buf[4] == ':' || buf[4] == ' ' || buf[4] == '\t'))
#define ISFROM(buf) (\
(buf[0] == 'F' || buf[0] == 'f') \
&& (buf[1] == 'R' || buf[1] == 'r') \
&& (buf[2] == 'O' || buf[2] == 'o') \
&& (buf[3] == 'M' || buf[3] == 'm') \
&& (buf[4] == ':' || buf[4] == ' ' || buf[4] == '\t'))
#define ISIN_REPLY_TO(buf) (\
(buf[0] == 'I' || buf[0] == 'i') \
&& (buf[1] == 'N' || buf[1] == 'n') \
&& (buf[2] == '-' || buf[2] == '-') \
&& (buf[3] == 'R' || buf[3] == 'r') \
&& (buf[4] == 'E' || buf[4] == 'e') \
&& (buf[5] == 'P' || buf[5] == 'p') \
&& (buf[6] == 'L' || buf[6] == 'l') \
&& (buf[7] == 'Y' || buf[7] == 'y') \
&& (buf[8] == '-') \
&& (buf[9] == 'T' || buf[9] == 't') \
&& (buf[10] == 'O' || buf[10] == 'o') \
&& (buf[11] == ':' || buf[11] == ' ' || buf[11] == '\t'))
#define ISMESSAGE_ID(buf) (\
(buf[0] == 'M' || buf[0] == 'm') \
&& (buf[1] == 'E' || buf[1] == 'e') \
&& (buf[2] == 'S' || buf[2] == 's') \
&& (buf[3] == 'S' || buf[3] == 's') \
&& (buf[4] == 'A' || buf[4] == 'a') \
&& (buf[5] == 'G' || buf[5] == 'g') \
&& (buf[6] == 'E' || buf[6] == 'e') \
&& (buf[7] == '-') \
&& (buf[8] == 'I' || buf[8] == 'i') \
&& (buf[9] == 'D' || buf[9] == 'd') \
&& (buf[10] == ':' || buf[10] == ' ' || buf[10] == '\t'))
#define ISREFERENCE(buf) (\
(buf[0] == 'R' || buf[0] == 'r') \
&& (buf[1] == 'E' || buf[1] == 'e') \
&& (buf[2] == 'F' || buf[2] == 'f') \
&& (buf[3] == 'E' || buf[3] == 'e') \
&& (buf[4] == 'R' || buf[4] == 'r') \
&& (buf[5] == 'E' || buf[5] == 'e') \
&& (buf[6] == 'n' || buf[6] == 'n') \
&& (buf[7] == 'C' || buf[7] == 'c') \
&& (buf[8] == 'E' || buf[8] == 'e') \
&& (buf[9] == ':' || buf[9] == ' ' || buf[9] == '\t'))
#define ISREPLY_TO(buf) (\
(buf[0] == 'R' || buf[0] == 'r') \
&& (buf[1] == 'E' || buf[1] == 'e') \
&& (buf[2] == 'P' || buf[2] == 'p') \
&& (buf[3] == 'L' || buf[3] == 'l') \
&& (buf[4] == 'Y' || buf[4] == 'y') \
&& (buf[5] == '-') \
&& (buf[6] == 'T' || buf[6] == 't') \
&& (buf[7] == 'O' || buf[7] == 'o') \
&& (buf[8] == ':' || buf[8] == ' ' || buf[8] == '\t'))
#define ISSENDER(buf) (\
(buf[0] == 'S' || buf[0] == 's') \
&& (buf[1] == 'E' || buf[1] == 'e') \
&& (buf[2] == 'N' || buf[2] == 'n') \
&& (buf[3] == 'D' || buf[3] == 'd') \
&& (buf[4] == 'E' || buf[4] == 'e') \
&& (buf[5] == 'R' || buf[5] == 'r') \
&& (buf[6] == ':' || buf[6] == ' ' || buf[6] == '\t'))
#define ISSTATUS(buf) (\
(buf[0] == 'S' || buf[0] == 's') \
&& (buf[1] == 'T' || buf[1] == 't') \
&& (buf[2] == 'A' || buf[2] == 'a') \
&& (buf[3] == 'T' || buf[3] == 't') \
&& (buf[4] == 'U' || buf[4] == 'u') \
&& (buf[5] == 'S' || buf[5] == 's') \
&& (buf[6] == ':' || buf[6] == ' ' || buf[6] == '\t'))
#define ISSUBJECT(buf) (\
(buf[0] == 'S' || buf[0] == 's') \
&& (buf[1] == 'U' || buf[1] == 'u') \
&& (buf[2] == 'B' || buf[2] == 'b') \
&& (buf[3] == 'J' || buf[3] == 'j') \
&& (buf[4] == 'E' || buf[4] == 'e') \
&& (buf[5] == 'C' || buf[5] == 'c') \
&& (buf[6] == 'T' || buf[6] == 't') \
&& (buf[7] == ':' || buf[7] == ' ' || buf[7] == '\t'))
#define ISTO(buf) (\
(buf[0] == 'T' || buf[0] == 't') \
&& (buf[1] == 'O' || buf[1] == 'o') \
&& (buf[2] == ':' || buf[2] == ' ' || buf[2] == '\t'))
#define ISX_IMAPBASE(buf) (\
(buf[0] == 'X' || buf[0] == 'x') \
&& (buf[1] == '-') \
&& (buf[2] == 'I' || buf[2] == 'i') \
&& (buf[3] == 'M' || buf[3] == 'm') \
&& (buf[4] == 'A' || buf[4] == 'a') \
&& (buf[5] == 'P' || buf[5] == 'p') \
&& (buf[6] == 'B' || buf[6] == 'b') \
&& (buf[7] == 'A' || buf[7] == 'a') \
&& (buf[8] == 'S' || buf[8] == 's') \
&& (buf[9] == 'E' || buf[9] == 'e') \
&& (buf[10] == ':' || buf[10] == ' ' || buf[10] == '\t'))
#define ISX_UIDL(buf) (\
(buf[0] == 'X' || buf[0] == 'x') \
&& (buf[1] == '-') \
&& (buf[2] == 'U' || buf[2] == 'u') \
&& (buf[3] == 'I' || buf[3] == 'i') \
&& (buf[4] == 'D' || buf[4] == 'd') \
&& (buf[5] == 'L' || buf[5] == 'l') \
&& (buf[6] == ':' || buf[6] == ' ' || buf[6] == '\t'))
#define ISX_UID(buf) (\
(buf[0] == 'X' || buf[0] == 'x') \
&& (buf[1] == '-') \
&& (buf[2] == 'U' || buf[2] == 'u') \
&& (buf[3] == 'I' || buf[3] == 'i') \
&& (buf[4] == 'D' || buf[4] == 'd') \
&& (buf[5] == ':' || buf[5] == ' ' || buf[5] == '\t'))
/* Skip prepend spaces. */
#define SKIPSPACE(p) while (*p == ' ') p++
#define ATOI(a,i) \
do {\
SKIPSPACE(a); \
for (i = 0; *a >= '0' && *a <= '9'; a++) \
i = 10 * i + (*a - '0'); \
} while (0)
/* Save/concatenate the field-value in the fast header(fhd) field. */
#define FAST_HEADER(field,buf,n) \
do { \
int i = 0; \
char *s = field; \
char *p = buf; \
if (s) \
while (*s++) i++; \
else \
p = memchr (buf, ':', n); \
if (p) \
{ \
int l; \
char *tmp; \
buf[n - 1] = '\0'; \
p++; \
if (!field) \
SKIPSPACE(p); \
l = n - (p - buf); \
tmp = realloc (field, (l + i + 1) * sizeof (char)); \
if (tmp) \
{ \
field = tmp; \
memcpy (field + i, p, l); \
} \
} \
} while (0)
#define FAST_H_BCC(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_BCC],buf,n); \
save_field = &(mum->fhdr[H_BCC])
#define FAST_H_CC(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_CC],buf,n); \
save_field = &(mum->fhdr[H_CC])
#define FAST_H_CONTENT_LANGUAGE(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_CONTENT_LANGUAGE],buf,n); \
save_field = &(mum->fhdr[H_CONTENT_LANGUAGE])
#define FAST_H_CONTENT_TRANSFER_ENCODING(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_CONTENT_TRANSFER_ENCODING],buf,n); \
save_field = &(mum->fhdr[H_CONTENT_TRANSFER_ENCODING])
#define FAST_H_CONTENT_TYPE(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_CONTENT_TYPE],buf,n); \
save_field = &(mum->fhdr[H_CONTENT_TYPE])
#define FAST_H_DATE(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_DATE],buf,n); \
save_field = &(mum->fhdr[H_DATE])
#define FAST_H_FROM(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_FROM],buf,n); \
save_field = &(mum->fhdr[H_FROM])
#define FAST_H_IN_REPLY_TO(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_IN_REPLY_TO],buf,n); \
save_field = &(mum->fhdr[H_IN_REPLY_TO])
#define FAST_H_MESSAGE_ID(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_MESSAGE_ID],buf,n); \
save_field = &(mum->fhdr[H_MESSAGE_ID])
#define FAST_H_REFERENCE(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_REFERENCE],buf,n); \
save_field = &(mum->fhdr[H_REFERENCE])
#define FAST_H_REPLY_TO(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_REPLY_TO],buf,n); \
save_field = &(mum->fhdr[H_REPLY_TO])
#define FAST_H_SENDER(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_SENDER],buf,n); \
save_field = &(mum->fhdr[H_SENDER])
#define FAST_H_SUBJECT(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_SUBJECT],buf,n); \
save_field = &(mum->fhdr[H_SUBJECT])
#define FAST_H_TO(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_TO],buf,n); \
save_field = &(mum->fhdr[H_TO])
#define FAST_H_X_UIDL(mum,save_field,buf,n) \
FAST_HEADER(mum->fhdr[H_X_UIDL],buf,n); \
save_field = &(mum->fhdr[H_X_UIDL])
/* Notifications ADD_MESG. */
#define DISPATCH_ADD_MSG(mbox,mud) \
do \
{ \
int bailing = 0; \
monitor_unlock (mbox->monitor); \
if (mbox->observable) \
bailing = observable_notify (mbox->observable, MU_EVT_MESSAGE_ADD); \
if (bailing != 0) \
{ \
if (pcount) \
*pcount = (mud)->messages_count; \
locker_unlock (mbox->locker); \
return EINTR; \
} \
monitor_wrlock (mbox->monitor); \
} while (0);
/* Notification MBX_PROGRESS
We do not want to fire up the progress notification every line, it will be
too expensive, so we do it arbitrarely every 10 000 Lines.
FIXME: maybe this should be configurable. */
/* This is more tricky we can not leave the mum struct incomplete. So we
only tell them about the complete messages. */
#define DISPATCH_PROGRESS(mbox,mud) \
do \
{ \
{ \
int bailing = 0; \
monitor_unlock (mbox->monitor); \
mud->messages_count--; \
if (mbox->observable) \
bailing = observable_notify (mbox->observable, MU_EVT_MAILBOX_PROGRESS); \
if (bailing != 0) \
{ \
if (pcount) \
*pcount = (mud)->messages_count; \
locker_unlock (mbox->locker); \
return EINTR; \
} \
mud->messages_count++; \
monitor_wrlock (mbox->monitor); \
} \
} while (0)
/* Allocate slots for the new messages. */
/* size_t num = 2 * ((mud)->messages_count) + 10; */
#define ALLOCATE_MSGS(mbox,mud) \
do \
{ \
if ((mud)->messages_count >= (mud)->umessages_count) \
{ \
mbox_message_t *m; \
size_t num = ((mud)->umessages_count) + 1; \
m = realloc ((mud)->umessages, num * sizeof (*m)); \
if (m == NULL) \
{ \
locker_unlock (mbox->locker); \
monitor_unlock (mbox->monitor); \
return ENOMEM; \
} \
(mud)->umessages = m; \
(mud)->umessages[num - 1] = calloc (1, sizeof (*(mum))); \
if ((mud)->umessages[num - 1] == NULL) \
{ \
locker_unlock (mbox->locker); \
monitor_unlock (mbox->monitor); \
return ENOMEM; \
} \
(mud)->umessages_count = num; \
} \
} while (0)
int
mbox_scan0 (mailbox_t mailbox, size_t msgno, size_t *pcount, int do_notif)
{
#define MSGLINELEN 1024
char buf[MSGLINELEN];
int inheader;
int inbody;
off_t total = 0;
mbox_data_t mud = mailbox->data;
mbox_message_t mum = NULL;
int status = 0;
size_t lines;
int newline;
size_t n = 0;
stream_t stream;
char **sfield = NULL;
size_t min_uid = 0;
int zn, isfrom = 0;
char *temp;
/* Sanity. */
if (mud == NULL)
return EINVAL;
/* Grab the lock. */
monitor_wrlock (mailbox->monitor);
#ifdef WITH_PTHREAD
/* read() is cancellation point since we're doing a potentially
long operation. Lets make sure we clean the state. */
pthread_cleanup_push (mbox_cleanup, (void *)mailbox);
#endif
/* Save the timestamp and size. */
status = stream_size (mailbox->stream, &(mud->size));
if (status != 0)
{
monitor_unlock (mailbox->monitor);
return status;
}
if((status = locker_lock (mailbox->locker)))
{
monitor_unlock (mailbox->monitor);
return status;
}
/* Seek to the starting point. */
if (mud->umessages && msgno > 0 && mud->messages_count > 0
&& msgno <= mud->messages_count)
{
mum = mud->umessages[msgno - 1];
if (mum)
total = mum->header_from;
mud->messages_count = msgno - 1;
}
else
mud->messages_count = 0;
#if 0
{
size_t j, k;
for (j = 0; j < mud->umessages_count; j++)
{
mum = mud->umessages[j];
for (k = 0; k < HDRSIZE; k++)
if (mum->fhdr[k])
free (mum->fhdr[k]);
}
}
#endif
newline = 1;
errno = lines = inheader = inbody = 0;
stream = mailbox->stream;
while ((status = stream_readline (mailbox->stream, buf, sizeof (buf),
total, &n)) == 0 && n != 0)
{
int nl;
total += n;
nl = (*buf == '\n') ? 1 : 0;
VALID(buf, temp, isfrom, zn);
isfrom = (isfrom) ? 1 : 0;
/* Which part of the message are we in ? */
inheader = isfrom | ((!nl) & inheader);
inbody = (!isfrom) & (!inheader);
if (buf[n - 1] == '\n')
lines++;
if (inheader)
{
/* New message. */
if (isfrom)
{
size_t j;
/* Signal the end of the body. */
if (mum && !mum->body_end)
{
mum->body_end = total - n - newline;
mum->body_lines = --lines - newline;
if (mum->uid <= min_uid)
{
mum->uid = ++min_uid;
/* Note that modification for when expunging. */
mum->attr_flags |= MU_ATTRIBUTE_MODIFIED;
}
else
min_uid = mum->uid;
if (do_notif)
DISPATCH_ADD_MSG(mailbox, mud);
}
/* Allocate_msgs will initialize mum. */
ALLOCATE_MSGS(mailbox, mud);
mud->messages_count++;
mum = mud->umessages[mud->messages_count - 1];
mum->mud = mud;
mum->header_from = total - n;
mum->header_from_end = total;
mum->body_end = mum->body = 0;
mum->attr_flags = 0;
lines = 0;
sfield = NULL;
for (j = 0; j < HDRSIZE; j++)
if (mum->fhdr[j])
{
free (mum->fhdr[j]);
mum->fhdr[j] = NULL;
}
}
else if (ISSTATUS(buf))
{
ATTRIBUTE_SET(buf, mum, 'r', 'R', MU_ATTRIBUTE_READ);
ATTRIBUTE_SET(buf, mum, 'o', 'O', MU_ATTRIBUTE_SEEN);
ATTRIBUTE_SET(buf, mum, 'a', 'A', MU_ATTRIBUTE_ANSWERED);
ATTRIBUTE_SET(buf, mum, 'd', 'D', MU_ATTRIBUTE_DELETED);
sfield = NULL;
}
else if (ISBCC(buf))
{
FAST_H_BCC(mum, sfield, buf, n);
}
else if (ISCC(buf))
{
FAST_H_CC(mum, sfield, buf, n);
}
else if (ISCONTENT_LANGUAGE(buf))
{
FAST_H_CONTENT_LANGUAGE(mum, sfield, buf, n);
}
else if (ISCONTENT_TRANSFER_ENCODING(buf))
{
FAST_H_CONTENT_TRANSFER_ENCODING(mum, sfield, buf, n);
}
else if (ISCONTENT_TYPE(buf))
{
FAST_H_CONTENT_TYPE(mum, sfield, buf, n);
}
else if (ISDATE(buf))
{
FAST_H_DATE(mum, sfield, buf, n);
}
else if (ISFROM(buf))
{
FAST_H_FROM(mum, sfield, buf, n);
}
else if (ISIN_REPLY_TO(buf))
{
FAST_H_IN_REPLY_TO(mum, sfield, buf, n);
}
else if (ISMESSAGE_ID(buf))
{
FAST_H_MESSAGE_ID(mum, sfield, buf, n);
}
else if (ISREFERENCE(buf))
{
FAST_H_REFERENCE(mum, sfield, buf, n);
}
else if (ISREPLY_TO(buf))
{
FAST_H_REPLY_TO(mum, sfield, buf, n);
}
else if (ISSENDER(buf))
{
FAST_H_SENDER (mum, sfield, buf, n);
}
else if (ISSUBJECT(buf))
{
FAST_H_SUBJECT (mum, sfield, buf, n);
}
else if (ISTO(buf))
{
FAST_H_TO (mum, sfield, buf, n);
}
else if (ISX_UIDL(buf))
{
FAST_H_X_UIDL (mum, sfield, buf, n);
}
else if (ISX_IMAPBASE(buf))
{
char *s = memchr (buf, ':', n);
if (s)
{
s++;
ATOI(s, mud->uidvalidity);
ATOI(s, mud->uidnext);
}
}
else if (ISX_UID(buf))
{
char *s = memchr (buf, ':', n);
if (s)
{
s++;
ATOI(s, mum->uid);
}
}
else if (sfield && (buf[0] == ' ' || buf[0] == '\t'))
{
char *save = *sfield;
FAST_HEADER (save, buf, n);
*sfield = save;
}
else
{
sfield = NULL;
}
}
/* Body. */
if (inbody)
{
/* Set the body position. */
if (mum && !mum->body)
{
mum->body = total - n + nl;
mum->header_lines = lines;
lines = 0;
}
}
newline = nl;
/* Every 100 mesgs update the lock, it should be every minute. */
if ((mud->messages_count % 100) == 0)
locker_touchlock (mailbox->locker);
/* Ping them every 1000 lines. Should be tunable. */
if (do_notif)
if (((lines +1) % 1000) == 0)
DISPATCH_PROGRESS(mailbox, mud);
} /* while */
if (mum)
{
mum->body_end = total - newline;
mum->body_lines = lines - newline;
if (mum->uid <= min_uid)
{
mum->uid = ++min_uid;
/* Note that modification for when expunging. */
mum->attr_flags |= MU_ATTRIBUTE_MODIFIED;
}
else
min_uid = mum->uid;
if (do_notif)
DISPATCH_ADD_MSG(mailbox, mud);
}
if (pcount)
*pcount = mud->messages_count;
locker_unlock (mailbox->locker);
monitor_unlock (mailbox->monitor);
/* Reset the uidvalidity. */
if (mud->messages_count > 0)
{
mum = mud->umessages[0];
if (mud->uidvalidity == 0)
{
mud->uidvalidity = (unsigned long)time (NULL);
mud->uidnext = mud->messages_count + 1;
/* Tell that we have been modified for expunging. */
mum->attr_flags |= MU_ATTRIBUTE_MODIFIED;
}
}
if (mud->messages_count > 0 && min_uid >= mud->uidnext)
{
mum = mud->umessages[0];
mud->uidnext = min_uid + 1;
mum->attr_flags |= MU_ATTRIBUTE_MODIFIED;
}
#ifdef WITH_PTHREAD
pthread_cleanup_pop (0);
#endif
return status;
}
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <registrar0.h>
#include <url0.h>
static void url_mbox_destroy (url_t purl);
static void
url_mbox_destroy (url_t url)
{
(void) url;
}
/* Default mailbox path generator */
static char *
_url_path_default (const char *spooldir, const char *user, int unused)
{
char *mbox = malloc (sizeof(spooldir) + strlen(user) + 2);
if (!mbox)
errno = ENOMEM;
else
sprintf (mbox, "%s/%s", spooldir, user);
return mbox;
}
/* Hashed indexing */
static char *
_url_path_hashed (const char *spooldir, const char *user, int param)
{
int i;
int ulen = strlen (user);
char *mbox;
unsigned hash;
if (param > ulen)
param = ulen;
for (i = 0, hash = 0; i < param; i++)
hash += user[i];
mbox = malloc (ulen + strlen (spooldir) + 5);
sprintf (mbox, "%s/%02X/%s", spooldir, hash % 256, user);
return mbox;
}
static int transtab[] = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', 'b', 'c', 'd', 'e', 'f',
'g', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', 'b', 'c', 'd', 'e', 'f',
'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', 'b', 'c', 'd', 'e', 'f', 'g',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', 'b', 'c', 'd', 'e', 'f', 'g'
};
/* Forward Indexing */
static char *
_url_path_index (const char *spooldir, const char *iuser, int index_depth)
{
const unsigned char* user = (const unsigned char*) iuser;
int i, ulen = strlen (user);
char *mbox, *p;
if (ulen == 0)
return NULL;
mbox = malloc (ulen + strlen (spooldir) + 2*index_depth + 2);
strcpy (mbox, spooldir);
p = mbox + strlen (mbox);
for (i = 0; i < index_depth && i < ulen; i++)
{
*p++ = '/';
*p++ = transtab[ user[i] ];
}
for (; i < index_depth; i++)
{
*p++ = '/';
*p++ = transtab[ user[ulen-1] ];
}
*p++ = '/';
strcpy (p, user);
return mbox;
}
/* Reverse Indexing */
static char *
_url_path_rev_index (const char *spooldir, const char *iuser, int index_depth)
{
const unsigned char* user = (const unsigned char*) iuser;
int i, ulen = strlen (user);
char *mbox, *p;
if (ulen == 0)
return NULL;
mbox = malloc (ulen + strlen (spooldir) + 2*index_depth + 1);
strcpy (mbox, spooldir);
p = mbox + strlen (mbox);
for (i = 0; i < index_depth && i < ulen; i++)
{
*p++ = '/';
*p++ = transtab[ user[ulen - i - 1] ];
}
for (; i < index_depth; i++)
{
*p++ = '/';
*p++ = transtab[ user[0] ];
}
*p++ = '/';
strcpy (p, user);
return mbox;
}
/*
UNIX Mbox
mbox:path[;type=TYPE][;param=PARAM][;user=USERNAME]
*/
int
_url_mbox_init (url_t url)
{
const char *name = url_to_string (url);
size_t len = strlen (name);
char *p;
/* reject the obvious */
if (name == NULL || strncmp (MU_MBOX_SCHEME, name, MU_MBOX_SCHEME_LEN) != 0
|| len < (MU_MBOX_SCHEME_LEN + 1) /* (scheme)+1(path)*/)
return EINVAL;
/* do I need to decode url encoding '% hex hex' ? */
/* TYPE */
url->_destroy = url_mbox_destroy;
/* SCHEME */
url->scheme = strdup (MU_MBOX_SCHEME);
if (url->scheme == NULL)
{
url_mbox_destroy (url);
return ENOMEM;
}
/* PATH */
name += MU_MBOX_SCHEME_LEN; /* pass the scheme */
url->path = strdup (name);
if (url->path == NULL)
{
url_mbox_destroy (url);
return ENOMEM;
}
p = strchr (url->path, ';');
if (p)
{
char *(*fun)() = _url_path_default;
char *user = NULL;
int param = 0;
*p++ = 0;
while (p)
{
char *q = strchr (p, ';');
if (q)
*q++ = 0;
if (strncasecmp (p, "type=", 5) == 0)
{
char *type = p + 5;
if (strcmp (type, "hash") == 0)
fun = _url_path_hashed;
else if (strcmp (type, "index") == 0)
fun = _url_path_index;
else if (strcmp (type, "rev-index") == 0)
fun = _url_path_rev_index;
else
{
url_mbox_destroy (url);
return ENOENT;
}
}
else if (strncasecmp (p, "user=", 5) == 0)
{
user = p + 5;
}
else if (strncasecmp (p, "param=", 6) == 0)
{
param = strtoul (p+6, NULL, 0);
}
p = q;
}
if (user)
{
p = fun (url->path, user, param);
free (url->path);
url->path = p;
}
else
{
url_mbox_destroy (url);
return ENOENT;
}
}
return 0;
}
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef ENABLE_MH
#include <errno.h>
#include <folder0.h>
#include <registrar0.h>
static struct _record _mh_record =
{
MU_MH_SCHEME,
_url_mh_init, /* Url init. */
_mailbox_mh_init, /* Mailbox init. */
NULL, /* Mailer init. */
_folder_mh_init, /* Folder init. */
NULL, /* back pointer. */
NULL, /* _is_scheme method. */
NULL, /* _get_url method. */
NULL, /* _get_mailbox method. */
NULL, /* _get_mailer method. */
NULL /* _get_folder method. */
};
record_t mh_record = &_mh_record;
int
_folder_mh_init (folder_t folder)
{
(void)folder;
return 0;
}
#else
#include <stdio.h>
#include <registrar0.h>
record_t mh_record = NULL;
#endif
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2002, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef ENABLE_MH
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <url0.h>
#include <registrar0.h>
static void
url_mh_destroy (url_t url)
{
(void) url;
}
/*
MH url
mh:path
*/
int
_url_mh_init (url_t url)
{
const char *name = url_to_string (url);
size_t len = strlen (name);
/* reject the obvious */
if (name == NULL || strncmp (MU_MH_SCHEME, name, MU_MH_SCHEME_LEN) != 0
|| len < (MU_MH_SCHEME_LEN + 1) /* (scheme)+1(path)*/)
return EINVAL;
/* do I need to decode url encoding '% hex hex' ? */
/* TYPE */
url->_destroy = url_mh_destroy;
/* SCHEME */
url->scheme = strdup (MU_MH_SCHEME);
if (url->scheme == NULL)
{
url_mh_destroy (url);
return ENOMEM;
}
/* PATH */
name += MU_MH_SCHEME_LEN; /* pass the scheme */
url->path = strdup (name);
if (url->path == NULL)
{
url_mh_destroy (url);
return ENOMEM;
}
return 0;
}
#endif
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef ENABLE_POP
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <mailutils/auth.h>
#include <mailutils/mailbox.h>
#include <folder0.h>
#include <registrar0.h>
#include <url0.h>
/* We export url parsing and the initialisation of
the mailbox, via the register entry/record. */
static struct _record _pop_record =
{
MU_POP_SCHEME,
_url_pop_init, /* Url init. */
_mailbox_pop_init, /* Mailbox init. */
NULL, /* Mailer init. */
_folder_pop_init, /* Folder init. */
NULL, /* No need for an back pointer. */
NULL, /* _is_scheme method. */
NULL, /* _get_url method. */
NULL, /* _get_mailbox method. */
NULL, /* _get_mailer method. */
NULL /* _get_folder method. */
};
record_t pop_record = &_pop_record;
static int folder_pop_open __P ((folder_t, int));
static int folder_pop_close __P ((folder_t));
static int folder_pop_get_authority __P ((folder_t, authority_t *));
extern int _pop_user __P ((authority_t));
extern int _pop_apop __P ((authority_t));
/* XXX: The way, the POP folder is handle is not clean at all.
the I/O functions should have been here on folder, not in mbx_pop.c */
int
_folder_pop_init (folder_t folder)
{
int status;
/* 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_pop_get_authority (folder, NULL);
if (status != 0)
return status;
folder->_open = folder_pop_open;
folder->_close = folder_pop_close;
return 0;
}
static int
folder_pop_open (folder_t folder, int flags)
{
mailbox_t mbox = folder->data;
return mailbox_open (mbox, flags);
}
static int
folder_pop_close (folder_t folder)
{
mailbox_t mbox = folder->data;
return mailbox_close (mbox);
}
static int
folder_pop_get_authority (folder_t folder, authority_t *pauth)
{
int status = 0;
if (folder->authority == NULL)
{
/* assert (folder->url); */
if (folder->url == NULL)
return EINVAL;
if (folder->url->auth == NULL
|| strcasecmp (folder->url->auth, "*") == 0)
{
status = authority_create (&folder->authority, NULL, folder);
authority_set_authenticate (folder->authority, _pop_user, folder);
}
/*
"+apop" could be supported.
Anything else starting with "+" is an extension mechanism.
Without a "+" it's a SASL mechanism.
*/
else if (strcasecmp (folder->url->auth, "+APOP") == 0)
{
status = authority_create (&folder->authority, NULL, folder);
authority_set_authenticate (folder->authority, _pop_apop, folder);
}
else
{
status = ENOSYS;
}
}
if (pauth)
*pauth = folder->authority;
return status;
}
#else
#include <stdio.h>
#include <registrar0.h>
record_t pop_record = NULL;
#endif
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef ENABLE_POP
#include <termios.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdarg.h>
#ifdef HAVE_ALLOCA_H
# include <alloca.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <md5.h>
#include <mailutils/attribute.h>
#include <mailutils/auth.h>
#include <mailutils/body.h>
#include <mailutils/debug.h>
#include <mailutils/errno.h>
#include <mailutils/error.h>
#include <mailutils/header.h>
#include <mailutils/message.h>
#include <mailutils/observer.h>
#include <mailutils/property.h>
#include <mailutils/stream.h>
#include <mailutils/url.h>
#include <folder0.h>
#include <mailbox0.h>
#include <registrar0.h>
#include <url0.h>
#define PROP_RFC822 1
/* Advance declarations. */
struct _pop_data;
struct _pop_message;
typedef struct _pop_data * pop_data_t;
typedef struct _pop_message * pop_message_t;
/* The different possible states of a Pop client, Note that POP3 is not
reentrant i.e. it is only one channel, so it is not possible to start
Another operation while one is running. The only resort is to close the
connection and reopen it again. This is what we do, the downside is that
the client as to get the authentication again user/pass. */
enum pop_state
{
POP_NO_STATE, POP_STATE_DONE,
POP_OPEN_CONNECTION,
POP_GREETINGS,
POP_APOP, POP_APOP_ACK,
POP_DELE, POP_DELE_ACK,
POP_LIST, POP_LIST_ACK, POP_LIST_RX,
POP_QUIT, POP_QUIT_ACK,
POP_NOOP, POP_NOOP_ACK,
POP_RETR, POP_RETR_ACK, POP_RETR_RX_HDR, POP_RETR_RX_BODY,
POP_RSET, POP_RSET_ACK,
POP_STAT, POP_STAT_ACK,
POP_TOP, POP_TOP_ACK, POP_TOP_RX,
POP_UIDL, POP_UIDL_ACK,
POP_AUTH, POP_AUTH_DONE,
POP_AUTH_USER, POP_AUTH_USER_ACK,
POP_AUTH_PASS, POP_AUTH_PASS_ACK
};
static void pop_destroy __P ((mailbox_t));
/* Functions/Methods that implements the mailbox_t API. */
static int pop_open __P ((mailbox_t, int));
static int pop_close __P ((mailbox_t));
static int pop_get_message __P ((mailbox_t, size_t, message_t *));
static int pop_messages_count __P ((mailbox_t, size_t *));
static int pop_messages_recent __P ((mailbox_t, size_t *));
static int pop_message_unseen __P ((mailbox_t, size_t *));
static int pop_expunge __P ((mailbox_t));
static int pop_scan __P ((mailbox_t, size_t, size_t *));
static int pop_is_updated __P ((mailbox_t));
/* The implementation of message_t */
int _pop_user __P ((authority_t));
int _pop_apop __P ((authority_t));
static int pop_get_size __P ((mailbox_t, off_t *));
/* We use pop_top for retreiving headers. */
/* static int pop_header_read (header_t, char *, size_t, off_t, size_t *); */
static int pop_body_fd __P ((stream_t, int *));
static int pop_body_size __P ((body_t, size_t *));
static int pop_body_lines __P ((body_t, size_t *));
static int pop_body_read __P ((stream_t, char *, size_t, off_t, size_t *));
static int pop_message_read __P ((stream_t, char *, size_t, off_t, size_t *));
static int pop_message_size __P ((message_t, size_t *));
static int pop_message_fd __P ((stream_t, int *));
static int pop_top __P ((header_t, char *, size_t, off_t, size_t *));
static int pop_retr __P ((pop_message_t, char *, size_t, off_t, size_t *));
static int pop_get_fd __P ((pop_message_t, int *));
static int pop_get_attribute __P ((attribute_t, int *));
static int pop_set_attribute __P ((attribute_t, int));
static int pop_unset_attribute __P ((attribute_t, int));
static int pop_uidl __P ((message_t, char *, size_t, size_t *));
static int pop_uid __P ((message_t, size_t *));
static int fill_buffer __P ((pop_data_t, char *, size_t));
static int pop_sleep __P ((int));
static int pop_readline __P ((pop_data_t));
static int pop_read_ack __P ((pop_data_t));
static int pop_writeline __P ((pop_data_t, const char *, ...));
static int pop_write __P ((pop_data_t));
static int pop_get_user __P ((authority_t));
static int pop_get_passwd __P ((authority_t));
static char *pop_get_timestamp __P ((pop_data_t));
static int pop_get_md5 __P ((pop_data_t));
/* This structure holds the info for a message. The pop_message_t
type, will serve as the owner of the message_t and contains the command to
send to "RETR"eive the specify message. The problem comes from the header.
If the POP server supports TOP, we can cleanly fetch the header.
But otherwise we use the clumsy approach. .i.e for the header we read 'til
^\n then discard the rest, for the body we read after ^\n and discard the
beginning. This is a waste, Pop was not conceive for this obviously. */
struct _pop_message
{
int inbody;
int skip_header;
int skip_body;
size_t body_size;
size_t header_size;
size_t body_lines;
size_t header_lines;
size_t message_size;
size_t num;
char *uidl; /* Cache the uidl string. */
int attr_flags;
message_t message;
pop_data_t mpd; /* Back pointer. */
};
/* Structure to hold things general to the POP mailbox, like its state, how
many messages we have so far etc ... */
struct _pop_data
{
void *func; /* Indicate a command is in operation, busy. */
size_t id; /* A second level of distincion, we maybe in the same function
but working on a different message. */
enum pop_state state;
pop_message_t *pmessages;
size_t pmessages_count;
size_t messages_count;
size_t size;
/* Working I/O buffers. */
char *buffer;
size_t buflen; /* Len of buffer. */
char *ptr; /* Points to the end of the buffer i.e the non consume chars. */
char *nl; /* Points to the '\n' char in te string. */
off_t offset; /* Dummy, this is use because of the stream buffering.
The stream_t maintains and offset and the offset we use must
be in sync. */
int is_updated;
char *user; /* Temporary holders for user and passwd. */
char *passwd; /* Temporary holders for passwd memset (0) when finish. */
mailbox_t mbox; /* Back pointer. */
} ;
/* Usefull little Macros, since these are very repetitive. */
/* Check if we're busy ? */
/* POP is a one channel download protocol, so if someone
is trying to execute a command while another is running
something is seriously incorrect, So the best course
of action is to close down the connection and start a new one.
For example mime_t only reads part of the message. If a client
wants to read different part of the message via mime it should
download it first. POP does not have the features of IMAP for
multipart messages.
Let see a concrete example:
{
mailbox_t mbox; message_t msg; stream_t stream; char buffer[105];
mailbox_create (&mbox, "pop://qnx.com");
mailbox_get_message (mbox, 1, &msg);
message_get_stream (msg, &stream);
while (stream_readline (stream, buffer, sizeof(buffer), NULL) != 0) { ..}
}
if in the while of the readline, one try to get another email. The pop
server will get seriously confused, and the second message will still
be the first one, There is no way to tell POP servers yo! stop/abort.
The approach is to close the stream and reopen again. So every time
we go in to a function our state is preserve by the triplets
mpd->{func,state,id}. The macro CHECK_BUSY checks if we are not
in another operation if not you get access if yes the stream is close
and pop_open() is recall again for a new connection.
*/
#define CHECK_BUSY(mbox, mpd, function, identity) \
do \
{ \
int err = monitor_wrlock (mbox->monitor); \
if (err != 0) \
return err; \
if ((mpd->func && mpd->func != function) \
|| (mpd->id && mpd->id != (size_t)identity)) \
{ \
mpd->id = 0; \
mpd->func = (void *)pop_open; \
mpd->state = POP_NO_STATE; \
monitor_unlock (mbox->monitor); \
err = pop_open (mbox, mbox->flags); \
if (err != 0) \
{ \
return err; \
} \
} \
else \
{ \
mpd->id = (size_t)identity; \
mpd->func = func; \
monitor_unlock (mbox->monitor); \
} \
} \
while (0)
/* Clear the state. */
#define CLEAR_STATE(mpd) \
mpd->id = 0, mpd->func = NULL, mpd->state = POP_NO_STATE
/* Clear the state and close the stream. */
#define CHECK_ERROR_CLOSE(mbox, mpd, status) \
do \
{ \
if (status != 0) \
{ \
stream_close (mbox->stream); \
CLEAR_STATE (mpd); \
mpd->func = (void *)-1; \
MAILBOX_DEBUG1(mbox, MU_DEBUG_PROT, "CHECK_ERROR_CLOSE: %s\n", mu_strerror (status));\
return status; \
} \
} \
while (0)
/* If error, clear the state and return. */
#define CHECK_ERROR(mpd, status) \
do \
{ \
if (status != 0) \
{ \
CLEAR_STATE (mpd); \
mpd->func = (void*)-1; \
MAILBOX_DEBUG1(mpd->mbox, MU_DEBUG_PROT, "CHECK_ERROR: %s\n", mu_strerror (status));\
return status; \
} \
} \
while (0)
/* Clear the state for non recoverable error. */
#define CHECK_EAGAIN(mpd, status) \
do \
{ \
if (status != 0) \
{ \
if (status != EAGAIN && status != EINPROGRESS && status != EINTR) \
{ \
CLEAR_STATE (mpd); \
mpd->func = (void *)-1; \
MAILBOX_DEBUG1(mpd->mbox, MU_DEBUG_PROT, "CHECK_EAGAIN: %s\n", mu_strerror (status));\
} \
return status; \
} \
} \
while (0)
/* Allocate mailbox_t, allocate pop internal structures. */
int
_mailbox_pop_init (mailbox_t mbox)
{
pop_data_t mpd;
int status = 0;
/* Allocate specifics for pop data. */
mpd = mbox->data = calloc (1, sizeof (*mpd));
if (mbox->data == NULL)
return ENOMEM;
mpd->mbox = mbox; /* Back pointer. */
mpd->state = POP_NO_STATE; /* Init with no state. */
/* Initialize the structure. */
mbox->_destroy = pop_destroy;
mbox->_open = pop_open;
mbox->_close = pop_close;
/* Messages. */
mbox->_get_message = pop_get_message;
mbox->_messages_count = pop_messages_count;
mbox->_messages_recent = pop_messages_recent;
mbox->_message_unseen = pop_message_unseen;
mbox->_expunge = pop_expunge;
mbox->_scan = pop_scan;
mbox->_is_updated = pop_is_updated;
mbox->_get_size = pop_get_size;
/* Set our properties. */
{
property_t property = NULL;
mailbox_get_property (mbox, &property);
property_set_value (property, "TYPE", "POP3", 1);
}
/* Hack! POP does not really have a folder. */
mbox->folder->data = mbox;
return status;
}
/* Cleaning up all the ressources associate with a pop mailbox. */
static void
pop_destroy (mailbox_t mbox)
{
if (mbox->data)
{
pop_data_t mpd = mbox->data;
size_t i;
monitor_wrlock (mbox->monitor);
/* Destroy the pop messages and ressources associated to them. */
for (i = 0; i < mpd->pmessages_count; i++)
{
if (mpd->pmessages[i])
{
message_destroy (&(mpd->pmessages[i]->message),
mpd->pmessages[i]);
if (mpd->pmessages[i]->uidl)
free (mpd->pmessages[i]->uidl);
free (mpd->pmessages[i]);
mpd->pmessages[i] = NULL;
}
}
if (mpd->buffer)
free (mpd->buffer);
if (mpd->pmessages)
free (mpd->pmessages);
free (mpd);
mbox->data = NULL;
monitor_unlock (mbox->monitor);
}
}
/* Simple User/pass authentication for pop. We ask for the info
from the standard input. */
int
_pop_user (authority_t auth)
{
folder_t folder = authority_get_owner (auth);
mailbox_t mbox = folder->data;
pop_data_t mpd = mbox->data;
int status;
switch (mpd->state)
{
case POP_AUTH:
/* Fetch the user from them. */
status = pop_get_user (auth);
if (status != 0 || mpd->user == NULL || mpd->user[0] == '\0')
{
CHECK_ERROR_CLOSE (mbox, mpd, EINVAL);
}
status = pop_writeline (mpd, "USER %s\r\n", mpd->user);
CHECK_ERROR_CLOSE(mbox, mpd, status);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
free (mpd->user);
mpd->user = NULL;
mpd->state = POP_AUTH_USER;
case POP_AUTH_USER:
/* Send username. */
status = pop_write (mpd);
CHECK_EAGAIN (mpd, status);
mpd->state = POP_AUTH_USER_ACK;
case POP_AUTH_USER_ACK:
/* Get the user ack. */
status = pop_read_ack (mpd);
CHECK_EAGAIN (mpd, status);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
if (strncasecmp (mpd->buffer, "+OK", 3) != 0)
{
observable_t observable = NULL;
mailbox_get_observable (mbox, &observable);
CLEAR_STATE (mpd);
observable_notify (observable, MU_EVT_AUTHORITY_FAILED);
CHECK_ERROR_CLOSE (mbox, mpd, EACCES);
}
status = pop_get_passwd (auth);
if (status != 0 || mpd->passwd == NULL || mpd->passwd[0] == '\0')
{
CHECK_ERROR_CLOSE (mbox, mpd, EINVAL);
}
status = pop_writeline (mpd, "PASS %s\r\n", mpd->passwd);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
/* Leave not trail of the passwd. */
memset (mpd->passwd, '\0', strlen (mpd->passwd));
free (mpd->passwd);
mpd->passwd = NULL;
CHECK_ERROR_CLOSE (mbox, mpd, status);
mpd->state = POP_AUTH_PASS;
case POP_AUTH_PASS:
/* Send passwd. */
status = pop_write (mpd);
CHECK_EAGAIN (mpd, status);
/* Clear the buffer it contains the passwd. */
memset (mpd->buffer, '\0', mpd->buflen);
mpd->state = POP_AUTH_PASS_ACK;
case POP_AUTH_PASS_ACK:
/* Get the ack from passwd. */
status = pop_read_ack (mpd);
CHECK_EAGAIN (mpd, status);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
if (strncasecmp (mpd->buffer, "+OK", 3) != 0)
{
observable_t observable = NULL;
mailbox_get_observable (mbox, &observable);
CLEAR_STATE (mpd);
observable_notify (observable, MU_EVT_AUTHORITY_FAILED);
CHECK_ERROR_CLOSE (mbox, mpd, EACCES);
}
mpd->state = POP_AUTH_DONE;
break; /* We're outta here. */
default:
break;
}
CLEAR_STATE (mpd);
return 0;
}
int
_pop_apop (authority_t auth)
{
folder_t folder = authority_get_owner (auth);
mailbox_t mbox = folder->data;
pop_data_t mpd = mbox->data;
int status;
switch (mpd->state)
{
case POP_AUTH:
/* Fetch the user from them. */
status = pop_get_user (auth);
if (status != 0 || mpd->user == NULL || mpd->user[0] == '\0')
{
CHECK_ERROR_CLOSE (mbox, mpd, EINVAL);
}
/* Fetch the secret from them. */
status = pop_get_passwd (auth);
if (status != 0 || mpd->passwd == NULL || mpd->passwd[0] == '\0')
{
CHECK_ERROR_CLOSE (mbox, mpd, EINVAL);
}
/* Make the MD5 digest string. */
status = pop_get_md5 (mpd);
if (status != 0)
{
CHECK_ERROR_CLOSE (mbox, mpd, status);
}
status = pop_writeline (mpd, "APOP %s %s\r\n", mpd->user, mpd->passwd);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
/* We have to obscure the md5 string. */
memset (mpd->passwd, '\0', strlen (mpd->passwd));
free (mpd->user);
free (mpd->passwd);
mpd->user = NULL;
mpd->passwd = NULL;
CHECK_ERROR_CLOSE (mbox, mpd, status);
mpd->state = POP_APOP;
case POP_APOP:
/* Send apop. */
status = pop_write (mpd);
CHECK_EAGAIN (mpd, status);
/* Clear the buffer it contains the md5. */
memset (mpd->buffer, '\0', mpd->buflen);
mpd->state = POP_APOP_ACK;
case POP_APOP_ACK:
status = pop_read_ack (mpd);
CHECK_EAGAIN (mpd, status);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
if (strncasecmp (mpd->buffer, "+OK", 3) != 0)
{
observable_t observable = NULL;
mailbox_get_observable (mbox, &observable);
CLEAR_STATE (mpd);
observable_notify (observable, MU_EVT_AUTHORITY_FAILED);
CHECK_ERROR_CLOSE (mbox, mpd, EACCES);
}
mpd->state = POP_AUTH_DONE;
break; /* We're outta here. */
default:
break;
}
CLEAR_STATE (mpd);
return 0;
}
/* Open the connection to the sever, and send the authentication.
FIXME: Should also send the CAPA command to detect for example the suport
for TOP, APOP, ... and DTRT(Do The Right Thing). */
static int
pop_open (mailbox_t mbox, int flags)
{
pop_data_t mpd = mbox->data;
int status;
char *host;
size_t hostlen = 0;
long port = 110;
/* Sanity checks. */
if (mpd == NULL)
return EINVAL;
/* Fetch the pop server name and the port in the url_t. */
status = url_get_host (mbox->url, NULL, 0, &hostlen);
if (status != 0)
return status;
host = alloca (hostlen + 1);
url_get_host (mbox->url, host, hostlen + 1, NULL);
url_get_port (mbox->url, &port);
mbox->flags = flags;
/* Do not check for reconnect here. */
/* CHECK_BUSY (mbox, mpd, func, 0); */
/* Enter the pop state machine, and boogy: AUTHORISATION State. */
switch (mpd->state)
{
case POP_NO_STATE:
/* Allocate a working io buffer. */
if (mpd->buffer == NULL)
{
/* 255 is the limit lenght of a POP3 command according to RFCs. */
mpd->buflen = 255;
mpd->buffer = calloc (mpd->buflen + 1, sizeof (char));
if (mpd->buffer == NULL)
{
CHECK_ERROR (mpd, ENOMEM);
}
}
else
{
/* Clear any residual from a previous connection. */
memset (mpd->buffer, '\0', mpd->buflen);
}
mpd->ptr = mpd->buffer;
/* Create the networking stack. */
if (mbox->stream == NULL)
{
status = tcp_stream_create (&mbox->stream, host, port, mbox->flags);
CHECK_ERROR(mpd, status);
/* Using the awkward stream_t buffering. */
stream_setbufsiz (mbox->stream, BUFSIZ);
}
else
{
/* This is sudden death: for many pop servers, it is important to
let them time to remove locks or move the .user.pop files. This
happen when we do BUSY_CHECK(). For example, the user does not
want to read the entire file, and wants start to read a new
message, closing the connection and immediately contact the
server again, and we'll end up having "-ERR Mail Lock busy" or
something similar. To prevent this race condition we sleep 2
seconds. */
stream_close (mbox->stream);
pop_sleep (2);
}
mpd->state = POP_OPEN_CONNECTION;
case POP_OPEN_CONNECTION:
/* Establish the connection. */
MAILBOX_DEBUG2 (mbox, MU_DEBUG_PROT, "open (%s:%d)\n", host, port);
status = stream_open (mbox->stream);
CHECK_EAGAIN (mpd, status);
/* Can't recover bailout. */
CHECK_ERROR_CLOSE (mbox, mpd, status);
mpd->state = POP_GREETINGS;
case POP_GREETINGS:
{
/* Swallow the greetings. */
status = pop_read_ack (mpd);
CHECK_EAGAIN (mpd, status);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
if (strncasecmp (mpd->buffer, "+OK", 3) != 0)
{
CHECK_ERROR_CLOSE (mbox, mpd, EACCES);
}
mpd->state = POP_AUTH;
}
case POP_AUTH:
case POP_AUTH_USER:
case POP_AUTH_USER_ACK:
case POP_AUTH_PASS:
case POP_AUTH_PASS_ACK:
case POP_APOP:
case POP_APOP_ACK:
/* Authenticate. */
status = authority_authenticate (mbox->folder->authority);
CHECK_EAGAIN (mpd, status);
case POP_AUTH_DONE:
break;
default:
/*
mu_error ("pop_open unknown state\n");
*/
break;
}/* End AUTHORISATION state. */
/* Clear any state. */
CLEAR_STATE (mpd);
return 0;
}
/* Send the QUIT and close the socket. */
static int
pop_close (mailbox_t mbox)
{
pop_data_t mpd = mbox->data;
void *func = (void *)pop_close;
int status;
size_t i;
if (mpd == NULL)
return EINVAL;
/* Should not check for Busy, we're shuting down anyway. */
/* CHECK_BUSY (mbox, mpd, func, 0); */
monitor_wrlock (mbox->monitor);
if (mpd->func && mpd->func != func)
mpd->state = POP_NO_STATE;
mpd->id = 0;
mpd->func = func;
monitor_unlock (mbox->monitor);
/* Ok boys, it's a wrap: UPDATE State. */
switch (mpd->state)
{
case POP_NO_STATE:
/* Initiate the close. */
status = pop_writeline (mpd, "QUIT\r\n");
CHECK_ERROR (mpd, status);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
mpd->state = POP_QUIT;
case POP_QUIT:
/* Send the quit. */
status = pop_write (mpd);
CHECK_EAGAIN (mpd, status);
mpd->state = POP_QUIT_ACK;
case POP_QUIT_ACK:
/* Glob the acknowledge. */
status = pop_read_ack (mpd);
CHECK_EAGAIN (mpd, status);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
/* Now what ! and how can we tell them about errors ? So far now
lets just be verbose about the error but close the connection
anyway. */
if (strncasecmp (mpd->buffer, "+OK", 3) != 0)
mu_error ("pop_close: %s\n", mpd->buffer);
stream_close (mbox->stream);
break;
default:
/*
mu_error ("pop_close unknow state");
*/
break;
} /* UPDATE state. */
/* Free the messages. */
for (i = 0; i < mpd->pmessages_count; i++)
{
if (mpd->pmessages[i])
{
message_destroy (&(mpd->pmessages[i]->message),
mpd->pmessages[i]);
if (mpd->pmessages[i]->uidl)
free (mpd->pmessages[i]->uidl);
free (mpd->pmessages[i]);
mpd->pmessages[i] = NULL;
}
}
/* And clear any residue. */
if (mpd->pmessages)
free (mpd->pmessages);
mpd->pmessages = NULL;
mpd->pmessages_count = 0;
mpd->is_updated = 0;
if (mpd->buffer)
free (mpd->buffer);
mpd->buffer = NULL;
CLEAR_STATE (mpd);
return 0;
}
/* Only build/setup the message_t structure for a mesgno. pop_message_t,
will act as the owner of messages. */
static int
pop_get_message (mailbox_t mbox, size_t msgno, message_t *pmsg)
{
pop_data_t mpd = mbox->data;
message_t msg = NULL;
pop_message_t mpm;
int status;
size_t i;
/* Sanity. */
if (pmsg == NULL || mpd == NULL || msgno > mpd->messages_count)
return EINVAL;
monitor_rdlock (mbox->monitor);
/* See if we have already this message. */
for (i = 0; i < mpd->pmessages_count; i++)
{
if (mpd->pmessages[i])
{
if (mpd->pmessages[i]->num == msgno)
{
*pmsg = mpd->pmessages[i]->message;
monitor_unlock (mbox->monitor);
return 0;
}
}
}
monitor_unlock (mbox->monitor);
mpm = calloc (1, sizeof (*mpm));
if (mpm == NULL)
return ENOMEM;
/* Back pointer. */
mpm->mpd = mpd;
mpm->num = msgno;
/* Create the message. */
{
stream_t stream = NULL;
if ((status = message_create (&msg, mpm)) != 0
|| (status = stream_create (&stream, mbox->flags, msg)) != 0)
{
stream_destroy (&stream, msg);
message_destroy (&msg, mpm);
free (mpm);
return status;
}
/* Help for the readline()s */
stream_setbufsiz (stream, 128);
stream_set_read (stream, pop_message_read, msg);
stream_set_fd (stream, pop_message_fd, msg);
message_set_stream (msg, stream, mpm);
message_set_size (msg, pop_message_size, mpm);
}
/* Create the header. */
{
header_t header = NULL;
if ((status = header_create (&header, NULL, 0, msg)) != 0)
{
message_destroy (&msg, mpm);
free (mpm);
return status;
}
header_set_fill (header, pop_top, msg);
message_set_header (msg, header, mpm);
}
/* Create the attribute. */
{
attribute_t attribute;
status = attribute_create (&attribute, msg);
if (status != 0)
{
message_destroy (&msg, mpm);
free (mpm);
return status;
}
attribute_set_get_flags (attribute, pop_get_attribute, msg);
attribute_set_set_flags (attribute, pop_set_attribute, msg);
attribute_set_unset_flags (attribute, pop_unset_attribute, msg);
message_set_attribute (msg, attribute, mpm);
}
/* Create the body and its stream. */
{
body_t body = NULL;
stream_t stream = NULL;
if ((status = body_create (&body, msg)) != 0
|| (status = stream_create (&stream, mbox->flags, body)) != 0)
{
body_destroy (&body, msg);
stream_destroy (&stream, body);
message_destroy (&msg, mpm);
free (mpm);
return status;
}
/* Helps for the readline()s */
stream_setbufsiz (stream, 128);
stream_set_read (stream, pop_body_read, body);
stream_set_fd (stream, pop_body_fd, body);
body_set_size (body, pop_body_size, msg);
body_set_lines (body, pop_body_lines, msg);
body_set_stream (body, stream, msg);
message_set_body (msg, body, mpm);
}
/* Set the UIDL call on the message. */
message_set_uidl (msg, pop_uidl, mpm);
/* Set the UID on the message. */
message_set_uid (msg, pop_uid, mpm);
/* Add it to the list. */
monitor_wrlock (mbox->monitor);
{
pop_message_t *m ;
m = realloc (mpd->pmessages, (mpd->pmessages_count + 1)*sizeof (*m));
if (m == NULL)
{
message_destroy (&msg, mpm);
free (mpm);
monitor_unlock (mbox->monitor);
return ENOMEM;
}
mpd->pmessages = m;
mpd->pmessages[mpd->pmessages_count] = mpm;
mpd->pmessages_count++;
}
monitor_unlock (mbox->monitor);
/* Save The message pointer. */
message_set_mailbox (msg, mbox, mpm);
*pmsg = mpm->message = msg;
return 0;
}
/* There is no such thing in pop all messages should be consider recent.
FIXME: We could cheat and peek at the status if it was not strip
by the server ... */
static int
pop_messages_recent (mailbox_t mbox, size_t *precent)
{
return pop_messages_count (mbox, precent);
}
/* There is no such thing in pop all messages should be consider unseen.
FIXME: We could cheat and peek at the status if it was not strip
by the server ... */
static int
pop_message_unseen (mailbox_t mbox, size_t *punseen)
{
size_t count = 0;
int status = pop_messages_count (mbox, &count);
if (status != 0)
return status;
if (punseen)
*punseen = (count > 0) ? 1 : 0;
return 0;
}
/* How many messages we have. Done with STAT. */
static int
pop_messages_count (mailbox_t mbox, size_t *pcount)
{
pop_data_t mpd = mbox->data;
int status;
void *func = (void *)pop_messages_count;
if (mpd == NULL)
return EINVAL;
/* Do not send a STAT if we know the answer. */
if (pop_is_updated (mbox))
{
if (pcount)
*pcount = mpd->messages_count;
return 0;
}
/* Flag busy. */
CHECK_BUSY (mbox, mpd, func, 0);
/* TRANSACTION state. */
switch (mpd->state)
{
case POP_NO_STATE:
status = pop_writeline (mpd, "STAT\r\n");
CHECK_ERROR (mpd, status);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
mpd->state = POP_STAT;
case POP_STAT:
/* Send the STAT. */
status = pop_write (mpd);
CHECK_EAGAIN (mpd, status);
mpd->state = POP_STAT_ACK;
case POP_STAT_ACK:
/* Get the ACK. */
status = pop_read_ack (mpd);
CHECK_EAGAIN (mpd, status);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
break;
default:
/*
mu_error ("pop_messages_count: unknow state\n");
*/
break;
}
/* Parse the answer. */
status = sscanf (mpd->buffer, "+OK %d %d", &(mpd->messages_count),
&(mpd->size));
/* Clear the state _after_ the scanf, since another thread could
start writing over mpd->buffer. */
CLEAR_STATE (mpd);
if (status == EOF || status != 2)
return EIO;
if (pcount)
*pcount = mpd->messages_count;
mpd->is_updated = 1;
return 0;
}
/* Update and scanning. */
static int
pop_is_updated (mailbox_t mbox)
{
pop_data_t mpd = mbox->data;
if (mpd == NULL)
return 0;
return mpd->is_updated;
}
/* We just simulate by sending a notification for the total msgno. */
/* FIXME is message is set deleted should we sent a notif ? */
static int
pop_scan (mailbox_t mbox, size_t msgno, size_t *pcount)
{
int status;
size_t i;
size_t count = 0;
status = pop_messages_count (mbox, &count);
if (pcount)
*pcount = count;
if (status != 0)
return status;
if (mbox->observable == NULL)
return 0;
for (i = msgno; i <= count; i++)
{
if (observable_notify (mbox->observable, MU_EVT_MESSAGE_ADD) != 0)
break;
if (((i +1) % 10) == 0)
{
observable_notify (mbox->observable, MU_EVT_MAILBOX_PROGRESS);
}
}
return 0;
}
/* This is where we actually send the DELE command. Meaning that when
the attribute on the message is set deleted the comand DELE is not
sent right away and if we did there is no way to mark a message undeleted
beside closing down the connection without going to the update state via
QUIT. So DELE is send only when in expunge. */
static int
pop_expunge (mailbox_t mbox)
{
pop_data_t mpd = mbox->data;
size_t i;
attribute_t attr;
int status;
void *func = (void *)pop_expunge;
if (mpd == NULL)
return EINVAL;
/* Busy ? */
CHECK_BUSY (mbox, mpd, func, 0);
for (i = (int)mpd->id; i < mpd->pmessages_count; mpd->id = ++i)
{
if (message_get_attribute (mpd->pmessages[i]->message, &attr) == 0)
{
if (attribute_is_deleted (attr))
{
switch (mpd->state)
{
case POP_NO_STATE:
status = pop_writeline (mpd, "DELE %d\r\n",
mpd->pmessages[i]->num);
CHECK_ERROR (mpd, status);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
mpd->state = POP_DELE;
case POP_DELE:
/* Send DELETE. */
status = pop_write (mpd);
CHECK_EAGAIN (mpd, status);
mpd->state = POP_DELE_ACK;
case POP_DELE_ACK:
/* Ack Delete. */
status = pop_read_ack (mpd);
CHECK_EAGAIN (mpd, status);
MAILBOX_DEBUG0 (mbox, MU_DEBUG_PROT, mpd->buffer);
if (strncasecmp (mpd->buffer, "+OK", 3) != 0)
{
CHECK_ERROR (mpd, ERANGE);
}
mpd->state = POP_NO_STATE;
break;
default:
/* mu_error ("pop_expunge: unknow state\n"); */
break;
} /* switch (state) */
} /* if attribute_is_deleted() */
} /* message_get_attribute() */
} /* for */
CLEAR_STATE (mpd);
/* Invalidate. But Really they should shutdown the channel POP protocol
is not meant for this like IMAP. */
mpd->is_updated = 0;
return 0;
}
/* Mailbox size ? It is part of the STAT command */
static int
pop_get_size (mailbox_t mbox, off_t *psize)
{
pop_data_t mpd = mbox->data;
int status = 0;
if (mpd == NULL)
return EINVAL;
if (! pop_is_updated (mbox))
status = pop_messages_count (mbox, &mpd->size);
if (psize)
*psize = mpd->size;
return status;
}
/* Form the RFC:
"It is important to note that the octet count for a message on the
server host may differ from the octet count assigned to that message
due to local conventions for designating end-of-line. Usually,
during the AUTHORIZATION state of the POP3 session, the POP3 server
can calculate the size of each message in octets when it opens the
maildrop. For example, if the POP3 server host internally represents
end-of-line as a single character, then the POP3 server simply counts
each occurrence of this character in a message as two octets."
This is not perfect if we do not know the number of lines in the message
then the octets returned will not be correct so we do our best.
*/
static int
pop_message_size (message_t msg, size_t *psize)
{
pop_message_t mpm = message_get_owner (msg);
pop_data_t mpd;
int status = 0;
void *func = (void *)pop_message_size;
size_t num;
if (mpm == NULL)
return EINVAL;
/* Did we have it already ? */
if (mpm->message_size != 0)
{
*psize = mpm->message_size;
return 0;
}
mpd = mpm->mpd;
/* Busy ? */
CHECK_BUSY (mpd->mbox, mpd, func, msg);
/* Get the size. */
switch (mpd->state)
{
case POP_NO_STATE:
status = pop_writeline (mpd, "LIST %d\r\n", mpm->num);
CHECK_ERROR (mpd, status);
MAILBOX_DEBUG0 (mpd->mbox, MU_DEBUG_PROT, mpd->buffer);
mpd->state = POP_LIST;
case POP_LIST:
/* Send the LIST. */
status = pop_write (mpd);
CHECK_EAGAIN (mpd, status);
mpd->state = POP_LIST_ACK;
case POP_LIST_ACK:
/* Resp from LIST. */
status = pop_read_ack (mpd);
CHECK_EAGAIN (mpd, status);
MAILBOX_DEBUG0 (mpd->mbox, MU_DEBUG_PROT, mpd->buffer);
break;
default:
/*
mu_error ("pop_message_size state\n");
*/
break;
}
status = sscanf (mpd->buffer, "+OK %d %d\n", &num, &mpm->message_size);
CLEAR_STATE (mpd);
if (status != 2)
status = EINVAL;
/* The size of the message is with the extra '\r' octet for everyline.
Substract to get, hopefully, a good count. */
if (psize)
*psize = mpm->message_size - (mpm->header_lines + mpm->body_lines);
return 0;
}
/* Another source of trouble, POP only gives the size of the message
not the size of subparts like headers, body etc .. Again we're doing
our best with what we know but the only way to get a precise number
is by dowloading the whole message. */
static int
pop_body_size (body_t body, size_t *psize)
{
message_t msg = body_get_owner (body);
pop_message_t mpm = message_get_owner (msg);
if (mpm == NULL)
return EINVAL;
/* Did we have it already ? */
if (mpm->body_size != 0)
{
*psize = mpm->body_size;
}
else if (mpm->message_size != 0)
{
/* Take a guest. */
*psize = mpm->message_size - mpm->header_size - mpm->body_lines;
}
else
*psize = 0;
return 0;
}
/* Not know until the whole message get downloaded. */
static int
pop_body_lines (body_t body, size_t *plines)
{
message_t msg = body_get_owner (body);
pop_message_t mpm = message_get_owner (msg);
if (mpm == NULL)
return EINVAL;
if (plines)
*plines = mpm->body_lines;
return 0;
}
/* Pop does not have any command for this, We fake by reading the "Status: "
header. But this is hackish some POP server(Qpopper) skip it. Also
because we call header_get_value the function may return EAGAIN... uncool.
To put it another way, many servers simply remove the "Status:" header
field, when you dowload a message, so a message will always look like
new even if you already read it. There is also no way to set an attribute
on remote mailbox via the POP server and many server once you do a RETR
and in some cases a TOP will mark the message as read; "Status: RO"
or maybe worst some ISP configure there servers to delete after
the RETR some go as much as deleting after the TOP, since technicaly
you can download a message via TOP without RET'reiving it. */
static int
pop_get_attribute (attribute_t attr, int *pflags)
{
message_t msg = attribute_get_owner (attr);
pop_message_t mpm = message_get_owner (msg);
char hdr_status[64];
header_t header = NULL;
if (mpm == NULL || pflags == NULL)
return EINVAL;
if (mpm->attr_flags == 0)
{
hdr_status[0] = '\0';
message_get_header (mpm->message, &header);
header_get_value (header, "Status", hdr_status, sizeof hdr_status, NULL);
string_to_flags (hdr_status, &(mpm->attr_flags));
}
*pflags = mpm->attr_flags;
return 0;
}
static int
pop_set_attribute (attribute_t attr, int flags)
{
message_t msg = attribute_get_owner (attr);
pop_message_t mpm = message_get_owner (msg);
if (mpm == NULL)
return EINVAL;
mpm->attr_flags |= flags;
return 0;
}
static int
pop_unset_attribute (attribute_t attr, int flags)
{
message_t msg = attribute_get_owner (attr);
pop_message_t mpm = message_get_owner (msg);
if (mpm == NULL)
return EINVAL;
mpm->attr_flags &= ~flags;
return 0;
}
/* Stub to call the fd from body object. */
static int
pop_body_fd (stream_t stream, int *pfd)
{
body_t body = stream_get_owner (stream);
message_t msg = body_get_owner (body);
pop_message_t mpm = message_get_owner (msg);
return pop_get_fd (mpm, pfd);
}
/* Stub to call the fd from message object. */
static int
pop_message_fd (stream_t stream, int *pfd)
{
message_t msg = stream_get_owner (stream);
pop_message_t mpm = message_get_owner (msg);
return pop_get_fd (mpm, pfd);
}
/* Finally return the fd. */
static int
pop_get_fd (pop_message_t mpm, int *pfd)
{
if (mpm && mpm->mpd && mpm->mpd->mbox)
return stream_get_fd (mpm->mpd->mbox->stream, pfd);
return EINVAL;
}
static int
pop_uid (message_t msg, size_t *puid)
{
pop_message_t mpm = message_get_owner (msg);
if (puid)
*puid = mpm->num;
return 0;
}
/* Get the UIDL. Client should be prepare since it may fail. UIDL is
optional on many POP servers.
FIXME: We should check this with CAPA and fall back to a md5 scheme ?
Or maybe check for "X-UIDL" a la Qpopper ? */
static int
pop_uidl (message_t msg, char *buffer, size_t buflen, size_t *pnwriten)
{
pop_message_t mpm = message_get_owner (msg);
pop_data_t mpd;
int status = 0;
void *func = (void *)pop_uidl;
size_t num;
/* According to the RFC uidl's are no longer then 70 chars. Still playit
safe */
char uniq[128];
if (mpm == NULL)
return EINVAL;
/* Is it cache ? */
if (mpm->uidl)
{
size_t len = strlen (mpm->uidl);
if (buffer)
{
buflen--; /* Leave space for the null. */
buflen = (len > buflen) ? buflen : len;
memcpy (buffer, mpm->uidl, buflen);
buffer[buflen] = '\0';
}
else
buflen = len;
if (pnwriten)
*pnwriten = buflen;
return 0;
}
mpd = mpm->mpd;
/* Busy ? */
CHECK_BUSY (mpd->mbox, mpd, func, 0);
/* Get the UIDL. */
switch (mpd->state)
{
case POP_NO_STATE:
status = pop_writeline (mpd, "UIDL %d\r\n", mpm->num);
CHECK_ERROR (mpd, status);
MAILBOX_DEBUG0 (mpd->mbox, MU_DEBUG_PROT, mpd->buffer);
mpd->state = POP_UIDL;
case POP_UIDL:
/* Send the UIDL. */
status = pop_write (mpd);
CHECK_EAGAIN (mpd, status);
mpd->state = POP_UIDL_ACK;
case POP_UIDL_ACK:
/* Resp from UIDL. */
status = pop_read_ack (mpd);
CHECK_EAGAIN (mpd, status);
MAILBOX_DEBUG0 (mpd->mbox, MU_DEBUG_PROT, mpd->buffer);
break;
default:
/*
mu_error ("pop_uidl state\n");
*/
break;
}
/* FIXME: I should cache the result. */
*uniq = '\0';
status = sscanf (mpd->buffer, "+OK %d %127s\n", &num, uniq);
if (status != 2)
{
status = EINVAL;
buflen = 0;
}
else
{
num = strlen (uniq);
uniq[num - 1] = '\0'; /* Nuke newline. */
if (buffer)
{
buflen--; /* Leave space for the null. */
buflen = (buflen < num) ? buflen : num;
memcpy (buffer, uniq, buflen);
buffer [buflen] = '\0';
}
else
buflen = num - 1; /* Do not count newline. */
mpm->uidl = strdup (uniq);
status = 0;
}
CLEAR_STATE (mpd);
if (pnwriten)
*pnwriten = buflen;
return status;
}
/* How we retrieve the headers. If it fails we jump to the pop_retr()
code .i.e send a RETR and skip the body, ugly.
NOTE: if the offset is different, flag an error, offset is meaningless
on a socket but we better warn them, some stuff like mime_t may try to
read ahead, for example for the headers. */
static int
pop_top (header_t header, char *buffer, size_t buflen,
off_t offset, size_t *pnread)
{
message_t msg = header_get_owner (header);
pop_message_t mpm = message_get_owner (msg);
pop_data_t mpd;
size_t nread = 0;
int status = 0;
void *func = (void *)pop_top;
if (mpm == NULL)
return EINVAL;
mpd = mpm->mpd;
/* Busy ? */
CHECK_BUSY (mpd->mbox, mpd, func, msg);
/* We start fresh then reset the sizes. */
if (mpd->state == POP_NO_STATE)
mpm->header_size = 0;
/* Throw an error if trying to seek back. */
if ((size_t)offset < mpm->header_size)
return ESPIPE;
/* Get the header. */
switch (mpd->state)
{
case POP_NO_STATE:
/* TOP is an optionnal command, if we want to be compliant we can not
count on it to exists. So we should be prepare when it fails and
fall to a second scheme. */
status = pop_writeline (mpd, "TOP %d 0\r\n", mpm->num);
CHECK_ERROR (mpd, status);
MAILBOX_DEBUG0 (mpd->mbox, MU_DEBUG_PROT, mpd->buffer);
mpd->state = POP_TOP;
case POP_TOP:
/* Send the TOP. */
status = pop_write (mpd);
CHECK_EAGAIN (mpd, status);
mpd->state = POP_TOP_ACK;
case POP_TOP_ACK:
/* Ack from TOP. */
status = pop_read_ack (mpd);
CHECK_EAGAIN (mpd, status);
MAILBOX_DEBUG0 (mpd->mbox, MU_DEBUG_PROT, mpd->buffer);
if (strncasecmp (mpd->buffer, "+OK", 3) != 0)
{
/* mu_error ("TOP not implemented\n"); */
/* Fall back to RETR call. */
mpd->state = POP_NO_STATE;
mpm->skip_header = 0;
mpm->skip_body = 1;
return pop_retr (mpm, buffer, buflen, offset, pnread);
}
mpd->state = POP_TOP_RX;
case POP_TOP_RX:
/* Get the header. */
do
{
/* Seek in position. */
ssize_t pos = offset - mpm->header_size;
/* Do we need to fill up. */
if (mpd->nl == NULL || mpd->ptr == mpd->buffer)
{
status = pop_readline (mpd);
CHECK_EAGAIN (mpd, status);
mpm->header_lines++;
}
/* If we have to skip some data to get to the offset. */
if (pos > 0)
nread = fill_buffer (mpd, NULL, pos);
else
nread = fill_buffer (mpd, buffer, buflen);
mpm->header_size += nread;
}
while (nread > 0 && (size_t)offset > mpm->header_size);
break;
default:
/* Probaly TOP was not supported so we have fall back to RETR. */
mpm->skip_header = 0;
mpm->skip_body = 1;
return pop_retr (mpm, buffer, buflen, offset, pnread);
} /* switch (state) */
if (nread == 0)
{
CLEAR_STATE (mpd);
}
if (pnread)
*pnread = nread;
return 0;
}
/* This is no longer use, see pop_top to retreive headers, we still
keep it around for debugging purposes. */
#if 0
/* Stub to call pop_retr (). Call form the stream object of the header. */
static int
pop_header_read (header_t header, char *buffer, size_t buflen, off_t offset,
size_t *pnread)
{
message_t msg = header_get_owner (header);
pop_message_t mpm = message_get_owner (msg);
pop_data_t mpd;
void *func = (void *)pop_header_read;
if (mpm == NULL)
return EINVAL;
mpd = mpm->mpd;
/* Busy ? */
CHECK_BUSY (mpd->mbox, mpd, func, msg);
/* We start fresh then reset the sizes. */
if (mpd->state == POP_NO_STATE)
mpm->header_size = mpm->inbody = 0;
/* Throw an error if trying to seek back. */
if ((size_t)offset < mpm->header_size)
return ESPIPE;
mpm->skip_header = 0;
mpm->skip_body = 1;
return pop_retr (mpm, buffer, buflen, offset, pnread);
}
#endif
/* Stub to call pop_retr (). Call from the stream object of the body. */
static int
pop_body_read (stream_t is, char *buffer, size_t buflen, off_t offset,
size_t *pnread)
{
body_t body = stream_get_owner (is);
message_t msg = body_get_owner (body);
pop_message_t mpm = message_get_owner (msg);
pop_data_t mpd;
void *func = (void *)pop_body_read;
if (mpm == NULL)
return EINVAL;
mpd = mpm->mpd;
/* Busy ? */
CHECK_BUSY (mpd->mbox, mpd, func, msg);
/* We start fresh then reset the sizes. */
if (mpd->state == POP_NO_STATE)
mpm->body_size = mpm->inbody = 0;
/* Can not seek back this a stream socket. */
if ((size_t)offset < mpm->body_size)
return ESPIPE;
mpm->skip_header = 1;
mpm->skip_body = 0;
return pop_retr (mpm, buffer, buflen, offset, pnread);
}
/* Stub to call pop_retr (), calling from the stream object of a message. */
static int
pop_message_read (stream_t is, char *buffer, size_t buflen, off_t offset,
size_t *pnread)
{
message_t msg = stream_get_owner (is);
pop_message_t mpm = message_get_owner (msg);
pop_data_t mpd;
void *func = (void *)pop_message_read;
if (mpm == NULL)
return EINVAL;
mpd = mpm->mpd;
/* Busy ? */
CHECK_BUSY (mpd->mbox, mpd, func, msg);
/* We start fresh then reset the sizes. */
if (mpd->state == POP_NO_STATE)
mpm->header_size = mpm->body_size = mpm->inbody = 0;
/* Can not seek back this is a stream socket. */
if ((size_t)offset < (mpm->body_size + mpm->header_size))
return ESPIPE;
mpm->skip_header = mpm->skip_body = 0;
return pop_retr (mpm, buffer, buflen, offset, pnread);
}
/* Little helper to fill the buffer without overflow. */
static int
fill_buffer (pop_data_t mpd, char *buffer, size_t buflen)
{
int nleft, n, nread = 0;
/* How much we can copy ? */
n = mpd->ptr - mpd->buffer;
nleft = buflen - n;
/* We got more then requested. */
if (nleft < 0)
{
size_t sentinel;
nread = buflen;
sentinel = mpd->ptr - (mpd->buffer + nread);
if (buffer)
memcpy (buffer, mpd->buffer, nread);
memmove (mpd->buffer, mpd->buffer + nread, sentinel);
mpd->ptr = mpd->buffer + sentinel;
}
else
{
/* Drain our buffer. */;
nread = n;
if (buffer)
memcpy (buffer, mpd->buffer, nread);
mpd->ptr = mpd->buffer;
}
return nread;
}
/* The heart of most funtions. Send the RETR and skip different parts. */
static int
pop_retr (pop_message_t mpm, char *buffer, size_t buflen, off_t offset,
size_t *pnread)
{
pop_data_t mpd;
size_t nread = 0;
int status = 0;
size_t oldbuflen = buflen;
/* Meaningless. */
(void)offset;
mpd = mpm->mpd;
if (pnread)
*pnread = nread;
/* Take care of the obvious. */
if (buffer == NULL || buflen == 0)
{
CLEAR_STATE (mpd);
return 0;
}
/* pop_retr() is not call directly so we assume that the locks were set. */
switch (mpd->state)
{
case POP_NO_STATE:
mpm->body_lines = mpm->body_size = 0;
status = pop_writeline (mpd, "RETR %d\r\n", mpm->num);
MAILBOX_DEBUG0 (mpd->mbox, MU_DEBUG_PROT, mpd->buffer);
CHECK_ERROR (mpd, status);
mpd->state = POP_RETR;
case POP_RETR:
/* Send the RETR command. */
status = pop_write (mpd);
CHECK_EAGAIN (mpd, status);
mpd->state = POP_RETR_ACK;
case POP_RETR_ACK:
/* RETR ACK. */
status = pop_read_ack (mpd);
CHECK_EAGAIN (mpd, status);
MAILBOX_DEBUG0 (mpd->mbox, MU_DEBUG_PROT, mpd->buffer);
if (strncasecmp (mpd->buffer, "+OK", 3) != 0)
{
CHECK_ERROR (mpd, EACCES);
}
mpd->state = POP_RETR_RX_HDR;
case POP_RETR_RX_HDR:
/* Skip/Take the header. */
while (!mpm->inbody)
{
/* Do we need to fill up. */
if (mpd->nl == NULL || mpd->ptr == mpd->buffer)
{
status = pop_readline (mpd);
if (status != 0)
{
/* Do we have something in the buffer flush it first. */
if (buflen != oldbuflen)
return 0;
CHECK_EAGAIN (mpd, status);
}
mpm->header_lines++;
}
/* Oops !! Hello houston we have a major problem here. */
if (mpd->buffer[0] == '\0')
{
/* Still Do the right thing. */
if (buflen != oldbuflen)
{
CLEAR_STATE (mpd);
}
else
mpd->state = POP_STATE_DONE;
return 0;
}
/* The problem is that we are using RETR instead of TOP to retreive
headers, i.e the server contacted does not support it. So we
have to make sure that the next line really means end of the
headers. Still some cases we may loose. But 99.9% of POPD
encounter support TOP. In the 0.1% case get GNU pop3d, or the
hack below will suffice. */
if (mpd->buffer[0] == '\n' && mpd->buffer[1] == '\0')
mpm->inbody = 1; /* break out of the while. */
if (!mpm->skip_header)
{
ssize_t pos = offset - mpm->header_size;
if (pos > 0)
{
nread = fill_buffer (mpd, NULL, pos);
mpm->header_size += nread;
}
else
{
nread = fill_buffer (mpd, buffer, buflen);
mpm->header_size += nread;
if (pnread)
*pnread += nread;
buflen -= nread ;
if (buflen > 0)
buffer += nread;
else
return 0;
}
}
else
mpd->ptr = mpd->buffer;
}
mpd->state = POP_RETR_RX_BODY;
case POP_RETR_RX_BODY:
/* Start/Take the body. */
while (mpm->inbody)
{
/* Do we need to fill up. */
if (mpd->nl == NULL || mpd->ptr == mpd->buffer)
{
status = pop_readline (mpd);
if (status != 0)
{
/* Flush The Buffer ? */
if (buflen != oldbuflen)
return 0;
CHECK_EAGAIN (mpd, status);
}
mpm->body_lines++;
}
if (mpd->buffer[0] == '\0')
mpm->inbody = 0; /* Breakout of the while. */
if (!mpm->skip_body)
{
/* If we did not skip the header, it means that we are
downloading the entire message and the header_size should be
part of the offset count. */
ssize_t pos = offset - (mpm->body_size + ((mpm->skip_header) ?
0 : mpm->header_size));
if (pos > 0)
{
nread = fill_buffer (mpd, NULL, pos);
mpm->body_size += nread;
}
else
{
nread = fill_buffer (mpd, buffer, buflen);
mpm->body_size += nread;
if (pnread)
*pnread += nread;
buflen -= nread ;
if (buflen > 0)
buffer += nread;
else
return 0;
}
}
else
{
mpm->body_size += (mpd->ptr - mpd->buffer);
mpd->ptr = mpd->buffer;
}
}
mpm->message_size = mpm->body_size + mpm->header_size;
mpd->state = POP_STATE_DONE;
/* Return here earlier, because we want to return nread = 0 to notify
the callee that we've finish, since there is already data
we have to return them first and _then_ tell them its finish. If we
don't we will start over again by sending another RETR. */
if (buflen != oldbuflen)
return 0;
case POP_STATE_DONE:
/* A convenient break, this is here we can return 0, we're done. */
default:
/* mu_error ("pop_retr unknow state\n"); */
break;
} /* Switch state. */
CLEAR_STATE (mpd);
mpm->skip_header = mpm->skip_body = 0;
return 0;
}
/* Extract the User from the URL or the ticket. */
static int
pop_get_user (authority_t auth)
{
folder_t folder = authority_get_owner (auth);
mailbox_t mbox = folder->data;
pop_data_t mpd = mbox->data;
ticket_t ticket = NULL;
int status;
/* Fetch the user from them. */
size_t n = 0;
authority_get_ticket (auth, &ticket);
if (mpd->user)
{
free (mpd->user);
mpd->user = NULL;
}
/* Was it in the URL? */
status = url_get_user (mbox->url, NULL, 0, &n);
if (status != 0 || n == 0)
ticket_pop (ticket, mbox->url, "Pop User: ", &mpd->user);
else
{
mpd->user = calloc (1, n + 1);
url_get_user (mbox->url, mpd->user, n + 1, NULL);
}
return 0;
}
/* Extract the User from the URL or the ticket. */
static int
pop_get_passwd (authority_t auth)
{
folder_t folder = authority_get_owner (auth);
mailbox_t mbox = folder->data;
pop_data_t mpd = mbox->data;
ticket_t ticket = NULL;
int status;
/* Fetch the user from them. */
size_t n = 0;
authority_get_ticket (auth, &ticket);
if (mpd->passwd)
{
free (mpd->passwd);
mpd->passwd = NULL;
}
/* Was it in the URL? */
status = url_get_passwd (mbox->url, NULL, 0, &n);
if (status != 0 || n == 0)
ticket_pop (ticket, mbox->url, "Pop Passwd: ", &mpd->passwd);
else
{
mpd->passwd = calloc (1, n + 1);
url_get_passwd (mbox->url, mpd->passwd, n + 1, NULL);
}
return 0;
}
static char *
pop_get_timestamp (pop_data_t mpd)
{
char *right, *left;
char *timestamp = NULL;
size_t len;
len = strlen (mpd->buffer);
right = memchr (mpd->buffer, '<', len);
if (right)
{
len = len - (right - mpd->buffer);
left = memchr (right, '>', len);
if (left)
{
len = left - right + 1;
timestamp = calloc (len + 1, 1);
if (timestamp != NULL)
{
memcpy (timestamp, right, len);
}
}
}
return timestamp;
}
/* Make the MD5 string. */
static int
pop_get_md5 (pop_data_t mpd)
{
MD5_CTX md5context;
unsigned char md5digest[16];
char digest[64]; /* Really it just has to be 32 + 1(null). */
char *tmp;
size_t n;
char *timestamp;
timestamp = pop_get_timestamp (mpd);
if (timestamp == NULL)
return EINVAL;
MD5Init (&md5context);
MD5Update (&md5context, (unsigned char *)timestamp, strlen (timestamp));
MD5Update (&md5context, (unsigned char *)mpd->passwd, strlen (mpd->passwd));
MD5Final (md5digest, &md5context);
for (tmp = digest, n = 0; n < 16; n++, tmp += 2)
sprintf (tmp, "%02x", md5digest[n]);
*tmp = '\0';
free (timestamp);
free (mpd->passwd);
mpd->passwd = strdup (digest);
return 0;
}
/* GRRRRR!! We can not use sleep in the library since this we'll
muck up any alarm() done by the user. */
static int
pop_sleep (int seconds)
{
struct timeval tval;
tval.tv_sec = seconds;
tval.tv_usec = 0;
return select (1, NULL, NULL, NULL, &tval);
}
/* C99 says that a conforming implementation of snprintf () should return the
number of char that would have been call but many old 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. */
static int
pop_writeline (pop_data_t mpd, const char *format, ...)
{
int len;
va_list ap;
int done = 1;
if (mpd->buffer == NULL)
return EINVAL;
va_start(ap, format);
do
{
len = vsnprintf (mpd->buffer, mpd->buflen - 1, format, ap);
if (len < 0 || len >= (int)mpd->buflen
|| !memchr (mpd->buffer, '\0', len + 1))
{
mpd->buflen *= 2;
mpd->buffer = realloc (mpd->buffer, mpd->buflen);
if (mpd->buffer == NULL)
return ENOMEM;
done = 0;
}
else
done = 1;
}
while (!done);
va_end(ap);
mpd->ptr = mpd->buffer + len;
return 0;
}
/* A socket may write less then expected and we have to cope with nonblocking.
if the write failed we keep track and restart where left. */
static int
pop_write (pop_data_t mpd)
{
int status = 0;
if (mpd->ptr > mpd->buffer)
{
size_t len;
size_t n = 0;
len = mpd->ptr - mpd->buffer;
status = stream_write (mpd->mbox->stream, mpd->buffer, len, 0, &n);
if (status == 0)
{
memmove (mpd->buffer, mpd->buffer + n, len - n);
mpd->ptr -= n;
}
}
else
mpd->ptr = mpd->buffer;
return status;
}
/* Call readline and reset the mpd->ptr to the buffer, signalling that we have
done the read to completion. */
static int
pop_read_ack (pop_data_t mpd)
{
int status = pop_readline (mpd);
if (status == 0)
mpd->ptr = mpd->buffer;
return status;
}
/* Read a complete line form the pop server. Transform CRLF to LF, remove
the stuff byte termination octet ".", put a null in the buffer
when done. */
static int
pop_readline (pop_data_t mpd)
{
size_t n = 0;
size_t total = mpd->ptr - mpd->buffer;
int status;
/* Must get a full line before bailing out. */
do
{
status = stream_readline (mpd->mbox->stream, mpd->buffer + total,
mpd->buflen - total, mpd->offset, &n);
if (status != 0)
return status;
/* The server went away: It maybe a timeout and some pop server
does not send the -ERR. Consider this like an error. */
if (n == 0)
return EIO;
total += n;
mpd->offset += n;
mpd->nl = memchr (mpd->buffer, '\n', total);
if (mpd->nl == NULL) /* Do we have a full line. */
{
/* Allocate a bigger buffer ? */
if (total >= mpd->buflen -1)
{
mpd->buflen *= 2;
mpd->buffer = realloc (mpd->buffer, mpd->buflen + 1);
if (mpd->buffer == NULL)
return ENOMEM;
}
}
mpd->ptr = mpd->buffer + total;
}
while (mpd->nl == NULL);
/* When examining a multi-line response, the client checks to see if the
line begins with the termination octet "."(DOT). If yes and if octets
other than CRLF follow, the first octet of the line (the termination
octet) is stripped away. */
if (total >= 3 && mpd->buffer[0] == '.')
{
if (mpd->buffer[1] != '\r' && mpd->buffer[2] != '\n')
{
memmove (mpd->buffer, mpd->buffer + 1, total - 1);
mpd->ptr--;
mpd->nl--;
}
/* And if CRLF immediately follows the termination character, then the
response from the POP server is ended and the line containing
".CRLF" is not considered part of the multi-line response. */
else if (mpd->buffer[1] == '\r' && mpd->buffer[2] == '\n')
{
mpd->buffer[0] = '\0';
mpd->ptr = mpd->buffer;
mpd->nl = NULL;
}
}
/* \r\n --> \n\0, conversion. */
if (mpd->nl > mpd->buffer)
{
*(mpd->nl - 1) = '\n';
*(mpd->nl) = '\0';
mpd->ptr = mpd->nl;
}
return 0;
}
#endif
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2003 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef ENABLE_POP
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <url0.h>
#include <registrar0.h>
static void url_pop_destroy (url_t url);
static void
url_pop_destroy (url_t url)
{
(void)url;
}
/*
POP URL:
pop://[<user>[;AUTH=<auth>]@]<host>[:<port>]
or:
pop://[<user>[:pass]@]<host>[:<port>]
*/
int
_url_pop_init (url_t url)
{
int status = 0;
url->_destroy = url_pop_destroy;
status = url_parse(url);
if(status)
return status;
/* is it pop? */
if (strcmp ("pop", url->scheme) != 0)
return EINVAL;
/* not valid in a pop url */
if (url->path || url->query || !url->host)
return EINVAL;
if (url->port == 0)
url->port = MU_POP_PORT;
return status;
}
#endif