/*
 * kbiffmonitor.cpp
 * Copyright (C) 1999-2008 Kurt Granroth <granroth@kde.org>
 *
 * This file contains the implementation of KBiffMonitor and
 * associated classes.
 */
#include "kbiffmonitor.h"
#include "kbiffmonitor.moc"

#include <tdemessagebox.h>

#include <sys/types.h>
#ifndef __STRICT_ANSI__
#define __STRICT_ANSI__
#include <sys/socket.h>
#undef __STRICT_ANSI__
#else
#include <sys/socket.h>
#endif
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <utime.h>

#include <fcntl.h>
#include <errno.h>

#include <kbiffurl.h>
#include <kdebug.h>

#include <tqapplication.h>
#include <tqstring.h>
#include <tqregexp.h>
#include <tqdir.h>
#include <tqdatetime.h>
#include <ksimpleconfig.h>

// Needed for CRAM-MD5 and APOP
#include <kmdcodec.h>
#include "kbiffcrypt.h"

#define MAXSTR (1024)

#define MAIL_STATE_FILE "kbiffstate"

static bool real_from(const TQString& buffer);
static const char* compare_header(const char* header, const char* field);

KBiffMonitor::KBiffMonitor()
    : TQObject(),
      poll(60),
      oldTimer(0),
      started(false),
      newCount(0),
      curCount(-1),
      oldCount(-1),
      firstRun(false),
      key(""),
      simpleURL(""),
      protocol(""),
      mailbox(""),
      server(""),
      user(""),
      password(""),
      port(0),
      preauth(false),
      keepalive(false),
      mailState(UnknownState),
      lastSize(0),
      imap(0),
      pop(0),
      nntp(0)
{
    lastRead.setTime_t(0);
    lastModified.setTime_t(0);
    b_new_lastSize = false;
    b_new_lastRead = false;
    b_new_lastModified = false;
    b_new_uidlList = false;
}

KBiffMonitor::~KBiffMonitor()
{
    if (imap)
    {
        delete imap;
        imap = 0;
    }
    if (pop)
    {
        delete pop;
        pop = 0;
    }
    if (nntp)
    {
        delete nntp;
        nntp = 0;
    }
}

void KBiffMonitor::readConfig()
{
    KSimpleConfig *config = new KSimpleConfig(MAIL_STATE_FILE);
    config->setDollarExpansion(false);

    TQString group;
    group = mailbox + "(" + key + ")";
    config->setGroup(group);

    TQStrList list;

    mailState = (KBiffMailState)config->readNumEntry("mailState", UnknownState);
    lastSize = config->readNumEntry("lastSize");
    config->readListEntry("lastRead", list);
    if (list.count()==6)
    {
        lastRead.setDate(TQDate(atoi(list.at(0)),atoi(list.at(1)),atoi(list.at(2))));
        lastRead.setTime(TQTime(atoi(list.at(3)),atoi(list.at(4)),atoi(list.at(5))));
    }
    config->readListEntry("lastModified", list);
    if (list.count()==6)
    {
      lastModified.setDate(TQDate(atoi(list.at(0)),atoi(list.at(1)),atoi(list.at(2))));
      lastModified.setTime(TQTime(atoi(list.at(3)),atoi(list.at(4)),atoi(list.at(5))));
    }
    config->readListEntry("uidlList", list);

    char *UIDL;
    uidlList.clear();
    for (UIDL = list.first(); UIDL != 0; UIDL = list.next())
    {
        uidlList.append( new TQString(UIDL) );
    }

    newCount = config->readNumEntry("newCount", 0);
    oldCount = config->readNumEntry("oldCount", -1);

    delete config;
}

void KBiffMonitor::saveConfig()
{
    // open the config file
    KSimpleConfig *config = new KSimpleConfig(MAIL_STATE_FILE);
    config->setDollarExpansion(false);

    TQString group;
    group = mailbox + "(" + key + ")";
    config->setGroup(group);

    TQStringList uidlist;
    TQString *UIDL;
    for (UIDL = uidlList.first(); UIDL != 0; UIDL = uidlList.next())
    {
        uidlist.append(*UIDL);
    }

    config->writeEntry("mailState", (int)mailState);
    config->writeEntry("lastSize", lastSize);
    config->writeEntry("lastRead",lastRead);
    config->writeEntry("lastModified",lastModified);
    config->writeEntry("uidlList",uidlist);
    config->writeEntry("newCount", newCount);
    config->writeEntry("oldCount", oldCount);

    delete config;
}

void KBiffMonitor::onStateChanged()
{
    saveConfig();
}

void KBiffMonitor::start()
{
    readConfig();
    started  = true;
    firstRun = true;
    oldTimer = startTimer(poll * 1000);
    emit(signal_checkMail());
}

void KBiffMonitor::stop()
{
    if (oldTimer > 0)
        killTimer(oldTimer);

    lastSize   = 0;
    oldTimer   = 0;
    mailState  = UnknownState;
    started    = false;
    lastRead.setTime_t(0);
    lastModified.setTime_t(0);
    uidlList.clear();
}

void KBiffMonitor::setPollInterval(const int interval)
{
    poll = interval;

    // Kill any old timers that may be running
    if (oldTimer > 0)
    {
        killTimer(oldTimer);

        // Start a new timer will the specified time
        if (started)
        {
            oldTimer = startTimer(interval * 1000);

            emit(signal_checkMail());
        }
    }
}

void KBiffMonitor::setMailbox(const TQString& url)
{
    KBiffURL kurl(url);
    setMailbox(kurl);
}

