/* 
   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.  

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

   Design goals:
   =============

     1. Enable user to fetch / delete messages in non-interactive manner.
        This means that user should be able to retrieve, and delete all
        messages from given server (or all servers) and possibly combine
        these actions.

     2. Enable user to check if there is new mail waiting for him at one
        of defined servers.

     3. Enable user to fetch messages from servers in interactive manner,
        where he sees messages' headers, and may fetch and / or delete
        messages directly at server side.

     4. It must be possible to use non-interactive commands with interval
        hooks, such that elmo checks / fetches / deletes mail regularly.

     5. Interactive commands must be scheduled for execution if new command
        is issued before all previous have been completed.

     6. Interactive and non-interactive commands may not interact.  It may
        not happen, that checking for new mail is executed while user is
        connected in interactive mode.

     7. Interval hooks may not be executed after some idle time.  It may
        not happen, that user cannot read his mail because elmo connects
        to the server to check, whether there are some new messages.

   Non-interactive possibilities:
      + connect - list - fetch - disconnect - (pick next)
      + connect - stat - delete - disconnect - (pick next)
      + connect - list - fetch - delete - disconnect - (pick next)
      + connect - stat - disconnect - pick next

   Interactive possibilities:
      + connect
      + fetch
      + delete
      + reset
      + disconnect


   Few words about APOP:
   =====================

   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.

*/
/****************************************************************************
 *    IMPLEMENTATION HEADERS
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#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 "misc.h"
#include "debug.h"
#include "clock.h"
#include "ask.h"
#include "file.h"
#include "hash.h"
#include "md5.h"
#include "procmail.h"
#include "run.h"
#include "status.h"
#include "fetch.h"

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

#define ONELINE_TERMINATOR   "\r\n$"
#define MULTILINE_TERMINATOR "(^\\-ERR.*\r\n$)|(\r\n\\.\r\n$)"

// state information is only used by job dispatcher for interactive
// commands which must be scheduled for execution when user enters
// few without waiting for previous one to be completed.
enum state {
        POP_DISCONNECTED,       // no connection
        POP_IDLE,               // accepting commands
        POP_PROCESSING          // processing command
};


// The operation mode is used to check what kind of operation is
// requested by user.
enum op_mode {
        INTERACTIVE,
        CHECK_NEW,
        FETCH,
        DELETE,
        FETCH_DEL,
        FETCH_ALL,
        DELETE_ALL,
        FETCH_DEL_ALL
};


// These are interactive commands, that may be requested by user
enum command {
        C_GET_HEADERS,          // retrieve headers
        C_FETCH,                // retrieve selected message
        C_DELETE,               // delete selected message
        C_RESET,                // reset connection state
        C_DISCONNECT            // disconnect
};


// Each operation (interactive or not) consists of many steps.  Each step
// is a POP3 operation.  Initialy stage is set to S_GREETING which means
// that we don't issue any POP3 command but just wait for a greeting from
// server.  Before issuing any POP3 operation the stage is set to the
// appropriate value such that when elmo receives answer from server, it
// knows what kind of command it is reply to.
enum stage {
        S_GREETING,
        S_USER,
        S_PASS,
        S_RETR,
        S_DELE,
        S_QUIT,
        S_RSET,
        S_STAT,
        S_LIST,
        S_TOP
};

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


struct job {
        struct job   *next;
        enum command  cmd;
        int           arg;
};


class Pop : public Net
{

private:
        enum state   state;
        enum op_mode mode;
        enum stage   stage;


        ask_t *ask;
        
        int    progress;
        str_t *send_buf;
        str_t *apop_digest;
        str_t *error_message;   /* error message that was sent along with */
                                /* with negative response is displayed to */
                                /* user */
        struct job *jobs;
        struct job *last;


        bool found_new_mail;    // true if we have already encountered
                                // server where there is new mail
        
        int mail_index;         // this is used when fetching / deleting
                                // all messages - this is an index of the
                                // last fetched message
        
        int maildrop_count;     // number of messages in the mailbox
        int maildrop_size;      // total size of messages in the mailbox
        int *mail_sizes;        // sizes of individual messages

        mail_array_t *marray;
        
        void send_pass (void);
        void send_user (char *user);
        void send_apop (char *user, char *pass);
        void send_user_apop (void);
        void send_quit (void);
        void send_retr (int num);
        void send_dele (int num);
        void send_rset (void);
        void send_stat (void);
        void send_list (void);
        void send_top  (int num);
        void next_action_new (void);
        void retrieve_next_message (void);
        void next_action_fetch (void);
        void delete_next_message (void);
        void next_action_delete (void);
        void fetch_del_next_message (void);
        void next_action_fetch_dele (void);
        void next_action_interactive (void);
        void next_action (void);
        void connect (void);

        void s_greeting (char *msg, int len);
        void s_user (char *msg, int len);
        void s_pass (char *msg, int len);
        void s_quit (char *msg, int len);
        void s_rset (char *msg, int len);
        void s_dele (char *msg, int len);
        void s_retr (char *msg, int len);
        void s_stat (char *msg, int len);
        void s_list (char *msg, int len);
        void s_top  (char *msg, int len);

        void tell_new_mail (void);
        void tell_no_mail (void);
        bool is_positive (char *buf);
        void report_error (const char *msg);
        void get_digest (char *msg);
        void get_sizes (char *msg);
        void cleanup (void);

        void add_job (enum command cmd, int arg = -1);
        void finish_job (void);
        enum command top_command (void);
        void dispatch_job (void);
        void destroy_jobs (struct job *job);

