/* 
   elmo - ELectronic Mail Operator

   Copyright (C) 2002, 2003, 2004 rzyjontko

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  

   ----------------------------------------------------------------------

   This module implements RFC1725 pop3 client side protocol.
   
*/
/****************************************************************************
 *    IMPLEMENTATION HEADERS
 ****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <unistd.h>
#include <ctype.h>

#include "networking.h"
#include "rstring.h"
#include "error.h"
#include "pop.h"
#include "xmalloc.h"
#include "mlex.h"
#include "gettext.h"
#include "str.h"
#include "md5.h"
#include "misc.h"
#include "debug.h"
#include "clock.h"
#include "ask.h"
#include "file.h"
#include "hash.h"

/****************************************************************************
 *    IMPLEMENTATION PRIVATE DEFINITIONS / ENUMERATIONS / SIMPLE TYPEDEFS
 ****************************************************************************/

enum state {
        POP_DISCONNECTED,
        POP_CONNECTED,
        POP_USER,
        POP_PASS,
        POP_STAT,
        POP_TRANSACTION,
        POP_LIST,
        POP_UIDL,
        POP_RETR,
        POP_TOP,
        POP_DELE,
        POP_RSET,
        POP_QUIT,
};

/****************************************************************************
 *    IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES
 ****************************************************************************/

struct mailinfo {
        int     num;
        int     size;
        char   *uidl;
        int     fetched;
        int     deleted;
        mail_t *mail;
};

/****************************************************************************
 *    IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID)
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE DATA
 ****************************************************************************/

static struct {

        /* State is used to determine set of possible actions. */
        enum state state;

        /* Error message sent by server in case of an error.  It is then
           used as a part of the error message displayed to the user. */
        str_t *error_message;
        str_t *send_buf;        /* data sent to server */
        int    nd;              /* networking descriptor */
        int    progress;        /* progress descriptor */

        /* The digest sent by server in an initial response.  Used to encypher
           the password.
           I noticed that some servers send digest, but do not support this
           kind of authentication.  My assumption is based on the fact that
           I found no mistake in my implementation, checked if sample
           password and digest (from RFC 1725) match sample result, and that
           fetchmail couldn't authenticate itself either.  This is why
           I don't try to use APOP authentication method without clear
           user's will.  These servers reply with "authentication failed",
           which might be misleading. */
        str_t *apop_digest;
        str_t *user_str;        /* username */
        str_t *pass_str;        /* password */
        enum auth_method method;

        int    maildrop_count;  /* messages count */
        int    maildrop_size;   /* messages total size */

        FILE  *fp;              /* used to write the message */

        /* These arrays are used to fetch info about the messages available
           at the server, and already fetched to the local mailbox.  Array
           net_info is built from the data received from the POP3 server
           (with LIST, and UIDL commands).  Array local_info is based upon
           the data stored in pop_info_dir for each server.  These arrays
           are compared and merged after establishing the connection to
           determine which messages have been already fetched. */
        struct mailinfo *net_info;
        struct mailinfo *local_info;
        int              local_size;
        
        /* Index of the message retrieved / deleted from the server.  Used to
           report error message, and to update net_info array. */
        int num;
        
        void (*ok_fun)(void);
        void (*fail_fun)(void);
} conn;

/****************************************************************************
 *    INTERFACE DATA
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES
 ****************************************************************************/

static void request_next_info (void);

/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTIONS
 ****************************************************************************/


static int
net_array_index (int num)
{
        int i;

        if (conn.net_info == NULL)
                return -1;
        
        for (i = 0; i < conn.maildrop_count; i++){
                if (conn.net_info[i].num == num)
                        return i;
        }
        return -1;
}



static int
net_array_num (int index)
{
        int i;

        if (conn.maildrop_count < 1 || conn.net_info == NULL)
                return -1;
        
        for (i = 0; i < conn.maildrop_count; i++){
                if (conn.net_info[i].mail && index == 0)
                        return i;
                else if (conn.net_info[i].mail)
                        index--;
        }
        return -1;
}



static int
unfilled_count (void)
{
        int i;
        int count = 0;
        
        if (conn.net_info == NULL)
                return -1;

        for (i = 0; i < conn.maildrop_count; i++){
                if (! conn.net_info[i].fetched && ! conn.net_info[i].deleted
                    && conn.net_info[i].mail == NULL)
                        count++;
        }
        return count;
}