void KBiffMonitor::setMailbox(KBiffURL& url)
{
    if (imap)
    {
        delete imap;
        imap = 0;
    }
    if (pop)
    {
        delete pop;
        pop = 0;
    }
    if (nntp)
    {
        delete nntp;
        nntp = 0;
    }

    protocol = url.protocol();

    if (protocol == "imap4")
    {
        disconnect(this);

        imap = new KBiffImap;

        connect(this, TQ_SIGNAL(signal_checkMail()), TQ_SLOT(checkImap()));
        server   = url.host();
        user     = url.user();
        password = url.pass();

        mailbox  = url.path().right(url.path().length() - 1); 
        port     = (url.port() > 0) ? url.port() : 143;

        preauth = url.searchPar("preauth") == "yes";
        keepalive = url.searchPar("keepalive") == "yes";
        bool async = url.searchPar("async") == "yes";  
        imap->setAsync(async);
#ifdef USE_SSL
        imap->setSSL(false);
#endif // USE_SSL
        simpleURL = "imap4://" + server + "/" + mailbox;
    }

#ifdef USE_SSL
    if (protocol == "imap4s")
    {
        disconnect(this);

        imap = new KBiffImap;

        connect(this, TQ_SIGNAL(signal_checkMail()), TQ_SLOT(checkImap()));
        server   = url.host();
        user     = url.user();
        password = url.pass();

        mailbox  = url.path().right(url.path().length() - 1); 
        port     = (url.port() > 0) ? url.port() : 993;

        preauth = url.searchPar("preauth") == "yes";
        keepalive = url.searchPar("keepalive") == "yes";
        bool async = url.searchPar("async") == "yes";
        imap->setAsync(async);
	imap->setSSL(true);
        simpleURL = "imap4s://" + server + "/" + mailbox;
    }
#endif // USE_SSL

    if (protocol == "pop3")
    {
        disconnect(this);

        pop = new KBiffPop;

        connect(this, TQ_SIGNAL(signal_checkMail()), TQ_SLOT(checkPop()));
        server   = url.host();
        user     = url.user();
        password = url.pass();
        mailbox  = url.user();
        port     = (url.port() > 0) ? url.port() : 110;

        keepalive = url.searchPar("keepalive") == "yes";
        bool async = url.searchPar("async") == "yes";
        pop->setAsync(async);
        // preserve existing behaviour, prior to adding disable apop,
        // by setting Apop on, even if no apop parameter is found in the mailbox url
        bool useApop = !( url.searchPar("apop") == "no" );
        pop->setApop( useApop );
#ifdef USE_SSL
        pop->setSSL(false);
#endif // USE_SSL

        simpleURL = "pop3://" + server + "/" + mailbox;
    }

#ifdef USE_SSL
    if (protocol == "pop3s")
    {
        disconnect(this);

        pop = new KBiffPop;

        connect(this, TQ_SIGNAL(signal_checkMail()), TQ_SLOT(checkPop()));
        server   = url.host();
        user     = url.user();
        password = url.pass();
        mailbox  = url.user();
        port     = (url.port() > 0) ? url.port() : 995;

        keepalive = url.searchPar("keepalive") == "yes";
        bool async = url.searchPar("async") == "yes";
        pop->setAsync(async);
        // preserve existing behaviour, prior to adding disable apop,
        // by setting Apop on, even if no apop parameter is found in the mailbox url
        bool useApop = !( url.searchPar("apop") == "no" );
        pop->setApop( useApop );
        pop->setSSL(true);

        simpleURL = "pop3s://" + server + "/" + mailbox;
    }
#endif // USE_SSL

    if (protocol == "mbox")
    {
        disconnect(this);

        connect(this, TQ_SIGNAL(signal_checkMail()), TQ_SLOT(checkMbox()));
        mailbox = url.path();

        simpleURL = "mbox:" + mailbox;
    }

    if (protocol == "file")
    {
        disconnect(this);

        connect(this, TQ_SIGNAL(signal_checkMail()), TQ_SLOT(checkLocal()));
        mailbox = url.path();

        simpleURL = "file:" + mailbox;
    }

    if (protocol == "maildir")
    {
        disconnect(this);

        connect(this, TQ_SIGNAL(signal_checkMail()), TQ_SLOT(checkMaildir()));
        mailbox = url.path();

        simpleURL = "maildir:" + mailbox;
    }

    if (protocol == "mh")
    {
        disconnect(this);

        connect(this, TQ_SIGNAL(signal_checkMail()), TQ_SLOT(checkMHdir()));
        mailbox = url.path();

        simpleURL = "mh:" + mailbox;
    }

    if (protocol == "nntp")
    {
        disconnect(this);

        nntp = new KBiffNntp;

        connect(this, TQ_SIGNAL(signal_checkMail()), TQ_SLOT(checkNntp()));
        server   = url.host();
        user     = url.user();
        password = url.pass();

        mailbox  = url.path().right(url.path().length() - 1); 
        port     = (url.port() > 0) ? url.port() : 119;

        keepalive = url.searchPar("keepalive") == "yes";
        bool async = url.searchPar("async") == "yes";
        nntp->setAsync(async);
#ifdef USE_SSL
        nntp->setSSL(false);
#endif // USE_SSL
        simpleURL = "nntp://" + server + "/" + mailbox;
    }

    fetchCommand = url.searchPar("fetch");
}

void KBiffMonitor::setMailboxIsRead()
{
  lastRead  = TQDateTime::currentDateTime();
  if (mailState == NewMail)
  {
    if (b_new_lastSize)     lastSize     = new_lastSize;
    if (b_new_lastRead)     lastRead     = new_lastRead;
    if (b_new_lastModified) lastModified = new_lastModified;
    if (b_new_uidlList)     uidlList     = new_uidlList;

    if (curCount!=-1) curCount+=newCount;
    newCount = 0;
    b_new_lastSize = false;
    b_new_lastRead = false;
    b_new_lastModified = false;
    b_new_uidlList = false;

    determineState(OldMail);
  }
}

void KBiffMonitor::checkMailNow()
{
    emit(signal_checkMail());
}

void KBiffMonitor::setPassword(const TQString& pass)
{
    password = pass;
}

void KBiffMonitor::setMailboxKey(const TQString& k)
{
    key = k;
}

void KBiffMonitor::timerEvent(TQTimerEvent *)
{
    emit(signal_checkMail());
}

void KBiffMonitor::checkLocal()
{
    // get the information about this local mailbox
    TQFileInfo mbox(mailbox);

    // run external fetch client
    if (!fetchCommand.isEmpty())
        emit(signal_fetchMail(fetchCommand));

    // check if we have new mail
    determineState(mbox.size(), mbox.lastRead(), mbox.lastModified());

    firstRun = false;
}

void KBiffMonitor::checkMbox()
{
    // get the information about this local mailbox
    TQFileInfo mbox(mailbox);

    // run external fetch client
    if (!fetchCommand.isEmpty())
        emit(signal_fetchMail(fetchCommand));

    // see if the state has changed
    if ((mbox.lastModified() != lastModified) || (mbox.size() != lastSize) ||
        (mailState == UnknownState) || (oldCount == -1))
    {
        lastModified = mbox.lastModified();
        lastSize     = mbox.size();

        // ok, the state *has* changed.  see if the number of
        // new messages has, too.
        newCount = mboxMessages();

        // Set access time of the file to what it was. If we don't do
        // this some (all?) MUAs think that the mail has already been
        // read.
        {
            utimbuf buf;
            buf.actime = mbox.lastRead().toTime_t();
            buf.modtime = mbox.lastModified().toTime_t();
            utime(TQFile::encodeName(mailbox), &buf);
        }

        // if there are any new messages, consider the state New
        if (newCount > 0)
          determineState(NewMail);
        else
        {
            if (oldCount == 0)
                determineState(NoMail);
            else
                determineState(OldMail);
        }
    }
    else if (firstRun)
    {
        KBiffMailState state(mailState);
        mailState = UnknownState;
        determineState(state);
    }

    firstRun = false;

    // handle the NoMail case
    if ((mbox.size() == 0) || (oldCount == 0))
    {
        newCount = 0;
        determineState(NoMail);
        return;
    }
}

