diff options
Diffstat (limited to 'src/operator-idle.cc')
-rw-r--r-- | src/operator-idle.cc | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/src/operator-idle.cc b/src/operator-idle.cc new file mode 100644 index 0000000..ccd70ea --- /dev/null +++ b/src/operator-idle.cc @@ -0,0 +1,239 @@ +/** -------------------------------------------------------------------- + * @file operator-idle.cc + * @brief Operator for the IDLE command. Described in RFC2177 / June 1997. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ------------------------------------------------------------------ **/ +#include <unistd.h> + +#include <string> +#include <iostream> + +#include "iodevice.h" +#include "iofactory.h" +#include "convert.h" +#include "depot.h" +#include "globals.h" +#include "mailbox.h" +#include "operators.h" +#include "pendingupdates.h" +#include "recursivedescent.h" +#include "session.h" + +static bool directoryChangeNotification = false; + +#ifdef HAVE_FNOTIFY // GNU dependencies removed +#include <sys/types.h> +#include <sys/select.h> +#include <stdio.h> +#include <signal.h> +#include <fcntl.h> + +void fnotifyEventHandler(int sig) +{ + directoryChangeNotification = true; +} +#endif + +using namespace ::std; +using namespace Binc; + +// Seconds between each poll. With FNOTIFY support, we can idle for 30 +// minutes before timing out. +#ifdef HAVE_FNOTIFY +static const int POLLTIMEOUT = 30 * 60; +#else +static const int POLLTIMEOUT = 30; +#endif + +//---------------------------------------------------------------------- +IdleOperator::IdleOperator(void) +{ +} + +//---------------------------------------------------------------------- +IdleOperator::~IdleOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string IdleOperator::getName(void) const +{ + return "IDLE"; +} + +//---------------------------------------------------------------------- +int IdleOperator::getState(void) const +{ + return Session::SELECTED; +} + +//---------------------------------------------------------------------- +Operator::ProcessResult IdleOperator::process(Depot &depot, + Request &command) +{ + Mailbox *mailbox = depot.getSelected(); + string mailboxDir = depot.mailboxToFilename(mailbox->getName()); + +#ifdef HAVE_FNOTIFY + // Check for FNOTIFY support. + bool waitForNotification = false; + int newfd = open((mailboxDir + "/new").c_str(), O_RDONLY); + int curfd = open((mailboxDir + "/cur").c_str(), O_RDONLY); + + // Watch for notifications for renames, deletes or creates. + if (newfd && curfd + && !fcntl(newfd, F_NOTIFY, DN_RENAME|DN_DELETE|DN_CREATE) + && !fcntl(curfd, F_NOTIFY, DN_RENAME|DN_DELETE|DN_CREATE)) { + struct sigaction fnotifyAction; + fnotifyAction.sa_handler = fnotifyEventHandler; + sigemptyset(&fnotifyAction.sa_mask); + fnotifyAction.sa_flags = SA_RESTART; + sigaction(SIGUSR1, &fnotifyAction, 0); + fcntl(newfd, F_SETSIG, SIGUSR1); + fcntl(curfd, F_SETSIG, SIGUSR1); + waitForNotification = true; + } +#endif + + // when not using FNOTIFY, we need to check the session timeout. + time_t startTime = time(0); +#ifdef HAVE_FNOTIFY + (void)startTime; // removes a compile warning +#endif + + bincClient << "+ idling" << endl; + bincClient.flush(); + + // loop until the session times out or the client submits DONE. + for (;;) { + int maxfd = 0; + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(0, &readfds); + + // check for data from stdin. with FNOTIFY enabled, this select + // will be interrupted by the notification signal. + string input; + struct timeval tv = {POLLTIMEOUT, 0}; + int ret = select(maxfd + 1, &readfds, 0, 0, &tv); + + // check if the select timed out. + if (ret == 0) { + Session &session = Session::getInstance(); +#ifdef HAVE_FNOTIFY + if (waitForNotification) { + bincClient << "* BYE Timeout after " << session.timeout() + << " seconds of inactivity." << endl; + session.setState(Session::LOGOUT); + close(newfd); + close(curfd); + return NOTHING; + } else +#endif + if (time(0) > startTime + IDLE_TIMEOUT) { + bincClient << "* BYE Timeout after " << IDLE_TIMEOUT + << " seconds of inactivity." << endl; + session.setState(Session::LOGOUT); + return NOTHING; + } + } + + // unless the select failed, attempt to read client input. + if (ret > 0 && FD_ISSET(0, &readfds)) { + if (bincClient.readStr(&input) == 0) { + break; + } else { + uppercase(input); + trim(input); + if (input == "DONE") { + break; + } else { + bincClient << "* BAD Syntax error: \"" << input << "\"" << endl; + bincClient.flush(); + continue; + } + } + } + + // at this point, we either got a directory change notification, + // or the select simply timed out, in which case we poll. + bool scanForChanges = false; +#ifdef HAVE_FNOTIFY + if (directoryChangeNotification) + scanForChanges = true; + else if (!waitForNotification) +#endif + scanForChanges = true; + + if (scanForChanges) { + if (directoryChangeNotification) { + // sleep the magic 1 second to ensure that anything that + // arrived in new/ the last second isn't skipped by + // pendingUpdates' scan. + sleep(1); + } + + // scan for changes in the mailbox and report to the client. + if (pendingUpdates(mailbox, PendingUpdates::EXPUNGE + | PendingUpdates::EXISTS + | PendingUpdates::RECENT + | PendingUpdates::FLAGS, true) == false) { + Session &session = Session::getInstance(); + bincClient << "* NO " << session.getLastError() << endl; + bincWarning << "when scanning mailbox: " + << session.getLastError() << endl; + +#ifdef HAVE_FNOTIFY + close(newfd); + close(curfd); +#endif + return NO; + } + +#ifdef HAVE_FNOTIFY + // if FNOTIFY is enabled, set it up again. + if (waitForNotification) { + directoryChangeNotification = false; + + // set up F_NOTIFY again. + if (!fcntl(newfd, F_NOTIFY, DN_MODIFY|DN_RENAME|DN_DELETE|DN_CREATE) + && !fcntl(curfd, F_NOTIFY, DN_MODIFY|DN_RENAME|DN_DELETE|DN_CREATE)) { + struct sigaction fnotifyAction; + fnotifyAction.sa_handler = fnotifyEventHandler; + sigemptyset(&fnotifyAction.sa_mask); + fnotifyAction.sa_flags = SA_RESTART; + sigaction(SIGUSR1, &fnotifyAction, 0); + } else { + waitForNotification = false; + } + } +#endif + bincClient.flush(); + } + } + +#ifdef HAVE_FNOTIFY + close(newfd); + close(curfd); +#endif + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult IdleOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) + return REJECT; + + Operator::ParseResult res; + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF after IDLE"); + return res; + } + + c_in.setName("IDLE"); + return ACCEPT; +} |