static int
filled_count (void)
{
        int i;
        int count = 0;
        
        if (conn.net_info == NULL)
                return 0;

        for (i = 0; i < conn.maildrop_count; i++){
                if (conn.net_info[i].mail)
                        count++;
        }
        return count;
}



static int
first_unfilled (void)
{
        int i;

        if (conn.net_info == NULL)
                return -1;

        for (i = 0; i < conn.maildrop_count; i++){
                if (! conn.net_info[i].fetched && ! conn.net_info[i].deleted
                    && conn.net_info[i].mail == NULL)
                        return i;
        }
        return -1;
}



static int
parse_answer (char *msg)
{
        char *end;

        if (msg == NULL)
                return 1;
  
        end = strchr (msg, '\r');

        if (end){
                *end = '\0';
        }
  
        if (strstr (msg, "+OK")){
                *end = '\r';
                return 0;
        }

        if (conn.error_message)
                str_destroy (conn.error_message);
        conn.error_message = NULL;
  
        if (strstr (msg, "-ERR")){
                conn.error_message = str_dup (msg + 5);
                return 1;
        }

        for (end = msg; isprint (*end); end++)
                ;
        *end = '\0';
        error_ (0, _("unusual server response: \"%s\""), msg);
        return 1;
}



static void
report_error (const char *str)
{
        if (conn.error_message){
                error_ (0, "%s (%s)", str, conn.error_message->str);
        }
        else {
                error_ (0, "%s", str);
        }
}



static void
stat_action (char *msg, int len)
{
        char *seek;
        char *rest;
        
        if (conn.state != POP_STAT){
                net_close (conn.nd);
                debug_msg (DEBUG_ERROR, "invalid state in stat_action");
                return;
        }

        if (parse_answer (msg)){
                report_error (_("couldn't aquire maildrop size"));
                net_close (conn.nd);
                return;
        }

        seek = strchr (msg, ' ');
        if (! seek){
                report_error (_("invalid answer from server"));
                net_close (conn.nd);
                return;
        }

        conn.maildrop_count = strtol (seek + 1, & rest, 10);
        if (*rest != ' '){
                report_error (_("invalid answer from server"));
                net_close (conn.nd);
                return;
        }

        conn.maildrop_size = atoi (rest + 1);
        
        if (conn.apop_digest)
                str_destroy (conn.apop_digest);
        if (conn.user_str)
                str_destroy (conn.user_str);
        if (conn.pass_str)
                str_destroy (conn.pass_str);
        if (conn.progress != -1)
                progress_close (conn.progress);

        conn.user_str    = NULL;
        conn.pass_str    = NULL;
        conn.apop_digest = NULL;
        conn.progress    = -1;

        conn.state = POP_TRANSACTION;

        if (conn.ok_fun)
                conn.ok_fun ();
}



static void
pass_action (char *msg, int len)
{
        if (conn.state != POP_PASS){
                net_close (conn.nd);
                debug_msg (DEBUG_ERROR, "invalid state in pass_action");
                return;
        }
        
        if (parse_answer (msg)){
                report_error (_("authorization failed"));
                net_close (conn.nd);
                return;
        }

        str_clear (conn.send_buf);
        str_put_string_len (conn.send_buf, "STAT\r\n", 6);

        conn.state = POP_STAT;

        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len, "\r\n$",
                   stat_action);
}



static void
user_action (char *msg, int len)
{
        if (conn.state != POP_USER){
                debug_msg (DEBUG_ERROR, "invalid state in user_action");
                net_close (conn.nd);
                return;
        }
        
        if (parse_answer (msg)){
                report_error (_("server rejected username"));
                net_close (conn.nd);
                return;
        }

        str_clear (conn.send_buf);
        str_sprintf (conn.send_buf, "PASS %s\r\n", conn.pass_str->str);

        conn.state = POP_PASS;

        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len, "\r\n$",
                   pass_action);
}