protected:
        void recv_fun (char *buf, int size);
        void on_shutdown (void);

public:
        Pop (void);
        ~Pop (void);

        void disconnect (void);
        void check_new_mail (void);
        void fetch (void);
        void purge (void);
        void fetch_purge (void);
        void fetch_all (void);
        void purge_all (void);
        void fetch_purge_all (void);

        void open_interactive (void);
        void fetch_interactive (int num);
        void delete_interactive (int num);
        void reset_interactive (void);
        void close_interactive (void);
        void delete_all_interactive (void);
        void fetch_all_interactive (void);

        mail_t *mail_info (int num);
        int     mail_count (void);
};

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

static Pop *pop = NULL;

/****************************************************************************
 *    INTERFACE DATA
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES
 ****************************************************************************/
/****************************************************************************
 *    MESSAGE LIST FUNCTIONS
 ****************************************************************************/
/****************************************************************************
 *    TRANSITIONS
 ****************************************************************************/

// Transitions, are commands issued to server.  Before sending a command
// the stage is updated so that we know what to do with server's response.
//
// The only non-trivial function is next_action which has to decide which
// action to execute next depending on mode of operation, stage, and
// connection state.

void
Pop::send_pass (void)
{
        char *pass = ask_get_field (ask, "password");

        str_clear (send_buf);
        str_sprintf (send_buf, "PASS %s\r\n", pass);

        stage = S_PASS;
        
        combo (send_buf->str, send_buf->len, ONELINE_TERMINATOR);
}


void
Pop::send_user (char *user)
{
        str_clear (send_buf);
        str_sprintf (send_buf, "USER %s\r\n", user);

        stage = S_USER;

        combo (send_buf->str, send_buf->len, ONELINE_TERMINATOR);
}


void
Pop::send_apop (char *user, char *pass)
{
        int           i;
        unsigned char digest[16];
        int           user_len = strlen (user);
        int           pass_len = strlen (pass);
        MD5_CTX_      ctx;
  
        if (apop_digest == NULL){
                error_ (0, "%s", _("Server doesn't support "
                                   "APOP authentication."));
                close ();
                return;
        }
        
        str_clear (send_buf);

        MD5Init (&ctx);
        MD5Update (&ctx, (unsigned char *) apop_digest->str, apop_digest->len);
        MD5Update (&ctx, (unsigned char *) pass, pass_len);
        MD5Final (digest, &ctx);

        str_put_string_len (send_buf, "APOP ", 5);
        str_put_string_len (send_buf, user, user_len);
        str_put_char (send_buf, ' ');
        for (i = 0; i < 16; i++){
                str_sprintf (send_buf, "%02x", digest[i]);
        }
        str_put_string_len (send_buf, "\r\n", 2);

        stage = S_PASS;
        
        combo (send_buf->str, send_buf->len, ONELINE_TERMINATOR);
}


