/** * @file operator-idle.cc * @brief Operator for the IDLE command. Described in RFC2177 / June 1997. * @author Andreas Aardal Hanssen * @date 2002-2005 */ #include "convert.h" #include "depot.h" #include "globals.h" #include "iodevice.h" #include "iofactory.h" #include "mailbox.h" #include "operators.h" #include "pendingupdates.h" #include "recursivedescent.h" #include "session.h" #include #include #include static bool directoryChangeNotification = false; #ifdef HAVE_FNOTIFY // GNU dependencies removed #include #include #include #include #include void fnotifyEventHandler(int sig) { directoryChangeNotification = true; } #endif using namespace Binc; using std::endl; // 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() {} IdleOperator::~IdleOperator() {} const std::string IdleOperator::getName() const { return "IDLE"; } int IdleOperator::getState() const { return Session::SELECTED; } Operator::ProcessResult IdleOperator::process(Depot &depot, Request &command) { Mailbox *mailbox = depot.getSelected(); std::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(nullptr); #ifdef HAVE_FNOTIFY ()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. std::string input; struct timeval tv = {POLLTIMEOUT, 0}; int ret = select(maxfd + 1, &readfds, nullptr, nullptr, &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(nullptr) > 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) == false) { 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)) { 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; }