static void
authorize_apop (void)
{
        int      i;
        char     digest[16];
        MD5_CTX  ctx;
  
        if (conn.apop_digest == NULL){
                error_ (0, "%s", _("server doesn't support "
                                   "APOP authentication"));
                net_close (conn.nd);
                return;
        }
        
        str_clear (conn.send_buf);

        MD5Init (&ctx);
        MD5Update (&ctx, conn.apop_digest->str, conn.apop_digest->len);
        MD5Update (&ctx, conn.pass_str->str, conn.pass_str->len);
        MD5Final (digest, &ctx);

        str_put_string_len (conn.send_buf, "APOP ", 5);
        str_put_string_len (conn.send_buf, conn.user_str->str, conn.user_str->len);
        str_put_char (conn.send_buf, ' ');
        for (i = 0; i < 16; i++){
                str_sprintf (conn.send_buf, "%02x", (unsigned char) digest[i]);
        }
        str_put_string_len (conn.send_buf, "\r\n", 2);

        conn.state = POP_PASS;
        
        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len, "\r\n$",
                   pass_action);
}



static void
authorize_plain (void)
{
        str_clear (conn.send_buf);
        str_sprintf (conn.send_buf, "USER %s\r\n", conn.user_str->str);

        conn.state = POP_USER;

        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len, "\r\n$",
                   user_action);
}



static void
connected_action (char *msg, int nothing)
{
        int        len;
        regmatch_t matches[1];

        if (conn.state != POP_CONNECTED){
                debug_msg (DEBUG_ERROR, "invalid state in connected action");
                net_close (conn.nd);
                return;
        }
        
        if (parse_answer (msg)){
                report_error (_("server rejected connection"));
                net_close (conn.nd);
                return;
        }

        if (misc_regex ("<.+@.+>", msg, matches)){
                if (conn.apop_digest)
                        str_destroy (conn.apop_digest);
                len              = matches[0].rm_eo - matches[0].rm_so;
                conn.apop_digest = str_create_size (len);
                str_put_string_len (conn.apop_digest, msg + matches[0].rm_so,
                                    len);

        }

        conn.progress = progress_setup (1, "%s", _("logging in..."));
        
        switch (conn.method){

                case AUTH_PLAIN:
                        authorize_plain ();
                        break;

                case AUTH_APOP:
                        authorize_apop ();
                        break;
        }
}



static void
list_action (char *msg, int len)
{
        int   i;
        char *seek;
        char *rest;
        
        if (conn.state != POP_LIST){
                debug_msg (DEBUG_ERROR, "invalid state in list_action");
                return;
        }

        conn.state = POP_TRANSACTION;

        if (parse_answer (msg)){
                report_error (_("couldn't retrieve list"));
                if (conn.fail_fun)
                        conn.fail_fun ();
                return;
        }

        if (conn.maildrop_count < 1){
                if (conn.ok_fun)
                        conn.ok_fun ();
                return;
        }

        conn.net_info = xcalloc (conn.maildrop_count, sizeof (struct mailinfo));
        seek          = strchr (msg, '\r');
        if (seek == NULL)
                seek  = msg;
        else
                seek += 2;

        for (i = 0; i < conn.maildrop_count; i++){
                conn.net_info[i].num  = strtol (seek, & rest, 10);
                conn.net_info[i].size = strtol (rest, & seek, 10);
        }

        if (conn.ok_fun)
                conn.ok_fun ();
}



static void
uidl_action (char *msg, int len)
{
        char *rest;
        char *seek;
        int   i;
        int   num;
        int   index;
        
        if (conn.state != POP_UIDL){
                debug_msg (DEBUG_ERROR, "invalid state in uidl_action");
                return;
        }

        conn.state = POP_TRANSACTION;

        if (parse_answer (msg)){
                report_error (_("couldn't get message identifiers"));
                if (conn.fail_fun)
                        conn.fail_fun ();
                return;
        }

        if (conn.maildrop_count < 1){
                if (conn.ok_fun)
                        conn.ok_fun ();
                return;
        }

        if (conn.net_info == NULL){
                if (conn.fail_fun)
                        conn.fail_fun ();
                return;
        }

        seek = strchr (msg, '\r');
        if (seek == NULL)
                seek  = msg;
        else
                seek += 2;
        
        for (i = 0; i < conn.maildrop_count; i++){
                num   = strtol (seek, & rest, 10);
                index = net_array_index (num);
                if (index == -1){
                        debug_msg (DEBUG_WARN, "uidl for message num %d "
                                   "which was not found", num);
                        break;
                }
                seek = strchr (rest, '\r');
                if (seek == NULL)
                        break;
                *seek = '\0';
                while (isspace (*rest))
                        rest++;
                conn.net_info[index].uidl = xstrdup (rest);
                *seek = '\r';
                seek += 2;
        }
        
        if (conn.ok_fun)
                conn.ok_fun ();
}