void
Pop::send_user_apop (void)
{
        char *user = ask_get_field (ask, "username");
        char *pass = ask_get_field (ask, "password");

        if (user == NULL){
                error_ (0, "%s", _("No username supplied, disconnecting..."));
                send_quit ();
                return;
        }

        if (pass == NULL){
                error_ (0, "%s", _("No password supplied, disconnecting..."));
                send_quit ();
                return;
        }

        progress = progress_setup (1, "%s", _("logging in..."));

        if (ask_get_field_int_default (ask, "use_apop", 0)){
                send_apop (user, pass);
        }
        else {
                send_user (user);
        }
}


void
Pop::send_quit (void)
{
        stage = S_QUIT;

        combo ("QUIT\r\n", 6, ONELINE_TERMINATOR);
}



void
Pop::send_retr (int num)
{
        str_clear (send_buf);
        str_sprintf (send_buf, "RETR %d\r\n", num);
        
        stage = S_RETR;

        if (mail_sizes != NULL){
                expect (mail_sizes[num - 1], _("fetching message %d"), num);
        }
        
        combo (send_buf->str, send_buf->len, MULTILINE_TERMINATOR);
}


void
Pop::send_dele (int num)
{
        str_clear (send_buf);
        str_sprintf (send_buf, "DELE %d\r\n", num);

        stage = S_DELE;

        combo (send_buf->str, send_buf->len, ONELINE_TERMINATOR);
}


void
Pop::send_rset (void)
{
        stage = S_RSET;

        combo ("RSET\r\n", 6, ONELINE_TERMINATOR);
}


void
Pop::send_stat (void)
{
        stage = S_STAT;

        combo ("STAT\r\n", 6, ONELINE_TERMINATOR);
}


void
Pop::send_list (void)
{
        stage = S_LIST;

        combo ("LIST\r\n", 6, MULTILINE_TERMINATOR);
}



void
Pop::send_top (int num)
{
        str_clear (send_buf);
        str_sprintf (send_buf, "TOP %d 0\r\n", num);
        
        stage = S_TOP;

        combo (send_buf->str, send_buf->len, MULTILINE_TERMINATOR);
}



void
Pop::connect (void)
{
        char *name;
        char *host;
        int   secure;
        int   port;

        host   = ask_get_field (ask, "server");
        secure = ask_get_field_int_default (ask, "ssl", 0);
        port   = ask_get_field_int_default (ask, "port", (secure) ? 995 : 110);
        
        if (host == NULL){
                name = ask_get_field (ask, "name");
                error_ (0, _("POP3 account %s is invalid: server address "
                             "not specified."), name);
                close ();
                return;
        }

        if (! open (host, port, secure)){
                close ();
                return;
        }

        stage = S_GREETING;
        net_recv (ONELINE_TERMINATOR);
}



void
Pop::next_action_new (void)
{
        switch (stage){

                case S_PASS:
                        send_stat ();
                        break;

                case S_STAT:
                        send_quit ();
                        status_add_info ("");
                        if (maildrop_count > 0){
                                found_new_mail = true;
                                tell_new_mail ();
                        }
                        break;

                case S_QUIT:
                        if (! found_new_mail){
                                connect ();
                        }
                        else {
                                cleanup ();
                        }
                        break;

                default:
                        error_ (0, _("Internal error in Pop::next_action_new"));
                        break;
        }
}



void
Pop::retrieve_next_message (void)
{
        if (mail_index >= maildrop_count){
                send_quit ();
        }
        else {
                send_retr (mail_index + 1);
                mail_index++;
        }
}



void
Pop::next_action_fetch (void)
{
        switch (stage){

                case S_PASS:
                        send_list ();
                        break;

                case S_LIST:
                        mail_index = 0;
                        if (maildrop_count > 0)
                                found_new_mail = true;
                case S_RETR:
                        retrieve_next_message ();
                        break;

                case S_QUIT:
                        connect ();
                        break;

                default:
                        error_ (0, _("Internal error in Pop::next_action_fetch"));
                        break;
        }
}



void
Pop::delete_next_message (void)
{
        if (mail_index >= maildrop_count){
                send_quit ();
        }
        else {
                send_dele (mail_index + 1);
                mail_index++;
        }
}