void KBiffMonitor::checkPop()
{
    firstRun = false;

    TQString command;

    // connect to the server unless it is active already
    if (pop->active() == false)
    {
        if(pop->connectSocket(server, port) == false)
        {
            determineState(NoConn);
            return;
        }

        // find out if APOP is supported
        pop->parseBanner();
        
        // find other possibly useful capabilities
        // we don't care if this fails
        pop->command("CAPA\r\n");
        
        if (pop->authenticate(user, password) == false )
        {
            pop->close();
            invalidLogin();
            return;
        }
    }
    
    command = "UIDL\r\n";
    if (pop->command(command) == false)
    {
        command = "STAT\r\n";
        if (pop->command(command) == false)
        {
            command = "LIST\r\n";
            if (pop->command(command) == false)
            {
                // if this still doesn't work, then we
                // close this port
                pop->close();
                return;
            }
        }
    }

    if (command == "UIDL\r\n")
    {
        determineState(pop->getUidlList());
        curCount = uidlList.count();
    }
    else
    {
        determineState(pop->numberOfMessages());
    }

    if (keepalive == false)
        pop->close();
}

void KBiffMonitor::checkImap()
{
    firstRun = false;

    TQString command;
    int seq = 1000;
    bool do_login = false;

    // run external client (probably to setup SSL)
    if (!fetchCommand.isEmpty()) {
        emit(signal_fetchMail(fetchCommand));

        // sleep a bit to allow the connection to take place
        sleep(1);
    }

    // connect to the server
    if (imap->active() == false)
    {
        if (imap->connectSocket(server, port) == false)
        {
            invalidLogin();
            return;
        }

        do_login = true;

        // check the server's capabilities (see RFC 3050, 6.1.1)
        command = TQString().setNum(seq) + " CAPABILITY\r\n";
        if (imap->command(command, seq) == false)
        {
            invalidLogin();
            return;
        }
        seq++;
    }

    // if we are preauthorized OR we want to keep the session alive, then
    // we don't login.  Otherwise, we do.
    if ((preauth == false) && (do_login == true))
    {
        if (imap->authenticate(&seq, user, password) == false)
        {
            invalidLogin();
            return;
        }
    }

    // reset the numbers from the last check
    imap->resetNumbers();
    
    // The STATUS COMMAND is documented in RFC2060, 6.3.10
    command = TQString().setNum(seq) + " STATUS " + mailbox + " (UNSEEN MESSAGES)\r\n";
    if ( ! imap->command(command, seq)) {
        return;
    }
    seq++;

    // lets not logout if we want to keep the session alive
    if (keepalive == false)
    {
        command = TQString().setNum(seq) + " LOGOUT\r\n";
        if (imap->command(command, seq) == false)
            return;
        imap->close();
    }

    // what state are we in?
    if (imap->numberOfMessages() == 0)
    {
        newCount = 0;
        determineState(NoMail);
    }
    else
    {
        newCount = imap->numberOfNewMessages();
	curCount = imap->numberOfMessages() - newCount;
        if (newCount > 0)
            determineState(NewMail);
        else
            determineState(OldMail);
    }
}

void KBiffMonitor::checkMaildir()
{
    firstRun = false;

    // get the information about this local mailbox
    TQDir mbox(mailbox);

    // run external fetch client
    if (!fetchCommand.isEmpty())
        emit(signal_fetchMail(fetchCommand));

    // make sure the mailbox exists
    if (mbox.exists())
    {
        // maildir stores its mail in MAILDIR/new and MAILDIR/cur
        TQDir new_mailbox(mailbox + "/new");
        TQDir cur_mailbox(mailbox + "/cur");

        // make sure both exist
        if (new_mailbox.exists() && cur_mailbox.exists())
        {
            // check only files
            new_mailbox.setFilter(TQDir::Files);
            cur_mailbox.setFilter(TQDir::Files);

            // determining "new" (or "unread") mail in maildir folders
            // is a *little* tricky.  all mail in the 'new' folder are
            // new, of course... but so is all mail in the 'cur'
            // folder that doesn't have a ':2,[F|R|S|T]' after it.
            newCount = new_mailbox.count();
            curCount = cur_mailbox.count();

            const TQFileInfoList *cur_list = cur_mailbox.entryInfoList();
            TQFileInfoListIterator it(*cur_list);
            TQFileInfo *info;

            static TQRegExp suffix(":2,?F?R?S?T?$");
            while ((info = it.current()))
            {
                if (info->fileName().findRev(suffix) == -1)
                {
                    newCount++;
                    curCount--;
                }
                ++it;
            }

            // all messages in 'new' are new
            if (newCount > 0)
            {
                determineState(NewMail);
            }
            // failing that, we look for any old ones
            else if (curCount > 0)
            {
                determineState(OldMail);
            }
            // failing that, we have no mail
            else
                determineState(NoMail);
        }
    }
}

void KBiffMonitor::checkNntp()
{
    firstRun = false;

    TQString command;
    bool do_login = false;

    // connect to the server
    if (nntp->active() == false)
    {
        if (nntp->connectSocket(server, port) == false)
        {
            determineState(NoConn);
            return;
        }

        do_login = true;
    }
    
    // if we are preauthorized OR we want to keep the session alive, then
    // we don't login.  Otherwise, we do.
    if ((preauth == false) && (do_login == true))
    {
        if (user.isEmpty() == false)
        {
            command = "authinfo user " + user + "\r\n";
            if (nntp->command(command) == false)
                return;
        }
        if (password.isEmpty() == false)
        {
            command = "authinfo pass " + password + "\r\n";
            if (nntp->command(command) == false)
                return;
        }
    }

    command = "group " + mailbox + "\r\n";
    if (nntp->command(command) == false)
        return;

    // lets not logout if we want to keep the session alive
    if (keepalive == false)
    {
        command = "QUIT\r\n";
        nntp->command(command);
        nntp->close();
    }

    // now, we process the .newsrc file
    TQString home(getenv("HOME"));
    TQString newsrc_path(home + "/.newsrc");
    TQFile newsrc(newsrc_path);
    if (newsrc.open(IO_ReadOnly) == false)
    {
        return;
    }

    char c_buffer[MAXSTR];
    while(newsrc.readLine(c_buffer, MAXSTR) > 0)
    {
        // search for our mailbox name
        TQString str_buffer(c_buffer);
        if (str_buffer.left(mailbox.length()) != mailbox)
            continue;

        // we now have our mailbox.  this parsing routine is so
        // ugly, however, that I could almost cry.  it assumes way
        // too much.  the "actual" range MUST be 1-something
        // continuously and our read sequence MUST be sequentially in
        // order
        bool range = false;
        int last = 1;
        newCount = 0;
        char *buffer = c_buffer;

        // skip over the mailbox name
        for(; buffer && *buffer != ' '; buffer++) {}

        // iterate over the sequence until we hit a newline or end of string
        while (buffer && *buffer != '\n' && *buffer != '\0')
        {
            // make sure that this is a digit
            if (!isdigit(*buffer))
            {
                buffer++;
                continue;
            }

            // okay, what digit are we looking at?  atoi() will convert
            // only those digits it recognizes to an it.  this will handily
            // skip spaces, dashes, commas, etc
            char *digit = buffer;
            int current = atoi(digit);

            // if our current digit is greater than is possible, then we
            // should just quit while we're (somewhat) ahead
            if (current > nntp->last())
                break;

            // we treat our sequences different ways if we are in a range
            // or not.  specifically, if we are in the top half of a range,
            // we don't do anything
            if (range == false)
            {
                if (current > last)
                    newCount += current - last - 1;
            }
            else
                range = false;

            // set our 'last' one for the next go-round
            last = current;

            // skip over all of these digits
            for(;buffer && isdigit(*buffer); buffer++) {}

            // is this a range?
            if (*buffer == '-')
                range = true;
        }

        // get the last few new ones
        if (last < nntp->last())
            newCount += nntp->last() - last;

        break;
    }
    // with newsgroups, it is either new or non-existant.  it
    // doesn't make sense to count the number of read mails
    if (newCount > 0)
        determineState(NewMail);
    else
        determineState(OldMail);
}

