diff options
Diffstat (limited to 'kbiff/kbiffmonitor.cpp')
-rw-r--r-- | kbiff/kbiffmonitor.cpp | 2225 |
1 files changed, 2225 insertions, 0 deletions
diff --git a/kbiff/kbiffmonitor.cpp b/kbiff/kbiffmonitor.cpp new file mode 100644 index 0000000..37b51e9 --- /dev/null +++ b/kbiff/kbiffmonitor.cpp @@ -0,0 +1,2225 @@ +/* + * kbiffmonitor.cpp + * Copyright (C) 1999-2008 Kurt Granroth <[email protected]> + * + * This file contains the implementation of KBiffMonitor and + * associated classes. + */ +#include "kbiffmonitor.h" +#include "kbiffmonitor.moc" + +#include <kmessagebox.h> + +#include <sys/types.h> +#ifndef __STRICT_ANSI__ +#define __STRICT_ANSI__ +#include <sys/socket.h> +#undef __STRICT_ANSI__ +#else +#include <sys/socket.h> +#endif +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <sys/types.h> +#include <utime.h> + +#include <fcntl.h> +#include <errno.h> + +#include <kbiffurl.h> +#include <kdebug.h> + +#include <qapplication.h> +#include <qstring.h> +#include <qregexp.h> +#include <qdir.h> +#include <qdatetime.h> +#include <ksimpleconfig.h> + +// Needed for CRAM-MD5 and APOP +#include <kmdcodec.h> +#include "kbiffcrypt.h" + +#define MAXSTR (1024) + +#define MAIL_STATE_FILE "kbiffstate" + +#if defined (_HPUX_SOURCE) +extern int h_errno; +#endif + +static bool real_from(const QString& buffer); +static const char* compare_header(const char* header, const char* field); + +KBiffMonitor::KBiffMonitor() + : QObject(), + poll(60), + oldTimer(0), + started(false), + newCount(0), + curCount(-1), + oldCount(-1), + firstRun(false), + key(""), + simpleURL(""), + protocol(""), + mailbox(""), + server(""), + user(""), + password(""), + port(0), + preauth(false), + keepalive(false), + mailState(UnknownState), + lastSize(0), + imap(0), + pop(0), + nntp(0) +{ + lastRead.setTime_t(0); + lastModified.setTime_t(0); + b_new_lastSize = false; + b_new_lastRead = false; + b_new_lastModified = false; + b_new_uidlList = false; +} + +KBiffMonitor::~KBiffMonitor() +{ + if (imap) + { + delete imap; + imap = 0; + } + if (pop) + { + delete pop; + pop = 0; + } + if (nntp) + { + delete nntp; + nntp = 0; + } +} + +void KBiffMonitor::readConfig() +{ + KSimpleConfig *config = new KSimpleConfig(MAIL_STATE_FILE); + config->setDollarExpansion(false); + + QString group; + group = mailbox + "(" + key + ")"; + config->setGroup(group); + + QStrList list; + + mailState = (KBiffMailState)config->readNumEntry("mailState", UnknownState); + lastSize = config->readNumEntry("lastSize"); + config->readListEntry("lastRead", list); + if (list.count()==6) + { + lastRead.setDate(QDate(atoi(list.at(0)),atoi(list.at(1)),atoi(list.at(2)))); + lastRead.setTime(QTime(atoi(list.at(3)),atoi(list.at(4)),atoi(list.at(5)))); + } + config->readListEntry("lastModified", list); + if (list.count()==6) + { + lastModified.setDate(QDate(atoi(list.at(0)),atoi(list.at(1)),atoi(list.at(2)))); + lastModified.setTime(QTime(atoi(list.at(3)),atoi(list.at(4)),atoi(list.at(5)))); + } + config->readListEntry("uidlList", list); + + char *UIDL; + uidlList.clear(); + for (UIDL = list.first(); UIDL != 0; UIDL = list.next()) + { + uidlList.append( new QString(UIDL) ); + } + + newCount = config->readNumEntry("newCount", 0); + oldCount = config->readNumEntry("oldCount", -1); + + delete config; +} + +void KBiffMonitor::saveConfig() +{ + // open the config file + KSimpleConfig *config = new KSimpleConfig(MAIL_STATE_FILE); + config->setDollarExpansion(false); + + QString group; + group = mailbox + "(" + key + ")"; + config->setGroup(group); + + QStringList uidlist; + QString *UIDL; + for (UIDL = uidlList.first(); UIDL != 0; UIDL = uidlList.next()) + { + uidlist.append(*UIDL); + } + + config->writeEntry("mailState", (int)mailState); + config->writeEntry("lastSize", lastSize); + config->writeEntry("lastRead",lastRead); + config->writeEntry("lastModified",lastModified); + config->writeEntry("uidlList",uidlist); + config->writeEntry("newCount", newCount); + config->writeEntry("oldCount", oldCount); + + delete config; +} + +void KBiffMonitor::onStateChanged() +{ + saveConfig(); +} + +void KBiffMonitor::start() +{ + readConfig(); + started = true; + firstRun = true; + oldTimer = startTimer(poll * 1000); + emit(signal_checkMail()); +} + +void KBiffMonitor::stop() +{ + if (oldTimer > 0) + killTimer(oldTimer); + + lastSize = 0; + oldTimer = 0; + mailState = UnknownState; + started = false; + lastRead.setTime_t(0); + lastModified.setTime_t(0); + uidlList.clear(); +} + +void KBiffMonitor::setPollInterval(const int interval) +{ + poll = interval; + + // Kill any old timers that may be running + if (oldTimer > 0) + { + killTimer(oldTimer); + + // Start a new timer will the specified time + if (started) + { + oldTimer = startTimer(interval * 1000); + + emit(signal_checkMail()); + } + } +} + +void KBiffMonitor::setMailbox(const QString& url) +{ + KBiffURL kurl(url); + setMailbox(kurl); +} + +void KBiffMonitor::setMailbox(KBiffURL& url) +{ + if (imap) + { + delete imap; + imap = 0; + } + if (pop) + { + delete pop; + pop = 0; + } + if (nntp) + { + delete nntp; + nntp = 0; + } + + protocol = url.protocol(); + + if (protocol == "imap4") + { + disconnect(this); + + imap = new KBiffImap; + + connect(this, SIGNAL(signal_checkMail()), SLOT(checkImap())); + server = url.host(); + user = url.user(); + password = url.pass(); + + mailbox = url.path().right(url.path().length() - 1); + port = (url.port() > 0) ? url.port() : 143; + + preauth = url.searchPar("preauth") == "yes"; + keepalive = url.searchPar("keepalive") == "yes"; + bool async = url.searchPar("async") == "yes"; + imap->setAsync(async); +#ifdef USE_SSL + imap->setSSL(false); +#endif // USE_SSL + simpleURL = "imap4://" + server + "/" + mailbox; + } + +#ifdef USE_SSL + if (protocol == "imap4s") + { + disconnect(this); + + imap = new KBiffImap; + + connect(this, SIGNAL(signal_checkMail()), SLOT(checkImap())); + server = url.host(); + user = url.user(); + password = url.pass(); + + mailbox = url.path().right(url.path().length() - 1); + port = (url.port() > 0) ? url.port() : 993; + + preauth = url.searchPar("preauth") == "yes"; + keepalive = url.searchPar("keepalive") == "yes"; + bool async = url.searchPar("async") == "yes"; + imap->setAsync(async); + imap->setSSL(true); + simpleURL = "imap4s://" + server + "/" + mailbox; + } +#endif // USE_SSL + + if (protocol == "pop3") + { + disconnect(this); + + pop = new KBiffPop; + + connect(this, SIGNAL(signal_checkMail()), SLOT(checkPop())); + server = url.host(); + user = url.user(); + password = url.pass(); + mailbox = url.user(); + port = (url.port() > 0) ? url.port() : 110; + + keepalive = url.searchPar("keepalive") == "yes"; + bool async = url.searchPar("async") == "yes"; + pop->setAsync(async); + // preserve existing behaviour, prior to adding disable apop, + // by setting Apop on, even if no apop parameter is found in the mailbox url + bool useApop = !( url.searchPar("apop") == "no" ); + pop->setApop( useApop ); +#ifdef USE_SSL + pop->setSSL(false); +#endif // USE_SSL + + simpleURL = "pop3://" + server + "/" + mailbox; + } + +#ifdef USE_SSL + if (protocol == "pop3s") + { + disconnect(this); + + pop = new KBiffPop; + + connect(this, SIGNAL(signal_checkMail()), SLOT(checkPop())); + server = url.host(); + user = url.user(); + password = url.pass(); + mailbox = url.user(); + port = (url.port() > 0) ? url.port() : 995; + + keepalive = url.searchPar("keepalive") == "yes"; + bool async = url.searchPar("async") == "yes"; + pop->setAsync(async); + // preserve existing behaviour, prior to adding disable apop, + // by setting Apop on, even if no apop parameter is found in the mailbox url + bool useApop = !( url.searchPar("apop") == "no" ); + pop->setApop( useApop ); + pop->setSSL(true); + + simpleURL = "pop3s://" + server + "/" + mailbox; + } +#endif // USE_SSL + + if (protocol == "mbox") + { + disconnect(this); + + connect(this, SIGNAL(signal_checkMail()), SLOT(checkMbox())); + mailbox = url.path(); + + simpleURL = "mbox:" + mailbox; + } + + if (protocol == "file") + { + disconnect(this); + + connect(this, SIGNAL(signal_checkMail()), SLOT(checkLocal())); + mailbox = url.path(); + + simpleURL = "file:" + mailbox; + } + + if (protocol == "maildir") + { + disconnect(this); + + connect(this, SIGNAL(signal_checkMail()), SLOT(checkMaildir())); + mailbox = url.path(); + + simpleURL = "maildir:" + mailbox; + } + + if (protocol == "mh") + { + disconnect(this); + + connect(this, SIGNAL(signal_checkMail()), SLOT(checkMHdir())); + mailbox = url.path(); + + simpleURL = "mh:" + mailbox; + } + + if (protocol == "nntp") + { + disconnect(this); + + nntp = new KBiffNntp; + + connect(this, SIGNAL(signal_checkMail()), SLOT(checkNntp())); + server = url.host(); + user = url.user(); + password = url.pass(); + + mailbox = url.path().right(url.path().length() - 1); + port = (url.port() > 0) ? url.port() : 119; + + keepalive = url.searchPar("keepalive") == "yes"; + bool async = url.searchPar("async") == "yes"; + nntp->setAsync(async); +#ifdef USE_SSL + nntp->setSSL(false); +#endif // USE_SSL + simpleURL = "nntp://" + server + "/" + mailbox; + } + + fetchCommand = url.searchPar("fetch"); +} + +void KBiffMonitor::setMailboxIsRead() +{ + lastRead = QDateTime::currentDateTime(); + if (mailState == NewMail) + { + if (b_new_lastSize) lastSize = new_lastSize; + if (b_new_lastRead) lastRead = new_lastRead; + if (b_new_lastModified) lastModified = new_lastModified; + if (b_new_uidlList) uidlList = new_uidlList; + + if (curCount!=-1) curCount+=newCount; + newCount = 0; + b_new_lastSize = false; + b_new_lastRead = false; + b_new_lastModified = false; + b_new_uidlList = false; + + determineState(OldMail); + } +} + +void KBiffMonitor::checkMailNow() +{ + emit(signal_checkMail()); +} + +void KBiffMonitor::setPassword(const QString& pass) +{ + password = pass; +} + +void KBiffMonitor::setMailboxKey(const QString& k) +{ + key = k; +} + +void KBiffMonitor::timerEvent(QTimerEvent *) +{ + emit(signal_checkMail()); +} + +void KBiffMonitor::checkLocal() +{ + // get the information about this local mailbox + QFileInfo mbox(mailbox); + + // run external fetch client + if (!fetchCommand.isEmpty()) + emit(signal_fetchMail(fetchCommand)); + + // check if we have new mail + determineState(mbox.size(), mbox.lastRead(), mbox.lastModified()); + + firstRun = false; +} + +void KBiffMonitor::checkMbox() +{ + // get the information about this local mailbox + QFileInfo mbox(mailbox); + + // run external fetch client + if (!fetchCommand.isEmpty()) + emit(signal_fetchMail(fetchCommand)); + + // see if the state has changed + if ((mbox.lastModified() != lastModified) || (mbox.size() != lastSize) || + (mailState == UnknownState) || (oldCount == -1)) + { + lastModified = mbox.lastModified(); + lastSize = mbox.size(); + + // ok, the state *has* changed. see if the number of + // new messages has, too. + newCount = mboxMessages(); + + // Set access time of the file to what it was. If we don't do + // this some (all?) MUAs think that the mail has already been + // read. + { + utimbuf buf; + buf.actime = mbox.lastRead().toTime_t(); + buf.modtime = mbox.lastModified().toTime_t(); + utime(QFile::encodeName(mailbox), &buf); + } + + // if there are any new messages, consider the state New + if (newCount > 0) + determineState(NewMail); + else + { + if (oldCount == 0) + determineState(NoMail); + else + determineState(OldMail); + } + } + else if (firstRun) + { + KBiffMailState state(mailState); + mailState = UnknownState; + determineState(state); + } + + firstRun = false; + + // handle the NoMail case + if ((mbox.size() == 0) || (oldCount == 0)) + { + newCount = 0; + determineState(NoMail); + return; + } +} + +void KBiffMonitor::checkPop() +{ + firstRun = false; + + QString command; + + // connect to the server unless it is active already + if (pop->active() == false) + { + if(pop->connectSocket(server, port) == false) + { + determineState(NoConn); + return; + } + + // find out if APOP is supported + pop->parseBanner(); + + // find other possibly useful capabilities + // we don't care if this fails + pop->command("CAPA\r\n"); + + if (pop->authenticate(user, password) == false ) + { + pop->close(); + invalidLogin(); + return; + } + } + + command = "UIDL\r\n"; + if (pop->command(command) == false) + { + command = "STAT\r\n"; + if (pop->command(command) == false) + { + command = "LIST\r\n"; + if (pop->command(command) == false) + { + // if this still doesn't work, then we + // close this port + pop->close(); + return; + } + } + } + + if (command == "UIDL\r\n") + { + determineState(pop->getUidlList()); + curCount = uidlList.count(); + } + else + { + determineState(pop->numberOfMessages()); + } + + if (keepalive == false) + pop->close(); +} + +void KBiffMonitor::checkImap() +{ + firstRun = false; + + QString command; + int seq = 1000; + bool do_login = false; + + // run external client (probably to setup SSL) + if (!fetchCommand.isEmpty()) { + emit(signal_fetchMail(fetchCommand)); + + // sleep a bit to allow the connection to take place + sleep(1); + } + + // connect to the server + if (imap->active() == false) + { + if (imap->connectSocket(server, port) == false) + { + invalidLogin(); + return; + } + + do_login = true; + + // check the server's capabilities (see RFC 3050, 6.1.1) + command = QString().setNum(seq) + " CAPABILITY\r\n"; + if (imap->command(command, seq) == false) + { + invalidLogin(); + return; + } + seq++; + } + + // if we are preauthorized OR we want to keep the session alive, then + // we don't login. Otherwise, we do. + if ((preauth == false) && (do_login == true)) + { + if (imap->authenticate(&seq, user, password) == false) + { + invalidLogin(); + return; + } + } + + // reset the numbers from the last check + imap->resetNumbers(); + + // The STATUS COMMAND is documented in RFC2060, 6.3.10 + command = QString().setNum(seq) + " STATUS " + mailbox + " (UNSEEN MESSAGES)\r\n"; + if ( ! imap->command(command, seq)) { + return; + } + seq++; + + // lets not logout if we want to keep the session alive + if (keepalive == false) + { + command = QString().setNum(seq) + " LOGOUT\r\n"; + if (imap->command(command, seq) == false) + return; + imap->close(); + } + + // what state are we in? + if (imap->numberOfMessages() == 0) + { + newCount = 0; + determineState(NoMail); + } + else + { + newCount = imap->numberOfNewMessages(); + curCount = imap->numberOfMessages() - newCount; + if (newCount > 0) + determineState(NewMail); + else + determineState(OldMail); + } +} + +void KBiffMonitor::checkMaildir() +{ + firstRun = false; + + // get the information about this local mailbox + QDir mbox(mailbox); + + // run external fetch client + if (!fetchCommand.isEmpty()) + emit(signal_fetchMail(fetchCommand)); + + // make sure the mailbox exists + if (mbox.exists()) + { + // maildir stores its mail in MAILDIR/new and MAILDIR/cur + QDir new_mailbox(mailbox + "/new"); + QDir cur_mailbox(mailbox + "/cur"); + + // make sure both exist + if (new_mailbox.exists() && cur_mailbox.exists()) + { + // check only files + new_mailbox.setFilter(QDir::Files); + cur_mailbox.setFilter(QDir::Files); + + // determining "new" (or "unread") mail in maildir folders + // is a *little* tricky. all mail in the 'new' folder are + // new, of course... but so is all mail in the 'cur' + // folder that doesn't have a ':2,[F|R|S|T]' after it. + newCount = new_mailbox.count(); + curCount = cur_mailbox.count(); + + const QFileInfoList *cur_list = cur_mailbox.entryInfoList(); + QFileInfoListIterator it(*cur_list); + QFileInfo *info; + + static QRegExp suffix(":2,?F?R?S?T?$"); + while ((info = it.current())) + { + if (info->fileName().findRev(suffix) == -1) + { + newCount++; + curCount--; + } + ++it; + } + + // all messages in 'new' are new + if (newCount > 0) + { + determineState(NewMail); + } + // failing that, we look for any old ones + else if (curCount > 0) + { + determineState(OldMail); + } + // failing that, we have no mail + else + determineState(NoMail); + } + } +} + +void KBiffMonitor::checkNntp() +{ + firstRun = false; + + QString command; + bool do_login = false; + + // connect to the server + if (nntp->active() == false) + { + if (nntp->connectSocket(server, port) == false) + { + determineState(NoConn); + return; + } + + do_login = true; + } + + // if we are preauthorized OR we want to keep the session alive, then + // we don't login. Otherwise, we do. + if ((preauth == false) && (do_login == true)) + { + if (user.isEmpty() == false) + { + command = "authinfo user " + user + "\r\n"; + if (nntp->command(command) == false) + return; + } + if (password.isEmpty() == false) + { + command = "authinfo pass " + password + "\r\n"; + if (nntp->command(command) == false) + return; + } + } + + command = "group " + mailbox + "\r\n"; + if (nntp->command(command) == false) + return; + + // lets not logout if we want to keep the session alive + if (keepalive == false) + { + command = "QUIT\r\n"; + nntp->command(command); + nntp->close(); + } + + // now, we process the .newsrc file + QString home(getenv("HOME")); + QString newsrc_path(home + "/.newsrc"); + QFile newsrc(newsrc_path); + if (newsrc.open(IO_ReadOnly) == false) + { + return; + } + + char c_buffer[MAXSTR]; + while(newsrc.readLine(c_buffer, MAXSTR) > 0) + { + // search for our mailbox name + QString str_buffer(c_buffer); + if (str_buffer.left(mailbox.length()) != mailbox) + continue; + + // we now have our mailbox. this parsing routine is so + // ugly, however, that I could almost cry. it assumes way + // too much. the "actual" range MUST be 1-something + // continuously and our read sequence MUST be sequentially in + // order + bool range = false; + int last = 1; + newCount = 0; + char *buffer = c_buffer; + + // skip over the mailbox name + for(; buffer && *buffer != ' '; buffer++) {} + + // iterate over the sequence until we hit a newline or end of string + while (buffer && *buffer != '\n' && *buffer != '\0') + { + // make sure that this is a digit + if (!isdigit(*buffer)) + { + buffer++; + continue; + } + + // okay, what digit are we looking at? atoi() will convert + // only those digits it recognizes to an it. this will handily + // skip spaces, dashes, commas, etc + char *digit = buffer; + int current = atoi(digit); + + // if our current digit is greater than is possible, then we + // should just quit while we're (somewhat) ahead + if (current > nntp->last()) + break; + + // we treat our sequences different ways if we are in a range + // or not. specifically, if we are in the top half of a range, + // we don't do anything + if (range == false) + { + if (current > last) + newCount += current - last - 1; + } + else + range = false; + + // set our 'last' one for the next go-round + last = current; + + // skip over all of these digits + for(;buffer && isdigit(*buffer); buffer++) {} + + // is this a range? + if (*buffer == '-') + range = true; + } + + // get the last few new ones + if (last < nntp->last()) + newCount += nntp->last() - last; + + break; + } + // with newsgroups, it is either new or non-existant. it + // doesn't make sense to count the number of read mails + if (newCount > 0) + determineState(NewMail); + else + determineState(OldMail); +} + +/* + * MH support provided by David Woodhouse <[email protected]> + */ +void KBiffMonitor::checkMHdir() +{ + firstRun = false; + + // get the information about this local mailbox + QDir mbox(mailbox); + char the_buffer[MAXSTR]; + char *buffer = the_buffer; + + // run external fetch client + if (!fetchCommand.isEmpty()) + emit(signal_fetchMail(fetchCommand)); + + + // make sure the mailbox exists + if (mbox.exists()) + { + QFile mhseq(mailbox+"/.mh_sequences"); + if (mhseq.open(IO_ReadOnly) == true) + { + // Check the .mh_sequences file for 'unseen:' + + buffer[MAXSTR-1]=0; + + while(mhseq.readLine(buffer, MAXSTR-2) > 0) + { + if (!strchr(buffer, '\n') && !mhseq.atEnd()) + { + // read till the end of the line + + int c; + while((c=mhseq.getch()) >=0 && c !='\n') {} + } + if (!strncmp(buffer, "unseen:", 7)) + { + // There are unseen messages + // we will now attempt to count exactly how + // many new messages there are + + // an unseen sequence looks something like so: + // unseen: 1, 5-9, 27, 35-41 + bool range = false; + int last = 0; + + // initialize the number of new messages + newCount = 0; + + // jump to the correct position and iterate through the + // rest of the buffer + buffer+=7; + while(*buffer != '\n' && buffer) + { + // is this a digit? if so, it is the first of possibly + // several digits + if (isdigit(*buffer)) + { + // whether or not this is a range, we are guaranteed + // of at least *one* new message + newCount++; + + // get a handle to this digit. atoi() will convert + // only those digits it recognizes to an int. so + // atoi("123garbage") would become 123 + char *digit = buffer; + + // if we are in the second half of a range, we need + // to compute the number of new messages. + if (range) + { + // remember that we have already counted the + // two extremes.. hence we need to subtract one. + newCount += atoi(digit) - last - 1; + range = false; + } + + // skip over all digits + for(;buffer && isdigit(*buffer); buffer++) {} + + // check if we are in a range + if (*buffer == '-') + { + // save the current digit for later computing + last = atoi(digit); + range = true; + } + } + else + buffer++; + } + mhseq.close(); + determineState(NewMail); + return; + } + } + mhseq.close(); + } + + // OK. No new messages listed in .mh_sequences. Check if + // there are any old ones. + //mbox.setFilter(QDir::Files); + QStringList mails = mbox.entryList(QDir::Files); + QStringList::Iterator str; + + for (str = mails.begin(); str != mails.end(); str++) + { + uint index; + // Check each file in the directory. + // If it's a numeric filename, then it's a mail. + + for (index = 0; index < (*str).length(); index++) + { + if (!(*str).at(index).isDigit()) + break; + } + if (index >= (*str).length()) + { + // We found a filename which was entirely + // made up of digits - it's a real mail, so + // respond accordingly. + + determineState(OldMail); + return; + } + } + + // We haven't found any valid filenames. No Mail. + determineState(NoMail); + } +} + +void KBiffMonitor::determineState(unsigned int size) +{ + // check for no mail + if (size == 0) + { + if (mailState != NoMail) + { + mailState = NoMail; + lastSize = 0; + newCount = 0; + emit(signal_noMail()); + emit(signal_noMail(simpleURL)); + onStateChanged(); + } + + emit(signal_currentStatus(newCount, key, mailState)); + return; + } + + // check for new mail + if (size > lastSize) + { + if (!b_new_lastSize || size > new_lastSize) + { + mailState = NewMail; + emit(signal_newMail()); + emit(signal_newMail(newCount, key)); + onStateChanged(); + } + new_lastSize = size; + b_new_lastSize = true; + newCount = size - lastSize; + emit(signal_currentStatus(newCount, key, mailState)); + return; + } + + // if we have *some* mail, but the state is unknown, + // then we'll consider it old + if (mailState == UnknownState) + { + mailState = OldMail; + lastSize = size; + emit(signal_oldMail()); + emit(signal_oldMail(simpleURL)); + + emit(signal_currentStatus(newCount, key, mailState)); + onStateChanged(); + return; + } + + // check for old mail + if (size < lastSize) + { + if (mailState != OldMail) + { + mailState = OldMail; + lastSize = size; + emit(signal_oldMail()); + emit(signal_oldMail(simpleURL)); + onStateChanged(); + } + } + + emit(signal_currentStatus(newCount, key, mailState)); +} + +void KBiffMonitor::determineState(KBiffUidlList uidl_list) +{ + QString *UIDL; + unsigned int messages = 0; + + // if the uidl_list is empty then the number of messages = 0 + if (uidl_list.isEmpty()) + { + if (mailState != NoMail) + { + lastSize = newCount = 0; + mailState = NoMail; + emit(signal_noMail()); + emit(signal_noMail(simpleURL)); + onStateChanged(); + } + } + else + { + // if a member of uidl_list is not in the old uidlList then we have + // new mail + for (UIDL = uidl_list.first(); UIDL != 0; UIDL = uidl_list.next()) + { + // If we already have new mail use new_uidlList to se if we have + // more new messages + if (b_new_uidlList) + { + if (new_uidlList.find(UIDL) == -1) + messages++; + } + else + { + if (uidlList.find(UIDL) == -1) + messages++; + } + } + // if there are any new messages, then notify.. + if (messages > 0) + { + mailState = NewMail; + emit(signal_newMail()); + emit(signal_newMail(newCount, key)); + onStateChanged(); + // now update newCount + if (b_new_uidlList) + { + // if we have used new_uidlList for a check + newCount += messages; + } + else + { + // if we have used uidlList for a check + newCount = messages; + } + new_uidlList = uidl_list; + b_new_uidlList = true; + } + // this is horrible. it will reset kbiff to OldMail the very next + // time a pop3 mailbox is checked. i don't know of a way around + // this, though :-( + // MZ: what's wrong with that? + else if ( (!b_new_uidlList) && mailState != OldMail) + { + newCount = 0; + mailState = OldMail; + emit(signal_oldMail()); + emit(signal_oldMail(simpleURL)); + onStateChanged(); + } + } + emit(signal_currentStatus(newCount, key, mailState)); +} + +void KBiffMonitor::determineState(KBiffMailState state) +{ + if ((state == NewMail) && (mailState != NewMail)) + { + mailState = NewMail; + emit(signal_newMail()); + emit(signal_newMail(newCount, key)); + onStateChanged(); + } + else + if ((state == NoMail) && (mailState != NoMail)) + { + mailState = NoMail; + emit(signal_noMail()); + emit(signal_noMail(simpleURL)); + onStateChanged(); + } + else + if ((state == OldMail) && (mailState != OldMail)) + { + mailState = OldMail; + emit(signal_oldMail()); + emit(signal_oldMail(simpleURL)); + onStateChanged(); + } + else + if ((state == NoConn) && (mailState != NoConn)) + { + mailState = NoConn; + emit(signal_noConn()); + emit(signal_noConn(simpleURL)); + onStateChanged(); + } + emit(signal_currentStatus(newCount, key, mailState)); +} + +void KBiffMonitor::determineState(unsigned int size, const QDateTime& last_read, const QDateTime& last_modified) +{ + // Check for NoMail + if (size == 0) + { + // Is this a new state? + if (mailState != NoMail) + { + // Yes, the user has just nuked the entire mailbox + mailState = NoMail; + lastRead = last_read; + lastSize = 0; + + // Let the world know of the new state + emit(signal_noMail()); + emit(signal_noMail(simpleURL)); + onStateChanged(); + } + } + else + // There is some mail. See if it is new or not. To be new, the + // mailbox must have been modified after it was last read AND the + // current size must be greater then it was before. + if (last_modified>=last_read && size>lastSize) + { + if (!b_new_lastSize || size>new_lastSize) + { + mailState = NewMail; + // Let the world know of the new state + emit(signal_newMail()); + emit(signal_newMail(1, key)); + onStateChanged(); + } + new_lastSize = size; + b_new_lastSize = true; + new_lastRead = last_read; + b_new_lastRead = true; + newCount = 1; + } + else + // Finally, check if the state needs to change to OldMail + if ((mailState != OldMail) && (last_read > lastRead)) + { + mailState = OldMail; + lastRead = last_read; + lastSize = size; + + // Let the world know of the new state + emit(signal_oldMail()); + emit(signal_oldMail(simpleURL)); + onStateChanged(); + } + + // If we get to this point, then the state now is exactly the + // same as the state when last we checked. Do nothing at this + // point. + emit(signal_currentStatus(newCount, key, mailState)); +} + +/** + * The following function is lifted from unixdrop.cpp in the korn + * distribution. It is (C) Sirtaj Singh Kang <[email protected]> and is + * used under the GPL license (and the author's permission). It has + * been slightly modified for formatting reasons. + */ +int KBiffMonitor::mboxMessages() +{ + QFile mbox(mailbox); + char buffer[MAXSTR]; + int count = 0; + int msg_count = 0; + bool in_header = false; + bool has_content_len = false; + bool msg_read = false; + long content_length = 0; + + oldCount = 0; + curCount = 0; + + if (mbox.open(IO_ReadOnly) == false) + return 0; + + buffer[MAXSTR-1] = 0; + + while (mbox.readLine(buffer, MAXSTR-2) > 0) + { + // read a line from the mailbox + + if (!strchr(buffer, '\n') && !mbox.atEnd()) + { + // read till the end of the line if we + // haven't already read all of it. + + int c; + + while((c=mbox.getch()) >=0 && c !='\n') {} + } + + if (!in_header && real_from(buffer)) + { + // check if this is the start of a message + has_content_len = false; + in_header = true; + msg_read = false; + } + else if (in_header) + { + // check header fields if we're already in one + + if (compare_header(buffer, "Content-Length")) + { + has_content_len = true; + content_length = atol(buffer + 15); + } + // This should handle those folders that double as IMAP or POP + // folders. Possibly PINE uses these always + if (strcmp(buffer, "Subject: DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA\n") == 0) + { + oldCount--; + curCount--; + } + else + { + if (compare_header(buffer, "Status")) + { + const char *field = buffer; + field += 7; + while (field && (*field== ' ' || *field == '\t')) + field++; + + if (*field == 'N' || *field == 'U' || *field == 0x0a) + msg_read = false; + else + msg_read = true; + } + // Netscape *sometimes* uses X-Mozilla-Status to determine + // unread vs read mail. The only pattern I could see for + // sure, though, was that Read mails started with an '8'. + // I make no guarantees on this... + else if (compare_header(buffer, "X-Mozilla-Status")) + { + const char *field = buffer; + field += 17; + while (field && (*field== ' ' || *field == '\t')) + field++; + + if (*field == '8') + msg_read = true; + else + msg_read = false; + } + else if (buffer[0] == '\n' ) + { + if (has_content_len) + mbox.at(mbox.at() + content_length); + + in_header = false; + + oldCount++; + + if (!msg_read) { + count++; + } else { + curCount++; + } + } + } + }//in header + + if(++msg_count >= 100 ) + { + qApp->processEvents(); + msg_count = 0; + } + }//while + + mbox.close(); + return count; +} + +void KBiffMonitor::invalidLogin() +{ + // first, we stop this monitor to be on the safe side + stop(); + determineState(NoConn); + newCount = -1; + + emit(signal_invalidLogin(key)); +} + +/////////////////////////////////////////////////////////////////////////// +// KBiffSocket +/////////////////////////////////////////////////////////////////////////// +KBiffSocket::KBiffSocket() : async(false), socketFD(-1), messages(0), newMessages(-1) +#ifdef USE_SSL + , ssltunnel(0) +#endif // USE_SSL +{ + FD_ZERO(&socketFDS); + + /* + * Set the socketTO once and DO NOT use it in any select call as this + * may alter its value! + */ + socketTO.tv_sec = SOCKET_TIMEOUT; + socketTO.tv_usec = 0; +} + +KBiffSocket::~KBiffSocket() +{ + close(); +#ifdef USE_SSL + if (ssltunnel) + { + delete ssltunnel; + ssltunnel = 0; + } +#endif // USE_SSL +} + +int KBiffSocket::numberOfMessages() +{ + return messages; +} + +int KBiffSocket::numberOfNewMessages() +{ + return (newMessages > -1) ? newMessages : 0; +} + +void KBiffSocket::close() +{ + +#ifdef USE_SSL + if (isSSL() && (socketFD != -1) && (ssltunnel != 0)) + { + ssltunnel->close(); + } +#endif // USE_SSL + + if (socketFD != -1) + ::close(socketFD); + + socketFD = -1; + FD_ZERO(&socketFDS); +} + +bool KBiffSocket::connectSocket(const QString& host, unsigned short int port) +{ + sockaddr_in sin; + hostent *hent; + int addr, n; + + // if we still have a socket, close it + if (socketFD != -1) + close(); + + // get the socket + socketFD = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + + // start setting up the socket info + memset((char *)&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + + // get the address + if ((addr = inet_addr(host.ascii())) == -1) + { + // get the address by host name + if ((hent = gethostbyname(host.ascii())) == 0) + { + switch (h_errno) + { + case HOST_NOT_FOUND: + break; + + case NO_ADDRESS: + break; + + case NO_RECOVERY: + break; + + case TRY_AGAIN: + break; + + default: + break; + } + + close(); + return false; + } + + memcpy((void *)&sin.sin_addr, *(hent->h_addr_list), hent->h_length); + } + else + // get the address by IP + memcpy((void *)&sin.sin_addr, (void *)&addr, sizeof(addr)); + + // Set up non-blocking io if requested + if (async) + { + int flags = fcntl(socketFD, F_GETFL); + if (flags < 0 || fcntl(socketFD, F_SETFL, flags | O_NONBLOCK) < 0) + { + async = false; + } + } + + + // the socket is correctly setup. now connect + if ((n = ::connect(socketFD, (sockaddr *)&sin, sizeof(sockaddr_in))) == -1 && + errno != EINPROGRESS) + { + close(); + return false; + } + + // Empty the file descriptor set + FD_ZERO(&socketFDS); + FD_SET(socketFD, &socketFDS); + + // For non-blocking io, the connection may need time to finish (n = -1) + if (n == -1 && async == true) + { + struct timeval tv = socketTO; + + // Wait for the connection to come up + if (select(socketFD+1, NULL, &socketFDS, NULL, &tv) != 1) + { + errno = ETIMEDOUT; + close(); + return false; + } + + // The connection has finished. Catch any error in a call to readLine() + } + +#ifdef USE_SSL + // Initialize SSL tunnel, if needed + if (isSSL()) + { + if (ssltunnel == 0) + ssltunnel = new KSSL(true); + else + ssltunnel->reInitialize(); + if (ssltunnel == 0) + { + close(); + return false; + } + if (ssltunnel->connect(socketFD) != 1) + { + close(); + return false; + } + } +#endif // USE_SSL + + // we're connected! see if the connection is good + QString line(readLine()); + if (line.isNull() || ((line.find("200") == -1 ) && (line.find("OK") == -1) && (line.find("PREAUTH") == -1))) + { + if (line.isNull()) + + close(); + return false; + } + + // everything is swell + banner = line; // save the banner for use by subclasses + return true; +} + +bool KBiffSocket::active() +{ + return socketFD != -1; +} + +bool KBiffSocket::isAsync() +{ + return async; +} + +void KBiffSocket::setAsync(bool on) +{ + int flags = 0; + + async = on; + + if (active()) + { + flags = fcntl(socketFD, F_GETFL); + + switch (async) + { + case false: + if (flags >= 0) + fcntl(socketFD, F_SETFL, flags & ~O_NONBLOCK); + break; + + case true: + if (flags < 0 || fcntl(socketFD, F_SETFL, flags | O_NONBLOCK) < 0) + async = false; + break; + } + } +} + +#ifdef USE_SSL +bool KBiffSocket::isSSL() +{ + return usessl; +} + +void KBiffSocket::setSSL(bool on) +{ + if (usessl == on) return; + if (!KSSL::doesSSLWork()) + { + usessl = false; + return; + } + usessl = on; + if (active()) + { + switch (usessl) + { + case false: + ssltunnel->close(); + delete ssltunnel; + ssltunnel = 0; + break; + case true: + if (ssltunnel == 0) + ssltunnel = new KSSL(true); + else + ssltunnel->reInitialize(); + if (ssltunnel == 0) + { + usessl = false; + break; + } + if (ssltunnel->connect(socketFD) != 1) + usessl = false; + break; + } + } +} +#endif // USE_SSL + +int KBiffSocket::writeLine(const QString& line) +{ + int bytes = 0; + + // Do not try to write to a non active socket. Return error. + if (!active()) + return -1; + +#ifdef USE_SSL + if (isSSL()) + { + if ((bytes = ssltunnel->write(line.ascii(), line.length())) <= 0) + close(); + } + else +#endif // USE_SSL + if ((bytes = ::write(socketFD, line.ascii(), line.length())) <= 0) + close(); + + return bytes; +} + +QString KBiffSocket::readLine() +{ + QString fault, response; + char buffer; + ssize_t bytes = -1; + +#ifdef USE_SSL + if (isSSL()) + { + while (((bytes = ssltunnel->read(&buffer, 1)) > 0) && (buffer != '\n')) + response += buffer; + } + else +#endif // USE_SSL + if (!async) + while (((bytes = ::read(socketFD, &buffer, 1)) > 0) && (buffer != '\n')) + response += buffer; + else + { + while ( (((bytes = ::read(socketFD, &buffer, 1)) > 0) && (buffer != '\n')) || + ((bytes < 0) && (errno == EWOULDBLOCK)) ) + { + if (bytes > 0) + response += buffer; + else + { + struct timeval tv = socketTO; + if (select(socketFD+1, &socketFDS, NULL, NULL, &tv) != 1) + { + errno = ETIMEDOUT; + break; + } + } + } + } + + if (bytes == -1) + { + // Close the socket and hope for better luck with a new one + close(); + return fault; + } + + return response; +} + +/////////////////////////////////////////////////////////////////////////// +// KBiffImap +/////////////////////////////////////////////////////////////////////////// +KBiffImap::KBiffImap() +{ + /* Assume that the IMAP server does no fancy authentication */ + auth_cram_md5 = false; +} + +KBiffImap::~KBiffImap() +{ + close(); +} + +bool KBiffImap::command(const QString& line, unsigned int seq) +{ + QString messagesListString; + QStringList messagesList; + bool tried_cram_md5; // are we trying CRAM-MD5 ? + + if (writeLine(line) <= 0) + { + close(); + return false; + } + + QString ok, bad, no, response; + ok.sprintf("%d OK", seq); + bad.sprintf("%d BAD", seq); + no.sprintf("%d NO", seq); + + // must be case insensitive + QRegExp status("\\* STATUS", FALSE); + QRegExp capability("\\* CAPABILITY", FALSE); + QRegExp cram_md5("AUTHENTICATE CRAM-MD5", FALSE); + + // are we trying CRAM-MD5 ? + tried_cram_md5 = cram_md5.search(line)>=0; + cram_md5 = QRegExp("\\+ ([A-Za-z0-9+/=]+)"); + + while (!(response = readLine()).isNull()) + { + // if an error has occurred, we get a null string in return + if (response.isNull()) + break; + + // if the response is either good or bad, then return + if (response.find(ok) > -1) + return true; + if ((response.find(bad) > -1) || (response.find(no) > -1)) + break; + + /* The STATUS response is documented in RFC2060, 6.3.10/7.2.4 + * Briefly: the response depends on command and looks like + * * STATUS "some-imap-folder" ( requested-info ) + * for example: + * C: . STATUS "INBOX" (UNSEEN MESSAGES) + * S: * STATUS "INBOX" (UNSEEN 2 MESSAGES 3) + * S: . OK STATUS Completed + */ + if (status.search(response) >= 0) { + QRegExp unseen("UNSEEN ([0-9]*)", FALSE); + if (unseen.search(response) >= 0) { + QString num = unseen.cap(1); + newMessages = num.toInt(); + } + + QRegExp number("MESSAGES ([0-9]*)", FALSE); + if (number.search(response) >= 0) { + QString num = number.cap(1); + messages = num.toInt(); + } + } + + /* The CAPABILITY response is documented in RFC 3050, + * sections 6.1.1 and 7.2.1 + * An example: + * C: . CAPABILITY + * S: * CAPABILITY IMAP4rev1 IDLE AUTH=PLAIN AUTH=CRAM-MD5 + * S: . OK CAPABILITY completed. + */ + if (capability.search(response) >= 0) { + QRegExp cram_md5_cap("AUTH=CRAM-MD5", FALSE); + if (cram_md5_cap.search(response) >= 0) { + auth_cram_md5 = true; + } + } + + /* AUTHENTICATE CRAM-MD5 response is documented in + * RFC 3050 6.2.2 and RFC 2195 + */ + if (tried_cram_md5 && cram_md5.search(response)>=0) { + chall_cram_md5 = KCodecs::base64Decode(cram_md5.cap(1).local8Bit()); + if (chall_cram_md5.isNull()) + break; + + return true; + } + } + + close(); + return false; +} + +QString KBiffImap::mungeUserPass(const QString& old_user) +{ + QString new_user(old_user); + + if (new_user.left(1) != "\"") + new_user.prepend("\""); + if (new_user.right(1) != "\"") + new_user.append("\""); + + return new_user; +} + +void KBiffImap::resetNumbers() +{ + messages = 0; + newMessages = 0; +} + +bool KBiffImap::authenticate(int *pseq, const QString& user, const QString& pass) +{ + QString cmd, username, password; + + // If CRAM-MD5 is available, use it. It's the best we know. + // RFC 2195 defines the CRAM-MD5 authentication method + // also see RFC 3501 section 6.2.2 for the AUTHENTICATE command + if( auth_cram_md5 ) + { + cmd = QString("%1 AUTHENTICATE CRAM-MD5\r\n").arg(*pseq); + if (command(cmd, *pseq) == false) + { + return false; + } + + // calculate the real response to the challenge + QString response = user + " " + KBiffCrypt::hmac_md5(chall_cram_md5, pass); + response = KCodecs::base64Encode(response.latin1()); + + // send the response + if (command(response+"\r\n", *pseq) == false) + { + return false; + } + + return true; + } + else // well, we tried, LOGIN is the best we can do + { + // imap allows spaces in usernames... we need to take care of that + username = mungeUserPass(user); + + // also asterisks (*) in passwords. maybe it's a good idea + // to _always_ munge the user and the password. + password = mungeUserPass(pass); + + cmd = QString().setNum(*pseq) + " LOGIN " + + username + " " + + password + "\r\n"; + if (command(cmd, *pseq) == false) + { + return false; + } + (*pseq)++; + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////// +// KBiffPop +/////////////////////////////////////////////////////////////////////////// +KBiffPop::KBiffPop() : use_apop( true ) +{ +} + +KBiffPop::~KBiffPop() +{ + close(); +} + +void KBiffPop::close() +{ + command("QUIT\r\n"); + KBiffSocket::close(); +} + +void KBiffPop::setApop( bool enabled ) +{ + use_apop = enabled; +} + +bool KBiffPop::command(const QString& line) +{ + if (writeLine(line) <= 0) + return false; + + QString response; + response = readLine(); + + // check if the response was bad. if so, return now + if (response.isNull() || response.left(4) == "-ERR") + { + // we used to close the socket here.. but this MAY be + // because the server didn't understand UIDL. the server + // may react better with LIST or STAT so just fail quitely + // thanks to David Barth ([email protected]) + return false; + } + + // if the command was UIDL then build up the newUidlList + if (line == "UIDL\r\n") + { + uidlList.clear(); + for (response = readLine(); + !response.isNull() && response.left(1) != "."; + response = readLine()) + { + uidlList.append(new QString(response.right(response.length() - + response.find(" ") - 1))); + } + } + else + // get all response lines from the LIST command + // LIST and UIDL are return multilines so we have to loop around + if (line == "LIST\r\n") + { + for (messages = 0, response = readLine(); + !response.isNull() && response.left(1) != "."; + messages++, response = readLine()) {} + } + else + if (line == "STAT\r\n") + { + if (!response.isNull()) + sscanf(response.ascii(), "+OK %d", &messages); + } + else + // find out what the server is capable of + if (line == "CAPA\r\n") + { + QRegExp rx("\\bCRAM-MD5\\b"); + + auth_cram_md5 = false; // assume no support + + for (response = readLine(); + !response.isNull() && response.left(1) != "."; + response = readLine()) + { + if (response.left(4) == "SASL") + auth_cram_md5 = response.find(rx) != -1; + } + } + else + // look for the CRAM-MD5 challenge + if (line == "AUTH CRAM-MD5\r\n") + { + QRegExp challenge("\\+ ([A-Za-z0-9+/=]+)"); + if (challenge.search(response) == -1 ) + { + return false; + } + + chall_cram_md5 = KCodecs::base64Decode(challenge.cap(1).local8Bit()); + } + + return !response.isNull(); +} + +KBiffUidlList KBiffPop::getUidlList() const +{ + return uidlList; +} + +/*! + This method parses the initial response from the POP3 server. + The response is defined in RFC 1939 sections 4 and 7. + + \fn KBiffPop::parse_banner(void) + */ +bool KBiffPop::parseBanner(void) +{ + // RFC 1939 section 3 says server MUST use uppercase + if( banner.left(3) != "+OK" ) { + auth_apop = false; + return false; + } + + // Look for the banner part that indicates APOP support + QRegExp rx("(<[a-zA-Z0-9_+.-]+@[a-zA-Z0-9_+.-]+>)"); + if( rx.search(banner) == -1 || !use_apop ) { + auth_apop = false; + } else { + chall_apop = rx.cap(1).latin1(); + auth_apop = true; + } + + return true; +} + +/*! + This method authenticates using the most secure + technique available. + \fn KBiffPop::authenticate(const QString& user, const QString& pass) + */ +bool KBiffPop::authenticate(const QString& user, const QString& pass) +{ + QString popcommand; + + // CRAM-MD5 authentication is the most secure we can handle + // the use of the AUTH command is documented in RFC 1734 + if( auth_cram_md5 ) + { + if (this->command("AUTH CRAM-MD5\r\n") == false) + { + return false; + } + + // calculate the real response to the challenge + QString response = user + " " + KBiffCrypt::hmac_md5(chall_cram_md5, pass); + response = KCodecs::base64Encode(response.latin1()); + + // send the response + if (this->command(response+"\r\n") == false) + { + return false; + } + + return true; + } + + // APOP is not as secure as CRAM-MD5 but it's still better + // than sending the password in the clear + if( auth_apop ) + { + QCString digest; + + KMD5 md5(chall_apop); + md5.update(pass); + + digest = md5.hexDigest(); + + popcommand = QString("APOP %1 %2\r\n").arg(user, digest.data()); + if (this->command(popcommand) == false) + { + return false; + } + + return true; + } + + // lastly we'll try regular, plain-text authentication + + popcommand = "USER " + user + "\r\n"; + if (this->command(popcommand) == false) + { + return false; + } + + popcommand = "PASS " + pass + "\r\n"; + if (this->command(popcommand) == false) + { + return false; + } + + return true; +} +/////////////////////////////////////////////////////////////////////////// +// KBiffNntp +/////////////////////////////////////////////////////////////////////////// +KBiffNntp::~KBiffNntp() +{ + close(); +} + +bool KBiffNntp::command(const QString& line) +{ + int bogus; + + if (writeLine(line) <= 0) + return false; + + QString response; + while (!(response = readLine()).isNull()) + { + // return if the response is bad + if (response.find("500") > -1) + { + close(); + return false; + } + + // find return codes for tcp, user, pass + QString code(response.left(3)); + if ((code == "200") || (code == "281") || (code == "381")) + return true; + + // look for the response to the GROUP command + // 211 <num> <first> <last> <group> + if (code == "211") + { + sscanf(response.ascii(), "%d %d %d %d", + &bogus, &messages, &firstMsg, &lastMsg); + return true; + } + } + + close(); + return false; +} + +int KBiffNntp::first() const +{ + return firstMsg; +} + +int KBiffNntp::last() const +{ + return lastMsg; +} + +///////////////////////////////////////////////////////////////////////// +/* The following is a (C) Sirtaj Singh Kang <[email protected]> */ + +#define whitespace(c) (c == ' ' || c == '\t') + +#define skip_white(c) while(c && (*c) && whitespace(*c) ) c++ +#define skip_nonwhite(c) while(c && (*c) && !whitespace(*c) ) c++ + +#define skip_token(buf) skip_nonwhite(buf); if(!*buf) return false; \ + skip_white(buf); if(!*buf) return false; + +static const char *month_name[13] = { + "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec", NULL +}; + +static const char *day_name[8] = { + "sun", "mon", "tue", "wed", "thu", "fri", "sat", 0 +}; + +static bool real_from(const QString& orig_buffer) +{ + /* + A valid from line will be in the following format: + + From <user> <weekday> <month> <day> <hr:min:sec> [TZ1 [TZ2]] <year> + */ + + int day; + int i; + int found; + + const char *buffer = (const char*)orig_buffer.ascii(); + + /* From */ + + if(!buffer || !*buffer) + return false; + + if (strncmp(buffer, "From ", 5)) + return false; + + buffer += 5; + + skip_white(buffer); + + /* <user> */ + if(*buffer == 0) return false; + skip_token(buffer); + + /* <weekday> */ + found = 0; + for (i = 0; day_name[i] != NULL; i++) + found = found || (qstrnicmp(day_name[i], buffer, 3) == 0); + + if (!found) + return false; + + skip_token(buffer); + + /* <month> */ + found = 0; + for (i = 0; month_name[i] != NULL; i++) + found = found || (qstrnicmp(month_name[i], buffer, 3) == 0); + if (!found) + return false; + + skip_token(buffer); + + /* <day> */ + if ( (day = atoi(buffer)) < 0 || day < 1 || day > 31) + return false; + + return true; +} + +static const char* compare_header(const char* header, const char* field) +{ + int len = strlen(field); + + if (qstrnicmp(header, field, len)) + return NULL; + + header += len; + + if( *header != ':' ) + return NULL; + + header++; + + while( *header && ( *header == ' ' || *header == '\t') ) + header++; + + return header; +} |