static void
quit_action (char *msg, int len)
{
        if (conn.state != POP_QUIT){
                debug_msg (DEBUG_ERROR, "invalid state in quit_action");
                net_close (conn.nd);
                return;
        }

        if (parse_answer (msg)){
                report_error (_("does your server love you so much?"));
                net_close (conn.nd);
                return;
        }

        conn.state = POP_DISCONNECTED;
        net_close (conn.nd);

        if (conn.ok_fun)
                conn.ok_fun ();
}



static void
rset_action (char *msg, int len)
{
        int i;
        
        if (conn.state != POP_RSET){
                debug_msg (DEBUG_ERROR, "invalid state in rset_action");
                return;
        }

        conn.state = POP_TRANSACTION;

        if (parse_answer (msg)){
                report_error (_("couldn't reset mailbox status"));
                if (conn.fail_fun)
                        conn.fail_fun ();
                return;
        }

        if (conn.net_info){
                for (i = 0; i < conn.maildrop_count; i++){
                        conn.net_info[i].deleted = 0;
                }
        }

        if (conn.ok_fun)
                conn.ok_fun ();
}



static void
dele_action (char *msg, int len)
{
        int index;
        
        if (conn.state != POP_DELE){
                debug_msg (DEBUG_ERROR, "invalid state in dele_action");
                return;
        }

        conn.state = POP_TRANSACTION;

        if (parse_answer (msg)){
                report_error (_("couldn't delete a message"));
                if (conn.fail_fun)
                        conn.fail_fun ();
                return;
        }

        index = net_array_index (conn.num);
        if (index != -1)
                conn.net_info[index].deleted = 1;

        if (conn.ok_fun)
                conn.ok_fun ();
}



static void
retr_action (char *msg, int len)
{
        int   index;
        char *seek;
        
        if (conn.state != POP_RETR){
                debug_msg (DEBUG_ERROR, "invalid state in retr_action");
                return;
        }

        conn.state = POP_TRANSACTION;

        if (parse_answer (msg)){
                report_error (_("couldn't retrieve message"));
                if (conn.fail_fun)
                        conn.fail_fun ();
                return;
        }
        
        seek = strchr (msg, '\r');
        if (seek == NULL)
                seek = msg;
        else
                seek++;

        while (*seek){
                seek++;
                if (*seek == '.'){
                        if (seek - msg == len - 3)
                                break;
                        if (seek[-1] == '\n' && seek[-2] == '\r')
                                continue;
                }
                else if (*seek == '\r'){
                        continue;
                }
                if (fputc (*seek, conn.fp) == EOF){
                        error_ (errno, "%s", _("writing to the stream"));
                        if (conn.fail_fun)
                                conn.fail_fun ();
                        return;
                }
        }
        conn.fp = NULL;

        index   = net_array_index (conn.num);
        if (index != -1)
                conn.net_info[index].fetched = 1;
        
        if (conn.ok_fun)
                conn.ok_fun ();
}



static void
top_action (char *msg, int len)
{
        int   index;
        char *seek;
        
        if (conn.state != POP_TOP){
                debug_msg (DEBUG_ERROR, "invalid state in top_action");
                return;
        }

        conn.state = POP_TRANSACTION;

        if (parse_answer (msg)){
                report_error (_("couldn't retrieve message header"));
                if (conn.fail_fun)
                        conn.fail_fun ();
                return;
        }

        index = net_array_index (conn.num);
        if (index == -1){
                debug_msg (DEBUG_WARN, "no place to write TOP result for %d",
                           conn.num);
                return;
        }
        
        seek = strchr (msg, '\r');
        if (seek == NULL)
                seek  = msg;
        else
                seek += 2;

        if (mlex_scan_buffer (seek) != NEXT_MAIL){
                error_ (0, _("error parsing message header (%d)"), conn.num);
                if (conn.fail_fun)
                        conn.fail_fun ();
                return;
        }

        conn.net_info[index].mail = xmalloc (sizeof (mail_t));
        memcpy (conn.net_info[index].mail, newmail, sizeof (mail_t));

        conn.net_info[index].mail->place.num = conn.num;
        conn.net_info[index].mail->type      = BOX_POP3;

        request_next_info ();
}