/*
 * MH support provided by David Woodhouse <David.Woodhouse@mvhi.com>
 */
void KBiffMonitor::checkMHdir()
{
    firstRun = false;

    // get the information about this local mailbox
    TQDir mbox(mailbox);
    char the_buffer[MAXSTR];
    char *buffer = the_buffer;

    // run external fetch client
    if (!fetchCommand.isEmpty())
        emit(signal_fetchMail(fetchCommand));


    // make sure the mailbox exists
    if (mbox.exists())
    {
        TQFile mhseq(mailbox+"/.mh_sequences");
        if (mhseq.open(IO_ReadOnly) == true)
        {
            // Check the .mh_sequences file for 'unseen:'
            
            buffer[MAXSTR-1]=0;
            
            while(mhseq.readLine(buffer, MAXSTR-2) > 0)
            {
                if (!strchr(buffer, '\n') && !mhseq.atEnd())
                {
                    // read till the end of the line

                    int c;
                    while((c=mhseq.getch()) >=0 && c !='\n') {}
                }
                if (!strncmp(buffer, "unseen:", 7))
                {
                    // There are unseen messages
                    // we will now attempt to count exactly how
                    // many new messages there are

                    // an unseen sequence looks something like so:
                    // unseen: 1, 5-9, 27, 35-41
                    bool range = false;
                    int last = 0;

                    // initialize the number of new messages
                    newCount = 0;

                    // jump to the correct position and iterate through the
                    // rest of the buffer
                    buffer+=7;
                    while(*buffer != '\n' && buffer)
                    {
                        // is this a digit?  if so, it is the first of possibly
                        // several digits
                        if (isdigit(*buffer))
                        {
                            // whether or not this is a range, we are guaranteed
                            // of at least *one* new message
                            newCount++;
                            
                            // get a handle to this digit.  atoi() will convert
                            // only those digits it recognizes to an int.  so
                            // atoi("123garbage") would become 123
                            char *digit = buffer;

                            // if we are in the second half of a range, we need
                            // to compute the number of new messages.
                            if (range)
                            {
                                // remember that we have already counted the
                                // two extremes.. hence we need to subtract one.
                                newCount += atoi(digit) - last - 1;
                                range = false;
                            }

                            // skip over all digits
                            for(;buffer && isdigit(*buffer); buffer++) {}

                            // check if we are in a range
                            if (*buffer == '-')
                            {
                                // save the current digit for later computing
                                last = atoi(digit);
                                range = true;
                            }
                        }
                        else
                            buffer++;
                    }
                    mhseq.close();
                    determineState(NewMail);
                    return;
                }
            }
            mhseq.close();
        }
            
        // OK. No new messages listed in .mh_sequences. Check if 
        //  there are any old ones.
        //mbox.setFilter(TQDir::Files);
        TQStringList mails = mbox.entryList(TQDir::Files);
        TQStringList::Iterator str;

        for (str = mails.begin(); str != mails.end(); str++)
        {
                uint index;
            // Check each file in the directory.
            // If it's a numeric filename, then it's a mail.

            for (index = 0; index < (*str).length(); index++)
            {
                if (!(*str).at(index).isDigit())
                    break;
            }
            if (index >= (*str).length())
            {
                // We found a filename which was entirely
                // made up of digits - it's a real mail, so
                // respond accordingly.

               determineState(OldMail);
                return;
            }    
        }

        // We haven't found any valid filenames. No Mail.
        determineState(NoMail);
    }      
}

void KBiffMonitor::determineState(unsigned int size)
{
    // check for no mail
    if (size == 0)
    {
        if (mailState != NoMail)
        {
            mailState = NoMail;
            lastSize  = 0;
            newCount  = 0;
            emit(signal_noMail());
            emit(signal_noMail(simpleURL));
            onStateChanged();
        }

        emit(signal_currentStatus(newCount, key, mailState));
        return;
    }

    // check for new mail
    if (size > lastSize)
    {
        if (!b_new_lastSize || size > new_lastSize)
        {
            mailState = NewMail;
            emit(signal_newMail());
            emit(signal_newMail(newCount, key));
            onStateChanged();
        }
        new_lastSize = size;
        b_new_lastSize = true;
        newCount  = size - lastSize;
        emit(signal_currentStatus(newCount, key, mailState));
        return;
    }

    // if we have *some* mail, but the state is unknown,
    // then we'll consider it old
    if (mailState == UnknownState)
    {
        mailState = OldMail;
        lastSize  = size;
        emit(signal_oldMail());
        emit(signal_oldMail(simpleURL));

        emit(signal_currentStatus(newCount, key, mailState));
        onStateChanged();
        return;
    }

    // check for old mail
    if (size < lastSize)
    {
        if (mailState != OldMail)
        {
            mailState = OldMail;
            lastSize  = size;
            emit(signal_oldMail());
            emit(signal_oldMail(simpleURL));
            onStateChanged();
        }
    }

    emit(signal_currentStatus(newCount, key, mailState));
}

