summaryrefslogtreecommitdiff
path: root/mailbox/pop/mbox.c
diff options
context:
space:
mode:
Diffstat (limited to 'mailbox/pop/mbox.c')
-rw-r--r--mailbox/pop/mbox.c2115
1 files changed, 2115 insertions, 0 deletions
diff --git a/mailbox/pop/mbox.c b/mailbox/pop/mbox.c
new file mode 100644
index 000000000..ab3e8183a
--- /dev/null
+++ b/mailbox/pop/mbox.c
@@ -0,0 +1,2115 @@
+/* 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 = mes