void
Pop::next_action_delete (void)
{
        switch (stage){

                case S_PASS:
                        send_stat ();
                        break;

                case S_STAT:
                        mail_index = 0;
                case S_DELE:
                        delete_next_message ();
                        break;

                case S_QUIT:
                        connect ();
                        break;

                default:
                        error_ (0, _("Internal error in Pop::next_action_delete"));
                        break;
        }
                        
}



void
Pop::fetch_del_next_message (void)
{
        if (mail_index >= maildrop_count){
                mail_index = 0;
                delete_next_message ();
        }
        else {
                send_retr (mail_index + 1);
                mail_index++;
        }
}



void
Pop::next_action_fetch_dele (void)
{
        switch (stage){

                case S_PASS:
                        send_list ();
                        break;

                case S_LIST:
                        mail_index = 0;
                        if (maildrop_count > 0)
                                found_new_mail = true;
                case S_RETR:
                        fetch_del_next_message ();
                        break;

                case S_DELE:
                        delete_next_message ();
                        break;

                case S_QUIT:
                        connect ();
                        break;

                default:
                        error_ (0, _("Internal error in Pop::next_action_fetch_dele"));
                        break;
        }
}



// It is very easy to determine the next action for a non-interactive
// task.  However in interactive mode we have first check if the last
// job has been already done (some jobs consist of few actions), and
// may not decide what to do next in their body.
// A best example for this is LIST action.  The s_list state may not
// determine what to do next, so it just hands the transition decision
// to next_action.  Then next_action may not just run dispatch_job
// because this job (GET_HEADERS) has not yet been finished.
void
Pop::next_action_interactive (void)
{
        switch (stage){

                case S_PASS:
                        send_list ();
                        return;
                
                case S_LIST:
                        mail_index = 0;
                        if (maildrop_count > 0){
                                marray = mail_array_create_size
                                        (maildrop_count + 1, BOX_POP3, NULL);
                                if (progress != -1)
                                        progress_close (progress);
                                progress = progress_setup (maildrop_count,
                                                           _("fetching headers..."));
                        }
                        else {
                                tell_no_mail ();
                                send_quit ();
                                return;
                        }
                        break;

                case S_TOP:
                        mail_index++;
                        break;

                default:
                        state = POP_IDLE;
                        fetch_redraw ();
                        finish_job ();
                        dispatch_job ();
                        return;
        }

        if (mail_index >= maildrop_count){
                if (progress != -1)
                        progress_close (progress);
                progress = -1;
                state    = POP_IDLE;
                fetch_show ();
                finish_job ();
                dispatch_job ();
                return;
        }

        send_top (mail_index + 1);
}



// This function determines what is the next action for the task
// given with mode.
void
Pop::next_action (void)
{
        switch (mode){

                case INTERACTIVE:
                        next_action_interactive ();
                        break;

                case CHECK_NEW:
                        next_action_new ();
                        break;

                case FETCH:
                case FETCH_ALL:
                        next_action_fetch ();
                        break;

                case DELETE:
                case DELETE_ALL:
                        next_action_delete ();
                        break;

                case FETCH_DEL:
                case FETCH_DEL_ALL:
                        next_action_fetch_dele ();
                        break;
        }
}


/****************************************************************************
 *    STATES
 ****************************************************************************/

// Here we have states - functions that parse server responses, and decide
// whether the last action was successful or not.  There are 2 possibiliies
// of what such a function may do upon success:
//   1. If there is only one possibility of taking next action (e.g. while
//      logging in), then use appropriate send_* function to do the
//      transition to the next state.
//   2. If there are multiple choices, use next_action to make a transition.
// 
// When the operation failed, or the connection is being closed, then there
// is no furhter transition.  The function on_shutdown does the transition
// in case of multi-account operation.


void
Pop::s_greeting (char *msg, int len)
{
        if (! is_positive (msg)){
                report_error (_("Connection has been rejected."));
                close ();
                return;
        }

        get_digest (msg);
        send_user_apop ();
}



void
Pop::s_user (char *msg, int len)
{
        if (! is_positive (msg)){
                report_error (_("Username has been rejected."));
                close ();
                return;
        }

        send_pass ();
}


void
Pop::s_pass (char *msg, int len)
{
        if (! is_positive (msg)){
                report_error (_("Authorization has failed."));
                close ();
                return;
        }

        if (apop_digest)
                str_destroy (apop_digest);
        apop_digest = NULL;

        if (progress != -1)
                progress_close (progress);
        progress = -1;

        next_action ();
}