void KBiffMonitor::determineState(KBiffUidlList uidl_list)
{
  TQString *UIDL;
  unsigned int messages = 0;

  // if the uidl_list is empty then the number of messages = 0
  if (uidl_list.isEmpty())
  {
    if (mailState != NoMail)
    {
      lastSize  = newCount = 0;
      mailState = NoMail;
      emit(signal_noMail());
      emit(signal_noMail(simpleURL));
      onStateChanged();
    }
  }
  else
  {
    // if a member of uidl_list is not in the old uidlList then we have
    // new mail
    for (UIDL = uidl_list.first(); UIDL != 0; UIDL = uidl_list.next())
    {
      // If we already have new mail use new_uidlList to se if we have
      // more new messages
      if (b_new_uidlList)
      {
        if (new_uidlList.find(UIDL) == -1)
          messages++;
      }
      else
      {
        if (uidlList.find(UIDL) == -1)
          messages++;
      }
    }
    // if there are any new messages, then notify..
    if (messages > 0)
    {
      mailState = NewMail;
      emit(signal_newMail());
      emit(signal_newMail(newCount, key));
      onStateChanged();
      // now update newCount
      if (b_new_uidlList)
      {
        // if we have used new_uidlList for a check
        newCount += messages;
      }
      else
      {
        // if we have used uidlList for a check
        newCount = messages;
      }
      new_uidlList = uidl_list;
      b_new_uidlList = true;
    }
    // this is horrible.  it will reset kbiff to OldMail the very next
    // time a pop3 mailbox is checked.  i don't know of a way around
    // this, though :-(
    // MZ: what's wrong with that?
    else if ( (!b_new_uidlList) && mailState != OldMail)
    {
      newCount = 0;
      mailState = OldMail;
      emit(signal_oldMail());
      emit(signal_oldMail(simpleURL));
      onStateChanged();
    }
  }
  emit(signal_currentStatus(newCount, key, mailState));
}

void KBiffMonitor::determineState(KBiffMailState state)
{
    if ((state == NewMail) && (mailState != NewMail))
    {
        mailState = NewMail;
        emit(signal_newMail());
        emit(signal_newMail(newCount, key));
        onStateChanged();
    }
    else
    if ((state == NoMail) && (mailState != NoMail))
    {
        mailState = NoMail;
        emit(signal_noMail());
        emit(signal_noMail(simpleURL));
        onStateChanged();
    }
    else
    if ((state == OldMail) && (mailState != OldMail))
    {
        mailState = OldMail;
        emit(signal_oldMail());
        emit(signal_oldMail(simpleURL));
        onStateChanged();
    }
    else
    if ((state == NoConn) && (mailState != NoConn))
    {
        mailState = NoConn;
        emit(signal_noConn());
        emit(signal_noConn(simpleURL));
        onStateChanged();
    }
    emit(signal_currentStatus(newCount, key, mailState));
}

void KBiffMonitor::determineState(unsigned int size, const TQDateTime& last_read, const TQDateTime& last_modified)
{
  // Check for NoMail
  if (size == 0)
  {
    // Is this a new state?
    if (mailState != NoMail)
    {
      // Yes, the user has just nuked the entire mailbox
      mailState = NoMail;
      lastRead  = last_read;
      lastSize  = 0;

      // Let the world know of the new state
      emit(signal_noMail());
      emit(signal_noMail(simpleURL));
      onStateChanged();
    }
  }
  else
    // There is some mail.  See if it is new or not.  To be new, the
    // mailbox must have been modified after it was last read AND the
    // current size must be greater then it was before.
    if (last_modified>=last_read && size>lastSize)
    {
      if (!b_new_lastSize || size>new_lastSize)
      {
        mailState = NewMail;
        // Let the world know of the new state
        emit(signal_newMail());
        emit(signal_newMail(1, key));
        onStateChanged();
      }
      new_lastSize = size;
      b_new_lastSize = true;
      new_lastRead = last_read;
      b_new_lastRead = true;
      newCount  = 1;
    }
    else
      // Finally, check if the state needs to change to OldMail
      if ((mailState != OldMail) && (last_read > lastRead))
      {
        mailState = OldMail;
        lastRead  = last_read;
        lastSize  = size;

        // Let the world know of the new state
        emit(signal_oldMail());
        emit(signal_oldMail(simpleURL));
        onStateChanged();
      }

  // If we get to this point, then the state now is exactly the
  // same as the state when last we checked.  Do nothing at this
  // point.
  emit(signal_currentStatus(newCount, key, mailState));
}

/**
 * The following function is lifted from unixdrop.cpp in the korn
 * distribution.  It is (C) Sirtaj Singh Kang <taj@kde.org> and is
 * used under the GPL license (and the author's permission).  It has
 * been slightly modified for formatting reasons.
 */
int KBiffMonitor::mboxMessages()
{
    TQFile mbox(mailbox);
    char buffer[MAXSTR];
    int count            = 0;
    int msg_count        = 0;
    bool in_header       = false;
    bool has_content_len = false;
    bool msg_read        = false;
    long content_length  = 0;

    oldCount = 0;
    curCount = 0;

    if (mbox.open(IO_ReadOnly) == false)
        return 0;

    buffer[MAXSTR-1] = 0;

    while (mbox.readLine(buffer, MAXSTR-2) > 0)
    {
        // read a line from the mailbox

        if (!strchr(buffer, '\n') && !mbox.atEnd())
        {
            // read till the end of the line if we
            // haven't already read all of it.

            int c;

            while((c=mbox.getch()) >=0 && c !='\n') {}
        }

        if (!in_header && real_from(buffer))
        {
            // check if this is the start of a message
            has_content_len = false;
            in_header       = true;
            msg_read        = false;
        }
        else if (in_header)
        {
            // check header fields if we're already in one

            if (compare_header(buffer, "Content-Length"))
            {
                has_content_len = true;
                content_length  = atol(buffer + 15);
            }
            // This should handle those folders that double as IMAP or POP
            // folders.  Possibly PINE uses these always
            if (strcmp(buffer, "Subject: DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA\n") == 0)
            {
                oldCount--;
		curCount--;
            }
            else
            {
                if (compare_header(buffer, "Status"))
                {
                    const char *field = buffer;
                    field += 7;
                    while (field && (*field== ' ' || *field == '\t'))
                        field++;

                    if (*field == 'N' || *field == 'U' || *field == 0x0a)
                        msg_read = false;
                    else
                        msg_read = true;
                }
                // Netscape *sometimes* uses X-Mozilla-Status to determine
                // unread vs read mail.  The only pattern I could see for
                // sure, though, was that Read mails started with an '8'.
                // I make no guarantees on this...
                else if (compare_header(buffer, "X-Mozilla-Status"))
                {
                    const char *field = buffer;
                    field += 17;
                    while (field && (*field== ' ' || *field == '\t'))
                        field++;

                    if (*field == '8')
                        msg_read = true;
                    else
                        msg_read = false;
                }
                else if (buffer[0] == '\n' )
                {
                    if (has_content_len)
                        mbox.at(mbox.at() + content_length);

                    in_header = false;

                    oldCount++;

                    if (!msg_read) {
                        count++;
		    } else {
		        curCount++;
		    }
                } 
            }
        }//in header

        if(++msg_count >= 100 )
        {
            tqApp->processEvents();
            msg_count = 0;
        }
    }//while

    mbox.close();
    return count;
}

void KBiffMonitor::invalidLogin()
{
  // first, we stop this monitor to be on the safe side
  stop();
  determineState(NoConn);
  newCount = -1;

  emit(signal_invalidLogin(key));
}

