You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2226 lines
56 KiB
2226 lines
56 KiB
/*
|
|
* 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 <ntqapplication.h>
|
|
#include <ntqstring.h>
|
|
#include <ntqregexp.h>
|
|
#include <ntqdir.h>
|
|
#include <ntqdatetime.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 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;
|
|
}
|