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.
kbiff/kbiff/kbiffmonitor.cpp

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, 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 = 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;
}