///////////////////////////////////////////////////////////////////////////
// KBiffSocket
///////////////////////////////////////////////////////////////////////////
KBiffSocket::KBiffSocket() : async(false), socketFD(-1), messages(0), newMessages(-1)
#ifdef USE_SSL
    , ssltunnel(0)
#endif // USE_SSL
{
    FD_ZERO(&socketFDS);

    /*
     * Set the socketTO once and DO NOT use it in any select call as this
     * may alter its value!
     */
    socketTO.tv_sec = SOCKET_TIMEOUT;
    socketTO.tv_usec = 0;
}

KBiffSocket::~KBiffSocket()
{
    close();
#ifdef USE_SSL
    if (ssltunnel)
    {
        delete ssltunnel;
        ssltunnel = 0;
    }
#endif // USE_SSL
}

int KBiffSocket::numberOfMessages()
{
    return messages;
}

int KBiffSocket::numberOfNewMessages()
{
    return (newMessages > -1) ? newMessages : 0;
}

void KBiffSocket::close()
{

#ifdef USE_SSL
    if (isSSL() && (socketFD != -1) && (ssltunnel != 0))
    {
        ssltunnel->close();
    }
#endif // USE_SSL

    if (socketFD != -1)
        ::close(socketFD);

    socketFD = -1;
    FD_ZERO(&socketFDS);
}

bool KBiffSocket::connectSocket(const TQString& host, unsigned short int port)
{
    sockaddr_in  sin;
    hostent     *hent; 
    int addr, n;

    // if we still have a socket, close it
    if (socketFD != -1)
        close();

    // get the socket
    socketFD = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);

    // start setting up the socket info
    memset((char *)&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port   = htons(port);

    // get the address
    if ((addr = inet_addr(host.ascii())) == -1)
    {
        // get the address by host name
        if ((hent = gethostbyname(host.ascii())) == 0)
        {
            switch (h_errno)
            {
                case HOST_NOT_FOUND: 
                    break;

                case NO_ADDRESS:
                    break;

                case NO_RECOVERY:
                    break;

                case TRY_AGAIN:
                    break;

               default:
                    break;
            }

            close();
            return false;
        }

        memcpy((void *)&sin.sin_addr, *(hent->h_addr_list), hent->h_length);
    }
    else
        // get the address by IP
        memcpy((void *)&sin.sin_addr, (void *)&addr, sizeof(addr));

    // Set up non-blocking io if requested
    if (async)
    {
        int flags = fcntl(socketFD, F_GETFL);
        if (flags < 0 || fcntl(socketFD, F_SETFL, flags | O_NONBLOCK) < 0)
        {
            async = false;
        } 
    }


    // the socket is correctly setup.  now connect
    if ((n = ::connect(socketFD, (sockaddr *)&sin, sizeof(sockaddr_in))) == -1 && 
        errno != EINPROGRESS)
    {
        close();
        return false;
    }

    // Empty the file descriptor set
    FD_ZERO(&socketFDS);
    FD_SET(socketFD, &socketFDS);

    // For non-blocking io, the connection may need time to finish (n = -1)
    if (n == -1 && async == true)
    {
        struct timeval tv = socketTO;

        // Wait for the connection to come up
        if (select(socketFD+1, NULL, &socketFDS, NULL, &tv) != 1)
        {
            errno = ETIMEDOUT;
            close();
            return false;
        }
      
        // The connection has finished. Catch any error in a call to readLine()
    }
    
#ifdef USE_SSL
    // Initialize SSL tunnel, if needed
    if (isSSL())
    {
        if (ssltunnel == 0)
            ssltunnel = new KSSL(true);
	else
	    ssltunnel->reInitialize();
	if (ssltunnel == 0)
	{
            close();
  	    return false;
	}
        if (ssltunnel->connect(socketFD) != 1)
        {
            close();
  	    return false;
        }
    }
#endif // USE_SSL

    // we're connected!  see if the connection is good
    TQString line(readLine());
    if (line.isNull() || ((line.find("200") == -1 ) && (line.find("OK") == -1) && (line.find("PREAUTH") == -1)))
    {
        if (line.isNull())

        close();
        return false;
    }

    // everything is swell
    banner = line;  // save the banner for use by subclasses
    return true;
}

bool KBiffSocket::active()
{
    return socketFD != -1;
}

bool KBiffSocket::isAsync()
{
    return async;
}

void KBiffSocket::setAsync(bool on)
{
    int flags = 0;

    async = on; 

    if (active())
    {
        flags = fcntl(socketFD, F_GETFL);

        switch (async)
        {
            case false:
                if (flags >= 0)
                    fcntl(socketFD, F_SETFL, flags & ~O_NONBLOCK);
                break;

            case true:
                if (flags < 0 || fcntl(socketFD, F_SETFL, flags | O_NONBLOCK) < 0)
                    async = false;
                break;
        }
    }
}

#ifdef USE_SSL
bool KBiffSocket::isSSL()
{
    return usessl;
}

void KBiffSocket::setSSL(bool on)
{
    if (usessl == on) return;
    if (!KSSL::doesSSLWork())
    {
        usessl = false;
        return;
    }
    usessl = on;
    if (active())
    {
        switch (usessl)
	{
	    case false:
	        ssltunnel->close();
		delete ssltunnel;
		ssltunnel = 0;
		break;
	    case true:
	        if (ssltunnel == 0)
		    ssltunnel = new KSSL(true);
		else
		    ssltunnel->reInitialize();
		if (ssltunnel == 0)
		{
		    usessl = false;
		    break;
		}
	        if (ssltunnel->connect(socketFD) != 1)
		    usessl = false;
		break;
	}
    }
}
#endif // USE_SSL

int KBiffSocket::writeLine(const TQString& line)
{
    int bytes = 0;

    // Do not try to write to a non active socket. Return error.
    if (!active())
        return -1;

#ifdef USE_SSL
    if (isSSL())
    {
        if ((bytes = ssltunnel->write(line.ascii(), line.length())) <= 0)
	    close();
    }
    else
#endif // USE_SSL
    if ((bytes = ::write(socketFD, line.ascii(), line.length())) <= 0)
        close();

    return bytes;
}

TQString KBiffSocket::readLine()
{
    TQString fault, response;
    char buffer;
    ssize_t bytes = -1;

#ifdef USE_SSL
    if (isSSL())
    {
        while (((bytes = ssltunnel->read(&buffer, 1)) > 0) && (buffer != '\n'))
	    response += buffer;
    }
    else
#endif // USE_SSL
    if (!async)
        while (((bytes = ::read(socketFD, &buffer, 1)) > 0) && (buffer != '\n'))
            response += buffer;
    else
    {
        while ( (((bytes = ::read(socketFD, &buffer, 1)) > 0) && (buffer != '\n')) || 
            ((bytes < 0) && (errno == EWOULDBLOCK)) )
        {
            if (bytes > 0)
                response += buffer;
            else
            {
                struct timeval tv = socketTO;
                if (select(socketFD+1,  &socketFDS, NULL, NULL, &tv) != 1)
                {
                    errno = ETIMEDOUT;
                    break;
                }
            }
        }
    }

    if (bytes == -1)
    {
        // Close the socket and hope for better luck with a new one
        close();
        return fault;
    }

    return response;
}

