summaryrefslogtreecommitdiffstats
path: root/kbiff/kbiffmonitor.cpp
diff options
context:
space:
mode:
authorSlávek Banko <slavek.banko@axis.cz>2013-07-02 18:32:11 +0200
committerSlávek Banko <slavek.banko@axis.cz>2013-07-02 18:32:11 +0200
commit5ec5bc2080bbc41e025fb98e6571a2c281b775cb (patch)
treea73cf2c57d306e6ce092996f70b29b8fbc0a006b /kbiff/kbiffmonitor.cpp
downloadkbiff-5ec5bc2080bbc41e025fb98e6571a2c281b775cb.tar.gz
kbiff-5ec5bc2080bbc41e025fb98e6571a2c281b775cb.zip
Initial import
Diffstat (limited to 'kbiff/kbiffmonitor.cpp')
-rw-r--r--kbiff/kbiffmonitor.cpp2225
1 files changed, 2225 insertions, 0 deletions
diff --git a/kbiff/kbiffmonitor.cpp b/kbiff/kbiffmonitor.cpp
new file mode 100644
index 0000000..37b51e9
--- /dev/null
+++ b/kbiff/kbiffmonitor.cpp
@@ -0,0 +1,2225 @@
+/*
+ * 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 <kmessagebox.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 <qapplication.h>
+#include <qstring.h>
+#include <qregexp.h>
+#include <qdir.h>
+#include <qdatetime.h>
+#include <ksimpleconfig.h>
+
+// Needed for CRAM-MD5 and APOP
+#include <kmdcodec.h>
+#include "kbiffcrypt.h"
+
+#define MAXSTR (1024)
+
+#define MAIL_STATE_FILE "kbiffstate"
+
+#if defined (_HPUX_SOURCE)
+extern int h_errno;
+#endif
+
+static bool real_from(const QString& buffer);
+static const char* compare_header(const char* header, const char* field);
+
+KBiffMonitor::KBiffMonitor()
+ : QObject(),
+ 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);
+
+ QString group;
+ group = mailbox + "(" + key + ")";
+ config->setGroup(group);
+
+ QStrList list;
+
+ mailState = (KBiffMailState)config->readNumEntry("mailState", UnknownState);
+ lastSize = config->readNumEntry("lastSize");
+ config->readListEntry("lastRead", list);
+ if (list.count()==6)
+ {
+ lastRead.setDate(QDate(atoi(list.at(0)),atoi(list.at(1)),atoi(list.at(2))));
+ lastRead.setTime(QTime(atoi(list.at(3)),atoi(list.at(4)),atoi(list.at(5))));
+ }
+ config->readListEntry("lastModified", list);
+ if (list.count()==6)
+ {
+ lastModified.setDate(QDate(atoi(list.at(0)),atoi(list.at(1)),atoi(list.at(2))));
+ lastModified.setTime(QTime(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 QString(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);
+
+ QString group;
+ group = mailbox + "(" + key + ")";
+ config->setGroup(group);
+
+ QStringList uidlist;
+ QString *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 QString& 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, SIGNAL(signal_checkMail()), 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, SIGNAL(signal_checkMail()), 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, SIGNAL(signal_checkMail()), 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, SIGNAL(signal_checkMail()), 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, SIGNAL(signal_checkMail()), SLOT(checkMbox()));
+ mailbox = url.path();
+
+ simpleURL = "mbox:" + mailbox;
+ }
+
+ if (protocol == "file")
+ {
+ disconnect(this);
+
+ connect(this, SIGNAL(signal_checkMail()), SLOT(checkLocal()));
+ mailbox = url.path();
+
+ simpleURL = "file:" + mailbox;
+ }
+
+ if (protocol == "maildir")
+ {
+ disconnect(this);
+
+ connect(this, SIGNAL(signal_checkMail()), SLOT(checkMaildir()));
+ mailbox = url.path();
+
+ simpleURL = "maildir:" + mailbox;
+ }
+
+ if (protocol == "mh")
+ {
+ disconnect(this);
+
+ connect(this, SIGNAL(signal_checkMail()), SLOT(checkMHdir()));
+ mailbox = url.path();
+
+ simpleURL = "mh:" + mailbox;
+ }
+
+ if (protocol == "nntp")
+ {
+ disconnect(this);
+
+ nntp = new KBiffNntp;
+
+ connect(this, SIGNAL(signal_checkMail()), 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 = QDateTime::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 QString& pass)
+{
+ password = pass;
+}
+
+void KBiffMonitor::setMailboxKey(const QString& k)
+{
+ key = k;
+}
+
+void KBiffMonitor::timerEvent(QTimerEvent *)
+{
+ emit(signal_checkMail());
+}
+
+void KBiffMonitor::checkLocal()
+{
+ // get the information about this local mailbox
+ QFileInfo 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
+ QFileInfo 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(QFile::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;
+
+ QString 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;
+
+ QString 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 = QString().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 = QString().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 = QString().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
+ QDir 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
+ QDir new_mailbox(mailbox + "/new");
+ QDir cur_mailbox(mailbox + "/cur");
+
+ // make sure both exist
+ if (new_mailbox.exists() && cur_mailbox.exists())
+ {
+ // check only files
+ new_mailbox.setFilter(QDir::Files);
+ cur_mailbox.setFilter(QDir::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 QFileInfoList *cur_list = cur_mailbox.entryInfoList();
+ QFileInfoListIterator it(*cur_list);
+ QFileInfo *info;
+
+ static QRegExp 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;
+
+ QString 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
+ QString home(getenv("HOME"));
+ QString newsrc_path(home + "/.newsrc");
+ QFile 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
+ QString 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
+ QDir 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())
+ {
+ QFile 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(QDir::Files);
+ QStringList mails = mbox.entryList(QDir::Files);
+ QStringList::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)
+{
+ QString *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 QDateTime& last_read, const QDateTime& 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()
+{
+ QFile 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 )
+ {
+ qApp->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 QString& 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
+ QString 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 QString& 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;
+}
+
+QString KBiffSocket::readLine()
+{
+ QString 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 QString& line, unsigned int seq)
+{
+ QString messagesListString;
+ QStringList messagesList;
+ bool tried_cram_md5; // are we trying CRAM-MD5 ?
+
+ if (writeLine(line) <= 0)
+ {
+ close();
+ return false;
+ }
+
+ QString ok, bad, no, response;
+ ok.sprintf("%d OK", seq);
+ bad.sprintf("%d BAD", seq);
+ no.sprintf("%d NO", seq);
+
+ // must be case insensitive
+ QRegExp status("\\* STATUS", FALSE);
+ QRegExp capability("\\* CAPABILITY", FALSE);
+ QRegExp cram_md5("AUTHENTICATE CRAM-MD5", FALSE);
+
+ // are we trying CRAM-MD5 ?
+ tried_cram_md5 = cram_md5.search(line)>=0;
+ cram_md5 = QRegExp("\\+ ([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) {
+ QRegExp unseen("UNSEEN ([0-9]*)", FALSE);
+ if (unseen.search(response) >= 0) {
+ QString num = unseen.cap(1);
+ newMessages = num.toInt();
+ }
+
+ QRegExp number("MESSAGES ([0-9]*)", FALSE);
+ if (number.search(response) >= 0) {
+ QString 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) {
+ QRegExp 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;
+}
+
+QString KBiffImap::mungeUserPass(const QString& old_user)
+{
+ QString 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 QString& user, const QString& pass)
+{
+ QString 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 = QString("%1 AUTHENTICATE CRAM-MD5\r\n").arg(*pseq);
+ if (command(cmd, *pseq) == false)
+ {
+ return false;
+ }
+
+ // calculate the real response to the challenge
+ QString 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 = QString().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 QString& line)
+{
+ if (writeLine(line) <= 0)
+ return false;
+
+ QString 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 QString(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")
+ {
+ QRegExp 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")
+ {
+ QRegExp 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
+ QRegExp 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 QString& user, const QString& pass)
+ */
+bool KBiffPop::authenticate(const QString& user, const QString& pass)
+{
+ QString 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
+ QString 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 )
+ {
+ QCString digest;
+
+ KMD5 md5(chall_apop);
+ md5.update(pass);
+
+ digest = md5.hexDigest();
+
+ popcommand = QString("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 QString& line)
+{
+ int bogus;
+
+ if (writeLine(line) <= 0)
+ return false;
+
+ QString 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
+ QString 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 QString& 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 || (qstrnicmp(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 || (qstrnicmp(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 (qstrnicmp(header, field, len))
+ return NULL;
+
+ header += len;
+
+ if( *header != ':' )
+ return NULL;
+
+ header++;
+
+ while( *header && ( *header == ' ' || *header == '\t') )
+ header++;
+
+ return header;
+}