void
Pop::s_quit (char *msg, int len)
{
        if (! is_positive (msg)){
                report_error (_("Does your server like you so much?"));
        }

        close ();
}


void
Pop::s_rset (char *msg, int len)
{
        int     i;
        mail_t *mail;
        
        if (! is_positive (msg)){
                report_error (_("Couldn't reset mailbox status."));
        }
        else if (marray) {
                for (i = 0; i < maildrop_count; i++){
                        mail = mail_array_get (marray, i);
                        mail->flags &= ~ FLAG_TRASHED;
                }
        }
        
        next_action ();
}


void
Pop::s_dele (char *msg, int len)
{
        mail_t *mail;
        
        if (! is_positive (msg)){
                report_error (_("Couldn't delete message."));
        }
        else if (marray){
                mail = mail_array_get (marray, jobs->arg - 1);
                mail->flags |= FLAG_TRASHED;
        }

        next_action ();
}


void
Pop::s_retr (char *msg, int len)
{
        char   *seek;
        mail_t *mail;
        
        if (progress != -1)
                progress_close (progress);
        progress = -1;
        
        if (! is_positive (msg)){
                report_error (_("Couldn't fetch message."));
        }
        else {
                seek = strchr (msg, '\r');
                if (seek == NULL)
                        seek = msg;
                else
                        seek += 2;

                procmail_deliver (seek);

                if (marray){
                        mail = mail_array_get (marray, jobs->arg - 1);
                        mail->flags |= FLAG_FETCHED;
                }
        }

        next_action ();
}


void
Pop::s_stat (char *msg, int len)
{
        char *seek;
        char *rest;
        int   new_m;
        
        if (! is_positive (msg)){
                report_error (_("Couldn't obtain information about maildrop."));
                next_action ();
                return;
        }

        seek = strchr (msg, ' ');

        if (! seek){
                error_ (0, "%s: %s", _("Invalid response from server"), msg);
                next_action ();
                return;
        }

        maildrop_count = strtol (seek + 1, & rest, 10);

        if (*rest != ' '){
                error_ (0, "%s: %s", _("Invalid response from server"), msg);
                next_action ();
                return;
        }

        maildrop_size = strtol (rest + 1, NULL, 10);

        next_action ();
}



void
Pop::s_list (char *msg, int len)
{
        if (! is_positive (msg)){
                report_error (_("Couldn't obtain information about sizes of messages."));
                next_action ();
                return;
        }

        get_sizes (msg);
        
        next_action ();
}



void
Pop::s_top (char *msg, int len)
{
        char *seek;
        
        if (! is_positive (msg)){
                report_error (_("Couldn't fetch message header."));
                next_action ();
                return;
        }

        seek = strchr (msg, '\r');

        if (seek == NULL){
                error_ (0, _("Server issued an invalid response."));
                next_action ();
                return;
        }

        if (mlex_scan_buffer (seek + 2) != NEXT_MAIL){
                error_ (0, _("Header of the %d message is invalid."),
                        mail_index + 1);
                next_action ();
                return;
        }

        newmail->type       = BOX_POP3;
        newmail->place.size = mail_sizes[mail_index];
        mail_array_insert (marray, newmail);
        
        progress_advance (progress, 1);
        
        next_action ();
}


/****************************************************************************
 *    JOB FUNCTIONS
 ****************************************************************************/


void
Pop::add_job (enum command cmd, int arg)
{
        struct job *job = (struct job *) xmalloc (sizeof (struct job));

        job->next  = NULL;
        job->cmd   = cmd;
        job->arg   = arg;

        if (last == NULL){
                jobs = last = job;
        }
        else {
                last->next = job;
                last       = job;
        }
}


void
Pop::finish_job (void)
{
        struct job *job = jobs;

        jobs = jobs->next;

        if (last == job)
                last = jobs;

        xfree (job);

        state = POP_IDLE;
}