static void
request_next_info (void)
{
        int index = first_unfilled ();

        if (index == -1){
                if (conn.progress != -1)
                        progress_close (conn.progress);
                conn.progress = -1;
                conn.state    = POP_TRANSACTION;

                if (conn.ok_fun)
                        conn.ok_fun ();
                return;
        }
        
        conn.num   = conn.net_info[index].num;
        conn.state = POP_TOP;
        
        str_clear (conn.send_buf);
        str_sprintf (conn.send_buf, "TOP %d 0\r\n", conn.num);

        if (conn.progress != -1)
                progress_advance (conn.progress, 1);
        
        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   "(\r\n.\r\n$)|(-ERR.*\r\n$)", top_action);
}


        
static void
destroy_arrays (void)
{
        int i;

        if (conn.net_info){
                for (i = 0; i < conn.maildrop_count; i++){
                        if (conn.net_info[i].uidl)
                                xfree (conn.net_info[i].uidl);
                        if (conn.net_info[i].mail){
                                mail_destroy (conn.net_info[i].mail, BOX_POP3);
                                xfree (conn.net_info[i].mail);
                        }
                }
                xfree (conn.net_info);
        }
        conn.net_info = NULL;

        if (conn.local_info){
                for (i = 0; i < conn.local_size; i++){
                        if (conn.local_info[i].uidl)
                                xfree (conn.local_info[i].uidl);
                }
                xfree (conn.local_info);
        }
        conn.local_info = NULL;
        conn.local_size = 0;
}



static void
cleanup (int n)
{
        if (conn.fail_fun)
                conn.fail_fun ();
        
        if (conn.error_message)
                str_destroy (conn.error_message);
        if (conn.send_buf)
                str_destroy (conn.send_buf);

        if (conn.apop_digest)
                str_destroy (conn.apop_digest);
        if (conn.user_str)
                str_destroy (conn.user_str);
        if (conn.pass_str)
                str_destroy (conn.pass_str);

        if (conn.progress != -1 && n != -1)
                progress_close (conn.progress);

        destroy_arrays ();

        conn.state          = POP_DISCONNECTED;
        conn.error_message  = NULL;
        conn.send_buf       = NULL;
        conn.nd             = -1;
        conn.progress       = -1;

        conn.apop_digest    = NULL;
        conn.user_str       = NULL;
        conn.pass_str       = NULL;
        conn.method         = AUTH_PLAIN;

        conn.maildrop_count = -1;
        conn.maildrop_size  = -1;
        conn.fp             = NULL;

        conn.local_size     = 0;
        conn.local_info     = NULL;
        conn.net_info       = NULL;

        conn.num            = -1;
        
        conn.ok_fun         = NULL;
        conn.fail_fun       = NULL;
}



/****************************************************************************
 *    INTERFACE FUNCTIONS
 ****************************************************************************/


void
pop_init (void)
{
        cleanup (-1);
}



void
pop_free_resources (void)
{
        cleanup (0);
}



int
pop_open (const char *hostname, unsigned short port, const char *user,
          const char *pass, enum auth_method method, int secure,
          void (*succ)(void), void (*fail)(void))
{
        port = (secure) ? ((port) ? port : 995) : ((port) ? port : 110);

        if (conn.state != POP_DISCONNECTED || conn.nd != -1){
                error_ (0, "%s", _("connection in progress"));
                return 1;
        }

        if (hostname == NULL || user == NULL || pass == NULL){
                error_ (0, "%s", _("invalid arguments"));
                return 1;
        }

        conn.nd = net_open (hostname, port, secure, cleanup);

        if (conn.nd == -1)
                return 1;

        conn.progress = -1;
        conn.fp       = NULL;
        conn.ok_fun   = succ;
        conn.fail_fun = fail;
        conn.send_buf = str_create ();
        conn.user_str = str_dup (user);
        conn.pass_str = str_dup (pass);
        conn.method   = method;
        conn.state    = POP_CONNECTED;

        net_recv_data (conn.nd, "\r\n$", connected_action);
        return 0;
}



void
pop_close (void (*succ)(void), void (*fail)(void))
{
        if (conn.state != POP_DISCONNECTED && conn.nd != -1){

                conn.ok_fun   = succ;
                conn.fail_fun = fail;
                
                str_clear (conn.send_buf);
                str_put_string_len (conn.send_buf, "QUIT\r\n", 6);

                conn.state = POP_QUIT;
                
                net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                           "\r\n$", quit_action);
        }
        else {
                cleanup (0);
        }
}



int
pop_maildrop_size (void)
{
        if (conn.state != POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_maildrop_size");
                return -1;
        }

        return conn.maildrop_size;
}