///////////////////////////////////////////////////////////////////////////
// KBiffImap
///////////////////////////////////////////////////////////////////////////
KBiffImap::KBiffImap()
{
    /* Assume that the IMAP server does no fancy authentication */
    auth_cram_md5 = false;
}

KBiffImap::~KBiffImap()
{
    close();
}

bool KBiffImap::command(const TQString& line, unsigned int seq)
{
    TQString messagesListString;
    TQStringList messagesList;
    bool tried_cram_md5; // are we trying CRAM-MD5 ?

    if (writeLine(line) <= 0)
    {
        close();
        return false;
    }

    TQString ok, bad, no, response;
    ok.sprintf("%d OK", seq);
    bad.sprintf("%d BAD", seq);
    no.sprintf("%d NO", seq);
    
    // must be case insensitive
    TQRegExp status("\\* STATUS", false);
    TQRegExp capability("\\* CAPABILITY", false);
    TQRegExp cram_md5("AUTHENTICATE CRAM-MD5", false);

    // are we trying CRAM-MD5 ?
    tried_cram_md5 = cram_md5.search(line)>=0;
    cram_md5 = TQRegExp("\\+ ([A-Za-z0-9+/=]+)");
    
    while (!(response = readLine()).isNull())
    {
        // if an error has occurred, we get a null string in return
        if (response.isNull())
            break;

        // if the response is either good or bad, then return
        if (response.find(ok) > -1)
            return true;
        if ((response.find(bad) > -1) || (response.find(no) > -1))
            break;

        /* The STATUS response is documented in RFC2060, 6.3.10/7.2.4
         * Briefly: the response depends on command and looks like
         *     * STATUS "some-imap-folder" ( requested-info )
         * for example:
         * C: . STATUS "INBOX" (UNSEEN MESSAGES)
         * S: * STATUS "INBOX" (UNSEEN 2 MESSAGES 3)
         * S: . OK STATUS Completed
         */
        if (status.search(response) >= 0) {
            TQRegExp unseen("UNSEEN ([0-9]*)", false);
            if (unseen.search(response) >= 0) {
                TQString num = unseen.cap(1);
                newMessages = num.toInt();
            }
            
            TQRegExp number("MESSAGES ([0-9]*)", false);
            if (number.search(response) >= 0) {
                TQString num = number.cap(1);
                messages = num.toInt();
            }
        }

        /* The CAPABILITY response is documented in RFC 3050,
         * sections 6.1.1 and 7.2.1
         * An example:
         * C: . CAPABILITY
         * S: * CAPABILITY IMAP4rev1 IDLE AUTH=PLAIN AUTH=CRAM-MD5
         * S: . OK CAPABILITY completed.
         */
        if (capability.search(response) >= 0) {
            TQRegExp cram_md5_cap("AUTH=CRAM-MD5", false);
            if (cram_md5_cap.search(response) >= 0) {
                auth_cram_md5 = true;
            }
        }

        /* AUTHENTICATE CRAM-MD5 response is documented in
         * RFC 3050 6.2.2 and RFC 2195
         */
        if (tried_cram_md5 && cram_md5.search(response)>=0) {
            chall_cram_md5 = KCodecs::base64Decode(cram_md5.cap(1).local8Bit());
            if (chall_cram_md5.isNull())
                break;

            return true;
        }
    }

    close();
    return false;
}

TQString KBiffImap::mungeUserPass(const TQString& old_user)
{
    TQString new_user(old_user);

    if (new_user.left(1) != "\"")
        new_user.prepend("\"");
    if (new_user.right(1) != "\"")
        new_user.append("\"");

    return new_user;
}

void KBiffImap::resetNumbers()
{
    messages = 0;
    newMessages = 0;
}

bool KBiffImap::authenticate(int *pseq, const TQString& user, const TQString& pass)
{
    TQString cmd, username, password;

    // If CRAM-MD5 is available, use it.  It's the best we know.
    // RFC 2195 defines the CRAM-MD5 authentication method
    // also see RFC 3501 section 6.2.2 for the AUTHENTICATE command
    if( auth_cram_md5 )
    {
        cmd = TQString("%1 AUTHENTICATE CRAM-MD5\r\n").arg(*pseq);
        if (command(cmd, *pseq) == false)
        {
            return false;
        }

        // calculate the real response to the challenge
        TQString response = user + " " + KBiffCrypt::hmac_md5(chall_cram_md5, pass);
        response = KCodecs::base64Encode(response.latin1());

        // send the response
        if (command(response+"\r\n", *pseq) == false)
        {
            return false;
        }

        return true;
    }
    else // well, we tried, LOGIN is the best we can do
    {
        // imap allows spaces in usernames... we need to take care of that
        username = mungeUserPass(user);

        // also asterisks (*) in passwords. maybe it's a good idea
        // to _always_ munge the user and the password.
        password = mungeUserPass(pass);

        cmd = TQString().setNum(*pseq) + " LOGIN "
            + username + " "
            + password + "\r\n";
        if (command(cmd, *pseq) == false)
        {
            return false;
        }
        (*pseq)++;
    }

    return true;
}

///////////////////////////////////////////////////////////////////////////
// KBiffPop
///////////////////////////////////////////////////////////////////////////
KBiffPop::KBiffPop() : use_apop( true )
{
}

KBiffPop::~KBiffPop()
{
    close();
}

void KBiffPop::close()
{
    command("QUIT\r\n");
    KBiffSocket::close();
}

void KBiffPop::setApop( bool enabled )
{
    use_apop = enabled;
}