void
Pop::dispatch_job (void)
{
        mail_t     *mail;
        struct job *job;

        if (state != POP_IDLE)
                return;
        
        job = jobs;
        
        if (job == NULL)
                return;
        
        switch (job->cmd){

                case C_FETCH:
                        if (marray){
                                mail = mail_array_get (marray, job->arg - 1);
                                if (mail->flags & FLAG_FETCHED){
                                        finish_job ();
                                        dispatch_job ();
                                        return;
                                }
                        }
                        send_retr (job->arg);
                        break;

                case C_DELETE:
                        if (marray){
                                mail = mail_array_get (marray, job->arg - 1);
                                if (mail->flags & FLAG_TRASHED){
                                        finish_job ();
                                        dispatch_job ();
                                        return;
                                }
                        }
                        send_dele (job->arg);
                        break;

                case C_RESET:
                        send_rset ();
                        break;

                case C_DISCONNECT:
                        send_quit ();
                        break;
        }

        state = POP_PROCESSING;
}


void
Pop::destroy_jobs (struct job *job)
{
        if (job == NULL)
                return;

        destroy_jobs (job->next);

        xfree (job);
}


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


void
Pop::on_shutdown (void)
{
        switch (mode){

                case INTERACTIVE:
                case FETCH:
                case DELETE:
                case FETCH_DEL:
                        cleanup ();
                        break;

                case FETCH_ALL:
                case FETCH_DEL_ALL:
                        ask = ask_select_next (ask);
                        if (ask){
                                stage = S_QUIT;
                                next_action ();
                        }
                        else {
                                if (found_new_mail)
                                        run_sound ();
                                cleanup ();
                        }
                        break;

                case DELETE_ALL:
                case CHECK_NEW:
                        ask = ask_select_next (ask);
                        if (ask){
                                stage = S_QUIT;
                                next_action ();
                        }
                        else {
                                cleanup ();
                        }
                        break;

        }
}


void
Pop::cleanup (void)
{
        if (ask)
                ask_destroy (ask);
        ask = NULL;

        if (progress != -1)
                progress_close (progress);
        progress = -1;

        if (apop_digest)
                str_destroy (apop_digest);
        apop_digest = NULL;

        if (error_message)
                str_destroy (error_message);
        error_message = NULL;

        if (mail_sizes)
                xfree (mail_sizes);
        mail_sizes = NULL;

        if (marray)
                mail_array_destroy (marray);
        marray = NULL;

        str_clear (send_buf);
        destroy_jobs (jobs);

        jobs  = NULL;
        last  = NULL;
        state = POP_DISCONNECTED;
}



void
Pop::tell_new_mail (void)
{
        str_t *str = str_create ();

        str_sprintf (str, _("  %d message(s) at %s"), maildrop_count,
                     ask_get_field (ask, "name"));
        status_add_info (str->str);
        str_destroy (str);
        run_sound ();
}


void
Pop::tell_no_mail (void)
{
        str_t *str = str_create ();

        str_sprintf (str, _("  no mail at %s"), ask_get_field (ask, "name"));
        status_add_info (str->str);
        str_destroy (str);
}


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