int
pop_maildrop_count (void)
{
        if (conn.state != POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_maildrop_count");
                return -1;
        }

        return conn.maildrop_count;
}



void
pop_list (void (*succ)(void), void (*fail)(void))
{
        if (conn.state != POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_list");
                return;
        }

        str_clear (conn.send_buf);
        str_put_string_len (conn.send_buf, "LIST\r\n", 6);

        conn.ok_fun   = succ;
        conn.fail_fun = fail;
        conn.state    = POP_LIST;

        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   "(\r\n.\r\n$)|(-ERR.*\r\n$)", list_action);
}



void
pop_uidl (void (*succ)(void), void (*fail)(void))
{
        if (conn.state != POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_uidl");
                return;
        }

        str_clear (conn.send_buf);
        str_put_string_len (conn.send_buf, "UIDL\r\n", 6);

        conn.ok_fun   = succ;
        conn.fail_fun = fail;
        conn.state    = POP_UIDL;

        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   "(\r\n.\r\n$)|(-ERR.*\r\n$)", uidl_action);
}



void
pop_retr (int num, FILE *fp, void (*succ)(void), void (*fail)(void))
{
        int index;
        
        if (conn.state != POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_retr");
                return;
        }

        index = net_array_index (num);

        if (index != -1 && conn.net_info[index].fetched){
                fail ();
                return;
        }
        
        str_clear (conn.send_buf);
        str_sprintf (conn.send_buf, "RETR %d\r\n", num);

        conn.ok_fun   = succ;
        conn.fail_fun = fail;
        conn.fp       = fp;
        conn.state    = POP_RETR;
        conn.num      = num;

        if (index != -1)
                net_expect (conn.nd, conn.net_info[index].size,
                            _("fetching message %d"), num);
        
        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len,
                   "(\r\n.\r\n$)|(-ERR.*\r\n$)", retr_action);
}



void
pop_dele (int num, void (*succ)(void), void (*fail)(void))
{
        int index;
        
        if (conn.state != POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_dele");
                return;
        }

        index = net_array_index (num);

        if (index != -1 && conn.net_info[index].deleted){
                succ ();
                return;
        }
        
        str_clear (conn.send_buf);
        str_sprintf (conn.send_buf, "DELE %d\r\n", num);

        conn.ok_fun   = succ;
        conn.fail_fun = fail;
        conn.num      = num;

        conn.state    = POP_DELE;

        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len, "\r\n$",
                   dele_action);
}



void
pop_rset (void (*succ)(void), void (*fail)(void))
{
        if (conn.state != POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_rset");
                return;
        }

        str_clear (conn.send_buf);
        str_put_string_len (conn.send_buf, "RSET\r\n", 6);

        conn.ok_fun   = succ;
        conn.fail_fun = fail;
        
        conn.state    = POP_RSET;

        net_combo (conn.nd, conn.send_buf->str, conn.send_buf->len, "\r\n$",
                   rset_action);
}



void
pop_get_infos (void (*succ)(void), void (*fail)(void))
{
        if (conn.state != POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_get_infos");
                return;
        }

        if (conn.maildrop_count < 1 || conn.net_info == NULL){
                if (succ)
                        succ ();
                return;
        }
        
        if (conn.progress != -1)
                progress_close (conn.progress);

        conn.ok_fun   = succ;
        conn.fail_fun = fail;
        conn.state    = POP_TOP;

        conn.progress = progress_setup (unfilled_count (), "%s",
                                        _("fetching message headers..."));
        request_next_info ();
}



void
pop_save_list (void)
{
        int         i;
        int         count = 0;
        FILE       *fp;
        char       *fname;
        char       *dir;
        memchunk_t *chunk;

        if (conn.state != POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_save_list");
                return;
        }
        
        if (conn.maildrop_count < 1 || conn.net_info == NULL)
                return;

        dir = ask_for_default ("pop_info_dir", NULL);
        if (dir == NULL){
                error_ (0, "%s", _("pop3 info dir not defined"));
                return;
        }

        fname = file_with_dir (dir, net_server_address (conn.nd));
        fp    = fopen (fname, "w");

        if (fp == NULL){
                error_ (errno, _("opening %s"), fname);
                xfree (fname);
                return;
        }

        chunk = memchunk_create_size (1000);
        for (i = 0; i < conn.maildrop_count; i++){
                if (conn.net_info[i].fetched && ! conn.net_info[i].deleted)
                        count++;
        }
        memchunk_intdump (chunk, count);
        for (i = 0; i < conn.maildrop_count; i++){
                if (conn.net_info[i].fetched && ! conn.net_info[i].deleted){
                        memchunk_intdump (chunk, conn.net_info[i].num);
                        memchunk_strdump (chunk, conn.net_info[i].uidl);
                }
        }

        memchunk_dump (chunk, fp);
        fclose (fp);
        xfree (fname);
        memchunk_destroy (chunk);
}



