summaryrefslogtreecommitdiff
path: root/src/operator-idle.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/operator-idle.cc')
-rw-r--r--src/operator-idle.cc239
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;
+}