bool
Pop::is_positive (char *msg)
{
        char *end;

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

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

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

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



void
Pop::get_digest (char *msg)
{
        int        len;
        regmatch_t matches[1];

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


void
Pop::get_sizes (char *msg)
{
        int        i;
        char      *seek;
        rstring_t *items = rstring_split_re (msg, "\r?\n");

        if (items->count < 2){
                error_ (0, _("Server issued an invalid response."));
                rstring_delete (items);
                return;
        }

        maildrop_count = items->count - 2;
        maildrop_size  = 0;
        mail_sizes     = (int *) xmalloc (maildrop_count * sizeof (int));
        
        for (i = 1; i < items->count - 1; i++){
                seek = strchr (items->array[i], ' ');
                if (seek == NULL){
                        error_ (0, _("Server issued an invalid response."));
                        xfree (mail_sizes);
                        mail_sizes = NULL;
                        rstring_delete (items);
                        return;
                }
                mail_sizes[i - 1] = atoi (seek + 1);
                maildrop_size    += mail_sizes[i - 1];
        }

        rstring_delete (items);
}


void
Pop::recv_fun (char *msg, int len)
{
        switch (stage){
                case S_GREETING:
                        s_greeting (msg, len);
                        break;

                case S_USER:
                        s_user (msg, len);
                        break;

                case S_PASS:
                        s_pass (msg, len);
                        break;

                case S_RETR:
                        s_retr (msg, len);
                        break;

                case S_DELE:
                        s_dele (msg, len);
                        break;

                case S_QUIT:
                        s_quit (msg, len);
                        break;

                case S_RSET:
                        s_rset (msg, len);
                        break;

                case S_STAT:
                        s_stat (msg, len);
                        break;

                case S_LIST:
                        s_list (msg, len);
                        break;

                case S_TOP:
                        s_top (msg, len);
                        break;
        }
}

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


void
pop_init (void)
{
        pop = new Pop ();
}


void
pop_free_resources (void)
{
        if (pop)
                delete (pop);

        pop = NULL;
}


void
pop_check_new_mail (void)
{
        if (pop == NULL)
                return;

        pop->check_new_mail ();
}


void
pop_fetch (void)
{
        if (pop == NULL)
                return;

        pop->fetch ();
}


void
pop_purge (void)
{
        if (pop == NULL)
                return;

        pop->purge ();
}


void
pop_fetch_purge (void)
{
        if (pop == NULL)
                return;

        pop->fetch_purge ();
}


void
pop_fetch_all (void)
{
        if (pop == NULL)
                return;

        pop->fetch_all ();
}


void
pop_purge_all (void)
{
        if (pop == NULL)
                return;

        pop->purge_all ();
}


void
pop_fetch_purge_all (void)
{
        if (pop == NULL)
                return;

        pop->fetch_purge_all ();
}


void
pop_open_interactive (void)
{
        if (pop == NULL)
                return;

        pop->open_interactive ();
}


void
pop_fetch_interactive (int num)
{
        if (pop == NULL)
                return;

        pop->fetch_interactive (num);
}


void
pop_delete_interactive (int num)
{
        if (pop == NULL)
                return;

        pop->delete_interactive (num);
}


void
pop_reset_interactive (void)
{
        if (pop == NULL)
                return;

        pop->reset_interactive ();
}


void
pop_close_interactive (void)
{
        if (pop == NULL)
                return;

        pop->close_interactive ();
}


void
pop_delete_all_interactive (void)
{
        if (pop == NULL)
                return;

        pop->delete_all_interactive ();
}


void
pop_fetch_all_interactive (void)
{
        if (pop == NULL)
                return;

        pop->fetch_all_interactive ();
}


mail_t *
pop_mail_info (int num)
{
        if (pop == NULL)
                return NULL;

        return pop->mail_info (num);
}


int
pop_mail_count (void)
{
        if (pop == NULL)
                return -1;

        return pop->mail_count ();
}


/****************************************************************************
 *    INTERFACE CLASS BODIES
 ****************************************************************************/

Pop::Pop (void) : Net ()
{
        state          = POP_DISCONNECTED;
        jobs           = NULL;
        last           = NULL;
        ask            = NULL;
        progress       = -1;
        apop_digest    = NULL;
        error_message  = NULL;
        send_buf       = str_create ();
        maildrop_count = 0;
        maildrop_size  = -1;
        mail_sizes     = NULL;
        marray         = NULL;
}


Pop::~Pop (void)
{
        cleanup ();
        
        if (send_buf)
                str_destroy (send_buf);
}


void
Pop::disconnect (void)
{

}


void
Pop::check_new_mail (void)
{
        if (state != POP_DISCONNECTED){
                error_ (0, _("Connection in progress."));
                return;
        }

        ask = ask_select_default ("pop_acc");

        if (ask == NULL){
                error_ (0, _("No POP3 account has been defined."));
                return;
        }

        state          = POP_PROCESSING;
        mode           = CHECK_NEW;
        found_new_mail = false;
        connect ();
}


void
Pop::fetch (void)
{
        if (state != POP_DISCONNECTED){
                error_ (0, _("Connection in progress."));
                return;
        }

        ask = ask_select ("pop_acc");

        if (ask == NULL)
                return;

        state          = POP_PROCESSING;
        mode           = FETCH;
        found_new_mail = false;
        connect ();
}


void
Pop::purge (void)
{
        if (state != POP_DISCONNECTED){
                error_ (0, _("Connection in progress."));
                return;
        }

        ask = ask_select ("pop_acc");

        if (ask == NULL)
                return;

        state = POP_PROCESSING;
        mode  = DELETE;
        connect ();
}


void
Pop::fetch_purge (void)
{
        if (state != POP_DISCONNECTED){
                error_ (0, _("Connection in progress."));
                return;
        }

        ask = ask_select ("pop_acc");

        if (ask == NULL)
                return;

        state          = POP_PROCESSING;
        mode           = FETCH_DEL;
        found_new_mail = false;
        connect ();
}


void
Pop::fetch_all (void)
{
        if (state != POP_DISCONNECTED){
                error_ (0, _("Connection in progress."));
                return;
        }

        ask = ask_select_default ("pop_acc");

        if (ask == NULL){
                error_ (0, _("No POP3 account has been defined."));
                return;
        }

        state          = POP_PROCESSING;
        mode           = FETCH_ALL;
        found_new_mail = false;
        connect ();
}


void
Pop::purge_all (void)
{
        if (state != POP_DISCONNECTED){
                error_ (0, _("Connection in progress."));
                return;
        }

        ask = ask_select_default ("pop_acc");

        if (ask == NULL){
                error_ (0, _("No POP3 account has been defined."));
                return;
        }

        state = POP_PROCESSING;
        mode  = DELETE_ALL;
        connect ();
}


void
Pop::fetch_purge_all (void)
{
        if (state != POP_DISCONNECTED){
                error_ (0, _("Connection in progress."));
                return;
        }

        ask = ask_select_default ("pop_acc");

        if (ask == NULL){
                error_ (0, _("No POP3 account has been defined."));
                return;
        }

        state          = POP_PROCESSING;
        mode           = FETCH_DEL_ALL;
        found_new_mail = false;
        connect ();
}


void
Pop::open_interactive (void)
{
        if (state != POP_DISCONNECTED){
                error_ (0, _("Connection in progress."));
                return;
        }

        ask = ask_select ("pop_acc");

        if (ask == NULL)
                return;

        state = POP_PROCESSING;
        mode  = INTERACTIVE;

        add_job (C_GET_HEADERS);

        connect ();
}


void
Pop::fetch_interactive (int num)
{
        if (state == POP_DISCONNECTED || mode != INTERACTIVE){
                error_ (0, _("Connect first."));
                return;
        }

        add_job (C_FETCH, num);
        dispatch_job ();
}


void
Pop::delete_interactive (int num)
{
        if (state == POP_DISCONNECTED || mode != INTERACTIVE){
                error_ (0, _("Connect first."));
                return;
        }

        add_job (C_DELETE, num);
        dispatch_job ();
}        


void
Pop::reset_interactive (void)
{
        if (state == POP_DISCONNECTED || mode != INTERACTIVE){
                error_ (0, _("Connect first."));
                return;
        }

        add_job (C_RESET);
        dispatch_job ();
}        


void
Pop::close_interactive (void)
{
        if (state == POP_DISCONNECTED || mode != INTERACTIVE){
                error_ (0, _("Connect first."));
                return;
        }

        add_job (C_DISCONNECT);
        dispatch_job ();
}


void
Pop::delete_all_interactive (void)
{
        int i;
        
        if (state == POP_DISCONNECTED || mode != INTERACTIVE){
                error_ (0, _("Connect first."));
                return;
        }

        for (i = 0; i < maildrop_count; i++){
                add_job (C_DELETE, i + 1);
        }
        dispatch_job ();
}


void
Pop::fetch_all_interactive (void)
{
        int i;
        
        if (state == POP_DISCONNECTED || mode != INTERACTIVE){
                error_ (0, _("Connect first."));
                return;
        }

        for (i = 0; i < maildrop_count; i++){
                add_job (C_FETCH, i + 1);
        }
        dispatch_job ();
}


mail_t *
Pop::mail_info (int num)
{
        if (state == POP_DISCONNECTED || mode != INTERACTIVE){
                error_ (0, _("Connect first."));
                return NULL;
        }

        return mail_array_get (marray, num);
}


int
Pop::mail_count (void)
{
        if (state == POP_DISCONNECTED || mode != INTERACTIVE){
                error_ (0, _("Connect first."));
                return -1;
        }

        return maildrop_count;
}


/****************************************************************************
 *
 *    END MODULE pop.c
 *
 ****************************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1