From 0b457bda897eb23a0e47e77101cda048a529b7f5 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Sun, 5 Sep 2010 23:49:54 +0300 Subject: Begin rewriting pop3 mailbox support. Several bugfixes. * examples/pop3client.c (com_capa): Call mu_pop3_capa_test. (com_stat): Count is size_t. * include/mailutils/opool.h (mu_opool_copy): New proto. * mailbox/opool.c (mu_opool_copy): New function. * mailbox/xscript-stream.c (_xscript_ctl) : Avoid coredumping if sp->transport is NULL. * include/mailutils/pop3.h (pop3_capa_test): Rename to mu_pop3_capa_test. (mu_pop3_stat): Third argument is a pointer to mu_off_t. * libproto/pop/pop3_capatst.c (pop3_capa_test): Rename to mu_pop3_capa_test. * libproto/pop/pop3_stat.c (mu_pop3_stat): Third argument is a pointer to mu_off_t. * libproto/pop/Makefile.am (libmu_pop_la_SOURCES): Put back folder.c, url.c and mbox.c. * libproto/pop/mbox.c: Begin rewriting. --- examples/pop3client.c | 4 +- include/mailutils/opool.h | 4 + include/mailutils/pop3.h | 6 +- libproto/pop/Makefile.am | 6 +- libproto/pop/mbox.c | 2649 +++++++++---------------------------------- libproto/pop/pop3_capatst.c | 2 +- libproto/pop/pop3_stat.c | 7 +- mailbox/opool.c | 19 + mailbox/xscript-stream.c | 5 +- 9 files changed, 547 insertions(+), 2155 deletions(-) diff --git a/examples/pop3client.c b/examples/pop3client.c index 24f621147..d3085ebb1 100644 --- a/examples/pop3client.c +++ b/examples/pop3client.c @@ -502,7 +502,7 @@ com_capa (int argc, char **argv) for (; i < argc; i++) { const char *elt; - int rc = pop3_capa_test (pop3, argv[i], &elt); + int rc = mu_pop3_capa_test (pop3, argv[i], &elt); switch (rc) { case 0: @@ -677,7 +677,7 @@ com_pass (int argc, char **argv) int com_stat (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED) { - unsigned count = 0; + size_t count = 0; size_t size = 0; int status = 0; diff --git a/include/mailutils/opool.h b/include/mailutils/opool.h index 78b66a7a1..49b1b02e1 100644 --- a/include/mailutils/opool.h +++ b/include/mailutils/opool.h @@ -62,6 +62,10 @@ size_t mu_opool_size (mu_opool_t opool); mu_opool_create, above). */ int mu_opool_coalesce (mu_opool_t opool, size_t *psize); +/* Copy at most SIZE bytes of collected data into BUF. Return the + actual number of bytes copied. */ +size_t mu_opool_copy (mu_opool_t opool, void *buf, size_t size); + /* Return the pointer to the current object head chunk. If mu_opool_coalesce was called before, the returned value points to the entire object. If PSIZE is not NULL, store the size of the head chunk to *PSIZE. */ diff --git a/include/mailutils/pop3.h b/include/mailutils/pop3.h index d85c49ea1..a6ced1008 100644 --- a/include/mailutils/pop3.h +++ b/include/mailutils/pop3.h @@ -59,8 +59,8 @@ int mu_pop3_stls (mu_pop3_t pop3); done with the iterator. The items returned by the iterator are of type "const char *", no processing is done on the item except the removal of the trailing newline. */ -int mu_pop3_capa (mu_pop3_t pop3, int reread, mu_iterator_t *piter); -int pop3_capa_test (mu_pop3_t pop3, const char *name, const char **pret); +int mu_pop3_capa (mu_pop3_t pop3, int reread, mu_iterator_t *piter); +int mu_pop3_capa_test (mu_pop3_t pop3, const char *name, const char **pret); int mu_pop3_dele (mu_pop3_t pop3, unsigned int mesgno); @@ -89,7 +89,7 @@ int mu_pop3_retr (mu_pop3_t pop3, unsigned int mesgno, int mu_pop3_rset (mu_pop3_t pop3); -int mu_pop3_stat (mu_pop3_t pop3, unsigned int *count, size_t *octets); +int mu_pop3_stat (mu_pop3_t pop3, size_t *count, mu_off_t *octets); /* A stream is returned with the multi-line answer. It is the responsability of the caller to call mu_stream_destroy() to dipose of the stream. */ diff --git a/libproto/pop/Makefile.am b/libproto/pop/Makefile.am index 18df08dcb..5865e4e35 100644 --- a/libproto/pop/Makefile.am +++ b/libproto/pop/Makefile.am @@ -24,10 +24,10 @@ lib_LTLIBRARIES = libmu_pop.la libmu_pop_la_LDFLAGS=-version-info @VI_CURRENT@:@VI_REVISION@:@VI_AGE@ libmu_pop_la_LIBADD = ${MU_LIB_AUTH} ${MU_LIB_MAILUTILS} @INTLLIBS@ -# folder.c\ -# url.c\ -# mbox.c libmu_pop_la_SOURCES = \ + mbox.c \ + folder.c\ + url.c\ \ pop3_apop.c \ pop3_capa.c \ diff --git a/libproto/pop/mbox.c b/libproto/pop/mbox.c index 5268a9f35..3b538a0ef 100644 --- a/libproto/pop/mbox.c +++ b/libproto/pop/mbox.c @@ -23,21 +23,13 @@ #ifdef ENABLE_POP -#include -#include #include -#include #include #include #include #include -#include -#include - -#ifdef HAVE_STRINGS_H -# include -#endif +#include #include #include #include @@ -57,1509 +49,389 @@ #include #include #include +#include #include #include #include #include -#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; +#define _POP3_MSG_INBODY 0x01 +#define _POP3_MSG_SKIPHDR 0x02 +#define _POP3_MSG_SKIPBDY 0x04 -/* 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 +struct _pop3_message { - POP_NO_STATE, POP_STATE_DONE, - POP_OPEN_CONNECTION, - POP_GREETINGS, - POP_CAPA, POP_CAPA_ACK, - 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_STLS, POP_STLS_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 -}; - -/* POP3 capabilities */ -#define CAPA_TOP 0x00000001 -#define CAPA_USER 0x00000002 -#define CAPA_UIDL 0x00000004 -#define CAPA_RESP_CODES 0x00000008 -#define CAPA_LOGIN_DELAY 0x00000010 -#define CAPA_PIPELINING 0x00000020 -#define CAPA_EXPIRE 0x00000040 -#define CAPA_SASL 0x00000080 -#define CAPA_STLS 0x00000100 -#define CAPA_IMPLEMENTATION 0x00000200 - -static void pop_destroy (mu_mailbox_t); -static int pop_capa (mu_mailbox_t); -static int pop_stls (mu_mailbox_t); - -/* Functions/Methods that implements the mu_mailbox_t API. */ -static int pop_open (mu_mailbox_t, int); -static int pop_close (mu_mailbox_t); -static int pop_get_message (mu_mailbox_t, size_t, mu_message_t *); -static int pop_messages_count (mu_mailbox_t, size_t *); -static int pop_messages_recent (mu_mailbox_t, size_t *); -static int pop_message_unseen (mu_mailbox_t, size_t *); -static int pop_expunge (mu_mailbox_t); -static int pop_scan (mu_mailbox_t, size_t, size_t *); -static int pop_is_updated (mu_mailbox_t); - -/* The implementation of mu_message_t */ -int _pop_user (mu_authority_t); -int _pop_apop (mu_authority_t); -static int pop_get_size (mu_mailbox_t, mu_off_t *); -/* We use pop_top for retreiving headers. */ -/* static int pop_header_read (mu_header_t, char *, size_t, mu_off_t, size_t *); */ -static int pop_body_transport (mu_stream_t, mu_transport_t *, mu_transport_t *); -static int pop_body_size (mu_body_t, size_t *); -static int pop_body_lines (mu_body_t, size_t *); -static int pop_body_read (mu_stream_t, char *, size_t, mu_off_t, size_t *); -static int pop_message_read (mu_stream_t, char *, size_t, mu_off_t, size_t *); -static int pop_message_size (mu_message_t, size_t *); -static int pop_message_transport (mu_stream_t, mu_transport_t *, mu_transport_t *); -static int pop_top (mu_header_t, char *, size_t, mu_off_t, size_t *); -static int pop_retr (pop_message_t, char *, size_t, mu_off_t, size_t *); -static int pop_get_transport2 (pop_message_t, mu_transport_t *, mu_transport_t *); -static int pop_get_attribute (mu_attribute_t, int *); -static int pop_set_attribute (mu_attribute_t, int); -static int pop_unset_attribute (mu_attribute_t, int); -static int pop_uidl (mu_message_t, char *, size_t, size_t *); -static int pop_uid (mu_message_t, size_t *); -static int fill_buffer (pop_data_t, char *, size_t); -static int pop_sleep (int); -static int pop_readline (pop_data_t); -static int pop_read_ack (pop_data_t); -static int pop_writeline (pop_data_t, const char *, ...) - MU_PRINTFLIKE(2,3); -static int pop_write (pop_data_t); -static int pop_get_user (mu_authority_t); -static int pop_get_passwd (mu_authority_t); -static char *pop_get_timestamp (pop_data_t); -static int pop_get_md5 (pop_data_t); - -/* This structure holds the info for a message. The pop_message_t - type, will serve as the owner of the mu_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; + int flags; size_t body_size; size_t header_size; size_t body_lines; size_t header_lines; - size_t mu_message_size; + size_t message_size; size_t num; char *uidl; /* Cache the uidl string. */ int attr_flags; mu_message_t message; - pop_data_t mpd; /* Back pointer. */ + struct _pop3_mailbox *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 + +struct _pop3_mailbox { - 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. */ - int pops; /* POPS or POP? */ - char *greeting_banner; /* A greeting banner */ - unsigned long capa; /* Server capabilities */ - 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. */ - mu_off_t offset; /* Dummy, this is use because of the stream buffering. - The mu_stream_t maintains and offset and the offset we use must - be in sync. */ - - int is_updated; + mu_pop3_t pop3; /* mu_pop3_t is the working horse */ + int pops; /* true if pop3 over SSL is being used */ + int is_updated; /* true if the mailbox info is up to date */ + + size_t msg_count; /* Number of messages in the mailbox */ + mu_off_t total_size; /* Total mailbox size. */ + struct _pop3_message **msg; /* Array of messages */ + size_t msg_max; /* Actual size of the array */ + mu_mailbox_t mbox; /* MU mailbox corresponding to this one. */ + char *user; /* Temporary holders for user and passwd. */ mu_secret_t secret; - char *digest; - mu_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 mu_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: - { - mu_mailbox_t mbox; mu_message_t msg; mu_stream_t stream; char buffer[105]; - mu_mailbox_create (&mbox, "pop://qnx.com"); - mu_mailbox_get_message (mbox, 1, &msg); - mu_message_get_stream (msg, &stream); - while (mu_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 = mu_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; \ - mu_monitor_unlock (mbox->monitor); \ - err = pop_open (mbox, mbox->flags); \ - if (err != 0) \ - { \ - return err; \ - } \ - } \ - else \ - { \ - mpd->id = (size_t)identity; \ - mpd->func = func; \ - mu_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) \ - { \ - mu_stream_close (mbox->stream); \ - CLEAR_STATE (mpd); \ - mpd->func = (void *)-1; \ - MU_DEBUG1 (mbox->debug, 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; \ - MU_DEBUG1(mpd->mbox->debug, 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; \ - MU_DEBUG1(mpd->mbox->debug, MU_DEBUG_PROT, \ - "CHECK_EAGAIN: %s\n", mu_strerror (status));\ - } \ - return status; \ - } \ - } \ -while (0) - - -/* Allocate mu_mailbox_t, allocate pop internal structures. */ +}; + static int -_mailbox_pop_and_pops_init (mu_mailbox_t mbox, int pops) +pop_open (mu_mailbox_t mbox, int flags) { - 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; + struct _pop3_mailbox *mpd = mbox->data; + int status; + const char *host; + long port = mpd->pops ? MU_POPS_PORT : MU_POP_PORT; + mu_stream_t stream; + + /* Sanity checks. */ + if (mpd == NULL) + return EINVAL; + + /* Fetch the pop server name and the port in the mu_url_t. */ + status = mu_url_sget_host (mbox->url, &host); + if (status != 0) + return status; + mu_url_get_port (mbox->url, &port); - mpd->mbox = mbox; /* Back pointer. */ - mpd->state = POP_NO_STATE; /* Init with no state. */ - mpd->pops = pops; + mbox->flags = flags; - /* Initialize the structure. */ - mbox->_destroy = pop_destroy; + status = mu_tcp_stream_create (&stream, host, port, mbox->flags); + if (status) + return status; +#ifdef WITH_TLS + if (mpd->pops) + { + mu_stream_t newstr; + + status = mu_stream_open (stream); + if (status) + { + mu_stream_destroy (&stream); + return status; + } + + status = mu_tls_client_stream_create (&newstr, stream, stream, 0); + mu_stream_unref (stream); + if (status) + { + mu_error ("pop_open: mu_tls_client_stream_create: %s", + mu_strerror (status)); + return status; + } + stream = newstr; + } +#endif /* WITH_TLS */ - mbox->_open = pop_open; - mbox->_close = pop_close; + /* FIXME: How to configure buffer size? */ + mu_stream_set_buffer (stream, mu_buffer_line, 1024); - /* 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; + status = mu_pop3_create (&mpd->pop3); + if (status) + { + mu_stream_destroy (&stream); + return status; + } + mu_pop3_set_carrier (mpd->pop3, stream); - mbox->_scan = pop_scan; - mbox->_is_updated = pop_is_updated; + if (mu_debug_check_level (mbox->debug, MU_DEBUG_PROT)) + mu_pop3_trace (mpd->pop3, MU_POP3_TRACE_SET); - mbox->_get_size = pop_get_size; + do + { + status = mu_pop3_connect (mpd->pop3); + if (status) + break; - /* Set our properties. */ - { - mu_property_t property = NULL; - mu_mailbox_get_property (mbox, &property); - mu_property_set_value (property, "TYPE", "POP3", 1); - } + status = mu_pop3_capa (mpd->pop3, 1, NULL); + if (status) + break; - /* Hack! POP does not really have a folder. */ - mbox->folder->data = mbox; + if (WITH_TLS && !mpd->pops && + mu_pop3_capa_test (mpd->pop3, "STLS", NULL) == 0) + { + status = mu_pop3_stls (mpd->pop3); + if (status) + break; + } + status = mu_authority_authenticate (mbox->folder->authority); + } + while (0); + + if (status) + mu_pop3_destroy (&mpd->pop3); return status; } - -int -_mailbox_pop_init (mu_mailbox_t mbox) -{ - return _mailbox_pop_and_pops_init (mbox, 0); -} - -int -_mailbox_pops_init (mu_mailbox_t mbox) + +static int +pop_close (mu_mailbox_t mbox) { - return _mailbox_pop_and_pops_init (mbox, 1); + struct _pop3_mailbox *mpd = mbox->data; + int status; + + status = mu_pop3_quit (mpd->pop3); + if (status) + mu_error ("mu_pop3_quit failed: %s", mu_strerror (status)); + status = mu_pop3_disconnect (mpd->pop3); + if (status) + mu_error ("mu_pop3_disconnect failed: %s", mu_strerror (status)); + mu_pop3_destroy (&mpd->pop3); + return 0; } -/* Cleaning up all the ressources associate with a pop mailbox. */ static void pop_destroy (mu_mailbox_t mbox) { - if (mbox->data) + struct _pop3_mailbox *mpd = mbox->data; + if (mpd) { - pop_data_t mpd = mbox->data; - size_t i; + size_t i; mu_monitor_wrlock (mbox->monitor); /* Destroy the pop messages and ressources associated to them. */ - for (i = 0; i < mpd->pmessages_count; i++) + for (i = 0; i < mpd->msg_count; i++) { - if (mpd->pmessages[i]) + if (mpd->msg[i]) { - mu_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; + mu_message_destroy (&mpd->msg[i]->message, mpd->msg[i]); + if (mpd->msg[i]->uidl) + free (mpd->msg[i]->uidl); + free (mpd->msg[i]); } } - if (mpd->greeting_banner) - free (mpd->greeting_banner); - if (mpd->buffer) - free (mpd->buffer); - if (mpd->pmessages) - free (mpd->pmessages); - free (mpd); - mbox->data = NULL; - mu_monitor_unlock (mbox->monitor); - } + mu_pop3_destroy (&mpd->pop3); + if (mpd->user) + free (mpd->user); + if (mpd->secret) + mu_secret_unref (mpd->secret); + } } +/* Update and scanning. */ static int -pop_mbox_uidls (mu_mailbox_t mbox, mu_list_t list) +pop_is_updated (mu_mailbox_t mbox) { - pop_data_t mpd = mbox->data; - int status; - - status = pop_writeline (mpd, "UIDL\r\n"); - CHECK_ERROR (mpd, status); - MU_DEBUG (mbox->debug, MU_DEBUG_PROT, mpd->buffer); - - status = pop_write (mpd); - CHECK_EAGAIN (mpd, status); - - status = pop_read_ack (mpd); - CHECK_EAGAIN (mpd, status); - MU_DEBUG (mpd->mbox->debug, MU_DEBUG_PROT, mpd->buffer); - - if (!mu_c_strncasecmp (mpd->buffer, "+OK", 3)) - { - do - { - char *p; - size_t num; - struct mu_uidl *uidl; - - status = pop_read_ack (mpd); - MU_DEBUG (mpd->mbox->debug, MU_DEBUG_PROT, mpd->buffer); - - num = strtoul (mpd->buffer, &p, 10); - if (*p == 0 || !mu_isblank (*p)) - continue; /* FIXME: or error? */ - p = mu_str_skip_class (p, MU_CTYPE_SPACE); - mu_rtrim_cset (p, "\r\n"); - - uidl = malloc (sizeof (uidl[0])); - if (!uidl) - { - status = ENOMEM; - break; - } - uidl->msgno = num; - strncpy (uidl->uidl, p, MU_UIDL_BUFFER_SIZE); - status = mu_list_append (list, uidl); - } - while (mpd->nl); - } - else - status = ENOSYS; - return status; + struct _pop3_mailbox *mpd = mbox->data; + if (mpd == NULL) + return 0; + return mpd->is_updated; } - -/* - POP3 CAPA support. - */ - + +/* Return the number of messages in the mailbox */ static int -pop_parse_capa (pop_data_t mpd) +pop_messages_count (mu_mailbox_t mbox, size_t *pcount) { + struct _pop3_mailbox *mpd = mbox->data; int status; - if (!mu_c_strncasecmp (mpd->buffer, "+OK", 3)) - { - mpd->capa = 0; - do - { - status = pop_read_ack (mpd); - MU_DEBUG (mpd->mbox->debug, MU_DEBUG_PROT, mpd->buffer); - - /* Here we check some common capabilities like TOP, USER, UIDL, - and STLS. The rest are ignored. Please note that some - capabilities might have an extra arguments. For instance, - SASL can have CRAM-MD5 and/or KERBEROS_V4, and etc. - This is why I suggest adding (in a future) an extra variable, - for example `capa_sasl'. It would hold the following flags: - SASL_CRAM_MD5, SASL_KERBEROS_V4, and so on. Also the EXPIRE - and LOGIN-DELAY capabilities have an extra arguments! - Note that there is no APOP capability, even though APOP - is an optional command in POP3. -- W.P. */ - - if (!mu_c_strncasecmp (mpd->buffer, "TOP", 3)) - mpd->capa |= CAPA_TOP; - else if (!mu_c_strncasecmp (mpd->buffer, "USER", 4)) - mpd->capa |= CAPA_USER; - else if (!mu_c_strncasecmp (mpd->buffer, "UIDL", 4)) - mpd->capa |= CAPA_UIDL; - else if (!mu_c_strncasecmp (mpd->buffer, "STLS", 4)) - mpd->capa |= CAPA_STLS; - } - while (mpd->nl); - - if (mpd->capa & CAPA_UIDL) - mpd->mbox->_get_uidls = pop_mbox_uidls; - return status; + if (mpd == NULL) + return EINVAL; + + if (pop_is_updated (mbox)) + { + if (pcount) + *pcount = mpd->msg_count; + return 0; } - else + + status = mu_pop3_stat (mpd->pop3, &mpd->msg_count, &mpd->total_size); + if (status == 0) { - /* mu_error ("CAPA not implemented"); */ /* FIXME */ - return ENOSYS; + if (pcount) + *pcount = mpd->msg_count; + mpd->is_updated = 1; } + return status; } - + static int -pop_capa (mu_mailbox_t mbox) -{ - pop_data_t mpd = mbox->data; - int status; - - status = pop_writeline (mpd, "CAPA\r\n"); - CHECK_ERROR (mpd, status); - MU_DEBUG (mbox->debug, MU_DEBUG_PROT, mpd->buffer); - - status = pop_write (mpd); - CHECK_EAGAIN (mpd, status); - mpd->state = POP_CAPA_ACK; - - /* POP_CAPA_ACK */ - status = pop_read_ack (mpd); - CHECK_EAGAIN (mpd, status); - MU_DEBUG (mpd->mbox->debug, MU_DEBUG_PROT, mpd->buffer); - - return pop_parse_capa (mpd); -} - -/* Simple User/pass authentication for pop. We ask for the info - from the standard input. */ -int -_pop_user (mu_authority_t auth) +pop_scan (mu_mailbox_t mbox, size_t msgno, size_t *pcount) { - mu_folder_t folder = mu_authority_get_owner (auth); - mu_mailbox_t mbox = folder->data; - pop_data_t mpd = mbox->data; int status; + size_t i; + size_t count = 0; - switch (mpd->state) + status = pop_messages_count (mbox, &count); + if (status != 0) + return status; + if (pcount) + *pcount = count; + if (mbox->observable == NULL) + return 0; + for (i = msgno; i <= count; i++) { - case POP_AUTH: - /* Fetch the user from them. */ - status = pop_get_user (auth); - if (status != 0 || mpd->user == NULL || mpd->user[0] == '\0') - { - pop_writeline (mpd, "QUIT\r\n"); - MU_DEBUG (mbox->debug, MU_DEBUG_PROT, mpd->buffer); - pop_write (mpd); - CHECK_ERROR_CLOSE (mbox, mpd, MU_ERR_NOUSERNAME); - } - status = pop_writeline (mpd, "USER %s\r\n", mpd->user); - CHECK_ERROR_CLOSE(mbox, mpd, status); - MU_DEBUG (mbox->debug, 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); - MU_DEBUG (mbox->debug, MU_DEBUG_PROT, mpd->buffer); - if (mu_c_strncasecmp (mpd->buffer, "+OK", 3) != 0) - { - mu_observable_t observable = NULL; - mu_mailbox_get_observable (mbox, &observable); - CLEAR_STATE (mpd); - mu_observable_notify (observable, MU_EVT_AUTHORITY_FAILED, NULL); - CHECK_ERROR_CLOSE (mbox, mpd, EACCES); - } - status = pop_get_passwd (auth); - if (status != 0 || mpd->secret == NULL) - { - pop_writeline (mpd, "QUIT\r\n"); - MU_DEBUG (mbox->debug, MU_DEBUG_PROT, mpd->buffer); - pop_write (mpd); - CHECK_ERROR_CLOSE (mbox, mpd, MU_ERR_NOPASSWORD); - } - status = pop_writeline (mpd, "PASS %s\r\n", - mu_secret_password (mpd->secret)); - mu_secret_password_unref (mpd->secret); - mu_secret_unref (mpd->secret); - mpd->secret = NULL; - MU_DEBUG (mbox->debug, MU_DEBUG_PROT, "PASS ***\n"); - CHECK_ERROR_CLOSE (mbox, mpd, status); - mpd->state = POP_AUTH_PASS; - /* FIXME: Merge these two cases */ - - 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); - MU_DEBUG (mbox->debug, MU_DEBUG_PROT, mpd->buffer); - if (mu_c_strncasecmp (mpd->buffer, "+OK", 3) != 0) + size_t tmp = i; + if (mu_observable_notify (mbox->observable, MU_EVT_MESSAGE_ADD, + &tmp) != 0) + break; + if (((i +1) % 10) == 0) { - mu_observable_t observable = NULL; - mu_mailbox_get_observable (mbox, &observable); - CLEAR_STATE (mpd); - mu_observable_notify (observable, MU_EVT_AUTHORITY_FAILED, NULL); - return MU_ERR_AUTH_FAILURE; + mu_observable_notify (mbox->observable, MU_EVT_MAILBOX_PROGRESS, + NULL); } - mpd->state = POP_AUTH_DONE; - break; /* We're outta here. */ - - default: - break; } - CLEAR_STATE (mpd); return 0; } -int -_pop_apop (mu_authority_t auth) +/* There's no way to retrieve this info via POP3 */ +static int +pop_message_unseen (mu_mailbox_t mbox, size_t *punseen) { - mu_folder_t folder = mu_authority_get_owner (auth); - mu_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->secret == NULL) - { - 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->digest); - MU_DEBUG (mbox->debug, MU_DEBUG_PROT, mpd->buffer); - /* We have to obscure the md5 string. */ - memset (mpd->digest, '\0', strlen (mpd->digest)); - free (mpd->user); - free (mpd->digest); - mpd->user = NULL; - mpd->digest = 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); - MU_DEBUG (mbox->debug, MU_DEBUG_PROT, mpd->buffer); - if (mu_c_strncasecmp (mpd->buffer, "+OK", 3) != 0) - { - mu_observable_t observable = NULL; - mu_mailbox_get_observable (mbox, &observable); - CLEAR_STATE (mpd); - mu_observable_notify (observable, MU_EVT_AUTHORITY_FAILED, NULL); - CHECK_ERROR_CLOSE (mbox, mpd, EACCES); - } - mpd->state = POP_AUTH_DONE; - break; /* We're outta here. */ - - default: - break; - } - CLEAR_STATE (mpd); + 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; } -/* - Client side STLS support. - */ - static int -pop_reader (void *iodata) +pop_get_size (mu_mailbox_t mbox, mu_off_t *psize) { + struct _pop3_mailbox *mpd = mbox->data; int status = 0; - pop_data_t iop = iodata; - status = pop_read_ack (iop); - CHECK_EAGAIN (iop, status); - MU_DEBUG (iop->mbox->debug, MU_DEBUG_PROT, iop->buffer); - return status;/*mu_c_strncasecmp (iop->buffer, "+OK", 3) == 0;*/ + + if (mpd == NULL) + return EINVAL; + + if (!pop_is_updated (mbox)) + status = pop_messages_count (mbox, NULL); + if (psize) + *psize = mpd->total_size; + return status; } + static int -pop_writer (void *iodata, char *buf) +pop_create_message (struct _pop3_message *mpm, struct _pop3_mailbox *mpd) { - pop_data_t iop = iodata; int status; + mu_message_t msg; - MU_DEBUG1 (iop->mbox->debug, MU_DEBUG_PROT, "%s\n", buf); - status = pop_writeline (iop, "%s\r\n", buf); - CHECK_ERROR (iop, status); - status = pop_write (iop); - CHECK_ERROR (iop, status); - return status; + status = mu_message_create (&msg, mpm); + if (status) + return status; + // FIXME... + mpm->message = msg; + return 0; } -static void -pop_stream_ctl (void *iodata, mu_stream_t *pold, mu_stream_t new) -{ - pop_data_t iop = iodata; - if (pold) - *pold = iop->mbox->stream; - if (new) - iop->mbox->stream = new; -} + +/* ------------------------------------------------------------------------- */ +/* Header */ static int -pop_stls (mu_mailbox_t mbox) +pop_header_fill (void *data, char **pbuf, size_t *plen) { -#ifdef WITH_TLS + struct _pop3_message *mpm = data; + struct _pop3_mailbox *mpd = mpm->mpd; + mu_stream_t stream; + mu_opool_t opool; int status; - pop_data_t mpd = mbox->data; - char *keywords[] = { "STLS", "CAPA", NULL }; + + status = mu_opool_create (&opool, 0); + if (status) + return status; + + if (mu_pop3_capa_test (mpd->pop3, "TOP", NULL) == 0) + status = mu_pop3_top (mpd->pop3, mpm->num, 0, &stream); + else + status = mu_pop3_retr (mpd->pop3, mpm->num, &stream); - if (!mu_tls_enable || !(mpd->capa & CAPA_STLS)) - return -1; + if (status == 0) + { + size_t size = 0; + char *buf = NULL; + size_t n; - status = mu_tls_begin (mpd, pop_reader, pop_writer, - pop_stream_ctl, keywords); + while (mu_stream_getline (stream, &buf, &size, &n) == 0 + && n > 0) + { + size_t len = mu_rtrim_cset (buf, "\r\n"); + if (len == 0) + break; + mu_opool_append (opool, buf, len); + mu_opool_append_char (opool, '\n'); + } - MU_DEBUG1 (mbox->debug, MU_DEBUG_PROT, "TLS negotiation %s\n", - status == 0 ? "succeeded" : "failed"); + if (!mu_stream_eof (stream)) + /* Drain the stream. */ + while (mu_stream_getline (stream, &buf, &size, &n) == 0 + && n > 0); + mu_stream_destroy (&stream); - if (status == 0) - pop_parse_capa (mpd); + n = mu_opool_size (opool); + if (n > size) + { + char *p = realloc (buf, n); + if (!p) + { + free (buf); + mu_opool_destroy (&opool); + return ENOMEM; + } + buf = p; + } + mu_opool_copy (opool, buf, n); + *pbuf = buf; + *plen = n; + status = 0; + } + mu_opool_destroy (&opool); + return status; -#else - return -1; -#endif /* WITH_TLS */ } -/* Open the connection to the sever, and send the authentication. */ static int -pop_open (mu_mailbox_t mbox, int flags) +pop_create_header (struct _pop3_message *mpm) { - pop_data_t mpd = mbox->data; int status; - const char *host; - long port = mpd->pops ? MU_POPS_PORT : MU_POP_PORT; - - /* Sanity checks. */ - if (mpd == NULL) - return EINVAL; + mu_header_t header = NULL; - /* Fetch the pop server name and the port in the mu_url_t. */ - status = mu_url_sget_host (mbox->url, &host); - if (status != 0) + status = mu_header_create (&header, NULL, 0); + if (status) return status; - mu_url_get_port (mbox->url, &port); + mu_header_set_fill (header, pop_header_fill, mpm); + mu_message_set_header (mpm->message, header, mpm); + return 0; +} - mbox->flags = flags; + +/* ------------------------------------------------------------------------- */ +/* Attributes */ - /* 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 = mu_tcp_stream_create (&mbox->stream, host, port, - mbox->flags); - CHECK_ERROR (mpd, status); - /* FIXME: How to configure the buffer size? */ - mu_stream_set_buffer (mbox->stream, mu_buffer_line, 1024); - -#ifdef WITH_TLS - if (mpd->pops) - { - mu_stream_t newstr; - - status = mu_stream_open (mbox->stream); - CHECK_EAGAIN (mpd, status); - CHECK_ERROR_CLOSE (mbox, mpd, status); - - status = mu_tls_client_stream_create (&newstr, - mbox->stream, - mbox->stream, 0); - if (status != 0) - { - mu_error ("pop_open: mu_tls_client_stream_create: %s", - mu_strerror (status)); - return status; - } - mbox->stream = newstr; - } -#endif /* WITH_TLS */ - } - 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. */ - mu_stream_close (mbox->stream); - pop_sleep (2); - } - mpd->state = POP_OPEN_CONNECTION; - - case POP_OPEN_CONNECTION: - /* Establish the connection. */ - MU_DEBUG2 (mbox->debug, MU_DEBUG_PROT, "open (%s:%ld)\n", host, port); - status = mu_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: - { - int gblen = 0; - status = pop_read_ack (mpd); - CHECK_EAGAIN (mpd, status); - MU_DEBUG (mbox->debug, MU_DEBUG_PROT, mpd->buffer); - if (mu_c_strncasecmp (mpd->buffer, "+OK", 3) != 0) - { - CHECK_ERROR_CLOSE (mbox, mpd, EACCES); - } - gblen = strlen (mpd->buffer); - mpd->greeting_banner = calloc (gblen, 1); - if (mpd->greeting_banner == NULL) - { - CHECK_ERROR (mpd, ENOMEM); - } - memcpy (mpd->greeting_banner, mpd->buffer, gblen); - mpd->state = POP_CAPA; - } - - case POP_CAPA: - case POP_CAPA_ACK: - pop_capa (mbox); - mpd->state = POP_STLS; - - case POP_STLS: - case POP_STLS_ACK: - if (!mpd->pops) - pop_stls (mbox); - 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 = mu_authority_authenticate (mbox->folder->authority); - CHECK_EAGAIN (mpd, status); - - case POP_AUTH_DONE: - break; - - default: - /* - mu_error ("pop_open: unknown state"); - */ - break; - }/* End AUTHORISATION state. */ - - /* Clear any state. */ - CLEAR_STATE (mpd); - return 0; -} - -/* Send the QUIT and close the socket. */ -static int -pop_close (mu_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); */ - mu_monitor_wrlock (mbox->monitor); - if (mpd->func && mpd->func != func) - mpd->state = POP_NO_STATE; - mpd->id = 0; - mpd->func = func; - mu_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); - MU_DEBUG (mbox->debug, 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); - MU_DEBUG (mbox->debug, 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 (mu_c_strncasecmp (mpd->buffer, "+OK", 3) != 0) - mu_error ("pop_close: %s", mpd->buffer); - mu_stream_close (mbox->stream); - break; - - default: - /* - mu_error ("pop_close: unknown state"); - */ - break; - } /* UPDATE state. */ - - /* Free the messages. */ - for (i = 0; i < mpd->pmessages_count; i++) - { - if (mpd->pmessages[i]) - { - mu_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->greeting_banner) - free (mpd->greeting_banner); - mpd->greeting_banner = NULL; - 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 mu_message_t structure for a mesgno. pop_message_t, - will act as the owner of messages. */ -static int -pop_get_message (mu_mailbox_t mbox, size_t msgno, mu_message_t *pmsg) -{ - pop_data_t mpd = mbox->data; - mu_message_t msg = NULL; - pop_message_t mpm; - int status; - size_t i; - - /* Sanity. */ - if (pmsg == NULL || mpd == NULL) - return EINVAL; - - /* If we did not start a scanning yet do it now. */ - if (!pop_is_updated (mbox)) - pop_scan (mbox, 1, NULL); - - if (msgno > mpd->messages_count) - return EINVAL; - - mu_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; - mu_monitor_unlock (mbox->monitor); - return 0; - } - } - } - mu_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. */ - { - mu_stream_t stream = NULL; - if ((status = mu_message_create (&msg, mpm)) != 0 - || (status = mu_stream_create (&stream, mbox->flags, msg)) != 0) - { - mu_stream_destroy (&stream, msg); - mu_message_destroy (&msg, mpm); - free (mpm); - return status; - } - /* Help for the readline()s */ - mu_stream_setbufsiz (stream, 128); - mu_stream_set_read (stream, pop_message_read, msg); - mu_stream_set_get_transport2 (stream, pop_message_transport, msg); - mu_message_set_stream (msg, stream, mpm); - mu_message_set_size (msg, pop_message_size, mpm); - } - - /* Create the header. */ - { - mu_header_t header = NULL; - if ((status = mu_header_create (&header, NULL, 0, msg)) != 0) - { - mu_message_destroy (&msg, mpm); - free (mpm); - return status; - } - mu_header_set_fill (header, pop_top, msg); - mu_message_set_header (msg, header, mpm); - } - - /* Create the attribute. */ - { - mu_attribute_t attribute; - status = mu_attribute_create (&attribute, msg); - if (status != 0) - { - mu_message_destroy (&msg, mpm); - free (mpm); - return status; - } - mu_attribute_set_get_flags (attribute, pop_get_attribute, msg); - mu_attribute_set_set_flags (attribute, pop_set_attribute, msg); - mu_attribute_set_unset_flags (attribute, pop_unset_attribute, msg); - mu_message_set_attribute (msg, attribute, mpm); - } - - /* Create the body and its stream. */ - { - mu_body_t body = NULL; - mu_stream_t stream = NULL; - if ((status = mu_body_create (&body, msg)) != 0 - || (status = mu_stream_create (&stream, mbox->flags, body)) != 0) - { - mu_body_destroy (&body, msg); - mu_stream_destroy (&stream, body); - mu_message_destroy (&msg, mpm); - free (mpm); - return status; - } - /* Helps for the readline()s */ - mu_stream_setbufsiz (stream, 128); - mu_stream_set_read (stream, pop_body_read, body); - mu_stream_set_get_transport2 (stream, pop_body_transport, body); - mu_body_set_size (body, pop_body_size, msg); - mu_body_set_lines (body, pop_body_lines, msg); - mu_body_set_stream (body, stream, msg); - mu_message_set_body (msg, body, mpm); - } - - /* Set the UIDL call on the message. */ - if (mpd->capa & CAPA_UIDL) - mu_message_set_uidl (msg, pop_uidl, mpm); - - /* Set the UID on the message. */ - mu_message_set_uid (msg, pop_uid, mpm); - - /* Add it to the list. */ - mu_monitor_wrlock (mbox->monitor); - { - pop_message_t *m ; - m = realloc (mpd->pmessages, (mpd->pmessages_count + 1)*sizeof (*m)); - if (m == NULL) - { - mu_message_destroy (&msg, mpm); - free (mpm); - mu_monitor_unlock (mbox->monitor); - return ENOMEM; - } - mpd->pmessages = m; - mpd->pmessages[mpd->pmessages_count] = mpm; - mpd->pmessages_count++; - } - mu_monitor_unlock (mbox->monitor); - - /* Save The message pointer. */ - mu_message_set_mailbox (msg, mbox, mpm); - *pmsg = mpm->message = msg; - - return 0; -} - -/* FIXME: Should use strtoumax ideally */ -static int -parse_answer0 (const char *buffer, size_t *n1, size_t *n2) -{ - char *p; - unsigned long m; - if (strlen (buffer) < 3 || memcmp (buffer, "+OK", 3)) - return 1; - m = *n1 = strtoul (buffer + 3, &p, 10); - if (!mu_isspace (*p) || m != *n1) - return 1; - m = *n2 = strtoul (p, &p, 10); - if (!(*p == 0 || mu_isspace (*p)) || m != *n2) - return 1; - return 0; -} - -/* FIXME: Should use strtoumax ideally */ -static int -parse_answer1 (const char *buffer, size_t *n1, char *buf, size_t bufsize) -{ - char *p; - unsigned long m; - if (strlen (buffer) < 3 || memcmp (buffer, "+OK", 3)) - return 1; - m = *n1 = strtoul (buffer + 3, &p, 0); - if (!mu_isspace (*p) || m != *n1) - return 1; - while (*p && mu_isspace (*p)) - p++; - if (strlen (p) >= bufsize) - return 1; - strcpy (buf, p); - 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 (mu_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 (mu_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 (mu_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); - MU_DEBUG (mbox->debug, 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); - MU_DEBUG (mbox->debug, MU_DEBUG_PROT, mpd->buffer); - break; - - default: - /* - mu_error ("pop_messages_count: unknown state"); - */ - break; - } - - - /* Parse the answer. */ - - status = parse_answer0 (mpd->buffer, &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) - return EIO; - - if (pcount) - *pcount = mpd->messages_count; - mpd->is_updated = 1; - return 0; -} - -/* Update and scanning. */ -static int -pop_is_updated (mu_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 (mu_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++) - { - size_t tmp = i; - if (mu_observable_notify (mbox->observable, MU_EVT_MESSAGE_ADD, - &tmp) != 0) - break; - if (((i +1) % 10) == 0) - { - mu_observable_notify (mbox->observable, MU_EVT_MAILBOX_PROGRESS, - NULL); - } - } - 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 (mu_mailbox_t mbox) -{ - pop_data_t mpd = mbox->data; - size_t i; - mu_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 (mu_message_get_attribute (mpd->pmessages[i]->message, &attr) == 0) - { - if (mu_attribute_is_deleted (attr)) - { - switch (mpd->state) - { - case POP_NO_STATE: - status = pop_writeline (mpd, "DELE %lu\r\n", - (unsigned long) - mpd->pmessages[i]->num); - CHECK_ERROR (mpd, status); - MU_DEBUG (mbox->debug, 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); - MU_DEBUG (mbox->debug, MU_DEBUG_PROT, mpd->buffer); - if (mu_c_strncasecmp (mpd->buffer, "+OK", 3) != 0) - { - CHECK_ERROR (mpd, ERANGE); - } - mpd->state = POP_NO_STATE; - break; - - default: - /* mu_error ("pop_expunge: unknown state); */ - break; - } /* switch (state) */ - } /* if mu_attribute_is_deleted() */ - } /* mu_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 (mu_mailbox_t mbox, mu_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 (mu_message_t msg, size_t *psize) -{ - pop_message_t mpm = mu_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->mu_message_size != 0) - { - *psize = mpm->mu_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 %lu\r\n", (unsigned long) mpm->num); - CHECK_ERROR (mpd, status); - MU_DEBUG (mpd->mbox->debug, 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); - MU_DEBUG (mpd->mbox->debug, MU_DEBUG_PROT, mpd->buffer); - break; - - default: - /* - mu_error ("pop_message_size: unknown state"); - */ - break; - } - - /* FIXME */ - status = parse_answer0 (mpd->buffer, &num, &mpm->mu_message_size); - CLEAR_STATE (mpd); - - if (status != 0) - return MU_ERR_PARSE; - - /* The size of the message is with the extra '\r' octet for everyline. - Substract to get, hopefully, a good count. */ - if (psize) - *psize = mpm->mu_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 (mu_body_t body, size_t *psize) -{ - mu_message_t msg = mu_body_get_owner (body); - pop_message_t mpm = mu_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->mu_message_size != 0) - { - /* Take a guest. */ - *psize = mpm->mu_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 (mu_body_t body, size_t *plines) -{ - mu_message_t msg = mu_body_get_owner (body); - pop_message_t mpm = mu_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 mu_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. */ +/* There is no POP3 command to return message attributes, therefore we + have to recurse to reading the "Status:" header. Unfortunately, some + servers remove it when you dowload a message, and in this case 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). Even worse, some servers may be configured + to delete after the RETR, and some go as much as deleting after the TOP, + since technicaly you can download a message via TOP without RETR'eiving + it. */ static int pop_get_attribute (mu_attribute_t attr, int *pflags) { - mu_message_t msg = mu_attribute_get_owner (attr); - pop_message_t mpm = mu_message_get_owner (msg); + struct _pop3_message *mpm = mu_attribute_get_owner (attr); char hdr_status[64]; mu_header_t header = NULL; @@ -1568,607 +440,257 @@ pop_get_attribute (mu_attribute_t attr, int *pflags) if (mpm->attr_flags == 0) { hdr_status[0] = '\0'; - mu_message_get_header (mpm->message, &header); - mu_header_get_value (header, "Status", hdr_status, sizeof hdr_status, NULL); - mu_string_to_flags (hdr_status, &(mpm->attr_flags)); - } - *pflags = mpm->attr_flags; - return 0; -} - -static int -pop_set_attribute (mu_attribute_t attr, int flags) -{ - mu_message_t msg = mu_attribute_get_owner (attr); - pop_message_t mpm = mu_message_get_owner (msg); - - if (mpm == NULL) - return EINVAL; - mpm->attr_flags |= flags; - return 0; -} - -static int -pop_unset_attribute (mu_attribute_t attr, int flags) -{ - mu_message_t msg = mu_attribute_get_owner (attr); - pop_message_t mpm = mu_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_transport (mu_stream_t stream, mu_transport_t *ptr, mu_transport_t *ptr2) -{ - mu_body_t body = mu_stream_get_owner (stream); - mu_message_t msg = mu_body_get_owner (body); - pop_message_t mpm = mu_message_get_owner (msg); - return pop_get_transport2 (mpm, ptr, ptr2); -} - -/* Stub to call the fd from message object. */ -static int -pop_message_transport (mu_stream_t stream, mu_transport_t *ptr, mu_transport_t *ptr2) -{ - mu_message_t msg = mu_stream_get_owner (stream); - pop_message_t mpm = mu_message_get_owner (msg); - return pop_get_transport2 (mpm, ptr, ptr2); -} -static int -pop_get_transport2 (pop_message_t mpm, mu_transport_t *ptr, mu_transport_t *ptr2) -{ - if (mpm && mpm->mpd && mpm->mpd->mbox) - return mu_stream_get_transport2 (mpm->mpd->mbox->stream, ptr, ptr2); - return EINVAL; -} - -static int -pop_uid (mu_message_t msg, size_t *puid) -{ - pop_message_t mpm = mu_message_get_owner (msg); - if (puid) - *puid = mpm->num; - return 0; -} - -/* Get the UIDL. The client should be prepared, since it may fail. UIDL is - optional on many POP servers. - FIXME: We should check the "mpd->capa & CAPA_UIDL" and fall back to - a md5 scheme ? Or maybe check for "X-UIDL" a la Qpopper ? */ -static int -pop_uidl (mu_message_t msg, char *buffer, size_t buflen, size_t *pnwriten) -{ - pop_message_t mpm = mu_message_get_owner (msg); - pop_data_t mpd; - int status = 0; - void *func = (void *)pop_uidl; - size_t num; - char uniq[MU_UIDL_BUFFER_SIZE]; - - if (mpm == NULL) - return EINVAL; - - /* Is it cached ? */ - 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 %lu\r\n", (unsigned long) mpm->num); - CHECK_ERROR (mpd, status); - MU_DEBUG (mpd->mbox->debug, 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); - MU_DEBUG (mpd->mbox->debug, MU_DEBUG_PROT, mpd->buffer); - break; - - default: - /* - mu_error ("pop_uidl: unknown state"); - */ - break; - } - - /* FIXME: I should cache the result. */ - *uniq = '\0'; - status = parse_answer1 (mpd->buffer, &num, uniq, sizeof uniq); - if (status) - { - status = MU_ERR_PARSE; - 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; + mu_message_get_header (mpm->message, &header); + mu_header_get_value (header, MU_HEADER_STATUS, + hdr_status, sizeof hdr_status, NULL); + mu_string_to_flags (hdr_status, &mpm->attr_flags); } + *pflags = mpm->attr_flags; + return 0; +} - CLEAR_STATE (mpd); +static int +pop_set_attribute (mu_attribute_t attr, int flags) +{ + struct _pop3_message *mpm = mu_attribute_get_owner (attr); - if (pnwriten) - *pnwriten = buflen; - return status; + if (mpm == NULL) + return EINVAL; + mpm->attr_flags |= flags; + return 0; } -/* 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 mu_mime_t may try to - read ahead, for example for the headers. */ static int -pop_top (mu_header_t header, char *buffer, size_t buflen, - mu_off_t offset, size_t *pnread) +pop_unset_attribute (mu_attribute_t attr, int flags) { - mu_message_t msg = mu_header_get_owner (header); - pop_message_t mpm = mu_message_get_owner (msg); - pop_data_t mpd; - size_t nread = 0; - int status = 0; - void *func = (void *)pop_top; + struct _pop3_message *mpm = mu_attribute_get_owner (attr); if (mpm == NULL) return EINVAL; + mpm->attr_flags &= ~flags; + return 0; +} - mpd = mpm->mpd; - - /* Busy ? */ - CHECK_BUSY (mpd->mbox, mpd, func, msg); +static int +pop_create_attribute (struct _pop3_message *mpm) +{ + int status; + mu_attribute_t attribute; + + status = mu_attribute_create (&attribute, mpm); + if (status) + return status; - /* We start fresh then reset the sizes. */ - if (mpd->state == POP_NO_STATE) - mpm->header_size = 0; + mu_attribute_set_get_flags (attribute, pop_get_attribute, mpm); + mu_attribute_set_set_flags (attribute, pop_set_attribute, mpm); + mu_attribute_set_unset_flags (attribute, pop_unset_attribute, mpm); + mu_message_set_attribute (mpm->message, attribute, mpm); + return 0; +} + +/* ------------------------------------------------------------------------- */ +/* Body */ - /* Throw an error if trying to seek back. */ - if ((size_t)offset < mpm->header_size) - return ESPIPE; +static int +pop_create_body (struct _pop3_message *mpm) +{ + /* FIXME */ + return ENOSYS; +} - /* Get the header. */ - switch (mpd->state) + +/* FIXME: change prototype to avoid fixed size buffer */ +static int +pop_uidl (mu_message_t msg, char *buffer, size_t buflen, size_t *pnwriten) +{ + struct _pop3_message *mpm = mu_message_get_owner (msg); + struct _pop3_mailbox *mpd = mpm->mpd; + size_t len; + + if (!mpm->uidl) { - case POP_NO_STATE: - if (mpd->capa & CAPA_TOP) - { - status = pop_writeline (mpd, "TOP %lu 0\r\n", - (unsigned long) mpm->num); - CHECK_ERROR (mpd, status); - MU_DEBUG (mpd->mbox->debug, MU_DEBUG_PROT, mpd->buffer); - mpd->state = POP_TOP; - } - else /* 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); - } - - 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); - MU_DEBUG (mpd->mbox->debug, MU_DEBUG_PROT, mpd->buffer); - if (mu_c_strncasecmp (mpd->buffer, "+OK", 3) != 0) - CHECK_ERROR (mpd, EINVAL); - mpd->state = POP_TOP_RX; - - case POP_TOP_RX: - /* Get the header. */ - do + if (mu_pop3_capa_test (mpd->pop3, "UIDL", NULL) == 0) { - /* Seek in position. */ - ssize_t po