diff options
Diffstat (limited to 'mailbox/pop/mbox.c')
-rw-r--r-- | mailbox/pop/mbox.c | 2115 |
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 |