void
pop_load_list (void)
{
        int         i;
        FILE       *fp;
        char       *fname;
        char       *dir;
        memchunk_t *chunk;

        if (conn.state != POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_load_list");
                return;
        }
        
        if (conn.maildrop_count < 1 || conn.net_info == NULL)
                return;

        dir = ask_for_default ("pop_info_dir", NULL);
        if (dir == NULL){
                error_ (0, "pop3 info dir not defined");
                return;
        }

        fname = file_with_dir (dir, net_server_address (conn.nd));
        fp    = fopen (fname, "r");

        if (fp == NULL){
                error_ (errno, _("opening %s"), fname);
                xfree (fname);
                return;
        }

        chunk = memchunk_create_size (1000);
        if (memchunk_read (chunk, fp)){
                fclose (fp);
                xfree (fname);
                memchunk_destroy (chunk);
                return;
        }

        conn.local_size = memchunk_intget (chunk);
        if (conn.local_size < 0){
                debug_msg (DEBUG_ERROR, "negative size of array in pop_load_list");
                fclose (fp);
                xfree (fname);
                memchunk_destroy (chunk);
                return;
        }

        conn.local_info = xcalloc (conn.local_size, sizeof (struct mailinfo));
        for (i = 0; i < conn.local_size; i++){
                conn.local_info[i].num     = memchunk_intget (chunk);
                conn.local_info[i].uidl    = memchunk_strget (chunk);
                conn.local_info[i].fetched = 1;
        }

        fclose (fp);
        xfree (fname);
        memchunk_destroy (chunk);
}



void
pop_merge_lists (void)
{
        int       i;
        htable_t *table;
        
        if (conn.state != POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_merge_lists");
                return;
        }

        if (conn.maildrop_count < 1 || conn.net_info == NULL
            || conn.local_info == NULL)
                return;

        table = htable_create (misc_logarithm (conn.local_size));
        for (i = 0; i < conn.local_size; i++){
                htable_insert (table, conn.local_info[i].uidl, NULL);
        }
        for (i = 0; i < conn.maildrop_count; i++){
                if (htable_lookup (table, conn.net_info[i].uidl))
                        conn.net_info[i].fetched = 1;
        }
        htable_destroy (table, NULL);
}



int
pop_mail_size (int num)
{
        int index;

        if (conn.state < POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_mail_size");
                return 0;
        }

        if (conn.maildrop_count < 1 ||  conn.net_info == NULL)
                return 0;

        index = net_array_index (num);
        return conn.net_info[index].size;
}



int
pop_header_count (void)
{
        if (conn.state < POP_TRANSACTION){
                debug_msg (DEBUG_WARN, "invalid state in pop_header_count");
                return 0;
        }

        if (conn.maildrop_count < 1 || conn.net_info == NULL)
                return 0;

        return filled_count ();
}



mail_t *
pop_header_info (int index)
{
        int i;
        
        if (conn.state < POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_header_info");
                return NULL;
        }

        i = net_array_num (index);
        if (i == -1)
                return NULL;

        return conn.net_info[i].mail;
}



int
pop_num (int index)
{
        int i;
        
        if (conn.state < POP_TRANSACTION){
                debug_msg (DEBUG_ERROR, "invalid state in pop_num");
                return -1;
        }

        i = net_array_num (index);
        if (i == -1)
                return -1;

        return conn.net_info[i].num;
}



void
pop_mark_mail (int index)
{
        int i;

        if (conn.state < POP_TRANSACTION){
                return;
        }
        
        i = net_array_num (index);
        if (i == -1)
                return;

        conn.net_info[i].fetched = 1;
}

/****************************************************************************
 *    INTERFACE CLASS BODIES
 ****************************************************************************/
/****************************************************************************
 *
 *    END MODULE pop.c
 *
 ****************************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1