bool KBiffPop::command(const TQString& line)
{
    if (writeLine(line) <= 0)
        return false;

    TQString response;
    response = readLine();

    // check if the response was bad.  if so, return now
    if (response.isNull() || response.left(4) == "-ERR")
    {
        // we used to close the socket here.. but this MAY be
        // because the server didn't understand UIDL.  the server
        // may react better with LIST or STAT so just fail quitely
        // thanks to David Barth (dbarth@videotron.ca)
        return false;
    }

    // if the command was UIDL then build up the newUidlList
    if (line == "UIDL\r\n")
    {
        uidlList.clear();
        for (response = readLine();
             !response.isNull() && response.left(1) != ".";
             response = readLine())
        {
            uidlList.append(new TQString(response.right(response.length() -
                    response.find(" ") - 1)));
        }
    }
    else
    // get all response lines from the LIST command    
    // LIST and UIDL are return multilines so we have to loop around
    if (line == "LIST\r\n")
    {
        for (messages = 0, response = readLine();
             !response.isNull() && response.left(1) != ".";
             messages++, response = readLine()) {}
    }
    else
    if (line == "STAT\r\n")
    {
        if (!response.isNull())
            sscanf(response.ascii(), "+OK %d", &messages);
    }
    else
    // find out what the server is capable of
    if (line == "CAPA\r\n")
    {
        TQRegExp rx("\\bCRAM-MD5\\b");

        auth_cram_md5 = false;  // assume no support

        for (response = readLine();
             !response.isNull() && response.left(1) != ".";
             response = readLine())
        {
            if (response.left(4) == "SASL")
                auth_cram_md5 = response.find(rx) != -1;
        }
    }
    else
    // look for the CRAM-MD5 challenge
    if (line == "AUTH CRAM-MD5\r\n")
    {
        TQRegExp challenge("\\+ ([A-Za-z0-9+/=]+)");
        if (challenge.search(response) == -1 )
        {
            return false;
        }

        chall_cram_md5 = KCodecs::base64Decode(challenge.cap(1).local8Bit()); 
    }

    return !response.isNull();
}

KBiffUidlList KBiffPop::getUidlList() const
{
    return uidlList;
}

/*!
   This method parses the initial response from the POP3 server.
   The response is defined in RFC 1939 sections 4 and 7.

    \fn KBiffPop::parse_banner(void)
 */
bool KBiffPop::parseBanner(void)
{
    // RFC 1939 section 3 says server MUST use uppercase
    if( banner.left(3) != "+OK" ) {
        auth_apop = false;
        return false;
    }

    // Look for the banner part that indicates APOP support
    TQRegExp rx("(<[a-zA-Z0-9_+.-]+@[a-zA-Z0-9_+.-]+>)");
    if( rx.search(banner) == -1 || !use_apop ) {
        auth_apop = false;
    } else {
        chall_apop = rx.cap(1).latin1();
        auth_apop  = true;
    }

    return true;
}

/*!
    This method authenticates using the most secure
    technique available.
    \fn KBiffPop::authenticate(const TQString& user, const TQString& pass)
 */
bool KBiffPop::authenticate(const TQString& user, const TQString& pass)
{
    TQString popcommand;

    // CRAM-MD5 authentication is the most secure we can handle
    // the use of the AUTH command is documented in RFC 1734
    if( auth_cram_md5 )
    {
        if (this->command("AUTH CRAM-MD5\r\n") == false)
        {
            return false;
        }

        // calculate the real response to the challenge
        TQString response = user + " " + KBiffCrypt::hmac_md5(chall_cram_md5, pass);
        response = KCodecs::base64Encode(response.latin1());

        // send the response
        if (this->command(response+"\r\n") == false)
        {
            return false;
        }
        
        return true;
    }

    // APOP is not as secure as CRAM-MD5 but it's still better
    // than sending the password in the clear
    if( auth_apop )
    {
        TQCString digest;

        KMD5 md5(chall_apop);
        md5.update(pass.utf8());

        digest = md5.hexDigest();
        
        popcommand = TQString("APOP %1 %2\r\n").arg(user, digest.data());
        if (this->command(popcommand) == false)
        {
            return false;
        }
        
        return true;
    }
    
    // lastly we'll try regular, plain-text authentication
    
    popcommand = "USER " + user + "\r\n";
    if (this->command(popcommand) == false)
    {
        return false;
    }

    popcommand = "PASS " + pass + "\r\n";
    if (this->command(popcommand) == false)
    {
        return false;
    }
    
    return true;
}
///////////////////////////////////////////////////////////////////////////
// KBiffNntp
///////////////////////////////////////////////////////////////////////////
KBiffNntp::~KBiffNntp()
{
    close();
}

bool KBiffNntp::command(const TQString& line)
{
    int bogus;

    if (writeLine(line) <= 0)
        return false;

    TQString response;
    while (!(response = readLine()).isNull())
    {
        // return if the response is bad
        if (response.find("500") > -1)
        {
            close();
            return false;
        }

        // find return codes for tcp, user, pass
        TQString code(response.left(3));
        if ((code == "200") || (code == "281") || (code == "381"))
            return true;

        // look for the response to the GROUP command
        // 211 <num> <first> <last> <group>
        if (code == "211")
        {
            sscanf(response.ascii(), "%d %d %d %d",
                    &bogus, &messages, &firstMsg, &lastMsg);
            return true;
        }
    }

    close();
    return false;
}

int KBiffNntp::first() const
{
    return firstMsg;
}

int KBiffNntp::last() const
{
    return lastMsg;
}

/////////////////////////////////////////////////////////////////////////
/* The following is a (C) Sirtaj Singh Kang <taj@kde.org> */

#define whitespace(c)    (c == ' ' || c == '\t')

#define skip_white(c)     while(c && (*c) && whitespace(*c) ) c++
#define skip_nonwhite(c) while(c && (*c) && !whitespace(*c) ) c++

#define skip_token(buf) skip_nonwhite(buf); if(!*buf) return false; \
    skip_white(buf); if(!*buf) return false;

static const char *month_name[13] = {
    "jan", "feb", "mar", "apr", "may", "jun",
    "jul", "aug", "sep", "oct", "nov", "dec", NULL
};

static const char *day_name[8] = {
    "sun", "mon", "tue", "wed", "thu", "fri", "sat", 0
};

static bool real_from(const TQString& orig_buffer)
{
    /*
        A valid from line will be in the following format:

        From <user> <weekday> <month> <day> <hr:min:sec> [TZ1 [TZ2]] <year>
     */

    int day;
    int i;
    int found;

    const char *buffer = (const char*)orig_buffer.ascii();

    /* From */

    if(!buffer || !*buffer)
        return false;

    if (strncmp(buffer, "From ", 5))
        return false;

    buffer += 5;

    skip_white(buffer);

    /* <user> */
    if(*buffer == 0) return false;
    skip_token(buffer);

    /* <weekday> */
    found = 0;
    for (i = 0; day_name[i] != NULL; i++)
        found = found || (tqstrnicmp(day_name[i], buffer, 3) == 0);

    if (!found)
        return false;

    skip_token(buffer);

    /* <month> */
    found = 0;
    for (i = 0; month_name[i] != NULL; i++)
        found = found || (tqstrnicmp(month_name[i], buffer, 3) == 0);
    if (!found)
        return false;

    skip_token(buffer);

    /* <day> */
    if ( (day = atoi(buffer)) < 0 || day < 1 || day > 31)
        return false;

    return true;
}

static const char* compare_header(const char* header, const char* field)
{
    int len = strlen(field);

    if (tqstrnicmp(header, field, len))
        return NULL;

    header += len;

    if( *header != ':' )
        return NULL;

    header++;

    while( *header && ( *header == ' ' || *header == '\t') )
        header++;

    return header;
}
