diff options
Diffstat (limited to 'src')
107 files changed, 19235 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..de9d16f --- /dev/null +++ b/src/Makefile @@ -0,0 +1,408 @@ +# Don't edit Makefile! Use ../conf-* for configuration. + +SHELL=/bin/sh + +CXX = c++ +CXXFLAGS = -Og -Iinclude -Wall +#LIBDL = -ldl + +bincimapd_OBJECTS = bincimapd.o address.o argparser.o authenticate.o base64.o \ + broker.o convert.o depot.o iodevice.o iofactory.o \ + imapparser.o imapserver.o mailbox.o maildir.o maildir-close.o \ + maildir-create.o maildir-delete.o maildir-expunge.o maildir-readcache.o \ + maildir-scan.o maildir-scanfilesnames.o maildir-select.o \ + maildir-updateflags.o maildir-writecache.o maildirmessage.o mime.o \ + mime-getpart.o mime-parsefull.o mime-parseonlyheader.o mime-printbody.o \ + mime-printdoc.o mime-printheader.o multilogdevice.o \ + operator-authenticate.o operator-capability.o operator-idle.o \ + operator-id.o operator-noop.o operator-noop-pending.o operator-login.o \ + operator-logout.o operator-append.o operator-examine.o \ + operator-select.o operator-create.o operator-delete.o operator-list.o \ + operator-lsub.o operator-namespace.o operator-rename.o \ + operator-status.o operator-subscribe.o operator-unsubscribe.o \ + operator-check.o operator-close.o operator-copy.o operator-expunge.o \ + operator-fetch.o operator-search.o operator-store.o pendingupdates.o \ + recursivedescent.o regmatch.o session.o session-initialize-bincimapd.o \ + status.o stdiodevice.o syslogdevice.o tools.o + +bincimap_up_OBJECTS = bincimap-up.o argparser.o authenticate.o base64.o \ + broker.o convert.o greeting.o imapparser.o imapserver.o iodevice.o \ + iofactory.o multilogdevice.o operator-authenticate.o \ + operator-capability.o operator-id.o operator-noop.o operator-login.o \ + operator-logout.o operator-starttls.o recursivedescent.o \ + session.o session-initialize-bincimap-up.o status.o stdiodevice.o \ + syslogdevice.o tools.o + +bincimap_updatecache_OBJECTS = bincimap-updatecache.o address.o argparser.o \ + convert.o depot.o imapparser.o iodevice.o \ + iofactory.o mailbox.o maildir.o maildir-scan.o maildir-create.o \ + maildir-close.o maildir-expunge.o maildir-readcache.o \ + maildir-scanfilesnames.o maildir-select.o maildir-updateflags.o \ + maildir-writecache.o maildir-delete.o maildirmessage.o mime.o \ + mime-parsefull.o mime-parseonlyheader.o mime-printdoc.o \ + mime-printbody.o mime-printheader.o mime-getpart.o pendingupdates.o \ + session.o status.o + +.PHONY: default +default: it-base +it-base: bincimapd bincimap-updatecache bincimap-up + +bincimapd: $(bincimapd_OBJECTS) + $(CXX) $(CXXFLAGS) -o $@ $(bincimapd_OBJECTS) +bincimap-up: $(bincimap_up_OBJECTS) + $(CXX) $(CXXFLAGS) -o $@ $(bincimap_up_OBJECTS) +bincimap-updatecache: $(bincimap_updatecache_OBJECTS) + $(CXX) $(CXXFLAGS) -o $@ $(bincimap_updatecache_OBJECTS) + +.SUFFIXES: +.SUFFIXES: .cc .o + +.cc.o: + $(CXX) $(CXXFLAGS) -o $@ -c $< + +.PHONY: clean +clean: + rm -f `cat TARGETS` + +# Header depends +address.o: address.cc include/address.h include/convert.h include/depot.h + +argparser.o: argparser.cc include/argparser.h include/convert.h \ + include/address.h include/depot.h + +authenticate.o: authenticate.cc include/authenticate.h include/depot.h \ + include/iodevice.h include/convert.h include/address.h \ + include/iofactory.h include/session.h include/argparser.h \ + include/globals.h + +base64.o: base64.cc include/base64.h + +bincimap-up.o: bincimap-up.cc include/imapserver.h + +bincimap-updatecache.o: bincimap-updatecache.cc include/depot.h \ + include/mailbox.h include/imapparser.h include/maildir.h \ + include/maildirmessage.h include/message.h include/address.h \ + include/mime.h include/session.h include/argparser.h + +bincimapd.o: bincimapd.cc include/imapserver.h + +broker.o: broker.cc include/broker.h include/depot.h include/operators.h \ + include/imapparser.h include/message.h include/convert.h \ + include/address.h include/recursivedescent.h include/session.h \ + include/argparser.h + +convert.o: convert.cc include/convert.h include/address.h include/depot.h + +depot.o: depot.cc include/depot.h include/mailbox.h include/imapparser.h \ + include/status.h include/convert.h include/address.h \ + include/iodevice.h include/iofactory.h + +greeting.o: greeting.cc include/iodevice.h include/convert.h \ + include/address.h include/depot.h include/iofactory.h \ + include/session.h include/argparser.h include/globals.h + +imapparser.o: imapparser.cc include/imapparser.h include/convert.h \ + include/address.h include/depot.h + +imapserver.o: imapserver.cc include/broker.h include/depot.h \ + include/operators.h include/imapparser.h include/message.h \ + include/globals.h include/imapserver.h include/iodevice.h \ + include/convert.h include/address.h include/iofactory.h \ + include/session.h include/argparser.h + +iodevice.o: iodevice.cc include/iodevice.h include/convert.h \ + include/address.h include/depot.h include/session.h \ + include/argparser.h + +iofactory.o: iofactory.cc include/iofactory.h include/iodevice.h \ + include/convert.h include/address.h include/depot.h + +mailbox.o: mailbox.cc include/mailbox.h include/imapparser.h \ + include/message.h + +maildir-close.o: maildir-close.cc include/maildir.h include/mailbox.h \ + include/imapparser.h include/maildirmessage.h include/message.h \ + include/address.h include/mime.h + +maildir-create.o: maildir-create.cc include/maildir.h include/mailbox.h \ + include/imapparser.h include/maildirmessage.h include/message.h \ + include/address.h include/mime.h + +maildir-delete.o: maildir-delete.cc include/maildir.h include/mailbox.h \ + include/imapparser.h include/maildirmessage.h include/message.h \ + include/address.h include/mime.h + +maildir-expunge.o: maildir-expunge.cc include/iodevice.h \ + include/convert.h include/address.h include/depot.h \ + include/iofactory.h include/maildir.h include/mailbox.h \ + include/imapparser.h include/maildirmessage.h include/message.h \ + include/mime.h + +maildir-readcache.o: maildir-readcache.cc include/maildir.h \ + include/mailbox.h include/imapparser.h include/maildirmessage.h \ + include/message.h include/address.h include/mime.h include/convert.h \ + include/depot.h include/globals.h + +maildir-scan.o: maildir-scan.cc include/iodevice.h include/convert.h \ + include/address.h include/depot.h include/iofactory.h \ + include/maildir.h include/mailbox.h include/imapparser.h \ + include/maildirmessage.h include/message.h include/mime.h + +maildir-scanfilesnames.o: maildir-scanfilesnames.cc include/maildir.h \ + include/mailbox.h include/imapparser.h include/maildirmessage.h \ + include/message.h include/address.h include/mime.h include/iodevice.h \ + include/convert.h include/depot.h include/iofactory.h + +maildir-select.o: maildir-select.cc include/maildir.h include/mailbox.h \ + include/imapparser.h include/maildirmessage.h include/message.h \ + include/address.h include/mime.h + +maildir-updateflags.o: maildir-updateflags.cc include/maildir.h \ + include/mailbox.h include/imapparser.h include/maildirmessage.h \ + include/message.h include/address.h include/mime.h include/iodevice.h \ + include/convert.h include/depot.h include/iofactory.h + +maildir-writecache.o: maildir-writecache.cc include/globals.h \ + include/maildir.h include/mailbox.h include/imapparser.h \ + include/maildirmessage.h include/message.h include/address.h \ + include/mime.h + +maildir.o: maildir.cc include/convert.h include/address.h include/depot.h \ + include/iodevice.h include/iofactory.h include/maildir.h \ + include/mailbox.h include/imapparser.h include/maildirmessage.h \ + include/message.h include/mime.h include/pendingupdates.h \ + include/session.h include/argparser.h include/status.h \ + include/globals.h + +maildirmessage.o: maildirmessage.cc include/maildir.h include/mailbox.h \ + include/imapparser.h include/maildirmessage.h include/message.h \ + include/address.h include/mime.h include/convert.h include/depot.h \ + include/iodevice.h include/iofactory.h include/mime-utils.h \ + include/mime-inputsource.h + +mime-getpart.o: mime-getpart.cc include/mime.h include/convert.h \ + include/address.h include/depot.h + +mime-parsefull.o: mime-parsefull.cc include/mime.h include/mime-utils.h \ + include/mime-inputsource.h include/convert.h include/address.h \ + include/depot.h + +mime-parseonlyheader.o: mime-parseonlyheader.cc include/mime.h \ + include/mime-utils.h include/mime-inputsource.h include/convert.h \ + include/address.h include/depot.h + +mime-printbody.o: mime-printbody.cc include/mime.h include/mime-utils.h \ + include/mime-inputsource.h include/convert.h include/address.h \ + include/depot.h include/iodevice.h include/iofactory.h + +mime-printdoc.o: mime-printdoc.cc include/mime.h include/mime-utils.h \ + include/mime-inputsource.h include/convert.h include/address.h \ + include/depot.h include/iodevice.h include/iofactory.h + +mime-printheader.o: mime-printheader.cc include/mime.h \ + include/mime-utils.h include/mime-inputsource.h include/convert.h \ + include/address.h include/depot.h include/iodevice.h \ + include/iofactory.h + +mime.o: mime.cc include/mime.h include/convert.h include/address.h \ + include/depot.h + +multilogdevice.o: multilogdevice.cc include/multilogdevice.h \ + include/iodevice.h include/convert.h include/address.h include/depot.h + +operator-append.o: operator-append.cc include/depot.h include/iodevice.h \ + include/convert.h include/address.h include/iofactory.h \ + include/mailbox.h include/imapparser.h include/operators.h \ + include/message.h include/recursivedescent.h include/pendingupdates.h \ + include/session.h include/argparser.h + +operator-authenticate.o: operator-authenticate.cc include/authenticate.h \ + include/depot.h include/base64.h include/convert.h include/address.h \ + include/iodevice.h include/iofactory.h include/globals.h \ + include/operators.h include/imapparser.h include/message.h \ + include/recursivedescent.h include/session.h include/argparser.h + +operator-capability.o: operator-capability.cc include/depot.h \ + include/iodevice.h include/convert.h include/address.h \ + include/iofactory.h include/operators.h include/imapparser.h \ + include/message.h include/recursivedescent.h include/session.h \ + include/argparser.h include/globals.h + +operator-check.o: operator-check.cc include/depot.h include/mailbox.h \ + include/imapparser.h include/operators.h include/message.h \ + include/recursivedescent.h include/pendingupdates.h include/session.h \ + include/argparser.h + +operator-close.o: operator-close.cc include/depot.h include/mailbox.h \ + include/imapparser.h include/operators.h include/message.h \ + include/recursivedescent.h include/session.h include/argparser.h + +operator-copy.o: operator-copy.cc include/depot.h include/iodevice.h \ + include/convert.h include/address.h include/iofactory.h \ + include/maildir.h include/mailbox.h include/imapparser.h \ + include/maildirmessage.h include/message.h include/mime.h \ + include/operators.h include/recursivedescent.h include/session.h \ + include/argparser.h + +operator-create.o: operator-create.cc include/depot.h include/mailbox.h \ + include/imapparser.h include/operators.h include/message.h \ + include/recursivedescent.h include/session.h include/argparser.h \ + include/convert.h include/address.h + +operator-delete.o: operator-delete.cc include/depot.h include/mailbox.h \ + include/imapparser.h include/operators.h include/message.h \ + include/recursivedescent.h include/session.h include/argparser.h \ + include/convert.h include/address.h + +operator-examine.o: operator-examine.cc include/operators.h \ + include/imapparser.h include/depot.h include/message.h + +operator-expunge.o: operator-expunge.cc include/depot.h include/mailbox.h \ + include/imapparser.h include/operators.h include/message.h \ + include/recursivedescent.h include/pendingupdates.h include/session.h \ + include/argparser.h + +operator-fetch.o: operator-fetch.cc include/depot.h include/iodevice.h \ + include/convert.h include/address.h include/iofactory.h \ + include/mailbox.h include/imapparser.h include/operators.h \ + include/message.h include/pendingupdates.h include/recursivedescent.h \ + include/session.h include/argparser.h + +operator-id.o: operator-id.cc include/depot.h include/iodevice.h \ + include/convert.h include/address.h include/iofactory.h \ + include/operators.h include/imapparser.h include/message.h \ + include/recursivedescent.h include/session.h include/argparser.h \ + include/globals.h + +operator-idle.o: operator-idle.cc include/iodevice.h include/convert.h \ + include/address.h include/depot.h include/iofactory.h \ + include/globals.h include/mailbox.h include/imapparser.h \ + include/operators.h include/message.h include/pendingupdates.h \ + include/recursivedescent.h include/session.h include/argparser.h + +operator-list.o: operator-list.cc include/convert.h include/address.h \ + include/depot.h include/iodevice.h include/iofactory.h \ + include/mailbox.h include/imapparser.h include/operators.h \ + include/message.h include/recursivedescent.h include/regmatch.h \ + include/session.h include/argparser.h + +operator-login.o: operator-login.cc include/authenticate.h \ + include/depot.h include/iodevice.h include/convert.h include/address.h \ + include/iofactory.h include/globals.h include/operators.h \ + include/imapparser.h include/message.h include/recursivedescent.h \ + include/session.h include/argparser.h + +operator-logout.o: operator-logout.cc include/iodevice.h \ + include/convert.h include/address.h include/depot.h \ + include/iofactory.h include/mailbox.h include/imapparser.h \ + include/recursivedescent.h include/operators.h include/message.h \ + include/session.h include/argparser.h + +operator-lsub.o: operator-lsub.cc include/convert.h include/address.h \ + include/depot.h include/iodevice.h include/iofactory.h \ + include/mailbox.h include/imapparser.h include/operators.h \ + include/message.h include/recursivedescent.h include/regmatch.h \ + include/session.h include/argparser.h + +operator-namespace.o: operator-namespace.cc include/depot.h \ + include/iodevice.h include/convert.h include/address.h \ + include/iofactory.h include/operators.h include/imapparser.h \ + include/message.h include/recursivedescent.h include/session.h \ + include/argparser.h + +operator-noop-pending.o: operator-noop-pending.cc include/mailbox.h \ + include/imapparser.h include/pendingupdates.h \ + include/recursivedescent.h include/operators.h include/depot.h \ + include/message.h include/session.h include/argparser.h + +operator-noop.o: operator-noop.cc include/recursivedescent.h \ + include/imapparser.h include/operators.h include/depot.h \ + include/message.h include/session.h include/argparser.h + +operator-logout.o: operator-logout.cc include/iodevice.h \ + include/depot.h include/mailbox.h include/imapparser.h \ + include/operators.h include/message.h include/recursivedescent.h \ + include/session.h include/argparser.h + +operator-search.o: operator-search.cc include/convert.h include/address.h \ + include/depot.h include/imapparser.h include/iodevice.h \ + include/iofactory.h include/mailbox.h include/mime.h \ + include/operators.h include/message.h include/recursivedescent.h \ + include/session.h include/argparser.h + +operator-select.o: operator-select.cc include/depot.h include/iodevice.h \ + include/convert.h include/address.h include/iofactory.h \ + include/mailbox.h include/imapparser.h include/operators.h \ + include/message.h include/recursivedescent.h include/pendingupdates.h \ + include/session.h include/argparser.h + +operator-starttls.o: operator-starttls.cc include/recursivedescent.h \ + include/imapparser.h include/operators.h include/depot.h \ + include/message.h include/iodevice.h include/convert.h \ + include/address.h include/iofactory.h include/session.h \ + include/argparser.h + +operator-status.o: operator-status.cc include/convert.h include/address.h \ + include/depot.h include/iodevice.h include/iofactory.h \ + include/mailbox.h include/imapparser.h include/operators.h \ + include/message.h include/recursivedescent.h include/session.h \ + include/argparser.h include/status.h + +operator-store.o: operator-store.cc include/depot.h include/imapparser.h \ + include/mailbox.h include/operators.h include/message.h \ + include/pendingupdates.h include/recursivedescent.h include/session.h \ + include/argparser.h + +operator-subscribe.o: operator-subscribe.cc include/convert.h \ + include/address.h include/depot.h include/recursivedescent.h \ + include/imapparser.h include/operators.h include/message.h \ + include/session.h include/argparser.h + +operator-unsubscribe.o: operator-unsubscribe.cc include/convert.h \ + include/address.h include/depot.h include/recursivedescent.h \ + include/imapparser.h include/operators.h include/message.h \ + include/session.h include/argparser.h + +pendingupdates.o: pendingupdates.cc include/iodevice.h include/convert.h \ + include/address.h include/depot.h include/iofactory.h \ + include/mailbox.h include/imapparser.h include/message.h \ + include/pendingupdates.h include/session.h include/argparser.h + +recursivedescent.o: recursivedescent.cc include/imapparser.h \ + include/recursivedescent.h include/operators.h include/depot.h \ + include/message.h include/iodevice.h include/convert.h \ + include/address.h include/iofactory.h include/session.h \ + include/argparser.h + +regmatch.o: regmatch.cc include/regmatch.h + +session-initialize-bincimap-up.o: session-initialize-bincimap-up.cc \ + include/broker.h include/depot.h include/operators.h \ + include/imapparser.h include/message.h include/convert.h \ + include/address.h include/globals.h include/iodevice.h \ + include/iofactory.h include/multilogdevice.h include/syslogdevice.h \ + include/stdiodevice.h include/session.h include/argparser.h \ + include/tools.h + +session-initialize-bincimapd.o: session-initialize-bincimapd.cc \ + include/broker.h include/depot.h include/operators.h \ + include/imapparser.h include/message.h include/maildir.h \ + include/mailbox.h include/maildirmessage.h include/address.h \ + include/mime.h include/globals.h include/iodevice.h include/convert.h \ + include/iofactory.h include/multilogdevice.h include/session.h \ + include/argparser.h include/stdiodevice.h include/syslogdevice.h \ + include/tools.h + +session.o: session.cc include/argparser.h include/convert.h \ + include/address.h include/depot.h include/globals.h include/session.h \ + include/tools.h + +status.o: status.cc include/status.h + +stdiodevice.o: stdiodevice.cc include/stdiodevice.h include/iodevice.h \ + include/convert.h include/address.h include/depot.h + +syslogdevice.o: syslogdevice.cc include/syslogdevice.h include/iodevice.h \ + include/convert.h include/address.h include/depot.h + +tools.o: tools.cc include/tools.h diff --git a/src/TARGETS b/src/TARGETS new file mode 100644 index 0000000..e286796 --- /dev/null +++ b/src/TARGETS @@ -0,0 +1,76 @@ +address.o +argparser.o +authenticate.o +base64.o +bincimap-up.o +bincimap-up +bincimapd.o +bincimapd +bincimap-updatecache.o +bincimap-updatecache +broker.o +convert.o +depot.o +greeting.o +imapparser.o +imapserver.o +iodevice.o +iofactory.o +mailbox.o +maildir-close.o +maildir-create.o +maildir-delete.o +maildir-expunge.o +maildir-readcache.o +maildir-scan.o +maildir-scanfilesnames.o +maildir-select.o +maildir-updateflags.o +maildir-writecache.o +maildir.o +maildirmessage.o +mime-getpart.o +mime-parsefull.o +mime-parseonlyheader.o +mime-printbody.o +mime-printdoc.o +mime-printheader.o +mime.o +multilogdevice.o +operator-append.o +operator-authenticate.o +operator-capability.o +operator-check.o +operator-close.o +operator-copy.o +operator-create.o +operator-delete.o +operator-examine.o +operator-expunge.o +operator-fetch.o +operator-idle.o +operator-list.o +operator-login.o +operator-logout.o +operator-lsub.o +operator-namespace.o +operator-noop-pending.o +operator-noop.o +operator-rename.o +operator-search.o +operator-select.o +operator-starttls.o +operator-status.o +operator-store.o +operator-subscribe.o +operator-unsubscribe.o +pendingupdates.o +recursivedescent.o +regmatch.o +session-initialize-bincimap-up.o +session-initialize-bincimapd.o +session.o +status.o +stdiodevice.o +syslogdevice.o +tools.o diff --git a/src/address.cc b/src/address.cc new file mode 100644 index 0000000..7739500 --- /dev/null +++ b/src/address.cc @@ -0,0 +1,65 @@ +/** ------------------------------------------------------------------- + * @file address.cc + * @brief Implementation of the Address class + * @author Andreas Aardal Hanssen + * @date 2005 + * ---------------------------------------------------------------- **/ +#include "address.h" +#include "convert.h" +#include <string> + +using namespace ::std; +using namespace Binc; + +//------------------------------------------------------------------------ +Address::Address(const string &name, const string &addr) +{ + string::size_type pos = addr.find('@'); + this->name = name; + if (pos != string::npos) { + this->local = addr.substr(0, pos); + this->host = addr.substr(pos + 1); + } else this->local = addr; +} + +//------------------------------------------------------------------------ +Address::Address(const string &wholeaddress) +{ + string::size_type start = wholeaddress.find('<'); + string addr; + if (start != string::npos) + addr = wholeaddress.substr(start + 1); + else + addr = wholeaddress; + + trim(addr, "<>"); + + if (start != string::npos) + name = wholeaddress.substr(0, start); + else + name = ""; + trim(name); + trim(name, "\""); + + start = addr.find('@'); + local = addr.substr(0, start); + host = addr.substr(start + 1); + + trim(local); + trim(host); + trim(name); +} + +//------------------------------------------------------------------------ +string Address::toParenList(void) const +{ + string tmp = "("; + tmp += name == "" ? "NIL" : toImapString(name); + tmp += " NIL "; + tmp += local == "" ? "\"\"" : toImapString(local); + tmp += " "; + tmp += host == "" ? "\"\"" : toImapString(host); + tmp += ")"; + + return tmp; +} diff --git a/src/argparser.cc b/src/argparser.cc new file mode 100644 index 0000000..c8b482c --- /dev/null +++ b/src/argparser.cc @@ -0,0 +1,322 @@ +/** -------------------------------------------------------------------- + * @file argparser.cc + * @brief Implementation of the command line argument parser + * @author Andreas Aardal Hanssen + * @date 2005 + * ------------------------------------------------------------------ **/ +#include "argparser.h" +#include "convert.h" + +#include <string> +#include <map> + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +CommandLineArgs::CommandLineArgs() +{ + errString = "Unknown error for args"; + ac = 0; +} + +//---------------------------------------------------------------------- +bool CommandLineArgs::parse(int argc, char *argv[]) +{ + ac = -1; + head = argv[0]; + head += " <options> --\n"; + + if (argc > 1) { + string lastKey; + bool lastIsBoolean = false; + + for (int i = 1; i < argc; ++i) { + string s = argv[i]; + if (s.length() < 2) { + unqualified.push_back(s); + continue; + } + + if (s[0] != '-') { + // read value of last argument + if (lastKey == "") { + unqualified.push_back(s); + continue; + } + + if (lastIsBoolean && (s != "yes" && s != "no")) { + errString = "syntax error: " + s; + errString += " (expected yes or no)"; + return false; + } + + args[lastKey] = s; + passedArgs[lastKey] = true; + lastKey = ""; + lastIsBoolean = false; + } else if (s[1] == '-') { + if (lastKey != "") { + if (lastIsBoolean) { + args[lastKey] = "yes"; + passedArgs[lastKey] = true; + lastKey = ""; + lastIsBoolean = false; + } else { + errString = "expected value of "; + errString += lastKey; + return false; + } + } + + // break if '--' is detected + if (s.length() == 2) { + ac = i + 1; + break; + } + + // parse --argument + string arg = s.substr(2); + string val; + string::size_type epos = arg.find('='); + if (epos != string::npos) { + val = arg.substr(epos + 1); + arg = arg.substr(0, epos); + } + + if (reg.find(arg) == reg.end()) { + errString = "unrecognized argument: --" + arg; + return false; + } + + if (reg.find(arg)->second.b) { + if (val != "" && val != "yes" && val != "no") { + errString = "syntax error: " + val; + errString += " (expected yes or no)"; + return false; + } else if (val == "") { + val = "yes"; + } + } + + if (val == "") { + errString = "syntax error: " + arg; + errString += " (expected --" + arg + "=<str>)"; + return false; + } + + args[arg] = val; + passedArgs[arg] = true; + + lastKey = ""; + lastIsBoolean = false; + } else { + if (lastKey != "") { + if (lastIsBoolean) { + args[lastKey] = "yes"; + passedArgs[lastKey] = true; + lastKey = ""; + lastIsBoolean = false; + } else { + errString = "expected value of "; + errString += lastKey; + return false; + } + } + + // parse -argument + string arg = s.substr(1); + if (arg.length() == 1) { + map<string, ArgOpts>::const_iterator it = reg.begin(); + bool match = false; + for (; it != reg.end(); ++it) { + if (it->second.c.find(arg[0]) != string::npos) { + lastKey = it->first; + if (it->second.b) lastIsBoolean = true; + match = true; + break; + } + } + + if (!match) { + errString = "unrecognized argument: -"; + errString += arg[0]; + return false; + } + } else { + string::const_iterator its = arg.begin(); + for (; its != arg.end(); ++its) { + map<string, ArgOpts>::const_iterator it = reg.begin(); + bool match = false; + for (; it != reg.end(); ++it) { + if (it->second.c.find(*its) != string::npos) { + if (!it->second.b) { + errString = "argument is not a boolean: "; + errString += "--" + it->first; + errString += " / -"; + errString += it->second.c; + return false; + } + + match = true; + args[it->first] = "yes"; + passedArgs[it->first] = true; + + lastKey = ""; + lastIsBoolean = false; + break; + } + } + + if (!match) { + errString = "unrecognized argument: "; + errString += s; + return false; + } + } + } + } + } + + if (lastKey != "") { + if (lastIsBoolean) { + args[lastKey] = "yes"; + passedArgs[lastKey] = true; + } else { + errString = "expected value of "; + errString += lastKey; + return false; + } + } + } + + // assign default "no" values for arguments that were not passed. + map<string, ArgOpts>::const_iterator it = reg.begin(); + for (; it != reg.end(); ++it) { + if (args.find(it->first) == args.end()) { + if (!it->second.o) { + errString = "missing argument: "; + errString += it->first; + return false; + } + if (it->second.b) args[it->first] = "no"; + } + } + if (ac == -1) ac = argc; + + return true; +} + +//---------------------------------------------------------------------- +string CommandLineArgs::errorString(void) const +{ + return errString; +} + +//---------------------------------------------------------------------- +const string CommandLineArgs::operator [](const string &arg) const +{ + if (args.find(arg) == args.end()) return ""; + return args.find(arg)->second; +} + +//---------------------------------------------------------------------- +void CommandLineArgs::addOptional(const string &arg, const string &desc, + bool boolean) +{ + registerArg(arg, desc, boolean, true); +} + +//---------------------------------------------------------------------- +void CommandLineArgs::addRequired(const string &arg, + const string &desc, bool boolean) +{ + registerArg(arg, desc, boolean, false); +} + +//---------------------------------------------------------------------- +void CommandLineArgs::registerArg(const string &arg, const string &desc, + bool boolean, bool optional) +{ + string name = arg; + + string shorts; + while (name.size() > 1 && name[1] == '|') { + shorts += name[0]; + name = name.substr(2); + } + + reg.insert(make_pair(name, ArgOpts(shorts, boolean, optional, desc))); +} + +//---------------------------------------------------------------------- +bool CommandLineArgs::hasArg(const std::string &arg) const +{ + string tmp = arg; lowercase(tmp); + return passedArgs.find(tmp) != passedArgs.end(); +} + +//---------------------------------------------------------------------- +string CommandLineArgs::usageString(void) const +{ + string tmp = head; + tmp += '\n'; + + map<string, ArgOpts>::const_iterator it = reg.begin(); + for (; it != reg.end(); ++it) { + if (it->second.c != "") { + string::const_iterator sit = it->second.c.begin(); + for (; sit != it->second.c.end(); ++sit) { + if (sit != it->second.c.begin()) tmp += '\n'; + tmp += " -"; + tmp += *sit; + } + tmp += ", "; + } else { + tmp += " "; + } + + tmp += "--"; + tmp += it->first; + if (!it->second.b) tmp += "=<str>"; + + if (!it->second.o) tmp += " (required)"; + + string::size_type lineStart = tmp.rfind('\n'); + if (lineStart == string::npos) lineStart = 0; + + int pad = 21 - (tmp.length() - lineStart); + if (pad < 0) { + tmp += '\n'; + pad = 20; + } + + tmp += string(pad, ' '); + tmp += it->second.desc; + tmp += '\n'; + } + + tmp += '\n'; + tmp += tail; + tmp += '\n'; + + return tmp; +} + +//---------------------------------------------------------------------- +int CommandLineArgs::argc(void) const +{ + return ac; +} + +//---------------------------------------------------------------------- +void CommandLineArgs::setTail(const string &str) +{ + tail = str; +} + +//---------------------------------------------------------------------- +const vector<string> &CommandLineArgs::getUnqualifiedArgs() const +{ + return unqualified; +} diff --git a/src/authenticate.cc b/src/authenticate.cc new file mode 100644 index 0000000..a448238 --- /dev/null +++ b/src/authenticate.cc @@ -0,0 +1,358 @@ +/** -------------------------------------------------------------------- + * @file authenticate.cc + * @brief Implementation of the common (C/R) authentication mechanism. + * @author Andreas Aardal Hanssen, Erwin Hoffmann + * @date 2002-2005, 2023 + * ----------------------------------------------------------------- **/ +#include <string> +#include <vector> + +#include <sys/types.h> +#include <grp.h> +#include <pwd.h> +#include <signal.h> +#include <unistd.h> +#include <errno.h> + +// #ifndef HAVE_SYS_WAIT_H +// #include <wait.h> +//#else +#include <sys/wait.h> +//#endif + +#include "authenticate.h" +#include "iodevice.h" +#include "iofactory.h" +#include "session.h" +#include "convert.h" +#include "globals.h" + +using namespace ::std; +using namespace Binc; + +// 0 = ok +// 1 = internal error +// 2 = failed +// 3 = timeout +// -1 = abort +//------------------------------------------------------------------------ +int Binc::authenticate(Depot &depot, const string &username, + const string &password, const string &challenge) +{ + Session &session = Session::getInstance(); + session.setUserID(username); + + // check if checkpassword is present + if (::access(session.unparsedArgs[0], X_OK) != 0) { // x is enough + bincError << "unable to start authenticator " << session.unparsedArgs[0] + << ": " << strerror(errno) << endl; + return 1; + } + + // The information supplied on descriptor 3 is a login name + // terminated by \0, a password terminated by \0, a timestamp + // terminated by \0, and possibly more data. There are no other + // restrictions on the form of the login name, password, and + // timestamp. + int authintercom[2]; + int intercomw[2]; + int intercomr[2]; + bool authenticated = false; + + if (pipe(authintercom) == -1) { + session.setLastError("An error occurred when creating pipes: " + + string(strerror(errno))); + return -1; + } + + if (pipe(intercomw) == -1) { + session.setLastError("An error occurred when creating pipes: " + + string(strerror(errno))); + close(authintercom[0]); + close(authintercom[1]); + return -1; + } + + if (pipe(intercomr) == -1) { + session.setLastError("An error occurred when creating pipes: " + + string(strerror(errno))); + close(intercomw[0]); + close(intercomr[0]); + close(authintercom[0]); + close(authintercom[1]); + return -1; + } + + string timestamp; + time_t t = time(0); + char *c; + if ((c = ctime(&t)) != 0) { + timestamp = c; + trim(timestamp); + } else + timestamp = "unknown timestamp"; + + string pid = to_string(session.getPid()); + + // execute authentication module + int result; + int childspid = fork(); + if (childspid == -1) { + bincLog << "bincimap-up: pid " << pid + << " failed to start main server: " + << strerror(errno) << endl; + return 1; + } + + if (childspid == 0) { + close(authintercom[1]); + close(intercomr[0]); + close(intercomw[1]); + + if (dup2(intercomr[1], 1) == -1) { + bincDebug << "bincimap-up: pid " << pid + << " authenticate(), [auth module] dup2 failed: " + << strerror(errno) << endl; + bincDebug.flush(); + exit(111); + } + + if (dup2(intercomw[0], 0) == -1) { + bincDebug << "bincimap-up: pid " << pid + << " authenticate(), [auth module] dup2 failed: " + << strerror(errno) << endl; + bincDebug.flush(); + exit(111); + } + + if (dup2(authintercom[0], 3) == -1) { + bincDebug << "bincimap-up: pid " << pid + << " authenticate(), [auth module] dup2 failed: " + << strerror(errno) << endl; + bincDebug.flush(); + exit(111); + } + + if (session.unparsedArgs[0] != 0) { + execvp(session.unparsedArgs[0], &session.unparsedArgs[0]); + bincDebug << "bincimap-up: pid " << pid + << " authenticate(), [auth module] invocation of " + << session.unparsedArgs[0] + << " failed: " << strerror(errno) << endl; + bincDebug.flush(); + exit(111); + } + + bincLog << "bincimap-up: pid " << pid + << " missing mandatory -- in argument list," + " after bincimap-up + arguments, before authenticator." + " Please check your run scripts and the man page bincimap(1) for" + " more on how to invoke Binc IMAP." << endl; + bincDebug.flush(); + exit(111); + } + + close(authintercom[0]); + + // create the string of data to be passed to the checkpassword stub + int dataSize = username.length() + password.length() + challenge.length() + timestamp.length(); + dataSize += 4; + char *checkpasswordData = new char[dataSize]; + char *cpTmp = checkpasswordData; + strcpy(cpTmp, username.c_str()); + cpTmp += username.length(); + *cpTmp++ = '\0'; + strcpy(cpTmp, password.c_str()); + cpTmp += password.length(); + *cpTmp++ = '\0'; + // add challenge + strcpy(cpTmp, challenge.c_str()); + cpTmp += challenge.length(); + *cpTmp++ = '\0'; + strcpy(cpTmp, timestamp.c_str()); + cpTmp += timestamp.length(); + *cpTmp++ = '\0'; + + bincDebug << "bincimap-up: pid " << pid + << " authenticate(), writing username/password to " + << session.unparsedArgs[0] << endl; + + // write the userid + signal(SIGPIPE, SIG_IGN); + int res = write(authintercom[1], checkpasswordData, dataSize); + delete[] checkpasswordData; + if (res != dataSize) { + bincWarning << "bincimap-up: pid " << pid + << " error writing to authenticator " + << session.unparsedArgs[0] << ": " + << strerror(errno) << endl; + return 1; + } + + // close the write channel. this is necessary for the checkpassword + // module to see an EOF. + close(authintercom[1]); + close(intercomr[1]); + close(intercomw[0]); + + fd_set rmask; + FD_ZERO(&rmask); + FD_SET(fileno(stdin), &rmask); + FD_SET(intercomr[0], &rmask); + + int maxfd = intercomr[0]; + bool disconnected = false; + bool timedout = false; + bincClient.clearFlags(IODevice::HasInputLimit); + + bool eof = false; + while (!eof) { + fd_set rtmp = rmask; + struct timeval timeout; + + // time out 5 minutes after the idle timeout. we expect the main + // server to time out at the right time, but will shut down at + // T+5m in case of a server lockup. + timeout.tv_sec = IDLE_TIMEOUT + AUTH_PENALTY * AUTH_TIMEOUT; + timeout.tv_usec = 0; + + // select sometimes returns when we attach to the process with + // tracing tools such as ktrace and strace, setting errno to + // EINTR. + int n; + do { + n = select(maxfd + 1, &rtmp, 0, 0, &timeout); + } while (n < 0 && errno == EINTR); + + if (n < 0) { + bincWarning << "bincimpa-up: pid " << pid + << " error: invalid exit from select, " + << strerror(errno) << endl; + break; + } + + if (n == 0) { + bincLog << "bincimap-up: pid " << pid + << " server timed out after " + << IDLE_TIMEOUT << " seconds" << endl; + timedout = true; + break; + } + + if (FD_ISSET(fileno(stdin), &rtmp)) { + authenticated = true; + + do { + string data; + int ret = bincClient.readStr(&data); + if (ret == 0 || ret == -1) { + session.setLastError("client disconnected"); + eof = true; + disconnected = true; + break; + } + + // Fall through. Triggered when there was no data + // to read, even though no error has occurred + if (ret == -2) continue; + + int w; + do { + w = write(intercomw[1], data.c_str(), data.length()); + } while (w < 0 && errno == EINTR); + + if (w > 0) Session::getInstance().addReadBytes(w); + + if (w < 0) { + bincDebug << "bincimap-up: pid " << pid + << " error writing to server: " + << strerror(errno) << endl; + eof = true; + } + } while (bincClient.canRead()); + } + + if (FD_ISSET(intercomr[0], &rtmp)) { + char buf[8192]; + int ret = read(intercomr[0], buf, sizeof(buf)); + if (ret == 0) { + // Main server has shut down + eof = true; + break; + } else if (ret == -1) { + bincDebug << "bincimap-up: pid " << pid + << " error reading from server: " + << strerror(errno) << endl; + eof = true; + break; + } else { + // umask(0); + Session::getInstance().addWriteBytes(ret); + + bincClient << string(buf, ret); + bincClient.flush(); + } + } + } + + close(intercomr[0]); + close(intercomw[1]); + + // catch the dead baby + if (waitpid(childspid, &result, 0) != childspid) { + bincLog << "bincimap-up: pid " << pid + << " <" << username << "> authentication failed: " + << (authenticated ? "server " : session.unparsedArgs[0]) + << " waitpid returned unexpected value" << endl; + string tmp = strerror(errno); + + return -1; + } + + // if the server died because we closed the sockets after a timeout, + // exit 3. + if (timedout) return 3; + + if (disconnected) return 0; + + if (WIFSIGNALED(result)) { + bincLog << "bincimap-up: pid " << pid + << " <" << username << "> authentication failed: " + << (authenticated ? "server" : session.unparsedArgs[0]) + << " died by signal " << WTERMSIG(result) << endl; + sleep(AUTH_PENALTY); + session.setState(Session::LOGOUT); + return -1; + } + + bincDebug << "bincimap-up: pid " << pid + << " authenticate() ," + << (authenticated ? "authenticator" : "server") + << " exited with code " << WEXITSTATUS(result) << endl; + + switch (WEXITSTATUS(result)) { + case 0: break; + case 1: + // authentication failed - sleep + bincLog << "bincimap-up: pid " << pid + << " <" << username << "> failed to log in" << endl; + sleep(AUTH_PENALTY); + return 2; + case 2: case 111: // wrong call or missing auth data + // abused + bincLog << "bincimap-up: pid " << pid + << " <" << username << "> authentication failed: " + << (authenticated ? "authenticator" : "server") + << " reports wrong usage" << endl; + return -1; + default: + // internal error -- or authenticator fooled us + bincLog << "bincimap-up: pid " << pid + << " <" << username << "> authentication failed: " + << (authenticated ? "authenticator" : "server") + << " returned " << WEXITSTATUS(result) << endl; + return -1; + } + + return 0; +} diff --git a/src/base64.cc b/src/base64.cc new file mode 100644 index 0000000..367a3e4 --- /dev/null +++ b/src/base64.cc @@ -0,0 +1,121 @@ +/** -------------------------------------------------------------------- + * @file base64.cc + * @brief Implementation of base64 Utilities + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "base64.h" +#include <string> +#include <iostream> + +using namespace ::std; + +typedef unsigned char bbyte; /* Byte type */ + +#define TRUE 1 +#define FALSE 0 + +#define LINELEN 72 /* Encoded line length (max 76) */ + +static bbyte dtable[256]; + +string Binc::base64encode(const string &s_in) +{ + int i; + string result; + + /* Fill dtable with character encodings. */ + + for (i = 0; i < 26; i++) { + dtable[i] = 'A' + i; + dtable[26 + i] = 'a' + i; + } + for (i = 0; i < 10; i++) { + dtable[52 + i] = '0' + i; + } + dtable[62] = '+'; + dtable[63] = '/'; + + string::const_iterator s_i = s_in.begin(); + while (s_i != s_in.end()) { + + bbyte igroup[3], ogroup[4]; + int c, n; + + igroup[0] = igroup[1] = igroup[2] = 0; + for (n = 0; n < 3 && s_i != s_in.end(); n++) { + c = *s_i++; + igroup[n] = (bbyte) c; + } + if (n > 0) { + ogroup[0] = dtable[igroup[0] >> 2]; + ogroup[1] = dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)]; + ogroup[2] = dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)]; + ogroup[3] = dtable[igroup[2] & 0x3F]; + + /* Replace characters in output stream with "=" pad + characters if fewer than three characters were + read from the end of the input stream. */ + + if (n < 3) { + ogroup[3] = '='; + if (n < 2) { + ogroup[2] = '='; + } + } + + for (i = 0; i < 4; i++) + result += ogroup[i]; + } + } + + return result; +} + +string Binc::base64decode(const string &s_in) +{ + string result; + int i; + + for (i = 0; i < 255; i++) { + dtable[i] = 0x80; + } + for (i = 'A'; i <= 'Z'; i++) { + dtable[i] = 0 + (i - 'A'); + } + for (i = 'a'; i <= 'z'; i++) { + dtable[i] = 26 + (i - 'a'); + } + for (i = '0'; i <= '9'; i++) { + dtable[i] = 52 + (i - '0'); + } + dtable[(int) '+'] = 62; + dtable[(int) '/'] = 63; + dtable[(int) '='] = 0; + + /*CONSTANTCONDITION*/ + string::const_iterator s_i = s_in.begin(); + while (s_i != s_in.end()) { + bbyte a[4], b[4], o[3]; + + for (i = 0; i < 4 && s_i != s_in.end(); i++) { + int c = *s_i++; + if (dtable[c] & 0x80) return result; + a[i] = (bbyte) c; + b[i] = (bbyte) dtable[c]; + } + + o[0] = (b[0] << 2) | (b[1] >> 4); + o[1] = (b[1] << 4) | (b[2] >> 2); + o[2] = (b[2] << 6) | b[3]; + + i = a[2] == '=' ? 1 : (a[3] == '=' ? 2 : 3); + + for (int j = 0; j < i; ++j) + result += o[j]; + + if (i < 3) break; + } + + return result; +} diff --git a/src/bincimap-up.cc b/src/bincimap-up.cc new file mode 100644 index 0000000..77576de --- /dev/null +++ b/src/bincimap-up.cc @@ -0,0 +1,16 @@ +/** -------------------------------------------------------------------- + * @file bincimap-up.cc + * @brief Implementation of the preauthenticated bincimap stub + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ------------------------------------------------------------------ **/ +#include "imapserver.h" + +//------------------------------------------------------------------------ +int main(int argc, char *argv[]) +{ + Binc::IMAPServer imapServer(argc, argv); + int initResult = imapServer.initialize(); + if (initResult != 0) return initResult; + return imapServer.runStub(); +} diff --git a/src/bincimap-updatecache.cc b/src/bincimap-updatecache.cc new file mode 100644 index 0000000..4efe723 --- /dev/null +++ b/src/bincimap-updatecache.cc @@ -0,0 +1,61 @@ +/** -------------------------------------------------------------------- + * @file bincimap-updatecache.cc + * @brief Implementation of the bincimap-updatecache tool. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "depot.h" +#include "mailbox.h" +#include "maildir.h" +#include "session.h" + +using namespace ::Binc; +using namespace ::std; + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + fprintf(stderr, "usage: %s <directory> ", argv[0]); + fprintf(stderr, "Updates the cache file in <directory>.\n"); + fprintf(stderr, "Use export $DEPOT=\"IMAPdir\" to enable updates for this type.\n"); + return 1; + } + + Session &session = Session::getInstance(); + + DepotFactory &depotfactory = DepotFactory::getInstance(); + depotfactory.assign(new IMAPdirDepot()); + depotfactory.assign(new MaildirPPDepot()); + + string depottype = session.getEnv("DEPOT"); + if (depottype == "") depottype = "Maildir++"; + + Depot *depot; + if ((depot = depotfactory.get(depottype)) == 0) { + fprintf(stderr, "Found no Depot for \"%s\". Please check " \ + " your configurations file under the Mailbox section.\n", + depottype.c_str()); + return 1; + } + + depot->assign(new Maildir()); + depot->setDefaultType("Maildir"); + + Mailbox *mailbox = depot->get(depot->filenameToMailbox(argv[1])); + + if (!mailbox) { + fprintf(stderr, "selecting mailbox failed: %s\n", + depot->getLastError().c_str()); + return 1; + } + + if (!mailbox->selectMailbox(argv[1], argv[1])) { + fprintf(stderr, "selecting mailbox failed: %s\n", + mailbox->getLastError().c_str()); + return 1; + } + + mailbox->closeMailbox(); + + return 0; +} diff --git a/src/bincimapd.cc b/src/bincimapd.cc new file mode 100644 index 0000000..d4d8508 --- /dev/null +++ b/src/bincimapd.cc @@ -0,0 +1,21 @@ +/** -------------------------------------------------------------------- + * @file bincimapd.cc + * @brief Implementation of the main bincimapd service + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "imapserver.h" + +namespace Binc { + void showGreeting(void) + { + } +} + +int main(int argc, char *argv[]) +{ + Binc::IMAPServer imapServer(argc, argv); + int initResult = imapServer.initialize(); + if (initResult != 0) return initResult; + return imapServer.run(); +} diff --git a/src/broker.cc b/src/broker.cc new file mode 100644 index 0000000..9d7f728 --- /dev/null +++ b/src/broker.cc @@ -0,0 +1,190 @@ +/** --------------------------------------------------------------------- + * @file broker.cc + * @brief Implementation of the Broker class + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <map> +#include <string> + +#include "broker.h" +#include "convert.h" +#include "operators.h" +#include "recursivedescent.h" +#include "session.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +BrokerFactory::BrokerFactory(void) +{ + brokers[Session::NONAUTHENTICATED] = new Broker(); + brokers[Session::AUTHENTICATED] = new Broker(); + brokers[Session::SELECTED] = new Broker(); +} + +//---------------------------------------------------------------------- +BrokerFactory::~BrokerFactory(void) +{ + for (map<int, Broker *>::iterator i = brokers.begin(); + i != brokers.end(); ++i) + delete i->second; +} + +//---------------------------------------------------------------------- +BrokerFactory &BrokerFactory::getInstance(void) +{ + static BrokerFactory brokerfactory; + return brokerfactory; +} + +//---------------------------------------------------------------------- +void BrokerFactory::addCapability(const std::string &c) +{ + for (map<int, Broker *>::iterator i = brokers.begin(); + i != brokers.end(); ++i) { + CapabilityOperator * o; + o = dynamic_cast<CapabilityOperator*>(i->second->get("CAPABILITY")); + if (o != 0) { + o->addCapability(c); + break; + } + } +} + +//---------------------------------------------------------------------- +void BrokerFactory::assign(const string &fname, Operator *o) +{ + int deletable = true; + for (map<int, Broker *>::iterator i = brokers.begin(); + i != brokers.end(); ++i) + if (i->first & o->getState()) { + i->second->assign(fname, o, deletable); + deletable = false; + } +} + +//---------------------------------------------------------------------- +Operator *BrokerFactory::getOperator(int state, const string &name) const +{ + if (brokers.find(state) == brokers.end()) + return 0; + else + return brokers.find(state)->second->get(name); +} + +//---------------------------------------------------------------------- +Broker *BrokerFactory::getBroker(int state) +{ + if (brokers.find(state) == brokers.end()) { + setLastError("No appropriate broker for state."); + return 0; + } + + return brokers[state]; +} + +//---------------------------------------------------------------------- +Broker::Broker(void) +{ +} + +//---------------------------------------------------------------------- +Broker::~Broker(void) +{ +} + +//---------------------------------------------------------------------- +void Broker::assign(const string &fname, Operator *o, bool deletable) +{ + deletables[fname] = deletable; + operators[fname] = o; +} + +//---------------------------------------------------------------------- +Operator *Broker::get(const string &name) const +{ + if (operators.find(name) == operators.end()) return 0; + + return operators.find(name)->second; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Broker::parseStub(Request &command) +{ + Session &session = Session::getInstance(); + + string tag; + string cmd; + + switch (expectTag(tag)) { + case Operator::ACCEPT: + break; + case Operator::REJECT: + session.setLastError("Syntax error; first token must be a tag"); + case Operator::ERROR: + return Operator::ERROR; + case Operator::TIMEOUT: + return Operator::TIMEOUT; + } + + switch (expectSPACE()) { + case Operator::ACCEPT: + break; + case Operator::REJECT: + session.setLastError("Syntax error; second token must be a SPACE"); + case Operator::ERROR: + return Operator::ERROR; + case Operator::TIMEOUT: + return Operator::TIMEOUT; + } + + switch (expectAstring(cmd)) { + case Operator::ACCEPT: + break; + case Operator::REJECT: + session.setLastError("Syntax error; third token must be a command"); + case Operator::ERROR: + return Operator::ERROR; + case Operator::TIMEOUT: + return Operator::TIMEOUT; + } + + uppercase(cmd); + + if (cmd == "UID") { + command.setUidMode(); + + switch (expectSPACE()) { + case Operator::ACCEPT: + break; + case Operator::REJECT: + session.setLastError("Syntax error; after UID there" + " must come a SPACE"); + case Operator::ERROR: + return Operator::ERROR; + case Operator::TIMEOUT: + return Operator::TIMEOUT; + } + + switch (expectAstring(cmd)) { + case Operator::ACCEPT: + break; + case Operator::REJECT: + session.setLastError("Syntax error; after UID " + "SPACE there must come a command"); + case Operator::ERROR: + return Operator::ERROR; + case Operator::TIMEOUT: + return Operator::TIMEOUT; + } + + uppercase(cmd); + } + + command.setTag(tag); + command.setName(cmd); + + return Operator::ACCEPT; +} diff --git a/src/compare.dirs b/src/compare.dirs new file mode 100644 index 0000000..314b91a --- /dev/null +++ b/src/compare.dirs @@ -0,0 +1,662 @@ +--- ./broker.cc 2023-08-22 22:04:58.667689000 +0200 ++++ ../../bincimap-2.0.5/src//broker.cc 2023-08-14 12:04:05.654922000 +0200 +@@ -105,7 +105,8 @@ + //---------------------------------------------------------------------- + Operator *Broker::get(const string &name) const + { +- if (operators.find(name) == operators.end()) return 0; ++ if (operators.find(name) == operators.end()) ++ return 0; + + return operators.find(name)->second; + } +--- ./imapserver.cc 2023-08-22 11:30:32.929436000 +0200 ++++ ../../bincimap-2.0.5/src//imapserver.cc 2023-08-19 15:30:18.579113000 +0200 +@@ -74,7 +74,6 @@ + showGreeting(); + } else { + bincInfo << "<" << session.getEnv("USER") << "> logged in" << "\n"; +- bincInfo << "<" << getenv("USER") << "> logged in" << "\n"; + } + bincInfo.flush(); + +--- ./iodevice.cc 2023-08-22 13:38:19.325053000 +0200 ++++ ../../bincimap-2.0.5/src//iodevice.cc 2023-08-14 12:59:30.985424000 +0200 +@@ -40,11 +40,13 @@ + + static std::ostream &(*endl_funcptr)(ostream &) = endl; + +- if (source != endl_funcptr) return *this; ++ if (source != endl_funcptr) ++ return *this; + + outputBuffer << "\r\n"; + +- if (dumpfd) ::write(dumpfd, "\r\n", 2); ++ if (dumpfd) ++ ::write(dumpfd, "\r\n", 2); + + if (flags & FlushesOnEndl) + flush(); +@@ -64,7 +66,8 @@ + //------------------------------------------------------------------------ + void IODevice::clear() + { +- if (!(flags & IsEnabled)) return; ++ if (!(flags & IsEnabled)) ++ return; + + inputBuffer.clear(); + outputBuffer.clear(); +@@ -73,20 +76,26 @@ + //------------------------------------------------------------------------ + bool IODevice::flush() + { +- if (!(flags & IsEnabled)) return true; ++ if (!(flags & IsEnabled)) ++ return true; + + WriteResult writeResult = WriteWait; + do { + unsigned int s = outputBuffer.getSize(); +- if (s == 0) break; +- if (!waitForWrite()) return false; ++ if (s == 0) ++ break; ++ ++ if (!waitForWrite()) ++ return false; ++ + writeResult = write(); +- if (writeResult == WriteError) return false; ++ if (writeResult == WriteError) ++ return false; ++ + writeCount += s - outputBuffer.getSize(); + } while (outputBuffer.getSize() > 0 && writeResult == WriteWait); + + outputBuffer.clear(); +- + return true; + } + +@@ -159,11 +168,13 @@ + bool IODevice::readStr(string *dest, unsigned int max) + { + // If max is 0, fill the input buffer once only if it's empty. +- if (!max && inputBuffer.getSize() == 0 && !fillInputBuffer()) return false; ++ if (!max && inputBuffer.getSize() == 0 && !fillInputBuffer()) ++ return false; + + // If max is != 0, wait until we have max. + while (max && inputBuffer.getSize() < max) { +- if (!fillInputBuffer()) return false; ++ if (!fillInputBuffer()) ++ return false; + } + + unsigned int bytesToRead = max ? max : inputBuffer.getSize(); +@@ -174,21 +185,22 @@ + + inputBuffer.popString(bytesToRead); + readCount += bytesToRead; +- + return true; + } + + //------------------------------------------------------------------------ + bool IODevice::readChar(char *dest) + { +- if (inputBuffer.getSize() == 0 && !fillInputBuffer()) return false; ++ if (inputBuffer.getSize() == 0 && !fillInputBuffer()) ++ return false; + + char c = inputBuffer.popChar(); +- if (dest) *dest = c; +- if (dumpfd) ::write(dumpfd, &c, 1); ++ if (dest) ++ *dest = c; ++ if (dumpfd) ++ ::write(dumpfd, &c, 1); + + ++readCount; +- + return true; + } + +@@ -209,8 +221,10 @@ + { + char dest = '\0'; + do { +- if (!readChar(&dest)) return false; +- if (dumpfd) ::write(dumpfd, &dest, 1); ++ if (!readChar(&dest)) ++ return false; ++ if (dumpfd) ++ ::write(dumpfd, &dest, 1); + } while (c != dest); + + return true; +@@ -278,6 +292,7 @@ + << Session::getInstance().getIP() << "-XXXXXX"; + char *safename = strdup(ss.str().c_str()); + dumpfd = mkstemp(safename); +- if (dumpfd == -1) dumpfd = 0; ++ if (dumpfd == -1) ++ dumpfd = 0; + delete safename; + } +--- ./operator-authenticate.cc 2023-08-24 10:14:07.627463000 +0200 ++++ ../../bincimap-2.0.5/src//operator-authenticate.cc 2023-08-19 15:24:29.665376000 +0200 +@@ -166,11 +166,9 @@ + + putenv(strdup(("BINCIMAP_LOGIN=AUTHENTICATE+" + command.getTag()).c_str())); + +- // FEH: put the username in the environment for logging purpose ++ // put the username in the environment for logging purpose + +- // FIXME: +- session.setEnv("USER", username.c_str()); +-// putenv(strdup(("USER=" + username).c_str())); ++ session.setEnv("USER", username.c_str()); + + // the authenticate function calls a stub which does the actual + // authentication. the function returns 0 (success), 1 (internal +--- ./operator-capability.cc 2023-08-23 12:02:49.204839000 +0200 ++++ ../../bincimap-2.0.5/src//operator-capability.cc 2023-08-21 15:12:32.915708000 +0200 +@@ -12,7 +12,6 @@ + #include "operators.h" + #include "recursivedescent.h" + #include "session.h" +-#include "globals.h" + + using namespace ::std; + using namespace Binc; +@@ -37,8 +36,8 @@ + int CapabilityOperator::getState(void) const + { + return Session::NONAUTHENTICATED +- | Session::AUTHENTICATED +- | Session::SELECTED; ++ | Session::AUTHENTICATED ++ | Session::SELECTED; + } + + //---------------------------------------------------------------------- +@@ -53,11 +52,10 @@ + { + Session &session = Session::getInstance(); + +- bincClient << "* CAPABILITY " << IMAP_VERSION ; ++ bincClient << "* CAPABILITY IMAP4rev1"; + + if (session.getState() == Session::NONAUTHENTICATED) { +- if (getenv("UCSPITLS")) +- if (!session.command.ssl) bincClient << " STARTTLS"; ++ if (!session.command.ssl) bincClient << " STARTTLS"; + + if (session.command.ssl || session.hasEnv("ALLOW_NONSSL_PLAINTEXT_LOGINS")) + bincClient << " AUTH=LOGIN AUTH=PLAIN"; +--- ./operator-fetch.cc 2023-08-22 11:28:00.260614000 +0200 ++++ ../../bincimap-2.0.5/src//operator-fetch.cc 2023-08-21 11:54:04.569792000 +0200 +@@ -372,9 +372,9 @@ + + pendingUpdates(mailbox, + PendingUpdates::FLAGS +- | PendingUpdates::EXISTS +- | PendingUpdates::EXPUNGE +- | PendingUpdates::RECENT, true); ++ | PendingUpdates::EXISTS ++ | PendingUpdates::EXPUNGE ++ | PendingUpdates::RECENT, true); + + return OK; + } +--- ./operator-id.cc 2023-08-23 22:43:11.362445000 +0200 ++++ ../../bincimap-2.0.5/src//operator-id.cc 2023-08-21 15:05:56.720615000 +0200 +@@ -1,8 +1,8 @@ + /** -------------------------------------------------------------------- + * @file operator-id.cc +- * @brief Operator for the ID extension. Described in RFC2971 Oct 2000. ++ * @brief Operator for the ID command. + * @author Erwin Hoffmann +- * @date 22.09.2023 ++ * @date 2023 + * ------------------------------------------------------------------ **/ + #include <string> + #include <iostream> +@@ -46,10 +46,13 @@ + Operator::ProcessResult IdOperator::process(Depot &depot, + Request &command) + { +- bincClient << "* ID (\"name\" \"Binc IMAP\"" ++ Session &session = Session::getInstance(); ++ ++ bincClient << " * ID (\"name\" \"Binc IMAP\"" + << " \"version\" \"" << BINC_VERSION "\")" << endl; ++ bincClient.flush(); + +- return NOTHING; ++ return OK; + } + + //---------------------------------------------------------------------- +@@ -57,15 +60,13 @@ + { + Session &session = Session::getInstance(); + +- if (c_in.getUidMode()) return REJECT; ++ if (c_in.getUidMode()) return ACCEPT; + +-/* FIXME: We are not interested in the parsing result + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } +-*/ + + c_in.setName("ID"); + +--- ./operator-login.cc 2023-08-23 22:42:22.681346000 +0200 ++++ ../../bincimap-2.0.5/src//operator-login.cc 2023-08-16 19:37:07.694225000 +0200 +@@ -48,7 +48,7 @@ + + //------------------------------------------------------------------------ + Operator::ProcessResult LoginOperator::process(Depot &depot, +- Request &command) ++ Request &command) + { + Session &session = Session::getInstance(); + +@@ -79,7 +79,7 @@ + break; + case -1: + bincClient << "* BYE The server died unexpectedly. Please contact " +- "your system administrator for more information." << endl; ++ "your system administrator for more information." << endl; + break; + } + +--- ./operator-logout.cc 2023-08-22 13:41:49.546630000 +0200 ++++ ../../bincimap-2.0.5/src//operator-logout.cc 2023-08-14 23:04:43.460303000 +0200 +@@ -41,8 +41,8 @@ + int LogoutOperator::getState(void) const + { + return Session::NONAUTHENTICATED +- | Session::AUTHENTICATED +- | Session::SELECTED; ++ | Session::AUTHENTICATED ++ | Session::SELECTED; + } + + //------------------------------------------------------------------------ +--- ./operator-noop.cc 2023-08-22 12:11:45.876657000 +0200 ++++ ../../bincimap-2.0.5/src//operator-noop.cc 2023-08-15 15:05:11.493837000 +0200 +@@ -51,7 +51,8 @@ + { + Session &session = Session::getInstance(); + +- if (c_in.getUidMode()) return REJECT; ++ if (c_in.getUidMode()) ++ return REJECT; + + Operator::ParseResult res; + if ((res = expectCRLF()) != ACCEPT) { +--- ./operator-starttls.cc 2023-08-24 10:24:53.204617000 +0200 ++++ ../../bincimap-2.0.5/src//operator-starttls.cc 2023-08-19 16:15:15.622679000 +0200 +@@ -1,13 +1,11 @@ + /** -------------------------------------------------------------------- + * @file operator-starttls.cc +- * @brief Implementation of the STARTTLS command - based on sslserver +- * @author Andreas Aardal Hanssen, Erwin Hoffmann +- * @date 2002-2005, 2023 ++ * @brief Implementation of the STARTTLS command. ++ * @author Andreas Aardal Hanssen ++ * @date 2002-2005 + * ----------------------------------------------------------------- **/ + #include <string> + #include <iostream> +-#include <unistd.h> +-#include <fcntl.h> + + #include "recursivedescent.h" + #include "iodevice.h" +@@ -43,37 +41,6 @@ + | Session::SELECTED; + } + +-//---------------------------------------------------------------------- +-int StarttlsOperator::goStartTLS (void) const +-{ +- Session &session = Session::getInstance(); +- +- if (getenv("UCSPITLS")) { +- string fdstr; +- int fd; +- +- fdstr = session.getEnv("SSLCTLFD"); +- fd = std::stoi(fdstr); +- if (write(fd,"Y",1) < 1) return NOTHING; +- +- fdstr = session.getEnv("SSLREADFD"); +- fd = std::stoi(fdstr); +- if (fcntl(fd,F_GETFL,0) == -1) return NOTHING; +- close (0); +- if (fcntl(fd,F_DUPFD,0) == -1) return NOTHING; +- close (fd); +- +- fdstr = session.getEnv("SSLWRITEFD"); +- fd = std::stoi(fdstr); +- if (fcntl(fd,F_GETFL,0) == -1) return NOTHING; +- close (1); +- if (fcntl(fd,F_DUPFD,1) == -1) return NOTHING; +- close (fd); +- } +- +- return ACCEPT; +-} +- + //------------------------------------------------------------------------ + Operator::ProcessResult StarttlsOperator::process(Depot &depot, + Request &command) +@@ -84,13 +51,11 @@ + return BAD; + } + +- bincClient << "OK STARTTLS completed, begin TLS session now" << endl; ++ bincClient << command.getTag() ++ << " OK STARTTLS completed, begin TLS negotiation now" << endl; + bincClient.flush(); + +- if (goStartTLS() == ACCEPT) +- session.command.ssl = true; +- else +- return NO; ++ session.command.ssl = true; + + return NOTHING; + } +@@ -100,7 +65,8 @@ + { + Session &session = Session::getInstance(); + +- if (c_in.getUidMode()) return REJECT; ++ if (c_in.getUidMode()) ++ return REJECT; + + Operator::ParseResult res; + if ((res = expectCRLF()) != ACCEPT) { +--- ./session-initialize-bincimap-up.cc 2023-08-24 10:30:35.277625000 +0200 ++++ ../../bincimap-2.0.5/src//session-initialize-bincimap-up.cc 2023-08-21 15:50:02.457680000 +0200 +@@ -1,8 +1,8 @@ + /** -------------------------------------------------------------------- + * @file session-initialize-bincimap-up.cc +- * @brief bincimap-up requires sslserver +- * @author Andreas Aardal Hanssen, Erwin Hoffmann +- * @date 2002-2005, 2023 ++ * @brief <---> ++ * @author Andreas Aardal Hanssen ++ * @date 2002-2005 + * ----------------------------------------------------------------- **/ + #include <syslog.h> + #include <ctype.h> +@@ -27,6 +27,15 @@ + + extern char **environ; + ++namespace { ++ //------------------------------------------------------------------------ ++ void usage(char *name) ++ { ++ printf("Please refer to the man pages for bincimap-up and bincimap\n"); ++ printf("for more information about how to invoke Binc IMAP.\n"); ++ } ++} ++ + //---------------------------------------------------------------------- + bool Session::initialize(int argc, char *argv[]) + { +@@ -51,26 +60,38 @@ + return false; + } + +- // Show version if asked for it ++ // imaps (port 993) -- requires sslserver with option -e ++ ++ int stls = 0; ++ string tlssession = session.getEnv("SSL_SESSION_ID"); ++ trim(tlssession); ++ if (tlssession.size() > 2) { ++ session.command.ssl = true; ++ stls = -1 ; ++ } ++ ++ // Show help if asked for it + if (session.command.version) { +- printf("Binc IMAP v" BINC_VERSION"\n"); ++ printf("Binc IMAP v" BINC_VERSION IMAP_VERSION "\n"); + return false; + } + + // Let the command line args override the global settings. + session.assignCommandLineArgs(); + +- // for log input ++ ++ // log settings + string ip = getenv("TCP6REMOTEIP") ? getenv("TCP6REMOTEIP") : + getenv("TCPREMOTEIP") ? getenv("TCPREMOTEIP") : "?"; + session.setIP(ip); + ++ + string logtype = session.getEnv("LOG_TYPE"); + lowercase(logtype); + trim(logtype); + if (logtype == "multilog" || logtype == "stderr") { +- MultilogDevice *device = new MultilogDevice(IODevice::IsEnabled +- | IODevice::FlushesOnEndl); ++ MultilogDevice *device = new MultilogDevice(IODevice::IsEnabled ++ | IODevice::FlushesOnEndl); + ioFactory.addDevice(device); + } else if (logtype == "" || logtype == "syslog") { + const string f = session.getEnv("SYSLOG_FACILITY"); +@@ -89,13 +110,13 @@ + else if (f == "LOG_LOCAL6") facility = LOG_LOCAL6; + else if (f == "LOG_LOCAL7") facility = LOG_LOCAL7; + else facility = LOG_DAEMON; +- } ++ } + + SyslogDevice *device = new SyslogDevice(IODevice::IsEnabled +- | IODevice::FlushesOnEndl, +- "bincimap-up", +- LOG_NDELAY | LOG_PID, +- facility); ++ | IODevice::FlushesOnEndl, ++ "bincimap-up", ++ LOG_NDELAY | LOG_PID, ++ facility); + ioFactory.addDevice(device); + } + +@@ -105,25 +126,34 @@ + + + MultilogDevice *device = new MultilogDevice(IODevice::IsEnabled +- | IODevice::FlushesOnEndl); +- ioFactory.addDevice(device); ++ | IODevice::FlushesOnEndl); ++ ioFactory.addDevice(device); + + // Now that we know the log type, we can flush. + IOFactory::getLogger().setFlags(IODevice::FlushesOnEndl); + IOFactory::getLogger().setOutputLevelLimit(IODevice::InfoLevel); + +- // imaps (port 993) -- requires sslserver with option -e ++ string ucspitls = session.getEnv("UCSPITLS"); ++ if (ucspitls == "+") stls = 1; ++ if (ucspitls == "-") stls = 0; ++ if (ucspitls == "!") stls = 2; + +- int stls = 0; +- if (getenv("SSL_SESSION_ID")) { +- session.command.ssl = true; +- stls = -1; +- // else we will do starttls - requires new FDs +- } else if (getenv("UCSPITLS")) { +- string ucspitls = session.getEnv("UCSPITLS"); +- if (ucspitls == "+") stls = 1; +- if (ucspitls == "-") stls = 0; +- if (ucspitls == "!") stls = 2; ++ if (stls > 0) { ++ string fdstr; ++ int fd; ++ fdstr = session.getEnv("SSLCTLFD"); ++ fd = std::stoi(fdstr); ++ if (write(fd,"Y",1) < 1) return 0; ++ ++ fdstr = session.getEnv("SSLREADFD"); ++ fd = std::stoi(fdstr); ++ if (dup2(0,fd) == -1) return 0; ++ close(fd); ++ ++ fdstr = session.getEnv("SSLWRITEFD"); ++ fd = std::stoi(fdstr); ++ if (dup2(1,fd) == -1) return 0; ++ close(fd); + } + + BrokerFactory &brokerfactory = BrokerFactory::getInstance(); +@@ -133,8 +163,7 @@ + brokerfactory.assign("LOGIN", new LoginOperator()); + brokerfactory.assign("LOGOUT", new LogoutOperator()); + brokerfactory.assign("NOOP", new NoopOperator()); +- brokerfactory.assign("ID", new IdOperator()); +- if (stls > 0) brokerfactory.assign("STARTTLS", new StarttlsOperator()); ++ if (stls) brokerfactory.assign("STARTTLS", new StarttlsOperator()); + + bincClient.setTimeout(60); + +--- ./session-initialize-bincimapd.cc 2023-08-24 10:37:13.007980000 +0200 ++++ ../../bincimap-2.0.5/src//session-initialize-bincimapd.cc 2023-08-21 12:20:24.757336000 +0200 +@@ -1,9 +1,12 @@ +-/** -------------------------------------------------------------------- +- * @file session-initialize-bincimapd.cc +- * @brief <---> +- * @author Andreas Aardal Hanssen, Erwin Hoffmann +- * @date 2002-2005, 2023 ++/* -------------------------------------------------------------------- ++ * Filename: ++ * session-initialize-bincimap-up.cc ++ * ++ * Description: ++ * <---> + * -------------------------------------------------------------------- ++ * Copyright 2002-2005 Andreas Aardal Hanssen ++ * -------------------------------------------------------------------- + */ + #include <unistd.h> + #include <syslog.h> +@@ -21,7 +24,6 @@ + #include "syslogdevice.h" + #include "tools.h" + #include "convert.h" +- + #include <string> + #include <map> + #include <signal.h> +@@ -31,14 +33,24 @@ + + extern char **environ; + ++namespace { ++ //------------------------------------------------------------------------ ++ void usage(char *name) ++ { ++ bincInfo << "Please refer to the man pages for bincimap-up and bincimapd" ++ << endl; ++ bincInfo << "for more information about how to invoke Binc IMAP." << endl; ++ bincInfo.flush(); ++ } ++} ++ + //---------------------------------------------------------------------- + bool Session::initialize(int argc, char *argv[]) + { + IOFactory &ioFactory = IOFactory::getInstance(); +- + IODevice *stdioDevice = new StdIODevice(IODevice::IsEnabled +- | IODevice::HasInputLimit +- | IODevice::HasTimeout); ++ | IODevice::HasInputLimit ++ | IODevice::HasTimeout); + stdioDevice->setFlags(IODevice::HasOutputLimit); + stdioDevice->setMaxOutputBufferSize(TRANSFER_BUFFER_SIZE); + ioFactory.addDevice(stdioDevice); +@@ -55,9 +67,9 @@ + return false; + } + +- // Show version if asked for it ++ // Show help if asked for it + if (session.command.version) { +- printf("Binc IMAP v" BINC_VERSION"\n"); ++ printf("Binc IMAP v" BINC_VERSION IMAP_VERSION"\n"); + return false; + } + +@@ -96,15 +108,27 @@ + session.setEnv("SYSLOG_FACILITY", toString(facility)); + + ioFactory.addDevice(new SyslogDevice(IODevice::IsEnabled, +- "bincimapd", +- LOG_NDELAY | LOG_PID, +- facility)); ++ "bincimapd", ++ LOG_NDELAY | LOG_PID, ++ facility)); + } + + // Now that we know the log type, we can flush. + IOFactory::getLogger().flush(); + IOFactory::getLogger().setFlags(IODevice::FlushesOnEndl); + IOFactory::getLogger().setOutputLevelLimit(IODevice::InfoLevel); ++ ++ // Show help if asked for it ++ if (session.command.help) { ++ usage(argv[0]); ++ return false; ++ } ++ ++ // Show help if asked for it ++ if (session.command.version) { ++ bincInfo << "Binc IMAP v" << BINC_VERSION IMAP_VERSION << endl; ++ return false; ++ } + + char *logindetails = getenv("BINCIMAP_LOGIN"); + if (logindetails == 0) { +--- ./session.cc 2023-08-22 11:08:44.043704000 +0200 ++++ ../../bincimap-2.0.5/src//session.cc 2023-08-20 13:56:00.410613000 +0200 +@@ -137,7 +137,7 @@ + { + args.addOptional("h|?|help", "Display this help screen", true); + args.addOptional("version", "Display the version of Binc IMAP", true); +- args.addOptional("a|allow-plain", "Allow authentication when not TLS protected", true); ++ args.addOptional("a|allow-plain", "Allow authentication when not in SSL", true); + args.addOptional("v|show-version", "Enable verbose IMAP greeting", false); + args.addOptional("l|log-type", "Sets the method used for logging", false); + args.addOptional("d|depot", "Sets the depot type", false); +--- ./syslogdevice.cc 2023-08-22 13:36:36.473713000 +0200 ++++ ../../bincimap-2.0.5/src//syslogdevice.cc 2023-08-19 21:24:47.622270000 +0200 +@@ -62,7 +62,8 @@ + out += *i; + } + +- if (out != "") syslog(priority, out.c_str(), out.size()); ++ if (out != "") ++ syslog(priority, out.c_str(), out.size()); + + outputBuffer.clear(); + return WriteDone; diff --git a/src/convert.cc b/src/convert.cc new file mode 100644 index 0000000..5af4f3c --- /dev/null +++ b/src/convert.cc @@ -0,0 +1,114 @@ +/** -------------------------------------------------------------------- + * @file convert.cc + * @brief Implementation of miscellaneous convertion functions + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "convert.h" +#include <string> + +using namespace ::std; +using namespace Binc; + +//------------------------------------------------------------------------ +BincStream::BincStream(void) +{ +} + +//------------------------------------------------------------------------ +BincStream::~BincStream(void) +{ + clear(); +} + +//------------------------------------------------------------------------ +string BincStream::popString(unsigned int size) +{ + if (size > nstr.length()) + size = nstr.length(); + string tmp = nstr.substr(0, size); + nstr = nstr.substr(size); + return tmp; +} + +//------------------------------------------------------------------------ +char BincStream::popChar(void) +{ + if (nstr.length() == 0) + return '\0'; + + char c = nstr[0]; + nstr = nstr.substr(1); + return c; +} + +//------------------------------------------------------------------------ +void BincStream::unpopChar(char c) +{ + nstr = c + nstr; +} + +//------------------------------------------------------------------------ +void BincStream::unpopStr(const string &s) +{ + nstr = s + nstr; +} + +//------------------------------------------------------------------------ +const string &BincStream::str(void) const +{ + return nstr; +} + +//------------------------------------------------------------------------ +void BincStream::clear(void) +{ + nstr = ""; +} + +//------------------------------------------------------------------------ +unsigned int BincStream::getSize(void) const +{ + return (unsigned int) nstr.length(); +} + +//------------------------------------------------------------------------ +BincStream &BincStream::operator << (std::ostream&(*)(std::ostream&)) +{ + nstr += "\r\n"; + return *this; +} + +//------------------------------------------------------------------------ +BincStream &BincStream::operator << (const string &t) +{ + nstr += t; + return *this; +} + +//------------------------------------------------------------------------ +BincStream &BincStream::operator << (int t) +{ + nstr += toString(t); + return *this; +} + +//------------------------------------------------------------------------ +BincStream &BincStream::operator << (unsigned long t) +{ + nstr += toString(t); + return *this; +} + +BincStream &BincStream::operator << (unsigned int t) +{ + nstr += toString(t); + return *this; +} + +//------------------------------------------------------------------------ +BincStream &BincStream::operator << (char t) +{ + nstr += t; + return *this; +} diff --git a/src/depot.cc b/src/depot.cc new file mode 100644 index 0000000..599bf97 --- /dev/null +++ b/src/depot.cc @@ -0,0 +1,717 @@ +/** -------------------------------------------------------------------- + * @file depot.cc + * @brief Implementation of the Depot class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ------------------------------------------------------------------ **/ +#include <map> +#include <string> +#include <unistd.h> +#include <errno.h> + +#include "depot.h" +#include "mailbox.h" +#include "status.h" +#include "convert.h" +#include "iodevice.h" +#include "iofactory.h" + +using namespace ::std; +using namespace Binc; + +//-------------------------------------------------------------------- +DepotFactory::DepotFactory(void) +{ +} + +//-------------------------------------------------------------------- +DepotFactory::~DepotFactory(void) +{ + for (vector<Depot *>::iterator i = depots.begin(); i != depots.end(); + ++i) + delete *i; +} + +//-------------------------------------------------------------------- +Depot *DepotFactory::get(const string &name) const +{ + for (vector<Depot *>::const_iterator i = depots.begin(); i != depots.end(); + ++i) + if ((*i)->getName() == name) + return *i; + + return 0; +} + +//-------------------------------------------------------------------- +void DepotFactory::assign(Depot *depot) +{ + depots.push_back(depot); +} + +//-------------------------------------------------------------------- +DepotFactory &DepotFactory::getInstance(void) +{ + static DepotFactory depotfactory; + return depotfactory; +} + +//-------------------------------------------------------------------- +Depot::Depot(void) : enditerator(0, 0) +{ + defaultmailbox = 0; + selectedmailbox = 0; + + delimiter = '/'; +} + +//-------------------------------------------------------------------- +Depot::Depot(const string &name) : enditerator(0, 0) +{ + defaultmailbox = 0; + selectedmailbox = 0; + + delimiter = '/'; + + this->name = name; +} + +//-------------------------------------------------------------------- +Depot::~Depot(void) +{ +} + +//-------------------------------------------------------------------- +const string &Depot::getLastError(void) const +{ + return lastError; +} + +//-------------------------------------------------------------------- +void Depot::setLastError(const string &error) const +{ + lastError = error; +} + +//-------------------------------------------------------------------- +void Depot::assign(Mailbox *m) +{ + for (vector<Mailbox *>::const_iterator i = backends.begin(); + i != backends.end(); ++i) + if (*i == m) break; + + backends.push_back(m); +} + +//-------------------------------------------------------------------- +Mailbox *Depot::get(const string &s_in) const +{ + for (vector<Mailbox *>::const_iterator i = backends.begin(); + i != backends.end(); ++i) + if ((*i)->isMailbox(mailboxToFilename(s_in))) + return *i; + + setLastError("No such mailbox " + toImapString(s_in)); + return 0; +} + +//-------------------------------------------------------------------- +bool Depot::setSelected(Mailbox *m) +{ + for (vector<Mailbox *>::const_iterator i = backends.begin(); + i != backends.end(); ++i) + if (*i == m) { + selectedmailbox = m; + return true; + } + + setLastError("Attempted to select unregistered Mailbox type in Depot"); + return false; +} + +//-------------------------------------------------------------------- +const string &Depot::getName(void) const +{ + return name; +} + +//-------------------------------------------------------------------- +const string &Depot::getPersonalNamespace(void) const +{ + return personalNamespace; +} + +//-------------------------------------------------------------------- +const string &Depot::getOthersNamespace(void) const +{ + return othersNamespace; +} + +//-------------------------------------------------------------------- +const string &Depot::getSharedNamespace(void) const +{ + return sharedNamespace; +} + +//-------------------------------------------------------------------- +void Depot::setDelimiter(char c) +{ + delimiter = c; +} + +//-------------------------------------------------------------------- +const char Depot::getDelimiter(void) const +{ + return delimiter; +} + +//-------------------------------------------------------------------- +bool Depot::setDefaultType(const string &name) +{ + for (vector<Mailbox *>::const_iterator i = backends.begin(); + i != backends.end(); ++i) + if ((*i)->getTypeName() == name) { + defaultmailbox = *i; + return true; + } + + setLastError("attempt to default to unregistered Mailbox type " + name); + return false; +} + +//-------------------------------------------------------------------- +Mailbox *Depot::getSelected(void) const +{ + return selectedmailbox; +} + +//-------------------------------------------------------------------- +void Depot::resetSelected(void) +{ + selectedmailbox = 0; +} + +//-------------------------------------------------------------------- +Mailbox *Depot::getDefault(void) const +{ + return defaultmailbox; +} + +//-------------------------------------------------------------------- +bool Depot::createMailbox(const string &s_in) const +{ + const string &mailboxname = mailboxToFilename(toCanonMailbox(s_in)); + if (mailboxname == "") { + setLastError("invalid mailbox name"); + return false; + } + + Mailbox *mailbox = getDefault(); + if (mailbox == 0) { + setLastError("no default mailbox defined"); + return false; + } + + bool result = mailbox->createMailbox(mailboxname, 0777); + if (result) + return true; + else { + setLastError(mailbox->getLastError()); + return false; + } +} + +//-------------------------------------------------------------------- +bool Depot::deleteMailbox(const string &s_in) const +{ + const string &mailboxname = mailboxToFilename(toCanonMailbox(s_in)); + + Mailbox *mailbox = get(s_in); + if (mailbox == 0) { + setLastError(s_in + ": no such mailbox"); + return false; + } + + bool result = mailbox->deleteMailbox(mailboxname); + if (result) + return true; + else { + setLastError(mailbox->getLastError()); + return false; + } +} + +//-------------------------------------------------------------------- +bool Depot::renameMailbox(const string &s_in, const string &t_in) const +{ + const string &source = mailboxToFilename(s_in).c_str(); + const string &dest = mailboxToFilename(t_in).c_str(); + + int nrenamed = 0; + const iterator e = end(); + for (iterator i = begin("."); i != e; ++i) { + string entry = *i; + + if (entry.substr(0, source.length()) == source) { + string sourcename, destname; + + if (entry.length() == source.length()) { + sourcename = source; + destname = dest; + + } else if (entry.length() > source.length() + && entry[source.length()] == '.') { + sourcename = entry; + destname = dest + entry.substr(source.length()); + } else continue; + + if (rename(sourcename.c_str(), destname.c_str()) != 0) { + bincWarning << "error renaming " << sourcename << " to " + << destname << ": " << strerror(errno) << endl; + } else + nrenamed++; + + Mailbox *mailbox; + if ((mailbox = get(filenameToMailbox(sourcename))) != 0) + mailbox->bumpUidValidity(filenameToMailbox(sourcename)); + if ((mailbox = get(filenameToMailbox(destname))) != 0) + mailbox->bumpUidValidity(filenameToMailbox(destname)); + } + } + + if (nrenamed == 0) { + setLastError("An error occurred when renaming " + + toImapString(s_in) + + " to " + toImapString(t_in) + + ". Try creating a new mailbox," + " then copy over all messages." + " Finally, delete the original mailbox"); + return false; + } else + return true; +} + +//-------------------------------------------------------------------- +bool Depot::getStatus(const std::string &s_in, Status &dest) const +{ + const string mailbox = toCanonMailbox(s_in); + Mailbox *m = get(mailbox); + if (m == 0) { + setLastError("Unrecognized mailbox: " + toImapString(s_in)); + return false; +} + + int statusid = m->getStatusID(mailboxToFilename(mailbox)); + if (mailboxstatuses.find(mailbox) != mailboxstatuses.end()) { + dest = mailboxstatuses[mailbox]; + if (dest.getStatusID() == statusid) + return true; + } + + if (!m->getStatus(mailboxToFilename(mailbox), dest)) { + setLastError(m->getLastError()); + return false; + } + + dest.setStatusID(statusid); + mailboxstatuses[mailbox] = dest; + return true; +} + +//---------------------------------------------------------------------- +vector<string> Depot::getSubscriptions(void) const +{ + return subscribed; +} + +//---------------------------------------------------------------------- +void Depot::subscribeTo(const std::string mailbox) +{ + for (vector<string>::iterator i = subscribed.begin(); + i != subscribed.end(); ++i) { + if (*i == mailbox) + return; + } + + subscribed.push_back(mailbox); +} + +//---------------------------------------------------------------------- +bool Depot::unsubscribeTo(const std::string mailbox) +{ + for (vector<string>::iterator i = subscribed.begin(); + i != subscribed.end(); ++i) { + if (*i == mailbox) { + subscribed.erase(i); + return true; + } + } + + return false; +} + +//---------------------------------------------------------------------- +void Depot::loadSubscribes(void) +{ + // drop all existing subscribed folders. + subscribed.clear(); + + // try loading the .subscribed file + bool ok = false; + FILE *fp = fopen(".subscribed", "r"); + map<string, bool> addedEntries; + if (fp) { + int c; + string current; + while ((c = fgetc(fp)) != EOF) { + if (c == '\n') { + if (current != "") { + if (current == "INBOX") + current = "."; + + if (current.substr(0, 5) == "INBOX") + current = current.substr(5); + + if (addedEntries.find(current) == addedEntries.end()) { + subscribed.push_back(filenameToMailbox(current)); + addedEntries[current] = true; + } + current = ""; + } + } else + current += c; + } + + fclose(fp); + ok = true; + } + + if (!ok) { + subscribed.push_back("INBOX"); + saveSubscribes(); + } +} + +//---------------------------------------------------------------------- +bool Depot::saveSubscribes(void) const +{ + // create a safe file name + string tpl = ".subscribed-tmp-XXXXXX"; + char *ftemplate = new char[tpl.length() + 1]; + + strcpy(ftemplate, tpl.c_str()); + int fd = mkstemp(ftemplate); + if (fd == -1) { + bincWarning << "unable to create temporary file \"" + << tpl << "\"" << endl; + delete[] ftemplate; + return false; + } + + map<string, bool> addedEntries; + for (vector<string>::const_iterator i = subscribed.begin(); + i != subscribed.end(); ++i) { + if (addedEntries.find(*i) == addedEntries.end()) { + addedEntries[*i] = true; + string w = mailboxToFilename(*i) + "\n"; + if (write(fd, w.c_str(), w.length()) != (ssize_t) w.length()) { + bincWarning << "failed to write to " << tpl << ": " + << strerror(errno) << endl; + break; + } + } + } + + if ((fsync(fd) && (errno != EROFS || errno != EINVAL)) || close(fd)) { + bincWarning << "failed to close " << ftemplate + << ": " << strerror(errno) << endl; + delete[] ftemplate; + return false; + } + + if (rename(ftemplate, ".subscribed") != 0) { + bincWarning << "failed to rename " << ftemplate + << " to .subscribed: " + << strerror(errno) << endl; + delete[] ftemplate; + return false; + } + + delete[] ftemplate; + return true; +} + +//-------------------------------------------------------------------- +Depot::iterator::iterator(void) +{ + dirp = 0; + ref = new int; + *ref = 1; +} + +//-------------------------------------------------------------------- +Depot::iterator::iterator(DIR *dp, struct dirent *sp) +{ + dirp = dp; + direntp = sp; + + ref = new int; + *ref = 1; +} + +//-------------------------------------------------------------------- +Depot::iterator::iterator(const iterator ©) +{ + if (*copy.ref != 0) + ++(*copy.ref); + + ref = copy.ref; + dirp = copy.dirp; + direntp = copy.direntp; +} + +//-------------------------------------------------------------------- +Depot::iterator::~iterator(void) +{ + deref(); +} + +//-------------------------------------------------------------------- +Depot::iterator &Depot::iterator::operator =(const iterator ©) +{ + if (*copy.ref != 0) + ++(*copy.ref); + + deref(); + + ref = copy.ref; + dirp = copy.dirp; + direntp = copy.direntp; + + return *this; +} + +//-------------------------------------------------------------------- +void Depot::iterator::deref(void) +{ + // decrease existing copy ref if there is one + if (*ref != 0 && --(*ref) == 0) { + if (dirp) { + closedir(dirp); + dirp = 0; + } + + delete ref; + ref = 0; + } +} + +//-------------------------------------------------------------------- +string Depot::iterator::operator * (void) const +{ + if (direntp == 0) + return ""; + + return direntp->d_name; +} + +//-------------------------------------------------------------------- +void Depot::iterator::operator ++ (void) +{ + direntp = readdir(dirp); +} + +//-------------------------------------------------------------------- +bool Depot::iterator::operator == (Depot::iterator i) const +{ + return direntp == i.direntp; +} + +//-------------------------------------------------------------------- +bool Depot::iterator::operator != (Depot::iterator i) const +{ + return direntp != i.direntp; +} + +//-------------------------------------------------------------------- +Depot::iterator Depot::begin(const string &path) const +{ + Depot::iterator i; + + if ((i.dirp = opendir(path.c_str())) == 0) { + bincWarning << "opendir on " << path << " failed" << endl; + setLastError("opendir on " + path + " failed"); + return end(); + } + + ++i; + return i; +} + +//-------------------------------------------------------------------- +const Depot::iterator &Depot::end(void) const +{ + return enditerator; +} + +//-------------------------------------------------------------------- +MaildirPPDepot::MaildirPPDepot(void) : Depot("Maildir++") +{ + privateNamespace = "INBOX"; + privateNamespace += getDelimiter(); +} + +//-------------------------------------------------------------------- +MaildirPPDepot::~MaildirPPDepot(void) +{ +} + +//-------------------------------------------------------------------- +const string &MaildirPPDepot::getPersonalNamespace(void) const +{ + return privateNamespace; +} + +//-------------------------------------------------------------------- +string MaildirPPDepot::mailboxToFilename(const string &m) const +{ + string prefix = "INBOX"; prefix += delimiter; + + string mm = m; + trim(mm, string(&delimiter, 1)); + string tmp = mm; + uppercase(tmp); + if (tmp != "INBOX" && tmp.substr(0, 6) != prefix) { + setLastError("With a Maildir++ depot, you must create all" + " mailboxes under INBOX. Try creating" + " " + prefix + mm + " ."); + return ""; + } + + string twodelim; + twodelim += delimiter; + twodelim += delimiter; + + if (mm == "INBOX") return "."; + else if (mm.length() <= 6) { + setLastError("With a Maildir++ depot, you must create all" + " mailboxes under INBOX."); + return ""; + } else if (mm.substr(0, 6) != prefix) { + setLastError("With a Maildir++ depot, you must create all" + " mailboxes under INBOX."); + return ""; + } else if (mm.find(twodelim) != string::npos) { + setLastError("Invalid character combination " + + twodelim + " in mailbox name"); + return ""; + } else if (mm != "" && delimiter != '.' && mm.substr(1).find('.') != string::npos) { + setLastError("Invalid character '.' in mailbox name"); + return ""; + } else { + string tmp = mm.substr(6); + for (string::iterator i = tmp.begin(); i != tmp.end(); ++i) + if (*i == delimiter) *i = '.'; + + return "." + tmp; + } +} + +//-------------------------------------------------------------------- +string MaildirPPDepot::filenameToMailbox(const string &m) const +{ + if (m == ".") return "INBOX"; + else if (delimiter != '.' && m.find(delimiter) != string::npos) return ""; + else if (m != "" && m[0] == '.') { + string tmp = m; + for (string::iterator i = tmp.begin(); i != tmp.end(); ++i) + if (*i == '.') *i = delimiter; + + return "INBOX" + tmp; + } else return ""; +} + +//-------------------------------------------------------------------- +IMAPdirDepot::IMAPdirDepot(void) : Depot("IMAPdir") +{ +} + +//-------------------------------------------------------------------- +IMAPdirDepot::~IMAPdirDepot(void) +{ +} + +//-------------------------------------------------------------------- +string IMAPdirDepot::mailboxToFilename(const string &m) const +{ + string tmp; + string mm = m; + trim(mm, string(&delimiter, 1)); + + string twodelim; + twodelim += delimiter; + twodelim += delimiter; + + if (mm.find(twodelim) != string::npos) { + setLastError("Invalid character combination " + + twodelim + " in mailbox name"); + return ""; + } + + string::const_iterator i = mm.begin(); + while (i != mm.end()) { + if (*i == delimiter) { + tmp += '.'; + } else if (*i == '\\') { + tmp += "\\\\"; + } else if (*i == '.') { + if (i == mm.begin()) + tmp += "."; + else + tmp += "\\."; + } else { + tmp += *i; + } + + ++i; + } + + return tmp; +} + +//-------------------------------------------------------------------- +string IMAPdirDepot::filenameToMailbox(const string &m) const +{ + string tmp; + bool escape = false; + + // hide the magic "." mailbox. + if (m == "." || m == "..") + return ""; + + string::const_iterator i = m.begin(); + while (i != m.end()) { + if (*i == '.') { + if (i != m.begin() && !escape) tmp += delimiter; + else if (i == m.begin() || escape) tmp += '.'; + escape = false; + } else if (*i == '\\') { + if (!escape) escape = true; else { + tmp += '\\'; + escape = false; + } + } else if (*i == delimiter) { + return ""; + } else { + if (escape) return ""; + else { + tmp += *i; + escape = false; + } + } + + ++i; + } + + return tmp; +} diff --git a/src/greeting.cc b/src/greeting.cc new file mode 100644 index 0000000..3a2a394 --- /dev/null +++ b/src/greeting.cc @@ -0,0 +1,47 @@ +/** -------------------------------------------------------------------- + * @file greeting.cc + * @brief Implementation of the inital greeting. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <time.h> + +#include "iodevice.h" +#include "iofactory.h" +#include "session.h" +#include "globals.h" + +using namespace ::std; +using namespace Binc; + +static const unsigned int ISO8601SIZE = 32; + +namespace Binc { + void showGreeting(void); +}; + + +//------------------------------------------------------------------------ +void Binc::showGreeting(void) +{ + Session &session = Session::getInstance(); + + time_t t = time(0); + struct tm *mytm = localtime(&t); + + char mytime[ISO8601SIZE]; + unsigned int size = strftime(mytime, sizeof(mytime), "%Y-%m-%d %H:%M:%S %z", mytm); + if (size >= sizeof(mytime) || size == 0) + mytime[0] = 0; + + if (session.hasEnv("VERBOSE_GREETING")) { + bincClient << "* OK Welcome to Binc IMAP " + << BINC_VERSION + << " " + << IMAP_VERSION + << " by Andreas Aardal Hanssen & Erwin Hoffmann at " + << mytime << endl; + } else { + bincClient << "* OK Welcome to Binc IMAP at " << mytime << endl; + } +} diff --git a/src/imapparser.cc b/src/imapparser.cc new file mode 100644 index 0000000..2e7d746 --- /dev/null +++ b/src/imapparser.cc @@ -0,0 +1,379 @@ +/** -------------------------------------------------------------------- + * @file imapparser.cc + * @brief Implementation of the common items for parsing IMAP input + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "imapparser.h" +#include "convert.h" + +#include <stdio.h> +#include <map> +#include <iostream> +#include <vector> +#include <string> +#include <exception> + +using namespace ::std; +using namespace Binc; + +//------------------------------------------------------------------------ +Request::Request(void) + : extra(0), flags(), statuses(), bset(), searchkey(), fatt() +{ + uidmode = false; +} + +Request::~Request(void) +{ + if (extra != 0) + delete extra; +} + +//------------------------------------------------------------------------ +void Request::setUidMode(void) +{ + uidmode = true; +} + +//------------------------------------------------------------------------ +bool Request::getUidMode(void) const +{ + return uidmode; +} + +//------------------------------------------------------------------------ +void Request::setTag(string &t_in) +{ + tag = t_in; +} + +//------------------------------------------------------------------------ +const string &Request::getTag(void) const +{ + return tag; +} + +//------------------------------------------------------------------------ +void Request::setMode(const string &m_in) +{ + mode = m_in; +} + +//------------------------------------------------------------------------ +const string &Request::getMode(void) const +{ + return mode; +} + +//------------------------------------------------------------------------ +void Request::setName(const string &s_in) +{ + name = s_in; +} + +//------------------------------------------------------------------------ +const string &Request::getName(void) const +{ + return name; +} + +//------------------------------------------------------------------------ +void Request::setAuthType(const string &s_in) +{ + authtype = s_in; +} + +//------------------------------------------------------------------------ +const string &Request::getAuthType(void) const +{ + return authtype; +} + +//------------------------------------------------------------------------ +void Request::setLiteral(const string &s_in) +{ + literal = s_in; +} + +//------------------------------------------------------------------------ +const string &Request::getLiteral(void) const +{ + return literal; +} + +//------------------------------------------------------------------------ +void Request::setDate(const string &s_in) +{ + date = s_in; +} + +//------------------------------------------------------------------------ +const string &Request::getDate(void) const +{ + return date; +} + +//------------------------------------------------------------------------ +void Request::setCharSet(const string &s_in) +{ + charset = s_in; + uppercase(charset); +} + +//------------------------------------------------------------------------ +const string &Request::getCharSet(void) const +{ + return charset; +} + +//------------------------------------------------------------------------ +void Request::setUserID(const string &s_in) +{ + userid = s_in; +} + +//------------------------------------------------------------------------ +const string &Request::getUserID(void) const +{ + return userid; +} + +//------------------------------------------------------------------------ +void Request::setPassword(const string &s_in) +{ + password = s_in; +} + +//------------------------------------------------------------------------ +const string &Request::getPassword(void) const +{ + return password; +} + +//------------------------------------------------------------------------ +void Request::setMailbox(const string &s_in) +{ + mailbox = s_in; +} + +//------------------------------------------------------------------------ +const string &Request::getMailbox(void) const +{ + return mailbox; +} + +//------------------------------------------------------------------------ +void Request::setListMailbox(const string &s_in) +{ + listmailbox = s_in; +} + +//------------------------------------------------------------------------ +const string &Request::getListMailbox(void) const +{ + return listmailbox; +} + +//------------------------------------------------------------------------ +void Request::setContextInfo(const string &s_in) +{ + contextInfo = s_in; +} + +//------------------------------------------------------------------------ +const string &Request::getContextInfo(void) const +{ + return contextInfo; +} + +//------------------------------------------------------------------------ +void Request::setNewMailbox(const string &s_in) +{ + newmailbox = s_in; +} + +//------------------------------------------------------------------------ +const string &Request::getNewMailbox(void) const +{ + return newmailbox; +} + +//------------------------------------------------------------------------ +SequenceSet &Request::getSet(void) +{ + return bset; +} + +//------------------------------------------------------------------------ +vector<string> &Request::getStatuses(void) +{ + return statuses; +} + +//------------------------------------------------------------------------ +vector<string> &Request::getFlags(void) +{ + return flags; +} + +//------------------------------------------------------------------------ +SequenceSet::SequenceSet(void) : limited(true), nullSet(false) +{ +} + +//------------------------------------------------------------------------ +SequenceSet::SequenceSet(const SequenceSet ©) + : limited(copy.limited), nullSet(copy.nullSet), internal(copy.internal) +{ +} + +//------------------------------------------------------------------------ +SequenceSet &SequenceSet::operator = (const SequenceSet ©) +{ + limited = copy.limited; + nullSet = copy.nullSet; + internal = copy.internal; + + return *this; +} + +//------------------------------------------------------------------------ +SequenceSet::~SequenceSet(void) +{ +} + +//------------------------------------------------------------------------ +SequenceSet &SequenceSet::null(void) +{ + static SequenceSet nil; + nil.nullSet = true; + return nil; +} + +//------------------------------------------------------------------------ +bool SequenceSet::isNull(void) const +{ + return nullSet; +} + +//------------------------------------------------------------------------ +SequenceSet &SequenceSet::all(void) +{ + static bool initialized = false; + static SequenceSet all; + + if (!initialized) { + all.addRange(1, (unsigned int)-1); + initialized = true; + } + + return all; +} + +//------------------------------------------------------------------------ +SequenceSet::Range::Range(unsigned int a, unsigned int b) +{ + if (a > b) { + from = b; + to = a; + } else { + from = a; + to = b; + } +} + +//------------------------------------------------------------------------ +void SequenceSet::addRange(unsigned int a, unsigned int b) +{ + if (a == (unsigned int)-1 || b == (unsigned int)-1) limited = false; + internal.push_back(Range(a, b)); +} + +//------------------------------------------------------------------------ +void SequenceSet::addNumber(unsigned int a) +{ + if (a == (unsigned int)-1) limited = false; + internal.push_back(Range(a, a)); +} + +//------------------------------------------------------------------------ +bool SequenceSet::isInSet(unsigned int n) const +{ + unsigned int maxvalue = 0; + for (vector<Range>::const_iterator i = internal.begin(); + i != internal.end(); ++i) { + const Range &r = *i; + if (r.from > maxvalue) maxvalue = r.from; + else if (r.to > maxvalue) maxvalue = r.to; + + if (n >= (*i).from && n <= (*i).to) { + return true; + } + } + + return (n > maxvalue && !limited); +} + +//------------------------------------------------------------------------ +BincImapParserFetchAtt::BincImapParserFetchAtt(const std::string &typeName) + : type(typeName) +{ + offsetstart = 0; + offsetlength = (unsigned int) -1; + hassection = false; +} + +//------------------------------------------------------------------------ +string BincImapParserFetchAtt::toString(void) +{ + string tmp; + if (type == "BODY.PEEK") + tmp = "BODY"; + else + tmp = type; + + if (type == "BODY" || type == "BODY.PEEK") { + if (hassection) { + tmp += "["; + tmp += section; + if (sectiontext != "") { + if (section != "") + tmp += "."; + tmp += sectiontext; + + if (headerlist.size() > 0) { + tmp += " ("; + for (vector<string>::iterator i = headerlist.begin(); + i != headerlist.end(); ++i) { + if (i != headerlist.begin()) + tmp += " "; + tmp += Binc::toImapString(*i); + } + tmp += ")"; + } + } + tmp += "]"; + + if (offsetstart == 0 && offsetlength == (unsigned int) -1) + tmp += " "; + else + tmp += "<" + Binc::toString(offsetstart) + "> "; + } + } + + return tmp; +} + +//------------------------------------------------------------------------ +BincImapParserSearchKey::BincImapParserSearchKey(void) +{ + type = 0; + number = 0; +} + +//------------------------------------------------------------------------ +const SequenceSet& BincImapParserSearchKey::getSet(void) const +{ + return bset; +} diff --git a/src/imapserver.cc b/src/imapserver.cc new file mode 100755 index 0000000..003352b --- /dev/null +++ b/src/imapserver.cc @@ -0,0 +1,221 @@ +/** -------------------------------------------------------------------- + * @file imapserver.cc + * @brief Implementation of the IMAPServer class. + * @author Andreas Aardal Hanssen + * @date 2005 + * -------------------------------------------------------------------- + */ +#include "broker.h" +#include "globals.h" +#include "imapparser.h" +#include "imapserver.h" +#include "iodevice.h" +#include "iofactory.h" +#include "session.h" + +using namespace ::Binc; +using namespace ::std; + +namespace Binc { + void showGreeting(void); +}; + +IMAPServer::IMAPServer(int argc, char **argv) +{ + this->argc = argc; + this->argv = argv; + this->stubMode = false; + Session::getInstance().setState(Session::AUTHENTICATED); +} + +IMAPServer::~IMAPServer(void) +{ +} + +int IMAPServer::initialize(void) +{ + Session &session = Session::getInstance(); + if (!session.initialize(argc, argv)) return 111; + return 0; +} + +void IMAPServer::prepareForNextRequest(void) +{ + serverStatus = OK; + + bincClient.setFlags(IODevice::HasInputLimit); + bincClient.flush(); + bincClient.setMaxInputBufferSize(INPUT_BUFFER_LIMIT); + + Session::getInstance().setLastError(""); + Session::getInstance().clearResponseCode(); +} + +int IMAPServer::runStub(void) +{ + bincDebug << "IMAPServer::runStub(), running stub" << endl; + this->stubMode = true; + Session::getInstance().setState(Session::NONAUTHENTICATED); + return run(); +} + +int IMAPServer::run(void) +{ + Session &session = Session::getInstance(); + bincLog.setOutputLevelLimit(IODevice::InfoLevel); + string pid = to_string(session.getPid()); + + bincDebug << "IMAPServer::run(), started server" << endl; + + if (this->stubMode) { + if (session.hasEnv("PROTOCOLDUMP")) + bincClient.enableProtocolDumping(); + bincLog << "bincimap-up: pid " << pid + << " Connected: " << session.getIP() << "\n"; + showGreeting(); + } else { + bincLog << "bincimapd: pid " << pid + << " Logged in: <" << session.getEnv("USER") + << "@" << session.getEnv("TCPREMOTEIP") << ">\n"; + } + bincLog.flush(); + + do { + bincDebug << "IMAPServer::run(), preparing for next request" << endl; + + prepareForNextRequest(); + + // Find the current state's broker. + BrokerFactory &brokerFactory = BrokerFactory::getInstance(); + Broker *broker = brokerFactory.getBroker(session.getState()); + + bincDebug << "IMAPServer::run(), found broker " << (uintptr_t) broker + << " for state " << session.getState() << endl; + + bool skipToNextRequest = false; + + // Parse the stub of the IMAP request. + Request clientRequest; + int stubParseResult = broker->parseStub(clientRequest); + if (stubParseResult == Operator::TIMEOUT) { + serverStatus = Timeout; + break; + } else if (stubParseResult == Operator::REJECT) { + serverStatus = RequestRejected; + } else if (stubParseResult == Operator::ERROR) { + serverStatus = RequestError; + } else { + // Find an operator that recognizes the name of the request, and + // have it continue the parsing. + Operator *o = broker->get(clientRequest.getName()); + if (!o) { + serverStatus = RequestRejected; + string err = "The command \""; + if (clientRequest.getUidMode()) err += "UID "; + err += clientRequest.getName(); + err += "\" is unsupported in this state. "; + session.setLastError(err); + skipToNextRequest = true; + } else { + int parseResult = o->parse(clientRequest); + if (parseResult == Operator::TIMEOUT) { + serverStatus = Timeout; + } else if (parseResult == Operator::REJECT) { + serverStatus = RequestRejected; + } else if (parseResult == Operator::ERROR) { + serverStatus = RequestError; + } else { + session.addStatement(); + Depot *dep = session.getDepot(); + + int processResult = o->process(*dep, clientRequest); + if (processResult == Operator::OK) { + } else if (processResult == Operator::NO) { + serverStatus = RequestRejected; + } else if (processResult == Operator::BAD) { + serverStatus = RequestError; + } else if (processResult == Operator::NOTHING) { + serverStatus = RequestIgnore; // answer given already + } else if (processResult == Operator::ABORT) { + session.setState(Session::LOGOUT); + } + } + } + } + + // If a syntax error was detected, we skip all characters in the + // input stream up to and including '\n'. + if (serverStatus == RequestRejected) { + bincClient << clientRequest.getTag() << " NO " + << session.getResponseCode() + << clientRequest.getName() << " failed: " + << session.getLastError() << endl; + } else if (serverStatus == RequestError) { + bincClient << "* BAD " + << session.getLastError() << endl; + skipToNextRequest = true; + } else if (serverStatus == RequestIgnore) { + ; + } else if (serverStatus == OK && session.getState() != Session::LOGOUT) { + bincClient << clientRequest.getTag() << " OK"; + if (clientRequest.getUidMode()) bincClient << " UID"; + bincClient << " " << session.getResponseCode() + << clientRequest.getName() << " completed"; + if (clientRequest.getContextInfo() != "") + bincClient << " (" << clientRequest.getContextInfo() << ")"; + + bincClient << endl; + } else { + // Timeout, ClientDisconnected + session.setState(Session::LOGOUT); + } + + bincClient.flush(); + + if (skipToNextRequest) { + if (!bincClient.skipTo('\n')) { + if (bincClient.getLastError() == IODevice::Timeout) { + serverStatus = Timeout; + } else + serverStatus = ClientDisconnected; + break; + } + } + } while (session.getState() != Session::LOGOUT); // do line 81 + + // Session finished - write some log information + + string userID = this->stubMode ? session.getIP() : session.getEnv("USER"); + + if (this->stubMode) { + bincLog << "bincimap-up: pid " << pid + << " (Read: " << session.getReadBytes() + << " Written: " << session.getWriteBytes() << ")\n"; + } else { + bincLog << "bincimapd: pid " << pid + << " (Bodies: " << session.getBodies() + << " Statements: " << session.getStatements() << ")\n"; + } + + if (serverStatus == Timeout) { + bincClient << "* BYE Timeout after " << session.timeout() + << " seconds of inactivity\n"; + bincClient.flush(); + bincLog << "bincimapd: pid " << pid + << " Timed out: <" << userID << "> after " << IDLE_TIMEOUT << "s"; + } else if (serverStatus == ClientDisconnected) { + bincLog << "bincimapd: pid " << pid + << "Disconnected: <" << userID << ">\n"; + } else { + if (this->stubMode) { + bincLog << "bincimap-up: pid " << pid + << " Logged out: <" << userID << ">\n"; + } else { + bincLog << "bincimapd: pid " << pid + << " Disconnected: <" << userID << ">\n"; + } + } + bincLog.flush(); + + return 0; +} diff --git a/src/include/address.h b/src/include/address.h new file mode 100644 index 0000000..a8aded9 --- /dev/null +++ b/src/include/address.h @@ -0,0 +1,29 @@ +/** -------------------------------------------------------------------- + * @file address.h + * @brief Declaration of the Address class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef address_h_included +#define address_h_included +#include <string> + +namespace Binc { + + //------------------------------------------------------------------------ + class Address { + public: + std::string name; + std::string local; + std::string host; + + //-- + std::string toParenList(void) const; + + //-- + Address(const std::string &name, const std::string &addr); + Address(const std::string &wholeaddr); + }; +} + +#endif diff --git a/src/include/argparser.h b/src/include/argparser.h new file mode 100644 index 0000000..6106974 --- /dev/null +++ b/src/include/argparser.h @@ -0,0 +1,69 @@ +/** -------------------------------------------------------------------- + * @file argparser.h + * @brief Declaration of the argument parser class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef ARGPARSER_H_INCLUDED +#define ARGPARSER_H_INCLUDED +#include <map> +#include <string> +#include <vector> + +namespace Binc { + class ArgOpts { + public: + std::string c; + bool b; + bool o; + std::string desc; + + inline ArgOpts(const std::string &chr, bool boolean, bool optional, + const std::string &descr) + { + c = chr; + b = boolean; + o = optional; + desc = descr; + } + }; + + class CommandLineArgs { + public: + CommandLineArgs(void); + + bool parse(int argc, char *argv[]); + std::string errorString(void) const; + + int argc(void) const; + + const std::string operator [](const std::string &arg) const; + + void addOptional(const std::string &arg, const std::string &desc, + bool boolean); + void addRequired(const std::string &arg, const std::string &desc, + bool boolean); + bool hasArg(const std::string &arg) const; + + std::string usageString(void) const; + + void setTail(const std::string &str); + + const std::vector<std::string> &getUnqualifiedArgs() const; + + private: + void registerArg(const std::string &arg, const std::string &desc, + bool boolean, bool optional); + + std::string errString; + std::map<std::string, ArgOpts> reg; + std::map<std::string, std::string> args; + std::map<std::string, bool> passedArgs; + std::vector<std::string> unqualified; + std::string tail; + std::string head; + int ac; + }; +} + +#endif diff --git a/src/include/authenticate.h b/src/include/authenticate.h new file mode 100644 index 0000000..0ef6796 --- /dev/null +++ b/src/include/authenticate.h @@ -0,0 +1,18 @@ +/** -------------------------------------------------------------------- + * @file authenticate.h + * @brief Declaration of the common authentication mechanism. + * @author Andreas Aardal Hanssen, Erwin Hoffmann + * @date 2002-2005, 2023 + * ----------------------------------------------------------------- **/ +#ifndef authenticate_h_included +#define authenticate_h_included +#include <string> + +#include "depot.h" + +namespace Binc { + int authenticate(Depot &, const std::string &username, + const std::string &password, const std::string &challenge); +} + +#endif diff --git a/src/include/base64.h b/src/include/base64.h new file mode 100644 index 0000000..25b0ff4 --- /dev/null +++ b/src/include/base64.h @@ -0,0 +1,18 @@ +/** -------------------------------------------------------------------- + * @file base64.h + * @brief Declaration of base64 Utilities + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef base64_h_included +#define base64_h_included +#include <string> + +namespace Binc { + + std::string base64decode(const std::string &s_in); + std::string base64encode(const std::string &s_in); + +} + +#endif diff --git a/src/include/broker.h b/src/include/broker.h new file mode 100644 index 0000000..6d148ae --- /dev/null +++ b/src/include/broker.h @@ -0,0 +1,84 @@ +/** -------------------------------------------------------------------- + * @file broker.h + * @brief Declaration of the Broker class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef broker_h_included +#define broker_h_included +#include "depot.h" +#include "operators.h" + +#include <string> +#include <map> + +namespace Binc { + + class Request; + class Broker; + + //------------------------------------------------------------------ + class BrokerFactory { + private: + std::map<int, Broker *> brokers; + + //-- + BrokerFactory(void); + + mutable std::string lastError; + + public: + Broker *getBroker(int state); + void assign(const std::string &fname, Operator *o); + void addCapability(const std::string &c); + Operator *getOperator(int state, const std::string &name) const; + + inline const std::string &getLastError(void) const; + inline void setLastError(const std::string &error) const; + + //-- + static BrokerFactory &getInstance(void); + ~BrokerFactory(void); + }; + + //------------------------------------------------------------------ + inline const std::string &BrokerFactory::getLastError(void) const + { + return lastError; + } + + //------------------------------------------------------------------ + inline void BrokerFactory::setLastError(const std::string &error) const + { + lastError = error; + } + + //------------------------------------------------------------------ + class Broker { + private: + std::map<std::string, Operator *> operators; + std::map<std::string, bool> deletables; + + public: + Operator * get(const std::string &name) const; + void assign(const std::string &fname, Operator *o, bool deletable = false); + Operator::ParseResult parseStub(Request &cmd); + + //-- + inline Broker(Broker &); + inline Broker(const Broker &); + Broker(void); + ~Broker(void); + }; + + inline Broker::Broker(Broker &) + { + } + + inline Broker::Broker(const Broker &) + { + } + +} + +#endif diff --git a/src/include/convert.h b/src/include/convert.h new file mode 100644 index 0000000..cea2906 --- /dev/null +++ b/src/include/convert.h @@ -0,0 +1,298 @@ +/** -------------------------------------------------------------------- + * @file convert.h + * @brief Declaration of miscellaneous convertion functions. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ + +#ifndef convert_h_included +#define convert_h_included +#include <cstring> +#include <string> +#include <vector> +#include <iomanip> +#include <iostream> + +#include <stdio.h> +#include <sys/stat.h> + +#include "address.h" +#include "depot.h" + +namespace Binc { + + //---------------------------------------------------------------------- + inline std::string toString(int i_in) + { + char intbuf[16]; + snprintf(intbuf, sizeof(intbuf), "%d", i_in); + return std::string(intbuf); + } + + //---------------------------------------------------------------------- + inline std::string toString(unsigned int i_in) + { + char intbuf[16]; + snprintf(intbuf, sizeof(intbuf), "%u", i_in); + return std::string(intbuf); + } + + //---------------------------------------------------------------------- + inline std::string toString(unsigned long i_in) + { + char longbuf[40]; + snprintf(longbuf, sizeof(longbuf), "%lu", i_in); + return std::string(longbuf); + } + + //---------------------------------------------------------------------- + inline std::string toString(const char *i_in) + { + return std::string(i_in); + } + + //---------------------------------------------------------------------- + inline int atoi(const std::string &s_in) + { + return ::atoi(s_in.c_str()); + } + + //---------------------------------------------------------------------- + inline std::string toHex(const std::string &s) + { + const char hexchars[] = "0123456789abcdef"; + std::string tmp; + for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) { + unsigned char c = (unsigned char)*i; + tmp += hexchars[((c & 0xf0) >> 4)]; + tmp += hexchars[c & 0x0f]; + } + + return tmp; + } + + //---------------------------------------------------------------------- + inline std::string fromHex(const std::string &s) + { + // const + char hexchars[] = "0123456789abcdef"; + std::string tmp; + for (std::string::const_iterator i = s.begin(); + i != s.end() && i + 1 != s.end(); i += 2) { + int n; + unsigned char c = *i; + unsigned char d = *(i + 1); + + char *t; + if ((t = strchr(hexchars, c)) == 0) return "out of range"; + n = (t - hexchars) << 4; + + if ((t = strchr(hexchars, d)) == 0) return "out of range"; + n += (t - hexchars); + + if (n >= 0 && n <= 255) + tmp += (char) n; + else + return "out of range"; + } + + return tmp; + } + + //---------------------------------------------------------------------- + inline std::string toImapString(const std::string &s_in) + { + for (std::string::const_iterator i = s_in.begin(); i != s_in.end(); ++i) { + unsigned char c = (unsigned char)*i; + if (c <= 31 || c >= 127 || c == '\"' || c == '\\') + return "{" + toString(s_in.length()) + "}\r\n" + s_in; + } + + return "\"" + s_in + "\""; + } + + //---------------------------------------------------------------------- + inline void uppercase(std::string &input) + { + for (std::string::iterator i = input.begin(); i != input.end(); ++i) + *i = toupper(*i); + } + + //---------------------------------------------------------------------- + inline void lowercase(std::string &input) + { + for (std::string::iterator i = input.begin(); i != input.end(); ++i) + *i = tolower(*i); + } + + //---------------------------------------------------------------------- + inline void chomp(std::string &s_in, const std::string &chars = " \t\r\n") + { + int n = s_in.length(); + while (n > 1 && chars.find(s_in[n - 1]) != std::string::npos) + s_in.resize(n-- - 1); + } + + //---------------------------------------------------------------------- + inline void trim(std::string &s_in, const std::string &chars = " \t\r\n") + { + while (s_in != "" && chars.find(s_in[0]) != std::string::npos) + s_in = s_in.substr(1); + chomp(s_in, chars); + } + + //---------------------------------------------------------------------- + inline const std::string unfold(const std::string &a, + bool removecomment = true) + { + std::string tmp; + bool incomment = false; + bool inquotes = false; + for (std::string::const_iterator i = a.begin(); i != a.end(); ++i) { + unsigned char c = (unsigned char)*i; + if (!inquotes && removecomment) { + if (c == '(') { + incomment = true; + tmp += " "; + } else if (c == ')') { + incomment = false; + } else if (c != 0x0a && c != 0x0d) { + tmp += *i; + } + } else if (c != 0x0a && c != 0x0d) { + tmp += *i; + } + + if (!incomment) { + if (*i == '\"') + inquotes = !inquotes; + } + } + + trim(tmp); + return tmp; + } + + //---------------------------------------------------------------------- + inline void split(const std::string &s_in, const std::string &delim, + std::vector<std::string> &dest, bool skipempty = true) + { + std::string token; + for (std::string::const_iterator i = s_in.begin(); i != s_in.end(); ++i) { + if (delim.find(*i) != std::string::npos) { + if (!skipempty || token != "") + dest.push_back(token); + token = ""; + } else + token += *i; + } + + if (token != "") + dest.push_back(token); + } + + //---------------------------------------------------------------------- + inline void splitAddr(const std::string &s_in, + std::vector<std::string> &dest, bool skipempty = true) + { + static const std::string delim = ","; + std::string token; + bool inquote = false; + for (std::string::const_iterator i = s_in.begin(); i != s_in.end(); ++i) { + if (inquote && *i == '\"') inquote = false; + else if (!inquote && *i == '\"') inquote = true; + + if (!inquote && delim.find(*i) != std::string::npos) { + if (!skipempty || token != "") dest.push_back(token); + token = ""; + } else + token += *i; + } + if (token != "") + dest.push_back(token); + } + + //---------------------------------------------------------------------- + inline std::string toCanonMailbox(const std::string &s_in) + { + if (s_in.find("..") != std::string::npos) return ""; + + if (s_in.length() >= 5) { + std::string a = s_in.substr(0, 5); + uppercase(a); + return a == "INBOX" ? + a + (s_in.length() > 5 ? s_in.substr(5) : "") : s_in; + } + + return s_in; + } + + //------------------------------------------------------------------------ + inline std::string toRegex(const std::string &s_in, char delimiter) + { + std::string regex = "^"; + for (std::string::const_iterator i = s_in.begin(); i != s_in.end(); ++i) { + if (*i == '.' || *i == '[' || *i == ']' || *i == '{' || *i == '}' || + *i == '(' || *i == ')' || *i == '^' || *i == '$' || *i == '?' || + *i == '+' || *i == '\\') { + regex += "\\"; + regex += *i; + } else if (*i == '*') + regex += ".*?"; + else if (*i == '%') { + regex += "(\\"; + regex += delimiter; + regex += "){0,1}"; + regex += "[^\\"; + regex += delimiter; + regex += "]*?"; + } else + regex += *i; + } + + if (regex[regex.length() - 1] == '?') + regex[regex.length() - 1] = '$'; + else + regex += "$"; + + return regex; + } + + //------------------------------------------------------------------------ + class BincStream { + private: + std::string nstr; + + public: + //-- + BincStream &operator << (std::ostream&(*)(std::ostream&)); + BincStream &operator << (const std::string &t); + BincStream &operator << (unsigned long t); + BincStream &operator << (unsigned int t); + BincStream &operator << (int t); + BincStream &operator << (char t); + + //-- + std::string popString(unsigned int size); + + //-- + char popChar(void); + void unpopChar(char c); + void unpopStr(const std::string &s); + + //-- + const std::string &str(void) const; + + //-- + unsigned int getSize(void) const; + + //-- + void clear(void); + + //-- + BincStream(void); + ~BincStream(void); + }; +} + +#endif diff --git a/src/include/depot.h b/src/include/depot.h new file mode 100644 index 0000000..844a987 --- /dev/null +++ b/src/include/depot.h @@ -0,0 +1,154 @@ +/** -------------------------------------------------------------------- + * @filec depot.h + * @file Declaration of the Depot class (the mail storage) + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef depot_h_included +#define depot_h_included +#include <map> +#include <string> +#include <vector> + +#include <dirent.h> + +namespace Binc { + + class Mailbox; + class Depot; + class Status; + + //------------------------------------------------------------------ + class DepotFactory { + private: + std::vector<Depot *> depots; + DepotFactory(void); + + public: + void assign(Depot *); + Depot *get(const std::string &name) const; + + static DepotFactory &getInstance(void); + ~DepotFactory(void); + }; + + //------------------------------------------------------------------ + class Depot { + public: + //-- + class iterator { + public: + std::string operator * (void) const; + void operator ++ (void); + bool operator != (iterator) const; + bool operator == (iterator) const; + + iterator(void); + iterator(const iterator ©); + iterator(DIR *, struct dirent *); + ~iterator(void); + + void deref(void); + + iterator &operator =(const iterator ©); + + friend class Depot; + + private: + DIR *dirp; + struct dirent *direntp; + int *ref; + }; + + private: + iterator enditerator; + std::vector<Mailbox *> backends; + Mailbox *defaultmailbox; + Mailbox *selectedmailbox; + std::vector<std::string> subscribed; + std::string personalNamespace; + std::string othersNamespace; + std::string sharedNamespace; + + protected: + mutable std::string lastError; + std::string name; + char delimiter; + mutable std::map<std::string, Status> mailboxstatuses; + + public: + virtual iterator begin(const std::string &) const; + virtual const iterator &end(void) const; + + void setDelimiter(char); + const char getDelimiter(void) const; + + virtual void assign(Mailbox *); + + bool setDefaultType(const std::string &n); + Mailbox *getDefault(void) const; + virtual Mailbox *get(const std::string &path) const; + + virtual bool setSelected(Mailbox *); + virtual Mailbox *getSelected(void) const; + void resetSelected(void); + + bool getStatus(const std::string &s_in, Status &dest) const; + + const std::string &getName(void) const; + + virtual const std::string &getPersonalNamespace(void) const; + virtual const std::string &getOthersNamespace(void) const; + virtual const std::string &getSharedNamespace(void) const; + + virtual std::string mailboxToFilename(const std::string &m) const = 0; + virtual std::string filenameToMailbox(const std::string &m) const = 0; + + virtual bool createMailbox(const std::string &m) const; + virtual bool deleteMailbox(const std::string &m) const; + virtual bool renameMailbox(const std::string &m, const std::string &n) const; + + const std::string &getLastError(void) const; + void setLastError(const std::string &error) const; + + virtual std::vector<std::string> getSubscriptions(void) const; + virtual void subscribeTo(const std::string mailbox); + virtual bool unsubscribeTo(const std::string mailbox); + virtual void loadSubscribes(void); + virtual bool saveSubscribes(void) const; + + //-- + Depot(void); + Depot(const std::string &name); + virtual ~Depot(void); + }; + + //------------------------------------------------------------------ + class MaildirPPDepot : public Depot { + public: + std::string mailboxToFilename(const std::string &m) const; + std::string filenameToMailbox(const std::string &m) const; + + const std::string &getPersonalNamespace(void) const; + + //-- + MaildirPPDepot(); + ~MaildirPPDepot(); + private: + std::string privateNamespace; + }; + + //------------------------------------------------------------------ + class IMAPdirDepot : public Depot { + public: + std::string mailboxToFilename(const std::string &m) const; + std::string filenameToMailbox(const std::string &m) const; + + //-- + IMAPdirDepot(); + ~IMAPdirDepot(); + }; + +} + +#endif diff --git a/src/include/globals.h b/src/include/globals.h new file mode 100644 index 0000000..dd63b5f --- /dev/null +++ b/src/include/globals.h @@ -0,0 +1,25 @@ +/** -------------------------------------------------------------------- + * @file globals.h + * @brief Global constants. + * @author Andreas Aardal Hanssen, Erwin Hoffmann + * @date 2002-2005, 2023 + * ----------------------------------------------------------------- **/ +#ifndef GLOBAL_H_INCLUDED +#define GLOBAL_H_INCLUDED + +#define BINC_VERSION "2.0.14" +#define IMAP_VERSION "IMAP4rev1" +#define BINC_CACHE "BINC-CACHE-1.0" +#define IMAP_PORT "143" +#define IMAPS_PORT "993" + +namespace Binc { + static const int IDLE_TIMEOUT = 30*60; + static const int AUTH_TIMEOUT = 60; + static const int AUTH_PENALTY = 5; + static const int TRANSFER_TIMEOUT = 20*60; + static const int TRANSFER_BUFFER_SIZE = 1024; + static const int INPUT_BUFFER_LIMIT = 8192; + +}; +#endif diff --git a/src/include/imapparser.h b/src/include/imapparser.h new file mode 100644 index 0000000..4f77985 --- /dev/null +++ b/src/include/imapparser.h @@ -0,0 +1,171 @@ +/** -------------------------------------------------------------------- + * @file imapparser.h + * @brief Declaration of the common items for parsing IMAP input + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef imapparser_h_included +#define imapparser_h_included + +/* stl includes */ +#include <string> +#include <map> +#include <vector> + +namespace Binc { + //------------------------------------------------------------------------ + class SequenceSet { + public: + void addRange(unsigned int a_in, unsigned int b_in); + bool isInSet(unsigned int n) const; + void addNumber(unsigned int a_in); + inline bool isLimited(void) const { return limited; } + + static SequenceSet &all(void); + + static SequenceSet &null(void); + + SequenceSet &operator = (const SequenceSet ©); + + SequenceSet(void); + SequenceSet(const SequenceSet ©); + ~SequenceSet(void); + + protected: + bool isNull(void) const; + + private: + bool limited; + bool nullSet; + + class Range { + public: + unsigned int from; + unsigned int to; + Range(unsigned int from, unsigned int to); + }; + + std::vector<Range> internal; + }; + + //------------------------------------------------------------------------ + class BincImapParserFetchAtt { + public: + std::string type; + std::string section; + std::string sectiontext; + std::vector<std::string> headerlist; + unsigned int offsetstart; + unsigned int offsetlength; + bool hassection; + + BincImapParserFetchAtt(const std::string &typeName = ""); + + std::string toString(void); + }; + + //------------------------------------------------------------------------ + class BincImapParserSearchKey { + public: + std::string name; + std::string date; + std::string astring; + std::string bstring; + int type; + unsigned int number; + SequenceSet bset; + enum {KEY_AND, KEY_OR, KEY_NOT, KEY_OTHER, KEY_SET}; + + std::vector<BincImapParserSearchKey> children; + + const SequenceSet& getSet(void) const; + + BincImapParserSearchKey(void); + }; + + //------------------------------------------------------------------------ + class BincImapParserData { + public: + virtual ~BincImapParserData(void) {} + }; + + //------------------------------------------------------------------------ + class Request { + private: + std::string tag; + std::string name; + std::string mode; + std::string date; + std::string userid; + std::string password; + std::string mailbox; + std::string newmailbox; + std::string authtype; + std::string listmailbox; + std::string charset; + std::string literal; + std::string contextInfo; + bool uidmode; + + public: + BincImapParserData * extra; + std::vector<std::string> flags; + std::vector<std::string> statuses; + + SequenceSet bset; + BincImapParserSearchKey searchkey; + std::vector<BincImapParserFetchAtt> fatt; + + void setUidMode(void); + bool getUidMode(void) const; + + void setTag(std::string &t_in); + const std::string &getTag(void) const; + + void setMode(const std::string &m_in); + const std::string &getMode(void) const; + + void setName(const std::string &s_in); + const std::string &getName(void) const; + + void setLiteral(const std::string &s_in); + const std::string &getLiteral(void) const; + + void setDate(const std::string &s_in); + const std::string &getDate(void) const; + + void setCharSet(const std::string &s_in); + const std::string &getCharSet(void) const; + + void setUserID(const std::string &s_in); + const std::string &getUserID(void) const; + + void setPassword(const std::string &s_in); + const std::string &getPassword(void) const; + + void setMailbox(const std::string &s_in); + const std::string &getMailbox(void) const; + + void setAuthType(const std::string &s_in); + const std::string &getAuthType(void) const; + + void setNewMailbox(const std::string &s_in); + const std::string &getNewMailbox(void) const; + + void setListMailbox(const std::string &s_in); + const std::string &getListMailbox(void) const; + + void setContextInfo(const std::string &s_in); + const std::string &getContextInfo(void) const; + + SequenceSet &getSet(void); + + std::vector<std::string> &getFlags(void); + std::vector<std::string> &getStatuses(void); + + Request(void); + ~Request(void); + }; +} + +#endif diff --git a/src/include/imapserver.h b/src/include/imapserver.h new file mode 100644 index 0000000..940234e --- /dev/null +++ b/src/include/imapserver.h @@ -0,0 +1,37 @@ +/** -------------------------------------------------------------------- + * @file imapserver.h + * @brief Declaration of the IMAPServer class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ + +namespace Binc { + + class IMAPServer { + public: + IMAPServer(int argc, char *argv[]); + ~IMAPServer(void); + + int initialize(void); + int runStub(void); + int run(void); + + enum ServerStatus { + OK, + RequestError, + RequestIgnore, // required for StartTLS, premature answer + RequestRejected, + ClientDisconnected, + Timeout + }; + + private: + void prepareForNextRequest(void); + + int argc; + char **argv; + bool stubMode; + + ServerStatus serverStatus; + }; +} diff --git a/src/include/iodevice.h b/src/include/iodevice.h new file mode 100644 index 0000000..070ac3f --- /dev/null +++ b/src/include/iodevice.h @@ -0,0 +1,382 @@ +/** -------------------------------------------------------------------- + * @file iodevice.h + * @brief Declaration of the IODevice class. + * @author Andreas Aardal Hanssen + * @date 2002, 2003 + * ----------------------------------------------------------------- **/ +#ifndef iodevice_h_included +#define iodevice_h_included + +#include "convert.h" // BincStream +//#include <iostream> +#include <string> +#include <unistd.h> // ::write + +const char CMS_END_OF_LINE[4] = { 0x0d, '\n', 0x00, 0x00 }; + +namespace Binc { + /*! + \class IODevice + \brief The IODevice class provides a framework for reading and + writing to device. + + Implement new devices by inheriting this class and overloading all + virtual methods. + + service() returns the service that the specific device is used + for. Two values are "log" and "client". + + \sa IOFactory, MultilogDevice, SyslogDevice, StdIODevice, SSLDevice + */ + class IODevice { + public: + /*! + Standard options for an IODevice. + */ + enum Flags { + None = 0, + FlushesOnEndl = 1 << 0, + HasInputLimit = 1 << 1, + HasOutputLimit = 1 << 2, + IsEnabled = 1 << 3, + HasTimeout = 1 << 4 + }; + + /*! + Errors from when an operation returned false. + */ + enum Error { + Unknown, + Timeout + }; + + /*! + Constructs an invalid IODevice. + + Instances of IODevice perform no operations, and all boolean + functions always return false. This constructor is only useful + if called from a subclass that reimplements all virtual methods. + */ + IODevice(int f = 0); + + /*! + Destructs an IODevice; does nothing. + */ + virtual ~IODevice(void); + + /*! + Clears all data in the input and output buffers. + */ + void clear(void); + + /*! + Sets one or more flags. + \param f A bitwise OR of flags from the Flags enum. + */ + void setFlags(unsigned int f); + + /*! + Clears one or more flags. + \param f A bitwise OR of flags from the Flags enum. + */ + void clearFlags(unsigned int f); + + /*! + Sets the maximum allowed input buffer size. If this size is + non-zero and exceeded, reading from the device will fail. This + functionality is used to prevent clients from forcing this class + to consume so much memory that the program crashes. + + Setting the max input buffer size to 0 disables the input size + limit. + + \param max The maximum input buffer size in bytes. + */ + void setMaxInputBufferSize(unsigned int max); + + /*! + Sets the maximum allowed output buffer size. If this size is + non-zero and exceeded, flush() is called implicitly. + + Setting the max output buffer size to 0 disables the output size + limit. This is generally discouraged. + + As a contrast to setMaxInputBufferSize(), this function is used + to bundle up consequent write calls, allowing more efficient use + of the underlying device as larger blocks of data are written at + a time. + + \param max The maximum output buffer size in bytes. + */ + void setMaxOutputBufferSize(unsigned int max); + + /*! + Sets the device's internal timeout in seconds. This timeout is + used both when waiting for data to read and for waiting for the + ability to write. + + If this timeout is exceeded, the read or write function that + triggered the timeout will fail. + + Setting the timeout to 0 disables the timeout. + + \param t The timeout in seconds. + \sa getTimeout() + */ + void setTimeout(unsigned int t); + + /*! + Returns the timeout in seconds, or 0 if there is no timeout. + + \sa setTimeout() + */ + unsigned int getTimeout(void) const; + + enum LogLevel { + ErrorLevel, + InfoLevel, + WarningLevel, + DebugLevel + }; + + /*! + Sets the output level for the following write operations on this + device. + + The output level is a number which gives the following write + operations a priority. You can use setOutputLevelLimit() to + filter the write operations valid for different operating modes. + This enables you to have certain write operations ignored. + + For instance, if the output level is set to 0, then "Hello" is + written, and the output level is set to 1, followed by writing + "Daisy", the output level limit value will decive wether only + "Hello" is written, or if also "Daisy" is written. + + A low value of the level gives higher priority, and a high level + will give low priority. The default value is 0, and write + operations that are done with output level 0 are never ignored. + + \param level The output level + \sa getOutputLevel(), setOutputLevelLimit() + */ + void setOutputLevel(LogLevel level); + + /*! + Returns the current output level. + + \sa setOutputLevel() + */ + LogLevel getOutputLevel(void) const; + + /*! + Sets the current output level limit. Write operations with a + level higher than the output level limit are ignored. + + \param level The output level limit + \sa setOutputLevel() + */ + void setOutputLevelLimit(LogLevel level); + + /*! + Returns the current output level limit. + + \sa setOutputLevelLimit() + */ + LogLevel getOutputLevelLimit(void) const; + + /*! + Returns the number of bytes that have been read from this device + since it was created. + */ + unsigned int getReadCount(void) const; + + /*! + Returns the number of bytes that have been written to this + device since it was created. + */ + unsigned int getWriteCount(void) const; + + /*! + Calling this function enables the built-in protocol dumping feature in + the device. All input and output to this device will be dumped to a file + in /tmp. + */ + void enableProtocolDumping(void); + + /*! + Writes data to the device. Depending on the value of the max + output buffer size, the data may not be written immediately. + + \sa setMaxOutputBufferSize() + */ + template <class T> IODevice &operator << (const T &source); + + /*! + Writes data to the device. This function specializes on standard + ostream derivates, such as std::endl. + */ + IODevice &operator << (std::ostream &(*source)(std::ostream &)); + + /*! + Returns true if data can be read from the device; otherwise + returns false. + */ + virtual bool canRead(void) const; + + /*! + Reads data from the device, and stores this in a string. Returns + true on success; otherwise returns false. + + \param dest The incoming data is stored in this string. + \param max No more than this number of bytes is read from the + device. + */ + bool readStr(std::string *dest, unsigned int max = 0); + + /*! + Reads exactly one byte from the device and stores this in a + char. Returns true on success; otherwise returns false. + + \param dest The incoming byte is stored in this char. + */ + bool readChar(char *dest = 0); + + /*! + FIXME: add docs + */ + void unreadChar(char c); + + /*! + FIXME: add docs + */ + void unreadStr(const std::string &s); + + /*! + Reads characters from the device, until and including one + certain character is found. All read characters are discarded. + + This function can be used to skip to the beginning of a line, + with the terminating character being '\n'. + + \param The certain character. + */ + bool skipTo(char c); + + /*! + Flushes the output buffer. Writes all data in the output buffer + to the device. + */ + bool flush(void); + + /*! + Returns the type of error that most recently occurred. + */ + Error getLastError(void) const; + + /*! + Returns a human readable description of the error that most + recently occurred. If no known error has occurred, this method + returns "Unknown error". + */ + std::string getLastErrorString(void) const; + + /*! + Returns the type of service provided by this device. Two valid + return values are "client" and "log". + */ + virtual std::string service(void) const; + + protected: + /*! + Waits until data can be written to the device. If the timeout is + 0, this function waits indefinitely. Otherwise, it waits until + the timeout has expired. + + If this function returns true, data can be written to the + device; otherwise, getLastError() must be checked to determine + whether a timeout occurred or whether an error with the device + prevents further writing. + */ + virtual bool waitForWrite(void) const; + + /*! + Waits until data can be read from the device. + + \sa waitForWrite() + */ + virtual bool waitForRead(void) const; + + /*! + Types of results from a write. + */ + enum WriteResult { + WriteWait = 0, + WriteDone = 1 << 0, + WriteError = 1 << 1 + }; + + /*! + Writes as much data as possible to the device. If some but not + all data was written, returns WriteWait. If all data was + written, returns WriteDone. If an error occurred, returns + WriteError. + */ + virtual WriteResult write(void); + + /*! + Reads data from the device, and stores it in the input buffer. + Returns true on success; otherwise returns false. + + This method will fail if there is no more data available, if a + timeout occurred or if an error with the device prevents more + data from being read. + + The number of bytes read from the device is undefined. + */ + virtual bool fillInputBuffer(void); + + BincStream inputBuffer; + BincStream outputBuffer; + + protected: + unsigned int flags; + unsigned int maxInputBufferSize; + unsigned int maxOutputBufferSize; + + unsigned int timeout; + + unsigned int readCount; + unsigned int writeCount; + + LogLevel outputLevel; + LogLevel outputLevelLimit; + + mutable Error error; + mutable std::string errorString; + + int dumpfd; + }; + + //---------------------------------------------------------------------- + template <class T> IODevice &IODevice::operator << (const T &source) + { + if ((flags & IsEnabled) && outputLevel <= outputLevelLimit) { + outputBuffer << source; + + if (dumpfd) { + BincStream ss; + ss << source; + ::write(dumpfd, ss.str().c_str(), ss.getSize()); + } + + if (flags & HasInputLimit) + if (outputBuffer.getSize() > maxOutputBufferSize) + flush(); + } + + return *this; + } +} + +#endif diff --git a/src/include/iofactory.h b/src/include/iofactory.h new file mode 100644 index 0000000..6ebccaa --- /dev/null +++ b/src/include/iofactory.h @@ -0,0 +1,53 @@ +/** -------------------------------------------------------------------- + * @file iofactory.h + * @brief Declaration of the IOFactory class. + * @author Andreas Aardal Hanssen + * @date 2002, 2003 + * ----------------------------------------------------------------- **/ +#ifndef IOFACTORY_H_INCLUDED +#define IOFACTORY_H_INCLUDED +#include <map> +#include <string> + +#include "iodevice.h" + +namespace Binc { + class IOFactory { + public: + ~IOFactory(void); + + static void addDevice(IODevice *dev); + static IOFactory &getInstance(void); + static IODevice &getClient(void); + static IODevice &getLogger(void); + + private: + IOFactory(void); + + std::map<std::string, IODevice *> devices; + }; +} + +#define bincClient \ + IOFactory::getClient() + +#if defined (DEBUG) +//#define bincError if (false) std::cout +#define bincError std::cerr +// #define bincWarning if (false) std::cout +#define bincWarning std::cerr +#define bincDebug std::cerr +//#define bincDebug if (false) std::cout +#else +#define bincError \ + IOFactory::getLogger().setOutputLevel(IODevice::ErrorLevel);IOFactory::getLogger() +#define bincWarning \ + IOFactory::getLogger().setOutputLevel(IODevice::WarningLevel);IOFactory::getLogger() +#define bincDebug \ + IOFactory::getLogger().setOutputLevel(IODevice::DebugLevel);IOFactory::getLogger() +#endif + +#define bincLog \ + IOFactory::getLogger().setOutputLevel(IODevice::InfoLevel);IOFactory::getLogger() + +#endif diff --git a/src/include/mailbox.h b/src/include/mailbox.h new file mode 100644 index 0000000..db98cc0 --- /dev/null +++ b/src/include/mailbox.h @@ -0,0 +1,136 @@ +/** -------------------------------------------------------------------- + * @file mailbox.h + * @brief Declaration of the Mailbox class (Mailbox is logical container) + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef mailbox_h_included +#define mailbox_h_included + +#include <map> +#include <string> +#include <queue> +#include <vector> + +#include <time.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include "imapparser.h" + +namespace Binc { + + class Message; + class Status; + class PendingUpdates; + class File; + + //------------------------------------------------------------------------ + class Mailbox { + public: + + //---------------------------------------------------------------------- + class BaseIterator { + public: + BaseIterator(int sqn = 0); + virtual ~BaseIterator(void); + + virtual Message &operator *(void) = 0; + virtual void operator ++(void) = 0; + virtual bool operator !=(const BaseIterator &d) const = 0; + virtual bool operator ==(const BaseIterator &d) const = 0; + + virtual void erase(void) = 0; + + unsigned int sqnr; + }; + + //---------------------------------------------------------------------- + class iterator { + public: + iterator(BaseIterator &i); + + Message &operator *(void); + void operator ++(void); + bool operator ==(const iterator &) const; + bool operator !=(const iterator &) const; + + unsigned int getSqnr() const; + + void erase(void); + + protected: + BaseIterator &realIterator; + }; + + enum Iterator { + INCLUDE_EXPUNGED = 1, + SKIP_EXPUNGED = 2 + }; + + enum Mode { + UID_MODE = 4, + SQNR_MODE = 8 + }; + + virtual iterator begin(const SequenceSet &bset, unsigned int mod = INCLUDE_EXPUNGED | SQNR_MODE) const = 0; + virtual iterator end(void) const = 0; + + //-- Generic for one mailbox type + virtual bool getStatus(const std::string &, Status &) const = 0; + virtual bool isMailbox(const std::string &) const = 0; + virtual bool isMarked(const std::string &) const = 0; + virtual unsigned int getStatusID(const std::string &) const = 0; + virtual void bumpUidValidity(const std::string &) const = 0; + + //-- Specific for one mailbox + void setReadOnly(bool readOnly); + bool isReadOnly(void) const; + + virtual const std::string getTypeName(void) const = 0; + const std::string getName(void) const; + void setName(const std::string &name); + + virtual unsigned int getMaxUid(void) const = 0; + virtual unsigned int getMaxSqnr(void) const = 0; + virtual unsigned int getUidNext(void) const = 0; + virtual unsigned int getUidValidity(void) const = 0; + + virtual bool getUpdates(bool scan, unsigned int type, + PendingUpdates &updates, bool forceScan) = 0; + + virtual void updateFlags(void) = 0; + virtual void expungeMailbox(void) = 0; + virtual bool selectMailbox(const std::string &name, const std::string &s_in) = 0; + virtual bool createMailbox(const std::string &s, mode_t mode, uid_t owner = 0, gid_t group = 0, bool root = false) = 0; + virtual bool deleteMailbox(const std::string &s) = 0; + virtual void closeMailbox(void) = 0; + + virtual Message *createMessage(const std::string &mbox, time_t idate = 0) = 0; + virtual bool commitNewMessages(const std::string &mbox) = 0; + virtual bool rollBackNewMessages(void) = 0; + virtual bool fastCopy(Message &source, Mailbox &desttype, const std::string &destname) = 0; + + const std::string &getLastError(void) const; + void setLastError(const std::string &error) const; + + //-- + Mailbox(void); + virtual ~Mailbox(void); + + friend class Mailbox::iterator; + + protected: + bool readOnly; + + private: + Mailbox(const Mailbox ©); + + mutable std::string lastError; + + std::string name; + }; +} + +#endif diff --git a/src/include/maildir.h b/src/include/maildir.h new file mode 100644 index 0000000..4a262b5 --- /dev/null +++ b/src/include/maildir.h @@ -0,0 +1,190 @@ +/** -------------------------------------------------------------------- + * @file maildir.h + * @brief Declaration of the Maildir class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef maildir_h_included +#define maildir_h_included +#include <string> +#include <vector> +#include <map> + +#include "mailbox.h" +#include "maildirmessage.h" + +namespace Binc { + static const std::string CACHEFILEVERSION = "1.0.5"; + static const std::string UIDVALFILEVERSION = "1.0.5"; + + //---------------------------------------------------------------------- + class Lock { + public: + Lock(const std::string &path); + ~Lock(); + + private: + std::string lock; + }; + + //------------------------------------------------------------------------ + class MaildirIndexItem { + public: + unsigned int uid; + std::string fileName; + }; + + //------------------------------------------------------------------------ + class MaildirIndex + { + private: + std::map<std::string, MaildirIndexItem> idx; + + public: + void insert(const std::string &unique, unsigned int uid, + const std::string &fileName = ""); + void remove(const std::string &unique); + void clear(void); + void clearFileNames(void); + void clearUids(void); + unsigned int getSize(void) const; + MaildirIndexItem *find(const std::string &unique); + }; + + //------------------------------------------------------------------------ + class Maildir : public Mailbox { + public: + typedef std::map<unsigned int, MaildirMessage> MessageMap; + + class iterator : public BaseIterator { + public: + iterator(void); + iterator(Maildir *home, MessageMap::iterator i, + const SequenceSet &bset, + unsigned int mod = INCLUDE_EXPUNGED | SQNR_MODE); + iterator(const iterator ©); + ~iterator(void); + + Message &operator *(void); + void operator ++(void); + bool operator ==(const BaseIterator &) const; + bool operator !=(const BaseIterator &) const; + + iterator &operator =(const iterator ©); + + void erase(void); + + friend class Maildir; + + protected: + void reposition(void); + MaildirMessage &curMessage(void); + + private: + Maildir *mailbox; + SequenceSet bset; + int mod; + + MessageMap::iterator i; + unsigned int uidmax; + unsigned int sqnrmax; + + iterator(iterator &external); + }; + + const std::string getTypeName(void) const; + + Mailbox::iterator begin(const SequenceSet &bset, unsigned int mod = INCLUDE_EXPUNGED | SQNR_MODE) const; + Mailbox::iterator end(void) const; + + unsigned int getMaxUid(void) const; + unsigned int getMaxSqnr(void) const; + unsigned int getUidValidity(void) const; + unsigned int getUidNext(void) const; + + bool getUpdates(bool doscan, unsigned int type, + PendingUpdates &updates, bool forceScan); + + const std::string &getPath(void) const; + void setPath(const std::string &path_in); + + void bumpUidValidity(const std::string &) const; + + unsigned int getStatusID(const std::string &) const; + bool getStatus(const std::string &, Status &) const; + void updateFlags(void); + + bool isMailbox(const std::string &) const; + bool isMarked(const std::string &) const; + bool selectMailbox(const std::string &name, const std::string &s_in); + void closeMailbox(void); + void expungeMailbox(void); + bool createMailbox(const std::string &s, mode_t mode, uid_t owner = 0, gid_t group = 0, bool root = false); + bool deleteMailbox(const std::string &s); + + Message *createMessage(const std::string &mbox, time_t idate = 0); + bool commitNewMessages(const std::string &mbox); + bool rollBackNewMessages(void); + + bool fastCopy(Message &source, Mailbox &desttype, const std::string &destname); + + //-- + Maildir(void); + ~Maildir(void); + + friend class Maildir::iterator; + friend class MaildirMessage; + + protected: + enum ReadCacheResult { + Ok, + NoCache, + Error + }; + + ReadCacheResult readCache(void); + bool writeCache(void); + bool scanFileNames(void) const; + + enum ScanResult { + Success = 0, + TemporaryError = 1, + PermanentError = 2 + }; + + ScanResult scan(bool forceScan = false); + + MaildirMessage *get(const std::string &id); + void add(MaildirMessage &m); + + private: + std::vector<MaildirMessage> newMessages; + + unsigned int uidvalidity; + unsigned int uidnext; + bool selected; + std::string path; + + mutable iterator beginIterator; + mutable iterator endIterator; + + mutable bool firstscan; + mutable bool cacheRead; + mutable MaildirIndex index; + mutable MessageMap messages; + + mutable unsigned int oldrecent; + mutable unsigned int oldexists; + + mutable time_t old_bincimap_cache_st_mtime; + mutable time_t old_bincimap_cache_st_ctime; + mutable time_t old_cur_st_mtime; + mutable time_t old_cur_st_ctime; + mutable time_t old_new_st_mtime; + mutable time_t old_new_st_ctime; + + mutable bool mailboxchanged; + }; +} + +#endif diff --git a/src/include/maildirmessage.h b/src/include/maildirmessage.h new file mode 100644 index 0000000..9e9c717 --- /dev/null +++ b/src/include/maildirmessage.h @@ -0,0 +1,309 @@ +/** -------------------------------------------------------------------- + * @file maildirmessage.h + * @brief Declaration of the MaildirMessage class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef maildirmessage_h_included +#define maildirmessage_h_included +#include <string> +#include <map> +#include <vector> +#include <exception> +#include <iostream> +#include <time.h> + +#include <stdio.h> +#include <string.h> + +#include "message.h" +#include "address.h" +#include "mime.h" + +namespace Binc { + + class Maildir; + + /*! + \class MaildirMessage + \brief The MaildirMessage class provides an interface for + IMAP messages. + + Mailbox independent operations and properties are available + through this interface. + + \sa Message + */ + class MaildirMessage : public Message { + public: + /*! + Sets the UID of a message. + \param uid The UID that will be set. + */ + void setUID(unsigned int uid); + + /*! + Returns the UID of a message. + */ + unsigned int getUID(void) const; + + /*! + Sets the size of the message. This size must be consistent with + the size reported when fetching the full message. + + \param size The size of the message in characters, after + any conversion to CRLF. + */ + void setSize(unsigned int size); + + /*! + Returns the size of the message, optionally determining the size + if it is not yet known. + + \param determine If render is true and the size is unknown, the + size will be calculated and stored implicitly. Otherwise if the + size is unknown, 0 is returned. + */ + unsigned int getSize(bool determine = false) const; + + /*! + Adds one or more flags to a message. + + \param flags This is a bitmask of flags from the Flags enum. + */ + void setStdFlag(unsigned char flags); + + /*! + Resets all flags on a message. + */ + void resetStdFlags(void); + + /*! + Returns the flags that are set on a message. + */ + unsigned char getStdFlags(void) const; + + /* + */ + void setCustomFlag(const std::string &flag); + void removeCustomFlag(const std::string &flag); + void resetCustomFlags(void); + std::vector<std::string> getCustomFlags(void) const; + + /*! + Sets the internal flags. + + \param flags a bitmask of the Flags enum. + */ + void setInternalFlag(unsigned char flags); + + /*! + Removes the internal flags. + + \param flags a bitmask of the Flags enum. + */ + void clearInternalFlag(unsigned char flags); + + /*! + Returns the internal flags. + */ + unsigned char getInternalFlags(void) const; + + /*! + Sets a state in a message that indicates that no flags have been + changed. Used together with hasFlagsChanged() to check if the + flags in this message have been changed. + */ + void setFlagsUnchanged(void); + + /*! + Returns true if flags have been added or reset since the last + call to setFlagsUnchanged(), otherwise returns false. + */ + bool hasFlagsChanged(void) const; + + /*! + Sets the internal date of a message. This is usually the date in + which the message arrived in the mailbox. + + \param internaldate The internal date of the message in seconds + since the epoch. + */ + void setInternalDate(time_t internaldate); + + /*! + Returns the internal date of the message in seconds since the + epoch. + */ + time_t getInternalDate(void) const; + + /*! + Reads a chunk of up to 4096 bytes from a message. Call close() + before readChunk() to read the first chunk from a message. + + readChunk() is used for copying or appending a message to a + mailbox. + + \param chunk The characters are stored in this string. + */ + int readChunk(std::string &chunk); + + /*! + Appends a chunk of bytes to a message. appendChunk() is used for + copying or appending a message to a mailbox. + + \param chunk The content of this string is appended to the + message. + */ + bool appendChunk(const std::string &chunk); + + /*! + Resets a message and frees all allocated resources. + */ + void close(void); + + /*! + Marks the message as expunged. Equivalent to calling + setStdFlag() with F_EXPUNGED. + */ + void setExpunged(void); + + /*! + Removes the F_EXPUNGED flag from the message. + */ + void setUnExpunged(void); + + /*! + Returns true if the message is marked as expunged, otherwise + returns false. + */ + bool isExpunged(void) const; + + /*! + Returns the first occurrance of a MIME header in a message, + counting from the top of the message and downwards, or "" if no + such header is found. + \param header The name of the header to be fetched. + */ + const std::string &getHeader(const std::string &header); + + bool headerContains(const std::string &header, + const std::string &text); + + bool bodyContains(const std::string &text); + bool textContains(const std::string &text); + + bool printBodyStructure(bool extended = true) const; + + bool printEnvelope(void) const; + + bool printHeader(const std::string §ion, + std::vector<std::string> headers, + bool includeHeaders = false, + unsigned int startOffset = 0, + unsigned int length = UINTMAX, + bool mime = false) const; + + unsigned int getHeaderSize(const std::string §ion, + std::vector<std::string> headers, + bool includeHeaders = false, + unsigned int startOffset = 0, + unsigned int length = UINTMAX, + bool mime = false) const; + + bool printBody(const std::string §ion = "", + unsigned int startOffset = 0, + unsigned int length = UINTMAX) const; + unsigned int getBodySize(const std::string §ion, + unsigned int startOffset = 0, + unsigned int length = UINTMAX) const; + + bool printDoc(unsigned int startOffset = 0, + unsigned int length = UINTMAX, + bool onlyText = false) const; + + unsigned int getDocSize(unsigned int startOffset = 0, + unsigned int length = UINTMAX, + bool onlyText = false) const; + + void setUnique(const std::string &s_in); + const std::string &getUnique(void) const; + + //-- + MaildirMessage(Maildir &home); + ~MaildirMessage(void); + + friend class Maildir; + + bool operator < (const MaildirMessage &a) const; + + MaildirMessage(const MaildirMessage ©); + MaildirMessage &operator = (const MaildirMessage ©); + + enum Flags { + None = 0x00, + Expunged = 0x01, + FlagsChanged = 0x02, + JustArrived = 0x04, + WasWrittenTo = 0x08, + Committed = 0x10, + CustomFlagsChanged = 0x20 + }; + + protected: + bool parseFull(void) const; + bool parseHeaders(void) const; + + std::string getFixedFilename(void) const; + std::string getFileName(void) const; + + void setFile(int fd); + int getFile(void) const; + + void setSafeName(const std::string &name); + const std::string &getSafeName(void) const; + + private: + mutable int fd; + mutable MimeDocument *doc; + mutable unsigned char internalFlags; + mutable unsigned char stdflags; + mutable unsigned int uid; + mutable unsigned int size; + mutable std::string unique; + mutable std::string safeName; + time_t internaldate; + + Maildir &home; + static std::string storage; + std::vector<std::string> *customFlags; + }; + + //------------------------------------------------------------------------ + class MaildirMessageCache + { + public: + ~MaildirMessageCache(); + + enum ParseStatus { + NotParsed, + HeaderParsed, + AllParsed + }; + + static MaildirMessageCache &getInstance(void); + + void removeStatus(const MaildirMessage *); + void addStatus(const MaildirMessage *, ParseStatus pstat); + ParseStatus getStatus(const MaildirMessage *) const; + void clear(void); + + private: + MaildirMessageCache(); + + mutable std::map<const MaildirMessage *, ParseStatus> statuses; + mutable std::deque<const MaildirMessage *> parsed; + }; +} + +#endif diff --git a/src/include/message.h b/src/include/message.h new file mode 100644 index 0000000..76607e5 --- /dev/null +++ b/src/include/message.h @@ -0,0 +1,151 @@ +/** -------------------------------------------------------------------- + * @file message.h + * @brief Declaration of the Message class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef message_h_included +#define message_h_included +#include <vector> +#include <string> +#include <time.h> + +#ifndef UINTMAX +#define UINTMAX ((unsigned int)-1) +#endif + +namespace Binc { + + /*! + \class Message + \brief The Message class provides an interface for + IMAP messages. + + Mailbox independent operations and properties are available + through this interface. + + This class is an abstract, and has no implementation. + + \sa MaildirMessage + */ + class Message { + public: + + /*! + Standard IMAP message flags. + + */ + enum Flags { + F_NONE = 0x00, /*!< No flag is set */ + F_SEEN = 0x01, /*!< The message has been seen */ + F_ANSWERED = 0x02, /*!< The message has been answered */ + F_DELETED = 0x04, /*!< The message is marked as deleted */ + F_DRAFT = 0x08, /*!< The message is a draft */ + F_RECENT = 0x10, /*!< The message arrived recently */ + F_FLAGGED = 0x20, /*!< The message is flagged / important */ + F_EXPUNGED = 0x40, /*!< The message has been expunged */ + F_PASSED = 0x80 /*!< The message has been bounced */ + }; + + virtual void setUID(unsigned int) = 0; + virtual unsigned int getUID(void) const = 0; + + virtual void setSize(unsigned int) = 0; + virtual unsigned int getSize(bool render = false) const = 0; + + virtual void setStdFlag(unsigned char) = 0; + virtual void resetStdFlags(void) = 0; + virtual unsigned char getStdFlags(void) const = 0; + + virtual void setCustomFlag(const std::string &flag) = 0; + virtual void removeCustomFlag(const std::string &flag) = 0; + virtual void resetCustomFlags(void) = 0; + virtual std::vector<std::string> getCustomFlags(void) const = 0; + + virtual void setFlagsUnchanged(void) = 0; + virtual bool hasFlagsChanged(void) const = 0; + + virtual void setInternalDate(time_t) = 0; + virtual time_t getInternalDate(void) const = 0; + + // virtual void rewind(void) = 0; + virtual int readChunk(std::string &) = 0; + virtual bool appendChunk(const std::string &) = 0; + virtual void close(void) = 0; + + virtual void setExpunged(void) = 0; + virtual void setUnExpunged(void) = 0; + virtual bool isExpunged(void) const = 0; + + virtual const std::string &getHeader(const std::string &header) = 0; + + virtual bool headerContains(const std::string &header, + const std::string &text) = 0; + + virtual bool bodyContains(const std::string &text) = 0; + virtual bool textContains(const std::string &text) = 0; + + virtual bool printBodyStructure(bool extended = true) const = 0; + + virtual bool printEnvelope(void) const = 0; + + virtual bool printHeader(const std::string §ion, + std::vector<std::string> headers, + bool includeHeaders = false, + unsigned int startOffset = 0, + unsigned int length = UINTMAX, + bool mime = false) const = 0; + + virtual unsigned int getHeaderSize(const std::string §ion, + std::vector<std::string> headers, + bool includeHeaders = false, + unsigned int startOffset = 0, + unsigned int length = UINTMAX, + bool mime = false) const = 0; + + virtual bool printBody(const std::string §ion, + unsigned int startOffset = 0, + unsigned int length = UINTMAX) const = 0; + + virtual unsigned int getBodySize(const std::string §ion, + unsigned int startOffset = 0, + unsigned int length = UINTMAX) const = 0; + + virtual bool printDoc(unsigned int startOffset = 0, + unsigned int length = UINTMAX, + bool onlyText = false) const = 0; + + virtual unsigned int getDocSize(unsigned int startOffset = 0, + unsigned int length = UINTMAX, + bool onlyText = false) const = 0; + + Message(void); + virtual ~Message(void); + + void setLastError(const std::string &) const; + const std::string &getLastError(void) const; + + private: + static std::string lastError; + }; + + inline Message::Message(void) + { + } + + inline Message::~Message(void) + { + } + + inline void Message::setLastError(const std::string &error) const + { + lastError = error; + } + + inline const std::string &Message::getLastError(void) const + { + return lastError; + } +} + +#endif diff --git a/src/include/mime-inputsource.h b/src/include/mime-inputsource.h new file mode 100644 index 0000000..e37d508 --- /dev/null +++ b/src/include/mime-inputsource.h @@ -0,0 +1,141 @@ +/** -------------------------------------------------------------------- + * @file mime-inputsource.h + * @brief The base class of the MIME input source + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef mime_inputsource_h_included +#define mime_inputsource_h_included + +#include <string.h> +#include <unistd.h> + +namespace Binc { + + class MimeInputSource { + public: + inline MimeInputSource(int fd, unsigned int start = 0); + virtual inline ~MimeInputSource(void); + + virtual inline bool fillInputBuffer(void); + virtual inline void reset(void); + + inline void seek(unsigned int offset); + inline bool getChar(char *c); + inline void ungetChar(void); + inline int getFileDescriptor(void) const; + + inline unsigned int getOffset(void) const; + + private: + int fd; + char data[16384]; + unsigned int offset; + unsigned int tail; + unsigned int head; + unsigned int start; + char lastChar; + }; + + inline MimeInputSource::MimeInputSource(int fd, unsigned int start) + { + this->fd = fd; + this->start = start; + offset = 0; + tail = 0; + head = 0; + lastChar = '\0'; + memset(data, '\0', sizeof(data)); + + seek(start); + } + + inline MimeInputSource::~MimeInputSource(void) + { + } + + inline bool MimeInputSource::fillInputBuffer(void) + { + char raw[4096]; + ssize_t nbytes = read(fd, raw, sizeof(raw)); + if (nbytes <= 0) { + // FIXME: If ferror(crlffile) we should log this. + return false; + } + + for (ssize_t i = 0; i < nbytes; ++i) { + const char c = raw[i]; + if (c == '\r') { + if (lastChar == '\r') { + data[tail++ & (0x4000-1)] = '\r'; + data[tail++ & (0x4000-1)] = '\n'; + } + } else if (c == '\n') { + data[tail++ & (0x4000-1)] = '\r'; + data[tail++ & (0x4000-1)] = '\n'; + } else { + if (lastChar == '\r') { + data[tail++ & (0x4000-1)] = '\r'; + data[tail++ & (0x4000-1)] = '\n'; + } + data[tail++ & (0x4000-1)] = c; + } + + lastChar = c; + } + + return true; + } + + inline void MimeInputSource::reset(void) + { + offset = head = tail = 0; + lastChar = '\0'; + + if (fd != -1) + lseek(fd, 0, SEEK_SET); + } + + inline void MimeInputSource::seek(unsigned int seekToOffset) + { + if (offset > seekToOffset) + reset(); + + char c; + int n = 0; + while (seekToOffset > offset) { + if (!getChar(&c)) break; + ++n; + } + } + + inline bool MimeInputSource::getChar(char *c) + { + if (head == tail && !fillInputBuffer()) + return false; + + *c = data[head++ & (0x4000-1)]; + ++offset; + return true; + } + + inline void MimeInputSource::ungetChar() + { + --head; + --offset; + } + + inline int MimeInputSource::getFileDescriptor(void) const + { + return fd; + } + + inline unsigned int MimeInputSource::getOffset(void) const + { + return offset; + } +} + +extern Binc::MimeInputSource *mimeSource; + +#endif diff --git a/src/include/mime-utils.h b/src/include/mime-utils.h new file mode 100644 index 0000000..732234a --- /dev/null +++ b/src/include/mime-utils.h @@ -0,0 +1,27 @@ +/** -------------------------------------------------------------------- + * @file mime.cc + * @brief Implementation of main mime parser components + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef mime_utils_h_included +#define mime_utils_h_included + +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <errno.h> + +using namespace ::std; + +inline bool compareStringToQueue(const char *s_in, char *bqueue, + int pos, int size) +{ + for (int i = 0; i < size; ++i) + if (s_in[i] != bqueue[(pos + i) % size]) + return false; + + return true; +} + +#endif diff --git a/src/include/mime.h b/src/include/mime.h new file mode 100644 index 0000000..2033442 --- /dev/null +++ b/src/include/mime.h @@ -0,0 +1,121 @@ +/** -------------------------------------------------------------------- + * @file mime.h + * @brief Declaration of main mime parser components + * @author Andreas Aardal Hanssen + * @date Andreas Aardal Hanssen + * ----------------------------------------------------------------- **/ +#ifndef mime_h_included +#define mime_h_included +#include <string> +#include <vector> +#include <map> +#include <stdio.h> + +namespace Binc { + //---------------------------------------------------------------------- + class HeaderItem { + private: + mutable std::string key; + mutable std::string value; + + public: + inline const std::string &getKey(void) const { return key; } + inline const std::string &getValue(void) const { return value; } + + //-- + HeaderItem(void); + HeaderItem(const std::string &key, const std::string &value); + }; + + //---------------------------------------------------------------------- + class Header { + private: + mutable std::vector<HeaderItem> content; + + public: + bool getFirstHeader(const std::string &key, HeaderItem &dest) const; + bool getAllHeaders(const std::string &key, std::vector<HeaderItem> &dest) const; + void add(const std::string &name, const std::string &content); + void clear(void) const; + + //-- + Header(void); + ~Header(void); + }; + + //---------------------------------------------------------------------- + class IODevice; + class MimeDocument; + class MimePart { + protected: + public: + mutable bool multipart; + mutable bool messagerfc822; + mutable std::string subtype; + mutable std::string boundary; + + mutable unsigned int headerstartoffsetcrlf; + mutable unsigned int headerlength; + + mutable unsigned int bodystartoffsetcrlf; + mutable unsigned int bodylength; + mutable unsigned int nlines; + mutable unsigned int nbodylines; + mutable unsigned int size; + + public: + enum FetchType { + FetchBody, + FetchHeader, + FetchMime + }; + + mutable Header h; + + mutable std::vector<MimePart> members; + + inline const std::string &getSubType(void) const { return subtype; } + inline bool isMultipart(void) const { return multipart; } + inline bool isMessageRFC822(void) const { return messagerfc822; } + inline unsigned int getSize(void) const { return bodylength; } + inline unsigned int getNofLines(void) const { return nlines; } + inline unsigned int getNofBodyLines(void) const { return nbodylines; } + inline unsigned int getBodyLength(void) const { return bodylength; } + inline unsigned int getBodyStartOffset(void) const { return bodystartoffsetcrlf; } + + void printBody(int fd, Binc::IODevice &output, unsigned int startoffset, unsigned int length) const; + void printHeader(int fd, Binc::IODevice &output, std::vector<std::string> headers, + bool includeheaders, unsigned int startoffset, unsigned int length, std::string &storage) const; + void printDoc(int fd, Binc::IODevice &output, unsigned int startoffset, unsigned int length) const; + virtual void clear(void) const; + + const MimePart *getPart(const std::string &findpart, std::string genpart, FetchType fetchType = FetchBody) const; + virtual int parseOnlyHeader(const std::string &toboundary) const; + virtual int parseFull(const std::string &toboundary, int &boundarysize) const; + + MimePart(void); + virtual ~MimePart(void); + }; + + //---------------------------------------------------------------------- + class MimeDocument : public MimePart { + private: + mutable bool headerIsParsed; + mutable bool allIsParsed; + + public: + void parseOnlyHeader(int fd) const; + void parseFull(int fd) const; + void clear(void) const; + + inline bool isHeaderParsed(void) { return headerIsParsed; } + inline bool isAllParsed(void) { return allIsParsed; } + + //-- + MimeDocument(void); + ~MimeDocument(void); + }; + +}; + +#endif diff --git a/src/include/multilogdevice.h b/src/include/multilogdevice.h new file mode 100644 index 0000000..a6f29f2 --- /dev/null +++ b/src/include/multilogdevice.h @@ -0,0 +1,29 @@ +/** -------------------------------------------------------------------- + * @file multilogdevice.h + * @brief Declaration of the MultilogDevice class. + * @author Andreas Aardal Hanssen + * @date Andreas Aardal Hanssen + * ----------------------------------------------------------------- **/ +#ifndef multilogdevice_h_included +#define multilogdevice_h_included + +#include "iodevice.h" + +namespace Binc { + class MultilogDevice : public IODevice { + public: + MultilogDevice(int flags); + ~MultilogDevice(); + + std::string service(void) const; + + protected: + bool waitForWrite(void) const; + bool waitForRead(void) const; + + WriteResult write(void); + bool fillInputBuffer(void); + }; +} + +#endif diff --git a/src/include/operators.h b/src/include/operators.h new file mode 100644 index 0000000..c030918 --- /dev/null +++ b/src/include/operators.h @@ -0,0 +1,476 @@ +/** -------------------------------------------------------------------- + * @file operators.h + * @brief Declaration of all operators. + * @author Andreas Aardal Hanssen, Erwin Hoffmann + * @date 2002-2005, 2023 + * ----------------------------------------------------------------- **/ +#ifndef operators_h_included +#define operators_h_included +#include <string> +#include <vector> + +#include "imapparser.h" +#include "depot.h" +#include "message.h" + +namespace Binc { + + //-------------------------------------------------------------------- + class Operator { + public: + enum ProcessResult {OK, BAD, NO, NOTHING, ABORT}; + enum ParseResult {ACCEPT, REJECT, ERROR, TIMEOUT}; + + virtual ProcessResult process(Depot &, Request &) = 0; + virtual ParseResult parse(Request &) const = 0; + virtual int getState(void) const = 0; + virtual const std::string getName(void) const = 0; + + //-- + virtual ~Operator(void) {}; + }; + + //-------------------------------------------------------------------- + class AppendOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + const std::string getName(void) const; + int getState(void) const; + + AppendOperator(void); + ~AppendOperator(void); + }; + + //-------------------------------------------------------------------- + class AuthenticateOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + ProcessResult Login(std::string& username, std::string& password); + ProcessResult Plain(std::string& username, std::string& password); + ProcessResult Cram(std::string& username, std::string& password, + std::string& challenge); + + AuthenticateOperator(void); + ~AuthenticateOperator(void); + }; + + //-------------------------------------------------------------------- + class CapabilityOperator : public Operator { + std::vector<std::string> capabilities; + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + void addCapability(const std::string &cap); + + CapabilityOperator(void); + ~CapabilityOperator(void); + }; + + //-------------------------------------------------------------------- + class CheckOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + CheckOperator(void); + ~CheckOperator(void); + }; + + //-------------------------------------------------------------------- + class CreateOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + CreateOperator(void); + ~CreateOperator(void); + }; + + //-------------------------------------------------------------------- + class CloseOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + CloseOperator(void); + ~CloseOperator(void); + }; + + //-------------------------------------------------------------------- + class CopyOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + CopyOperator(void); + ~CopyOperator(void); + }; + + //-------------------------------------------------------------------- + class DeleteOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + DeleteOperator(void); + ~DeleteOperator(void); + }; + + //-------------------------------------------------------------------- + class ExpungeOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + ExpungeOperator(void); + ~ExpungeOperator(void); + }; + + //-------------------------------------------------------------------- + class FetchOperator : public Operator { + protected: + ParseResult expectSectionText(BincImapParserFetchAtt &f_in) const; + ParseResult expectSection(BincImapParserFetchAtt &f_in) const; + ParseResult expectFetchAtt(BincImapParserFetchAtt &f_in) const; + ParseResult expectOffset(BincImapParserFetchAtt &f_in) const; + ParseResult expectHeaderList(BincImapParserFetchAtt &f_in) const; + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + FetchOperator(void); + ~FetchOperator(void); + }; + + //-------------------------------------------------------------------- + class IdOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + IdOperator(void); + ~IdOperator(void); + }; + + //-------------------------------------------------------------------- + class IdleOperator : public Operator { + protected: + ParseResult expectSectionText(BincImapParserFetchAtt &f_in) const; + ParseResult expectSection(BincImapParserFetchAtt &f_in) const; + ParseResult expectFetchAtt(BincImapParserFetchAtt &f_in) const; + ParseResult expectOffset(BincImapParserFetchAtt &f_in) const; + ParseResult expectHeaderList(BincImapParserFetchAtt &f_in) const; + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + IdleOperator(void); + ~IdleOperator(void); + }; + + //-------------------------------------------------------------------- + class ListOperator : public Operator { + protected: + enum MailboxFlags { + DIR_SELECT = 0x01, + DIR_MARKED = 0x02, + DIR_NOINFERIORS = 0x04, + DIR_LEAF = 0x08 + }; + + std::map<std::string, unsigned int> cache; + time_t cacheTimeout; + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + ListOperator(void); + ~ListOperator(void); + }; + + //-------------------------------------------------------------------- + class LoginOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + LoginOperator(void); + ~LoginOperator(void); + }; + + //-------------------------------------------------------------------- + class LogoutOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + LogoutOperator(void); + ~LogoutOperator(void); + }; + + //-------------------------------------------------------------------- + class LsubOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + LsubOperator(void); + ~LsubOperator(void); + }; + + //-------------------------------------------------------------------- + class NamespaceOperator : public Operator { + public: + virtual ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + NamespaceOperator(void); + ~NamespaceOperator(void); + }; + + //-------------------------------------------------------------------- + class NoopOperator : public Operator { + public: + virtual ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + NoopOperator(void); + ~NoopOperator(void); + }; + + //-------------------------------------------------------------------- + class NoopPendingOperator : public NoopOperator { + public: + ProcessResult process(Depot &, Request &); + + NoopPendingOperator(void); + ~NoopPendingOperator(void); + }; + + //-------------------------------------------------------------------- + class RenameOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + RenameOperator(void); + ~RenameOperator(void); + }; + + //-------------------------------------------------------------------- + class SearchOperator : public Operator { + protected: + ParseResult expectSearchKey(BincImapParserSearchKey &s_in) const; + + //------------------------------------------------------------------ + class SearchNode { + + std::string date; + std::string astring; + std::string bstring; + unsigned int number; + + int type; + mutable int weight; + const SequenceSet *bset; + + std::vector<SearchNode> children; + + public: + enum { + S_ALL, S_ANSWERED, S_BCC, S_BEFORE, S_BODY, S_CC, S_DELETED, + S_FLAGGED, S_FROM, S_KEYWORD, S_NEW, S_OLD, S_ON, S_RECENT, + S_SEEN, S_SINCE, S_SUBJECT, S_TEXT, S_TO, S_UNANSWERED, + S_UNDELETED, S_UNFLAGGED, S_UNKEYWORD, S_UNSEEN, S_DRAFT, + S_HEADER, S_LARGER, S_NOT, S_OR, S_SENTBEFORE, S_SENTON, + S_SENTSINCE, S_SMALLER, S_UID, S_UNDRAFT, S_SET, S_AND + }; + + static bool convertDate(const std::string &date, time_t &t, const std::string &delim = "-"); + static bool convertDateHeader(const std::string &d_in, time_t &t); + + void order(void); + + bool match(Mailbox *, Message *, + unsigned seqnr, unsigned int lastmessage, + unsigned int lastuid) const; + + int getType(void) const; + int getWeight(void) const; + void setWeight(int i); + + void init(const BincImapParserSearchKey &a); + + //- + static bool compareNodes(const SearchNode &a, + const SearchNode &b) + { + return a.getWeight() < b.getWeight(); + } + + SearchNode(void); + SearchNode(const BincImapParserSearchKey &a); + }; + + public: + + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + SearchOperator(void); + ~SearchOperator(void); + }; + + //-------------------------------------------------------------------- + class SelectOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + SelectOperator(void); + ~SelectOperator(void); + }; + + //-------------------------------------------------------------------- + class ExamineOperator : public SelectOperator { + public: + const std::string getName(void) const; + ExamineOperator(void); + ~ExamineOperator(void); + }; + + //-------------------------------------------------------------------- + class StarttlsOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + int goStartTLS(void) const; + + StarttlsOperator(void); + ~StarttlsOperator(void); + }; + + //-------------------------------------------------------------------- + class StatusOperator : public Operator { + + std::map<int, Status> statuses; + + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + StatusOperator(void); + ~StatusOperator(void); + }; + + //-------------------------------------------------------------------- + class StoreOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + StoreOperator(void); + ~StoreOperator(void); + }; + + //-------------------------------------------------------------------- + class SubscribeOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + SubscribeOperator(void); + ~SubscribeOperator(void); + }; + + //-------------------------------------------------------------------- + class UnsubscribeOperator : public Operator { + public: + ProcessResult process(Depot &, Request &); + virtual ParseResult parse(Request &) const; + + const std::string getName(void) const; + int getState(void) const; + + UnsubscribeOperator(void); + ~UnsubscribeOperator(void); + }; +} + +#endif diff --git a/src/include/pendingupdates.h b/src/include/pendingupdates.h new file mode 100644 index 0000000..ea192de --- /dev/null +++ b/src/include/pendingupdates.h @@ -0,0 +1,107 @@ +/** -------------------------------------------------------------------- + * @file pendingupdates.h + * @brief <---> + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <map> +#include <vector> + +#ifndef pendingupdates_h_included +#define pendingupdates_h_included + +namespace Binc { + class Mailbox; + + //------------------------------------------------------------------------ + class PendingUpdates { + public: + enum { + EXPUNGE = 0x01, + FLAGS = 0x02, + EXISTS = 0x04, + RECENT = 0x08 + }; + + //---------------------------------------------------------------------- + class expunged_const_iterator { + private: + std::vector<unsigned int>::iterator internal; + + public: + unsigned int operator * (void) const; + void operator ++ (void); + bool operator != (expunged_const_iterator) const; + bool operator == (expunged_const_iterator) const; + + //-- + expunged_const_iterator(void); + expunged_const_iterator(std::vector<unsigned int>::iterator i); + }; + + //-- + expunged_const_iterator beginExpunged(void); + expunged_const_iterator endExpunged(void); + + //---------------------------------------------------------------------- + class flagupdates_const_iterator { + private: + std::map<unsigned int, unsigned int>::iterator internal; + std::map<unsigned int, unsigned int> *sqnrtouid; + std::map<unsigned int, std::vector<std::string> > *sqnrtocflags; + + public: + unsigned int first(void) const; + unsigned int second(void) const; + std::vector<std::string> getCustomFlags(void) const; + unsigned int getUID(void) const; + + void operator ++ (void); + bool operator != (flagupdates_const_iterator) const; + + //-- + flagupdates_const_iterator(void); + flagupdates_const_iterator(std::map<unsigned int, unsigned int>::iterator i, + std::map<unsigned int, std::vector<std::string> > *, + std::map<unsigned int, unsigned int> *); + }; + + //-- + flagupdates_const_iterator beginFlagUpdates(void); + flagupdates_const_iterator endFlagUpdates(void); + + //-- + void addExpunged(unsigned int uid); + void addFlagUpdates(unsigned int sqnr, unsigned int uid, + unsigned int flags, + const std::vector<std::string> &cflags); + void setExists(unsigned int n); + void setRecent(unsigned int n); + unsigned int getExists(void) const; + unsigned int getRecent(void) const; + bool newExists(void) const; + bool newRecent(void) const; + + //-- + PendingUpdates(void); + ~PendingUpdates(void); + + private: + std::vector<unsigned int> expunges; + std::map<unsigned int, unsigned int> flagupdates; + std::map<unsigned int, unsigned int> sqnrtouid; + std::map<unsigned int, std::vector<std::string> > sqnrtocflags; + + unsigned int exists; + unsigned int recent; + bool newexists; + bool newrecent; + }; + + bool pendingUpdates(Mailbox *, int type, bool rescan, + bool showAll = false, + bool forceScan = false, + bool uidfetchflags = false); +} + +#endif diff --git a/src/include/recursivedescent.h b/src/include/recursivedescent.h new file mode 100644 index 0000000..ea8fe33 --- /dev/null +++ b/src/include/recursivedescent.h @@ -0,0 +1,64 @@ +/** -------------------------------------------------------------------- + * @file recursivedescent.h + * @brief Declaration of a recursive descent IMAP command parser. + * @author Andreas Aardal Hanssen + * @date Andreas Aardal Hanssen + * ----------------------------------------------------------------- **/ +#ifndef expectcommand_h_inluded +#define expectcommand_h_inluded +#include <stack> +#include <string> + +#include "imapparser.h" +#include "operators.h" + +namespace Binc { + + extern std::stack<int> inputBuffer; + extern int charnr; + + int readChar(void); + void unReadChar(int c_in); + void unReadChar(const std::string &s_in); + + Operator::ParseResult expectTag(std::string &s_in); + Operator::ParseResult expectTagChar(int &c_in); + Operator::ParseResult expectSPACE(void); + + Operator::ParseResult expectFlag(std::vector<std::string> &v_in); + + Operator::ParseResult expectListMailbox(std::string &s_in); + Operator::ParseResult expectListWildcards(int &c_in); + + Operator::ParseResult expectDateTime(std::string &s_in); + Operator::ParseResult expectTime(std::string &s_in); + Operator::ParseResult expectZone(std::string &s_in); + + Operator::ParseResult expectMailbox(std::string &s_in); + Operator::ParseResult expectAstring(std::string &s_in); + Operator::ParseResult expectAtom(std::string &s_in); + Operator::ParseResult expectAtomChar(int &i_in); + Operator::ParseResult expectString(std::string &s_in); + + Operator::ParseResult expectDate(std::string &s_in); + + Operator::ParseResult expectNumber(unsigned int &i_in); + Operator::ParseResult expectDigit(unsigned int &i_in); + Operator::ParseResult expectDigitNZ(unsigned int &i_in); + + Operator::ParseResult expectLiteral(std::string &s_in); + Operator::ParseResult expectQuoted(std::string &s_in); + Operator::ParseResult expectQuotedChar(int &c_in); + + Operator::ParseResult expectSet(SequenceSet &s_in); + Operator::ParseResult expectSequenceNum(unsigned int &i_in); + Operator::ParseResult expectNZNumber(unsigned int &i_in); + + Operator::ParseResult expectCRLF(void); + Operator::ParseResult expectCR(void); + Operator::ParseResult expectLF(void); + + Operator::ParseResult expectThisString(const std::string &s_in); +} + +#endif diff --git a/src/include/regmatch.h b/src/include/regmatch.h new file mode 100644 index 0000000..1471e90 --- /dev/null +++ b/src/include/regmatch.h @@ -0,0 +1,17 @@ +/** -------------------------------------------------------------------- + * @file regex.h + * @brief Declaration of miscellaneous regexp functions. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef regex_h_included +#define regex_h_included + +#include <string> + +namespace Binc { + + int regexMatch(const std::string &data_in, const std::string &p_in); +} + +#endif diff --git a/src/include/session.h b/src/include/session.h new file mode 100644 index 0000000..94825cd --- /dev/null +++ b/src/include/session.h @@ -0,0 +1,116 @@ +/** -------------------------------------------------------------------- + * @file session.h + * @brief <---> + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef session_h_included +#define session_h_included +#include <string> +#include <vector> +#include <map> +#include <sys/types.h> + +#include "argparser.h" + +namespace Binc { + + class Depot; + + //-------------------------------------------------------------------- + class Session { + public: + std::map<std::string, std::string> attrs; + + char **unparsedArgs; + + struct { + bool help; + bool version; + bool ssl; + } command; + + bool mailboxchanges; + + enum State { + NONAUTHENTICATED = 0x01, + AUTHENTICATED = 0x02, + SELECTED = 0x04, + LOGOUT = 0x00 + }; + + CommandLineArgs args; + + int timeout() const; + + bool hasEnv(const std::string &key) const; + std::string getEnv(const std::string &key); + void setEnv(const std::string &key, const std::string &value); + + const int getState(void) const; + void setState(int n); + bool parseCommandLine(int argc, char * argv[]); + void assignCommandLineArgs(void); + int getWriteBytes(void) const; + int getReadBytes(void) const; + void addWriteBytes(int); + void addReadBytes(int); + int getBodies(void) const; + int getStatements(void) const; + void addBody(void); + void addStatement(void); + void setLogFacility(int facility); + int getLogFacility(void) const; + + const std::string &getLastError(void) const; + const std::string &getResponseCode(void) const; + const std::string &getIP(void) const; + const std::string &getUserID() const; + pid_t getPid(void); + const std::string &getHostname(void); + void setLastError(const std::string &error) const; + void setResponseCode(const std::string &error) const; + void clearResponseCode(void) const; + void setIP(const std::string &ip); + void setUserID(const std::string &s); + + inline Depot *getDepot(void) const; + + //-- + static Session &getInstance(void); + + bool initialize(int argc, char *argv[]); + + private: + //-- + int state; + std::string userid; + std::string ip; + char **argv; + int argc; + + int logfacility; + + int readbytes; + int writebytes; + int statements; + int bodies; + + Depot *depot; + + mutable std::string lastError; + mutable std::string responseCode; + + pid_t pid; + std::string hostname; + + Session(void); + }; + + inline Depot *Session::getDepot(void) const + { + return depot; + } +} + +#endif diff --git a/src/include/status.h b/src/include/status.h new file mode 100644 index 0000000..f7f9e1f --- /dev/null +++ b/src/include/status.h @@ -0,0 +1,50 @@ +/** -------------------------------------------------------------------- + * @file status.h + * @brief Declaration of the Status class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#ifndef status_h_included +#define status_h_included + +namespace Binc { + + //------------------------------------------------------------------------ + class Status { + + //-- + int recent; + int messages; + int unseen; + int uidvalidity; + int uidnext; + + //-- + int statusid; + + public: + + //-- + inline void setMessages(int i) { messages = i; } + inline void setRecent(int i) { recent = i; } + inline void setStatusID(int i) { statusid = i; } + inline void setUnseen(int i) { unseen = i; } + inline void setUidValidity(int i) { uidvalidity = i; } + inline void setUidNext(int i) { uidnext = i; } + + //-- + inline int getMessages(void) const { return messages; } + inline int getRecent(void) const { return recent; } + inline int getStatusID(void) const { return statusid; } + inline int getUnseen(void) const { return unseen; } + inline int getUidValidity(void) const { return uidvalidity; } + inline int getUidNext(void) const { return uidnext; } + + + //-- + Status(void); + ~Status(void); + }; +} + +#endif diff --git a/src/include/stdiodevice.h b/src/include/stdiodevice.h new file mode 100644 index 0000000..026386c --- /dev/null +++ b/src/include/stdiodevice.h @@ -0,0 +1,31 @@ +/** -------------------------------------------------------------------- + * @file stdiodevice.h + * @brief Declaration of the StdIODevice class. + * @author Andreas Aardal Hanssen + * @date 2002, 2003 + * ----------------------------------------------------------------- **/ +#ifndef stdiodevice_h_included +#define stdiodevice_h_included + +#include "iodevice.h" + +namespace Binc { + class StdIODevice : public IODevice { + public: + StdIODevice(int flags); + ~StdIODevice(); + + std::string service(void) const; + + bool canRead(void) const; + + protected: + bool waitForWrite(void) const; + bool waitForRead(void) const; + + WriteResult write(void); + bool fillInputBuffer(void); + }; +} + +#endif diff --git a/src/include/syslogdevice.h b/src/include/syslogdevice.h new file mode 100644 index 0000000..2269fb2 --- /dev/null +++ b/src/include/syslogdevice.h @@ -0,0 +1,41 @@ +/** -------------------------------------------------------------------- + * @file Syslogdevice.h + * @brief Declaration of the SyslogDevice class. + * @author Andreas Aardal Hanssen + * @date 2002, 2003 + * ----------------------------------------------------------------- **/ +#ifndef syslogdevice_h_included +#define syslogdevice_h_included + +#include "iodevice.h" +#include <syslog.h> + +namespace Binc { + class SyslogDevice : public IODevice { + public: + SyslogDevice(int flags, const char *ident = "bincimap", + int option = LOG_NDELAY | LOG_PID, + int facility = LOG_USER); + ~SyslogDevice(); + + void setPriority(int p); + + std::string service(void) const; + + protected: + bool waitForWrite(void) const; + bool waitForRead(void) const; + + WriteResult write(void); + bool fillInputBuffer(void); + + private: + static std::string ident; + + int option; + int facility; + int priority; + }; +} + +#endif diff --git a/src/include/tools.h b/src/include/tools.h new file mode 100644 index 0000000..67ed91e --- /dev/null +++ b/src/include/tools.h @@ -0,0 +1,23 @@ +/** -------------------------------------------------------------------- + * @file tools.h + * @brief Declaration of miscellaneous tools. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> + +namespace Binc { + + class Tools { + private: + Tools(void); + + public: + void setenv(const std::string &key, const std::string &value) const; + std::string getenv(const std::string &key) const; + + //-- + static Tools &getInstance(void); + }; + +} diff --git a/src/iodevice.cc b/src/iodevice.cc new file mode 100644 index 0000000..7197354 --- /dev/null +++ b/src/iodevice.cc @@ -0,0 +1,283 @@ +/** -------------------------------------------------------------------- + * @file iodevice.cc + * @brief Implementation of the IODevice class. + * @author Andreas Aardal Hanssen + * @date 2002, 2003 + * ----------------------------------------------------------------- **/ +#include "iodevice.h" +#include "convert.h" // BincStream +#include "session.h" // getEnv/hasEnv + +#include <stdlib.h> +#include <unistd.h> + +using namespace ::std; +using namespace ::Binc; + +//------------------------------------------------------------------------ +IODevice::IODevice(int f) : flags(f | IsEnabled), + maxInputBufferSize(0), + maxOutputBufferSize(0), + timeout(0), + readCount(0), writeCount(0), + outputLevel(ErrorLevel), + outputLevelLimit(ErrorLevel), + error(Unknown), errorString("Unknown device error"), + dumpfd(0) +{ +} + +//------------------------------------------------------------------------ +IODevice::~IODevice(void) +{ +} + +//------------------------------------------------------------------------ +IODevice &IODevice::operator <<(ostream &(*source)(ostream &)) +{ + if (!(flags & IsEnabled) || outputLevel > outputLevelLimit) + return *this; + + static std::ostream &(*endl_funcptr)(ostream &) = endl; + + if (source != endl_funcptr) return *this; + + outputBuffer << "\r\n"; + + if (dumpfd) ::write(dumpfd, "\r\n", 2); + + if (flags & FlushesOnEndl) + flush(); + else if (flags & HasOutputLimit) + if (outputBuffer.getSize() > maxOutputBufferSize) + flush(); + + return *this; +} + +//------------------------------------------------------------------------ +bool IODevice::canRead(void) const +{ + return false; +} + +//------------------------------------------------------------------------ +void IODevice::clear() +{ + if (!(flags & IsEnabled)) return; + + inputBuffer.clear(); + outputBuffer.clear(); +} + +//------------------------------------------------------------------------ +bool IODevice::flush() +{ + if (!(flags & IsEnabled)) return true; + + WriteResult writeResult = WriteWait; + do { + unsigned int s = outputBuffer.getSize(); + if (s == 0) break; + if (!waitForWrite()) return false; + writeResult = write(); + if (writeResult == WriteError) return false; + writeCount += s - outputBuffer.getSize(); + } while (outputBuffer.getSize() > 0 && writeResult == WriteWait); + + outputBuffer.clear(); + + return true; +} + +//------------------------------------------------------------------------ +void IODevice::setFlags(unsigned int f) +{ + flags |= f; +} + +//------------------------------------------------------------------------ +void IODevice::clearFlags(unsigned int f) +{ + flags &= ~f; +} + +//------------------------------------------------------------------------ +void IODevice::setMaxInputBufferSize(unsigned int max) +{ + maxInputBufferSize = max; +} + +//------------------------------------------------------------------------ +void IODevice::setMaxOutputBufferSize(unsigned int max) +{ + maxOutputBufferSize = max; +} + +//------------------------------------------------------------------------ +void IODevice::setTimeout(unsigned int t) +{ + timeout = t; + + if (t) + flags |= HasTimeout; + else + flags &= ~HasTimeout; +} + +//------------------------------------------------------------------------ +unsigned int IODevice::getTimeout(void) const +{ + return timeout; +} + +//------------------------------------------------------------------------ +void IODevice::setOutputLevel(LogLevel level) +{ + outputLevel = level; +} + +//------------------------------------------------------------------------ +IODevice::LogLevel IODevice::getOutputLevel(void) const +{ + return outputLevel; +} + +//------------------------------------------------------------------------ +void IODevice::setOutputLevelLimit(LogLevel level) +{ + outputLevelLimit = level; +} + +//------------------------------------------------------------------------ +IODevice::LogLevel IODevice::getOutputLevelLimit(void) const +{ + return outputLevelLimit; +} + +//------------------------------------------------------------------------ +bool IODevice::readStr(string *dest, unsigned int max) +{ + // If max is 0, fill the input buffer once only if it's empty. + if (!max && inputBuffer.getSize() == 0 && !fillInputBuffer()) return false; + + // If max is != 0, wait until we have max. + while (max && inputBuffer.getSize() < max) { + if (!fillInputBuffer()) return false; + } + + unsigned int bytesToRead = max ? max : inputBuffer.getSize(); + *dest += inputBuffer.str().substr(0, bytesToRead); + if (dumpfd) { + ::write(dumpfd, inputBuffer.str().substr(0, bytesToRead).c_str(), bytesToRead); + } + + inputBuffer.popString(bytesToRead); + readCount += bytesToRead; + + return true; +} + +//------------------------------------------------------------------------ +bool IODevice::readChar(char *dest) +{ + if (inputBuffer.getSize() == 0 && !fillInputBuffer()) return false; + + char c = inputBuffer.popChar(); + if (dest) *dest = c; + if (dumpfd) ::write(dumpfd, &c, 1); + + ++readCount; + + return true; +} + +//------------------------------------------------------------------------ +void IODevice::unreadChar(char c) +{ + inputBuffer.unpopChar(c); +} + +//------------------------------------------------------------------------ +void IODevice::unreadStr(const string &s) +{ + inputBuffer.unpopStr(s); +} + +//------------------------------------------------------------------------ +bool IODevice::skipTo(char c) +{ + char dest = '\0'; + do { + if (!readChar(&dest)) return false; + if (dumpfd) ::write(dumpfd, &dest, 1); + } while (c != dest); + + return true; +} + +//------------------------------------------------------------------------ +string IODevice::service(void) const +{ + return "nul"; +} + +//------------------------------------------------------------------------ +bool IODevice::waitForWrite(void) const +{ + return false; +} + +//------------------------------------------------------------------------ +bool IODevice::waitForRead(void) const +{ + return false; +} + +//------------------------------------------------------------------------ +IODevice::WriteResult IODevice::write(void) +{ + return WriteError; +} + +//------------------------------------------------------------------------ +bool IODevice::fillInputBuffer(void) +{ + return false; +} + +//------------------------------------------------------------------------ +IODevice::Error IODevice::getLastError(void) const +{ + return error; +} + +//------------------------------------------------------------------------ +string IODevice::getLastErrorString(void) const +{ + return errorString; +} + +//------------------------------------------------------------------------ +unsigned int IODevice::getReadCount(void) const +{ + return readCount; +} + +//------------------------------------------------------------------------ +unsigned int IODevice::getWriteCount(void) const +{ + return writeCount; +} + +//------------------------------------------------------------------------ +void IODevice::enableProtocolDumping(void) +{ + BincStream ss; + ss << "/tmp/bincimap-dump-" << (int) time(0) << "-" + << Session::getInstance().getIP() << "-XXXXXX"; + char *safename = strdup(ss.str().c_str()); + dumpfd = mkstemp(safename); + if (dumpfd == -1) dumpfd = 0; + delete[] safename; +} diff --git a/src/iofactory.cc b/src/iofactory.cc new file mode 100644 index 0000000..ced087c --- /dev/null +++ b/src/iofactory.cc @@ -0,0 +1,66 @@ +/* -------------------------------------------------------------------- + * @file iofactory.cc + * @brief Implementation of the IOFactory class. + * @author Andreas Aardal Hanssen + * @date 2002, 2003 + * ----------------------------------------------------------------- **/ +#include "iofactory.h" +#include "iodevice.h" + +using namespace ::Binc; +using namespace ::std; + +//------------------------------------------------------------------------ +IOFactory::IOFactory(void) +{ +} + +//------------------------------------------------------------------------ +IOFactory::~IOFactory(void) +{ +} + +//------------------------------------------------------------------------ +IOFactory &IOFactory::getInstance(void) +{ + static IOFactory ioFactory; + return ioFactory; +} + +//------------------------------------------------------------------------ +void IOFactory::addDevice(IODevice *dev) +{ + IODevice *ioDevice = IOFactory::getInstance().devices[dev->service()]; + + // FIXME: Delete correct object. Now, only IODevice's destructor is + // called, and only IODevice's memory is freed. + if (ioDevice) + delete ioDevice; + + IOFactory::getInstance().devices[dev->service()] = dev; +} + +//------------------------------------------------------------------------ +IODevice &IOFactory::getClient(void) +{ + static IODevice nulDevice; + + IOFactory &ioFactory = IOFactory::getInstance(); + + if (ioFactory.devices.find("client") != ioFactory.devices.end()) + return *ioFactory.devices["client"]; + + return nulDevice; +} + +//------------------------------------------------------------------------ +IODevice &IOFactory::getLogger(void) +{ + static IODevice nulDevice; + + IOFactory &ioFactory = IOFactory::getInstance(); + + if (ioFactory.devices.find("log") != ioFactory.devices.end()) + return *ioFactory.devices["log"]; + return nulDevice; +} diff --git a/src/mailbox.cc b/src/mailbox.cc new file mode 100644 index 0000000..53c8606 --- /dev/null +++ b/src/mailbox.cc @@ -0,0 +1,112 @@ +/** -------------------------------------------------------------------- + * @file mailbox.cc + * @brief Implementation of the Mailbox class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> + +#include "mailbox.h" +#include "message.h" + +using namespace ::std; +using namespace Binc; + +//------------------------------------------------------------------------ +Mailbox::BaseIterator::BaseIterator(int sqn) +{ + sqnr = sqn; +} + +//------------------------------------------------------------------------ +Mailbox::BaseIterator::~BaseIterator(void) +{ +} + +//------------------------------------------------------------------------ +Mailbox::Mailbox(void) : readOnly(false) +{ +} + +//------------------------------------------------------------------------ +Mailbox::~Mailbox(void) +{ +} + +//------------------------------------------------------------------------ +bool Mailbox::isReadOnly(void) const +{ + return readOnly; +} + +//------------------------------------------------------------------------ +void Mailbox::setReadOnly(bool readOnly) +{ + this->readOnly = readOnly; +} + +//------------------------------------------------------------------------ +Mailbox::iterator::iterator(BaseIterator &i) + : realIterator(i) +{ +} + +//------------------------------------------------------------------------ +Message &Mailbox::iterator::operator *(void) +{ + return *realIterator; +} + +//------------------------------------------------------------------------ +void Mailbox::iterator::operator ++(void) +{ + ++realIterator; +} + +//------------------------------------------------------------------------ +bool Mailbox::iterator::operator ==(const iterator &i) const +{ + return realIterator == i.realIterator; +} + +//------------------------------------------------------------------------ +bool Mailbox::iterator::operator !=(const iterator &i) const +{ + return realIterator != i.realIterator; +} + +//------------------------------------------------------------------------ +void Mailbox::iterator::erase(void) +{ + realIterator.erase(); +} + +//------------------------------------------------------------------------ +unsigned int Mailbox::iterator::getSqnr(void) const +{ + return realIterator.sqnr; +} + +//------------------------------------------------------------------------ +void Mailbox::setName(const string &name) +{ + this->name = name; +} + +//------------------------------------------------------------------------ +const string Mailbox::getName(void) const +{ + return name; +} + +//------------------------------------------------------------------------ +const string &Mailbox::getLastError(void) const +{ + return lastError; +} + +//------------------------------------------------------------------------ +void Mailbox::setLastError(const string &error) const +{ + lastError = error; +} diff --git a/src/maildir-close.cc b/src/maildir-close.cc new file mode 100644 index 0000000..a886d97 --- /dev/null +++ b/src/maildir-close.cc @@ -0,0 +1,46 @@ +/** -------------------------------------------------------------------- + * @file maildir-close.cc + * @brief Implementation of the Maildir class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "maildir.h" + +#include <fcntl.h> +#include <unistd.h> + +using namespace ::std; +using namespace Binc; + +//------------------------------------------------------------------------ +void Binc::Maildir::closeMailbox(void) +{ + if (!selected) + return; + + if (mailboxchanged && !readOnly) + writeCache(); + + mailboxchanged = false; + + MaildirMessageCache::getInstance().clear(); + + messages.clear(); + index.clear(); + newMessages.clear(); + oldrecent = 0; + oldexists = 0; + firstscan = true; + cacheRead = false; + uidvalidity = 0; + uidnext = 1; + selected = false; + path = ""; + + old_bincimap_cache_st_mtime = 0; + old_bincimap_cache_st_ctime = 0; + old_cur_st_mtime = 0; + old_cur_st_ctime = 0; + old_new_st_mtime = 0; + old_new_st_ctime = 0; +} diff --git a/src/maildir-create.cc b/src/maildir-create.cc new file mode 100644 index 0000000..6bbd7d7 --- /dev/null +++ b/src/maildir-create.cc @@ -0,0 +1,78 @@ +/** -------------------------------------------------------------------- + * @file maildir-create.cc + * @brief Implementation of the Maildir class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "maildir.h" + +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <sys/stat.h> + +using namespace ::std; +using namespace Binc; + +//------------------------------------------------------------------------ +bool Binc::Maildir::createMailbox(const string &s_in, mode_t mode, + uid_t owner, gid_t group, bool root) +{ + if (s_in != "." && mkdir(s_in.c_str(), mode) == -1) { + setLastError("unable to create " + s_in + ": " + + string(strerror(errno))); + return false; + } + + // Allow uidvalidity, which is generated from time(0), to + // increase with one second to avoid race conditions. + sleep(1); + + if (mkdir((s_in + "/cur").c_str(), mode) == -1) { + setLastError("unable to create " + s_in + "/cur: " + + string(strerror(errno))); + return false; + } + + if (mkdir((s_in + "/new").c_str(), mode) == -1) { + setLastError("unable to create " + s_in + "/new: " + + string(strerror(errno))); + return false; + } + + if (mkdir((s_in + "/tmp").c_str(), mode) == -1) { + setLastError("unable to create " + s_in + "/tmp: " + + string(strerror(errno))); + return false; + } + + if (owner == 0 && group == 0) + return true; + + if (chown(s_in.c_str(), owner, group) == -1) { + setLastError("unable to chown " + s_in + ": " + + string(strerror(errno))); + return false; + } + + if (chown((s_in + "/cur").c_str(), owner, group) == -1) { + setLastError("unable to chown " + s_in + "/cur: " + + string(strerror(errno))); + return false; + } + + if (chown((s_in + "/new").c_str(), owner, group) == -1) { + setLastError("unable to chown " + s_in + "/new: " + + string(strerror(errno))); + return false; + } + + if (chown((s_in + "/tmp").c_str(), owner, group) == -1) { + setLastError("unable to chown " + s_in + "/tmp: " + + string(strerror(errno))); + return false; + } + + return true; +} diff --git a/src/maildir-delete.cc b/src/maildir-delete.cc new file mode 100644 index 0000000..9ed162b --- /dev/null +++ b/src/maildir-delete.cc @@ -0,0 +1,88 @@ +/** -------------------------------------------------------------------- + * @file maildir-delete.cc + * @brief Implementation of the Maildir class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "maildir.h" + +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <dirent.h> +#include <unistd.h> + +using namespace ::std; +using namespace Binc; + +namespace { + + bool recursiveDelete(const string &path) + { + DIR *mydir = opendir(path.c_str()); + if (mydir == 0) + return false; + + struct dirent *mydirent; + while ((mydirent = readdir(mydir)) != 0) { + string d = mydirent->d_name; + if (d == "." || d == "..") + continue; + + string f = path + "/" + d; + + struct stat mystat; + if (lstat(f.c_str(), &mystat) != 0) { + if (errno == ENOENT) + continue; + return false; + } + + if (S_ISDIR(mystat.st_mode)) { + if (!recursiveDelete(f)) { + closedir(mydir); + return false; + } + if (rmdir(f.c_str()) != 0 && errno != ENOENT) { + closedir(mydir); + return false; + } + } else { + if (unlink(f.c_str()) != 0 && errno != ENOENT) { + closedir(mydir); + return false; + } + } + } + + closedir(mydir); + return true; + } +} + +//------------------------------------------------------------------------ +bool Binc::Maildir::deleteMailbox(const string &s_in) +{ + if (s_in == ".") { + setLastError("disallowed by rule"); + return false; + } + + if (!recursiveDelete(s_in)) { + setLastError("error deleting Maildir - status is undefined"); + return false; + } + + if (rmdir(s_in.c_str()) != 0) { + setLastError("error deleting Maildir: " + + string(strerror(errno)) + + " - status is undefined"); + return false; + } + + return true; +} diff --git a/src/maildir-expunge.cc b/src/maildir-expunge.cc new file mode 100644 index 0000000..3091d7f --- /dev/null +++ b/src/maildir-expunge.cc @@ -0,0 +1,61 @@ +/** -------------------------------------------------------------------- + * @file maildir-expunge.cc + * @brief Implementation of the Maildir class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <unistd.h> +#include <errno.h> + +#include "iodevice.h" +#include "iofactory.h" +#include "maildir.h" +#include "maildirmessage.h" + +using namespace ::std; +using namespace Binc; + +//------------------------------------------------------------------------ +void Maildir::expungeMailbox(void) +{ + if (readOnly) return; + + Mailbox::iterator i = begin(SequenceSet::all(), SQNR_MODE|INCLUDE_EXPUNGED); + + bool success = true; + for (; success && i != end(); ++i) { + MaildirMessage &message = reinterpret_cast<MaildirMessage &>(*i); + + if ((message.getStdFlags() & Message::F_DELETED) == 0) + continue; + + message.setExpunged(); + + const string &id = message.getUnique(); + + // The message might be gone already + MaildirIndexItem *item = index.find(id); + if (!item) + continue; + + string fpath = path + "/cur/" + item->fileName; + + while (unlink(fpath.c_str()) != 0) { + if (errno != ENOENT) { + bincWarning << "unable to remove " << fpath << ": " + << strerror(errno) << endl; + break; + } + + if (!scanFileNames()) { + success = false; + break; + } + + if ((item = index.find(id))) + break; + else + fpath = path + "/cur/" + item->fileName; + } + } +} diff --git a/src/maildir-readcache.cc b/src/maildir-readcache.cc new file mode 100644 index 0000000..a108242 --- /dev/null +++ b/src/maildir-readcache.cc @@ -0,0 +1,151 @@ +/** -------------------------------------------------------------------- + * @file maildir-readcache.cc + * @brief Implementation of the Maildir class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <algorithm> +#include "maildir.h" +#include "convert.h" +#include "globals.h" + +using namespace ::std; +using namespace Binc; + +//------------------------------------------------------------------------ +Maildir::ReadCacheResult Maildir::readCache(void) +{ + index.clearUids(); + + const string cachefilename = path + "/bincimap-cache"; + FILE *fp = fopen(cachefilename.c_str(), "r"); + if (!fp) { + uidvalidity = time(0); + uidnext = 1; + messages.clear(); + index.clear(); + newMessages.clear(); + return NoCache; + } + + char inputBuffer[512]; + if (!fgets(inputBuffer, sizeof(inputBuffer), fp)) { + fclose(fp); + uidvalidity = time(0); + uidnext = 1; + messages.clear(); + index.clear(); + newMessages.clear(); + return NoCache; + } + + // terminate the buffer + inputBuffer[sizeof(inputBuffer) - 1] = '\0'; + + char cacheFileVersionBuffer[512]; + unsigned int readUidValidity; + unsigned int readUidNext; + + if (sscanf(inputBuffer, "%s %u %u", cacheFileVersionBuffer, &readUidValidity, &readUidNext) != 3 + || strcmp(cacheFileVersionBuffer, BINC_CACHE) != 0) { + // bump cache + fclose(fp); + uidvalidity = time(0); + uidnext = 1; + messages.clear(); + index.clear(); + newMessages.clear(); + return NoCache; + } + + uidnext = readUidNext; + uidvalidity = readUidValidity; + + unsigned int readUID; + unsigned int readSize; + unsigned int readInternalDate; + char readUnique[512]; + + while (fgets(inputBuffer, sizeof(inputBuffer), fp)) { + inputBuffer[sizeof(inputBuffer) - 1] = '\0'; + if (sscanf(inputBuffer, "%u %u %u %s", &readUID, + &readInternalDate, &readSize, readUnique) != 4) { + // error in input + fclose(fp); + uidvalidity = time(0); + uidnext = 1; + messages.clear(); + index.clear(); + newMessages.clear(); + return NoCache; + } + + vector<string> customFlags; + + char *flagStart = inputBuffer; + for (int i = 0; flagStart != 0 && *flagStart != '\0' && i < 4; ++i) { + flagStart = strchr(flagStart, ' '); + + // skip consecutive white space + while (flagStart && *flagStart == ' ') + ++flagStart; + } + + // get flags + while (flagStart) { + char *lastOffset = flagStart; + flagStart = strchr(flagStart, ' '); + if (flagStart) { + *flagStart = '\0'; + ++flagStart; + } + + customFlags.push_back(lastOffset); + + // skip consecutive white space + while (flagStart && *flagStart != '\0' && *flagStart == ' ') + ++flagStart; + } + + MaildirMessage m(*this); + m.setUnique(readUnique); + m.setInternalDate(readInternalDate); + + if (index.find(readUnique) == 0) { + for (vector<string>::const_iterator it = customFlags.begin(); + it != customFlags.end(); ++it) { + string tmpFlag = *it; + trim(tmpFlag, " \n"); + m.setCustomFlag(tmpFlag); + } + + m.setUID(readUID); + m.setInternalFlag(MaildirMessage::JustArrived); + m.setSize(readSize); + add(m); + } else { + // Remember to insert the uid of the message again - we reset this + // at the top of this function. + index.insert(readUnique, readUID); + MaildirMessage &existingMessage = messages.find(readUID)->second; + + vector<string> oldCustomFlags = existingMessage.getCustomFlags(); + sort(oldCustomFlags.begin(), oldCustomFlags.end()); + sort(customFlags.begin(), customFlags.end()); + if (oldCustomFlags != customFlags) { + existingMessage.resetCustomFlags(); + for (vector<string>::const_iterator it = customFlags.begin(); + it != customFlags.end(); ++it) { + string tmpFlag = *it; + trim(tmpFlag, " \n"); + existingMessage.setCustomFlag(tmpFlag); + } + } + } + } + + fclose(fp); + + return Ok; +} + diff --git a/src/maildir-scan.cc b/src/maildir-scan.cc new file mode 100644 index 0000000..4628d5b --- /dev/null +++ b/src/maildir-scan.cc @@ -0,0 +1,476 @@ +/** -------------------------------------------------------------------- + * @file maildir-scan.cc + * @brief Implementation of the Maildir class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <fcntl.h> +#include <dirent.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +#include "iodevice.h" +#include "iofactory.h" +#include "maildir.h" + +using namespace Binc; +using namespace ::std; + +Lock::Lock(const string &path) +{ + lock = (path == "" ? "." : path) + "/bincimap-scan-lock"; + + int lockfd = -1; + while ((lockfd = ::open(lock.c_str(), + O_CREAT | O_WRONLY | O_EXCL, 0666)) == -1) { + if (errno != EEXIST) { + bincWarning << "unable to lock mailbox: " << lock + << ", " << string(strerror(errno)) << endl; + return; + } + + struct stat mystat; + bincWarning << "waiting for mailbox lock " << lock << "." << endl; + if (lstat(lock.c_str(), &mystat) == 0) { + if ((time(0) - mystat.st_ctime) > 300) { + if (unlink(lock.c_str()) == 0) continue; + else bincWarning << "failed to force mailbox lock: " << lock + << ", " << string(strerror(errno)) << endl; + } + } else { + if (errno != ENOENT) { + string err = "invalid lock " + lock + ": " + + strerror(errno); + bincWarning << err << endl; + return; + } + } + + // sleep one second. + sleep(1); + } + + close(lockfd); +} + +Lock::~Lock() +{ + // remove the lock + if (unlink(lock.c_str()) != 0) + bincWarning << "failed to unlock mailbox: " << lock << ", " + << strerror(errno) << endl; +} + +//------------------------------------------------------------------------ +// scan the maildir. update flags, find messages in new/ and move them +// to cur, setting the recent flag in memory only. check for expunged +// messages. give newly arrived messages uids. +//------------------------------------------------------------------------ +Maildir::ScanResult Maildir::scan(bool forceScan) +{ + const string newpath = path + "/new/"; + const string curpath = path + "/cur/"; + const string cachepath = path + "/bincimap-cache"; + + // check wether or not we need to bother scanning the folder. + if (firstscan || forceScan) { + struct stat oldstat; + if (stat(newpath.c_str(), &oldstat) != 0) { + setLastError("Invalid Mailbox, " + newpath + ": " + + string(strerror(errno))); + return PermanentError; + } + + old_new_st_mtime = oldstat.st_mtime; + old_new_st_ctime = oldstat.st_ctime; + + if (stat(curpath.c_str(), &oldstat) != 0) { + setLastError("Invalid Mailbox, " + curpath + ": " + + string(strerror(errno))); + return PermanentError; + } + + old_cur_st_mtime = oldstat.st_mtime; + old_cur_st_ctime = oldstat.st_ctime; + + if (stat(cachepath.c_str(), &oldstat) == 0) { + old_bincimap_cache_st_mtime = oldstat.st_mtime; + old_bincimap_cache_st_ctime = oldstat.st_ctime; + } else { + old_bincimap_cache_st_mtime = 0; + old_bincimap_cache_st_ctime = 0; + } + } else { + struct stat oldcurstat; + struct stat oldnewstat; + struct stat oldbincimapcachestat; + if (stat(newpath.c_str(), &oldnewstat) != 0) { + setLastError("Invalid Mailbox, " + newpath + ": " + + string(strerror(errno))); + return PermanentError; + } + + if (stat(curpath.c_str(), &oldcurstat) != 0) { + setLastError("Invalid Mailbox, " + curpath + ": " + + string(strerror(errno))); + return PermanentError; + } + + if (stat(cachepath.c_str(), &oldbincimapcachestat) != 0) { + oldbincimapcachestat.st_ctime = 0; + oldbincimapcachestat.st_mtime = 0; + } + + if (oldnewstat.st_mtime == old_new_st_mtime + && oldnewstat.st_ctime == old_new_st_ctime + && oldcurstat.st_mtime == old_cur_st_mtime + && oldcurstat.st_ctime == old_cur_st_ctime + && oldbincimapcachestat.st_mtime == old_bincimap_cache_st_mtime + && oldbincimapcachestat.st_ctime == old_bincimap_cache_st_ctime) { + return Success; + } + + old_bincimap_cache_st_mtime = oldbincimapcachestat.st_mtime; + old_bincimap_cache_st_ctime = oldbincimapcachestat.st_ctime; + old_cur_st_mtime = oldcurstat.st_mtime; + old_cur_st_ctime = oldcurstat.st_ctime; + old_new_st_mtime = oldnewstat.st_mtime; + old_new_st_ctime = oldnewstat.st_ctime; + } + + // lock the directory as we are scanning. this prevents race + // conditions with uid delegation + Lock lock(path); + + // Read the cache file if it's there. It holds important information + // about the state of the depository, and serves to communicate + // changes to the depot across Binc IMAP instances that can not be + // communicated via the depot itself. + switch (readCache()) { + case NoCache: + case Error: + // An error with reading the cache files when it's not the first + // time we scan the depot is treated as an error. + if (!firstscan && !readOnly) { + old_cur_st_mtime = (time_t) 0; + old_cur_st_ctime = (time_t) 0; + old_new_st_mtime = (time_t) 0; + old_new_st_ctime = (time_t) 0; + return TemporaryError; + } + mailboxchanged = true; + break; + default: + break; + } + + // open new/ directory + DIR *pdir = opendir(newpath.c_str()); + if (pdir == 0) { + string reason = "failed to open \"" + newpath + "\" ("; + reason += strerror(errno); + reason += ")"; + setLastError(reason); + + return PermanentError; + } + + // scan all entries + struct dirent *pdirent; + while ((pdirent = readdir(pdir)) != 0) { + // "Unless you're writing messages to a maildir, the format of a + // unique name is none of your business. A unique name can be + // anything that doesn't contain a colon (or slash) and doesn't + // start with a dot. Do not try to extract information from unique + // names." - The Maildir spec from cr.yp.to + string filename = pdirent->d_name; + if (filename[0] == '.' + || filename.find(':') != string::npos + || filename.find('/') != string::npos) + continue; + + string fullfilename = newpath + filename; + + // We need to find the timestamp of the message in order to + // determine whether or not it's safe to move the message in from + // new/. qmail's default message file naming algorithm forces us + // to never move messages out of new/ that are less than one + // second old. + struct stat mystat; + if (stat(fullfilename.c_str(), &mystat) != 0) { + if (errno == ENOENT) { + // prevent looping due to stale symlinks + if (lstat(fullfilename.c_str(), &mystat) == 0) { + bincWarning << "dangling symlink: " << fullfilename << endl; + continue; + } + + // a rare race between readdir and stat force us to restart the scan. + closedir(pdir); + + if ((pdir = opendir(newpath.c_str())) == 0) { + string reason = "Warning: opendir(\"" + newpath + "\") == 0 ("; + reason += strerror(errno); + reason += ")"; + setLastError(reason); + return PermanentError; + } + } else + bincWarning << "junk in Maildir: \"" << fullfilename << "\": " + << strerror(errno); + + continue; + } + + // this is important. do not move messages from new/ that are not + // at least one second old or messages may disappear. this + // introduces a special case: we can not cache the old st_ctime + // and st_mtime. the next time the mailbox is scanned, it must not + // simply be skipped. :-) + + vector<MaildirMessage>::const_iterator newIt = newMessages.begin(); + bool ours = false; + for (; newIt != newMessages.end(); ++newIt) { + if ((filename == (*newIt).getUnique()) + && ((*newIt).getInternalFlags() & MaildirMessage::Committed)) { + ours = true; + break; + } + } + + if (!ours && ::time(0) <= mystat.st_mtime) { + old_cur_st_mtime = (time_t) 0; + old_cur_st_ctime = (time_t) 0; + old_new_st_mtime = (time_t) 0; + old_new_st_ctime = (time_t) 0; + continue; + } + + // move files from new/ to cur/ + string newName = curpath + pdirent->d_name; + if (rename((newpath + pdirent->d_name).c_str(), + (newName + ":2,").c_str()) != 0) { + bincWarning << "error moving messages from new to cur: skipping " + << newpath + << pdirent->d_name << ": " << strerror(errno) << endl; + continue; + } + } + + closedir(pdir); + + // Now, assume all known messages were expunged and have them prove + // otherwise. + { + Mailbox::iterator i = begin(SequenceSet::all(), + INCLUDE_EXPUNGED | SQNR_MODE); + for (; i != end(); ++i) + (*i).setExpunged(); + } + + // Then, scan cur + // open directory + + if ((pdir = opendir(curpath.c_str())) == 0) { + string reason = "Maildir::scan::opendir(\"" + curpath + "\") == 0 ("; + reason += strerror(errno); + reason += ")"; + + setLastError(reason); + return PermanentError; + } + + // erase all old maps between fixed filenames and actual file names. + // we'll get a new list now, which will be more up to date. + index.clearFileNames(); + + // this is to sort recent messages by internaldate + multimap<unsigned int, MaildirMessage> tempMessageMap; + + // scan all entries + while ((pdirent = readdir(pdir)) != 0) { + string filename = pdirent->d_name; + if (filename[0] == '.') continue; + + string uniquename; + string standard; + string::size_type pos; + if ((pos = filename.find(':')) != string::npos) { + uniquename = filename.substr(0, pos); + string tmp = filename.substr(pos); + if ((pos = tmp.find("2,")) != string::npos) + standard = tmp.substr(pos + 2); + } else + uniquename = filename; + + unsigned char mflags = Message::F_NONE; + for (string::const_iterator i = standard.begin(); + i != standard.end(); ++i) { + switch (*i) { + case 'R': mflags |= Message::F_ANSWERED; break; + case 'S': mflags |= Message::F_SEEN; break; + case 'T': mflags |= Message::F_DELETED; break; + case 'D': mflags |= Message::F_DRAFT; break; + case 'F': mflags |= Message::F_FLAGGED; break; + case 'P': mflags |= Message::F_PASSED; break; + default: break; + } + } + + struct stat mystat; + MaildirMessage *message = get(uniquename); + if (!message || message->getInternalDate() == 0) { + string fullfilename = curpath + filename; + if (stat(fullfilename.c_str(), &mystat) != 0) { + if (errno == ENOENT) { + // prevent looping due to stale symlinks + if (lstat(fullfilename.c_str(), &mystat) == 0) { + bincWarning << "dangling symlink: " << fullfilename << endl; + continue; + } + // a rare race between readdir and stat force us to restart + // the scan. + index.clearFileNames(); + + closedir(pdir); + + if ((pdir = opendir(newpath.c_str())) == 0) { + string reason = "Warning: opendir(\"" + newpath + "\") == 0 ("; + reason += strerror(errno); + reason += ")"; + setLastError(reason); + return PermanentError; + } + } + + continue; + } + + mailboxchanged = true; + } + + index.insert(uniquename, 0, filename); + + // If we have this message in memory already.. + if (message) { + if (message->getInternalDate() == 0) { + mailboxchanged = true; + message->setInternalDate(mystat.st_mtime); + } + + // then confirm that this message was not expunged + message->setUnExpunged(); + + // update the flags with what new flags we found in the filename, + // but keep the \Recent flag regardless. + if (mflags != (message->getStdFlags() & ~Message::F_RECENT)) { + int oldflags = message->getStdFlags(); + message->resetStdFlags(); + message->setStdFlag(mflags | (oldflags & Message::F_RECENT)); + } + + continue; + } + + // Wait with delegating UIDs until all entries have been + // read. Only then can we sort by internaldate and delegate new + // UIDs. + MaildirMessage m(*this); + m.setUID(0); + m.setSize(0); + m.setInternalDate(mystat.st_mtime); + m.setStdFlag((mflags | Message::F_RECENT) & ~Message::F_EXPUNGED); + m.setUnique(uniquename); + tempMessageMap.insert(make_pair((unsigned int) mystat.st_mtime, m)); + + mailboxchanged = true; + } + + closedir(pdir); + + // Recent messages are added, ordered by internaldate. + { + int readonlyuidnext = uidnext; + multimap<unsigned int, MaildirMessage>::iterator i = tempMessageMap.begin(); + while (i != tempMessageMap.end()) { + i->second.setUID(readOnly ? readonlyuidnext++ : uidnext++); + multimap<unsigned int, MaildirMessage>::iterator itmp = i; + ++itmp; + add(i->second); + tempMessageMap.erase(i); + i = itmp; + mailboxchanged = true; + } + } + + tempMessageMap.clear(); + + // Messages that existed in the cache that we read, but did not + // exist in the Maildir, are removed from the messages list. + Mailbox::iterator jj = begin(SequenceSet::all(), INCLUDE_EXPUNGED | SQNR_MODE); + while (jj != end()) { + MaildirMessage &message = (MaildirMessage &)*jj; + + if (message.isExpunged()) { + mailboxchanged = true; + if (message.getInternalFlags() & MaildirMessage::JustArrived) { + jj.erase(); + continue; + } + } else if (message.getInternalFlags() & MaildirMessage::JustArrived) { + message.clearInternalFlag(MaildirMessage::JustArrived); + } + + ++jj; + } + + // Special case: The first time we scan is in SELECT. All flags + // changes for new messages will then appear to be recent, and + // to avoid this to be sent to the client as a pending update, + // we explicitly unset the "flagsChanged" flag in all messages. + if (firstscan) { + unsigned int lastuid = 0; + + Mailbox::iterator ii + = begin(SequenceSet::all(), INCLUDE_EXPUNGED | SQNR_MODE); + for (; ii != end(); ++ii) { + MaildirMessage &message = (MaildirMessage &)*ii; + message.clearInternalFlag(MaildirMessage::JustArrived); + + if (lastuid < message.getUID()) + lastuid = message.getUID(); + else { + bincWarning << "UID values are not strictly ascending in this" + " mailbox: " << path << ". This is usually caused by " + << "access from a broken accessor. Bumping UIDVALIDITY." + << endl; + + setLastError("An error occurred while scanning the mailbox. " + "Please contact your system administrator."); + + if (!readOnly) { + bumpUidValidity(path); + old_cur_st_mtime = (time_t) 0; + old_cur_st_ctime = (time_t) 0; + old_new_st_mtime = (time_t) 0; + old_new_st_ctime = (time_t) 0; + return TemporaryError; + } else { + return PermanentError; + } + } + + message.setFlagsUnchanged(); + } + } + + if (mailboxchanged && !readOnly) { + if (!writeCache()) return PermanentError; + mailboxchanged = false; + } + + firstscan = false; + newMessages.clear(); + return Success; +} diff --git a/src/maildir-scanfilesnames.cc b/src/maildir-scanfilesnames.cc new file mode 100644 index 0000000..643460b --- /dev/null +++ b/src/maildir-scanfilesnames.cc @@ -0,0 +1,53 @@ +/** -------------------------------------------------------------------- + * @file maildir-scanfilesnames.cc + * @brief Implementation of the Maildir class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "maildir.h" + +#include <iostream> +#include <iomanip> + +#include <dirent.h> +#include <unistd.h> +#include <errno.h> + +#include "iodevice.h" +#include "iofactory.h" + +using namespace ::std; + +//------------------------------------------------------------------------ +bool Binc::Maildir::scanFileNames(void) const +{ + string curpath = path + "/cur/"; + DIR *pdir = opendir(curpath.c_str()); + if (pdir == 0) { + setLastError("when scanning mailbox \"" + + path + "\": " + string(strerror(errno))); + bincWarning << getLastError() << endl; + return false; + } + + index.clearFileNames(); + + struct dirent *pdirent; + while ((pdirent = readdir(pdir)) != 0) { + if (!isdigit(pdirent->d_name[0])) continue; + + string filename = pdirent->d_name; + string uniquename; + + string::size_type pos; + if ((pos = filename.find(':')) == string::npos) + uniquename = filename; + else + uniquename = filename.substr(0, pos); + + index.insert(uniquename, 0, pdirent->d_name); + } + + closedir(pdir); + return true; +} diff --git a/src/maildir-select.cc b/src/maildir-select.cc new file mode 100644 index 0000000..78daf9d --- /dev/null +++ b/src/maildir-select.cc @@ -0,0 +1,45 @@ +/** -------------------------------------------------------------------- + * @file maildir-select.cc + * @brief Implementation of the Maildir class. + * @author Andreas Aardal Hanssen + * @date Andreas Aardal Hanssen + * ----------------------------------------------------------------- **/ +#include "maildir.h" + +#include <fcntl.h> +#include <unistd.h> + +using namespace ::std; +using namespace Binc; + +//------------------------------------------------------------------------ +bool Binc::Maildir::selectMailbox(const std::string &name, + const std::string &s_in) +{ + setName(name); + + if (selected) { + closeMailbox(); + selected = false; + } + + oldrecent = 0; + oldexists = 0; + + mailboxchanged = false; + + setPath(s_in); + + switch (scan()) { + case Success: + break; + case TemporaryError: + if (scan() == Success) + break; + case PermanentError: + return false; + } + + selected = true; + return true; +} diff --git a/src/maildir-updateflags.cc b/src/maildir-updateflags.cc new file mode 100644 index 0000000..be37234 --- /dev/null +++ b/src/maildir-updateflags.cc @@ -0,0 +1,115 @@ +/** -------------------------------------------------------------------- + * @file maildir-updateflags.cc + * @brief Implementation of the Maildir class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "maildir.h" + +#include <dirent.h> +#include <unistd.h> +#include <errno.h> + +#include "iodevice.h" +#include "iofactory.h" + +using namespace ::std; + +//------------------------------------------------------------------------ +void Binc::Maildir::updateFlags(void) +{ + // don't update a read-only mailbox. + if (readOnly) return; + + // open the cur/ directory + string curpath = path + "/cur/"; + DIR *pdir = opendir(curpath.c_str()); + if (pdir == 0) { + bincError << "failed to open " << curpath << ": " + << strerror(errno) << endl; + return; + } + + // read all entries in the directory + vector<string> entries; + struct dirent *pdirent; + while ((pdirent = readdir(pdir)) != 0) { + string filename = pdirent->d_name; + if (filename[0] == '.') + continue; + entries.push_back(filename); + } + closedir(pdir); + + bool customFlagsChanged = false; + + // loop through all messages in cur/, and update the flags that have + // changed. + for (vector<string>::const_iterator it = entries.begin(); it != entries.end(); ++it) { + string filename = *it; + + // separate the unique name from the flags. accept messages that do not + // contain the flags section. + string uniquename; + string::size_type pos; + if ((pos = filename.find(":2,")) != string::npos) + uniquename = filename.substr(0, pos); + else + uniquename = filename; + + // only update flags for messages that are known. + MaildirMessage *message = get(uniquename); + if (!message) + continue; + + // check if custom flags have changed; if so, we need to regenerate the + // cache file. + if (message->internalFlags & MaildirMessage::CustomFlagsChanged) + customFlagsChanged = true; + + // generate the flag string. + string flags; + int mflags = message->getStdFlags(); + if (mflags & Message::F_DRAFT) flags += "D"; + if (mflags & Message::F_FLAGGED) flags += "F"; + if (mflags & Message::F_PASSED) flags += "P"; + if (mflags & Message::F_ANSWERED) flags += "R"; + if (mflags & Message::F_SEEN) flags += "S"; + if (mflags & Message::F_DELETED) flags += "T"; + + // prepare the old and new message name. if equal, skip to the next + // message. + string srcname = curpath + filename; + string destname = curpath + uniquename + ":2," + flags; + if (srcname == destname) + continue; + + // rename the file + if (rename(srcname.c_str(), destname.c_str()) != 0) { + if (errno == ENOENT) { + closedir(pdir); + if ((pdir = opendir(curpath.c_str())) == 0) { + bincError << "failed to open " << curpath << ": " + << strerror(errno) << endl; + return; + } + + // restart scan. this catches the race condition where concurrent + // clients may have set a flag or removed the message. + continue; + } + + bincError << "failed to rename " << srcname + << " to " << destname << ": " + << strerror(errno) << endl; + } else { + index.insert(uniquename, 0, uniquename + ":2," + flags); + } + } + + // finally, regenerate the cache file if the custom flags have changed. + if (customFlagsChanged) { + Lock lock(path); + writeCache(); + } +} diff --git a/src/maildir-writecache.cc b/src/maildir-writecache.cc new file mode 100644 index 0000000..feda4bd --- /dev/null +++ b/src/maildir-writecache.cc @@ -0,0 +1,74 @@ +/** -------------------------------------------------------------------- + * @file maildir-writecache.cc + * @brief Implementation of the Maildir class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ------------------------------------------------------------------ **/ +#include <dirent.h> +#include <fcntl.h> +#include <unistd.h> + +#include "globals.h" +#include "maildir.h" + +using namespace ::std; + +//------------------------------------------------------------------------ +bool Binc::Maildir::writeCache(void) +{ + if (readOnly) + return true; + + char *safename = strdup((path + "/.bincimap-cache-tmp-XXXXXX").c_str()); + int fd = mkstemp(safename); + if (!fd) { + free(safename); + return false; + } + + string safeName = safename; + free(safename); + + FILE *fp = fdopen(fd, "w"); + if (!fp) { + unlink(safeName.c_str()); + return false; + } + + if (uidvalidity == 0 || uidnext == 0) { + uidvalidity = time(0); + uidnext = messages.size() + 1; + } + + fprintf(fp, "%s %u %u\n", BINC_CACHE, uidvalidity, uidnext); + Mailbox::iterator i = begin(SequenceSet::all(), INCLUDE_EXPUNGED); + for (; i != end(); ++i) { + MaildirMessage &message = (MaildirMessage &)*i; + fprintf(fp, "%u %u %u %s", message.getUID(), + (unsigned int) message.getInternalDate(), message.getSize(), + message.getUnique().c_str()); + vector<string> cflags = message.getCustomFlags(); + for (vector<string>::const_iterator it = cflags.begin(); + it != cflags.end(); ++it) { + fprintf(fp, " %s", (*it).c_str()); + } + fprintf(fp, "\n"); + } + + if (fflush(fp) || (fsync(fd) && (errno != EROFS || errno != EINVAL)) || fclose(fp)) { + unlink(safeName.c_str()); + return false; + } + + if (rename(safeName.c_str(), (path + "/bincimap-cache").c_str()) != 0) { + unlink(safeName.c_str()); + return false; + } + + int dfd = open(path.c_str(), O_RDONLY); + if (dfd == -1 || (fsync(fd) && (errno != EROFS || errno != EINVAL)) || close(dfd)) { + return false; + } + + return true; +} diff --git a/src/maildir.cc b/src/maildir.cc new file mode 100644 index 0000000..11f472c --- /dev/null +++ b/src/maildir.cc @@ -0,0 +1,839 @@ +/** -------------------------------------------------------------------- + * @file maildir.cc + * @brief Implementation of the Maildir class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <iostream> +#include <iomanip> +#include <algorithm> + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#include "convert.h" +#include "iodevice.h" +#include "iofactory.h" +#include "maildir.h" +#include "maildirmessage.h" +#include "pendingupdates.h" +#include "session.h" +#include "status.h" +#include "globals.h" + +using namespace ::std; +using namespace Binc; + +// Used to generate the unique names for Maildir delivery +static int numDeliveries = 0; + +//------------------------------------------------------------------------ +Maildir::iterator::iterator(void) +{ +} + +//------------------------------------------------------------------------ +Maildir::iterator::iterator(Maildir *home, + MessageMap::iterator it, + const SequenceSet &_bset, + unsigned int _mod) + : BaseIterator(1), mailbox(home), bset(_bset), mod(_mod), i(it) +{ + uidmax = home->getMaxUid(); + sqnrmax = home->getMaxSqnr(); +} + +//------------------------------------------------------------------------ +Maildir::iterator::iterator(const iterator ©) + : BaseIterator(copy.sqnr), mailbox(copy.mailbox), + bset(copy.bset), mod(copy.mod), i(copy.i), uidmax(copy.uidmax), + sqnrmax(copy.sqnrmax) +{ +} + +//------------------------------------------------------------------------ +Maildir::iterator &Maildir::iterator::operator =(const iterator ©) +{ + sqnr = copy.sqnr; + mailbox = copy.mailbox; + bset = copy.bset; + mod = copy.mod; + i = copy.i; + uidmax = copy.uidmax; + sqnrmax = copy.sqnrmax; + return *this; +} + +//------------------------------------------------------------------------ +Maildir::iterator::~iterator(void) +{ +} + +//------------------------------------------------------------------------ +MaildirMessage &Maildir::iterator::curMessage(void) +{ + return i->second; +} + +//------------------------------------------------------------------------ +Message &Maildir::iterator::operator *(void) +{ + return curMessage(); +} + +//------------------------------------------------------------------------ +void Maildir::iterator::operator ++(void) +{ + ++i; + ++sqnr; + reposition(); +} + +//------------------------------------------------------------------------ +bool Maildir::iterator::operator ==(const BaseIterator &a) const +{ + const iterator *b = dynamic_cast<const iterator *>(&a); + return b ? (i == b->i) : false; +} + +//------------------------------------------------------------------------ +bool Maildir::iterator::operator !=(const BaseIterator &a) const +{ + return !((*this) == a); +} + +//------------------------------------------------------------------------ +void Maildir::iterator::reposition(void) +{ + for (;;) { + if (i == mailbox->messages.end()) + break; + + Message &message = curMessage(); + if ((mod & SKIP_EXPUNGED) && message.isExpunged()) { + ++i; + continue; + } + + bool inset = false; + if (mod & SQNR_MODE) { + if (bset.isInSet(sqnr) || (!bset.isLimited() && sqnr == sqnrmax)) + inset = true; + } else { + if (bset.isInSet(message.getUID()) || (!bset.isLimited() && message.getUID() == uidmax)) + inset = true; + } + + if (!inset) { + ++i; + if (!message.isExpunged()) + ++sqnr; + continue; + } + + break; + } +} + +//------------------------------------------------------------------------ +Mailbox::iterator Maildir::begin(const SequenceSet &bset, + unsigned int mod) const +{ + beginIterator = iterator((Maildir *)this, messages.begin(), bset, mod); + beginIterator.reposition(); + + return Mailbox::iterator(beginIterator); +} + +//------------------------------------------------------------------------ +Mailbox::iterator Maildir::end(void) const +{ + endIterator = iterator((Maildir *)this, messages.end(), + endIterator.bset, endIterator.mod); + return Mailbox::iterator(endIterator); +} + +//------------------------------------------------------------------------ +void Maildir::iterator::erase(void) +{ + MessageMap::iterator iter = i; + ++iter; + + MaildirMessageCache::getInstance().removeStatus(&curMessage()); + mailbox->index.remove(i->second.getUnique()); + mailbox->messages.erase(i); + + i = iter; + reposition(); +} + +//------------------------------------------------------------------------ +Maildir::Maildir(void) : Mailbox() +{ + firstscan = true; + cacheRead = false; + uidvalidity = 0; + uidnext = 1; + selected = false; + oldrecent = 0; + oldexists = 0; +} + +//------------------------------------------------------------------------ +Maildir::~Maildir(void) +{ +} + +//------------------------------------------------------------------------ +void Maildir::setPath(const string &path_in) +{ + path = path_in; +} + +//------------------------------------------------------------------------ +bool Maildir::getUpdates(bool doscan, unsigned int type, + PendingUpdates &updates, bool forceScan) +{ + if (doscan && scan(forceScan) != Success) + return false; + + unsigned int exists = 0; + unsigned int recent = 0; + bool displayExists = false; + + // count messages, find recent + if (!readOnly && (type & PendingUpdates::EXPUNGE)) { + Mailbox::iterator i = begin(SequenceSet::all(), + INCLUDE_EXPUNGED | SQNR_MODE); + + while (i != end()) { + Message &message = *i; + + if (message.isExpunged()) { + updates.addExpunged(i.getSqnr()); + i.erase(); + mailboxchanged = true; + displayExists = true; + } else + ++i; + } + } + + Mailbox::iterator i = begin(SequenceSet::all(), + INCLUDE_EXPUNGED | SQNR_MODE); + for (; i != end(); ++i) { + Message & message = *i; + // at this point, there is a message that is not expunged + ++exists; + if (message.getStdFlags() & Message::F_RECENT) ++recent; + } + + if (displayExists || exists != oldexists) + updates.setExists(oldexists = exists); + + if (recent != oldrecent) + updates.setRecent(oldrecent = recent); + + if (type & PendingUpdates::FLAGS) { + Mailbox::iterator i = begin(SequenceSet::all(), SQNR_MODE); + for (; i != end(); ++i) { + Message &message = *i; + if (message.hasFlagsChanged()) { + int flags = message.getStdFlags(); + + updates.addFlagUpdates(i.getSqnr(), message.getUID(), flags, + message.getCustomFlags()); + + message.setFlagsUnchanged(); + } + } + } + + return true; +} + +//------------------------------------------------------------------------ +bool Maildir::isMailbox(const std::string &s_in) const +{ + struct stat mystat; + + return ((stat((s_in + "/cur").c_str(), &mystat) == 0 + && S_ISDIR(mystat.st_mode)) + && (stat((s_in + "/new").c_str(), &mystat) == 0 + && S_ISDIR(mystat.st_mode)) + && (stat((s_in + "/tmp").c_str(), &mystat) == 0 + && S_ISDIR(mystat.st_mode))); +} + +//------------------------------------------------------------------------ +const std::string Maildir::getTypeName(void) const +{ + return "Maildir"; +} + +//------------------------------------------------------------------------ +void Maildir::bumpUidValidity(const string &s_in) const +{ + unlink((s_in + "/bincimap-uidvalidity").c_str()); + unlink((s_in + "/bincimap-cache").c_str()); +} + +//------------------------------------------------------------------------ +bool Maildir::isMarked(const std::string &s_in) const +{ + DIR *dirp = opendir((s_in + "/new").c_str()); + if (dirp == 0) return false; + + struct dirent *direntp; + while ((direntp = readdir(dirp)) != 0) { + string s = direntp->d_name; + if (s[0] != '.' + && s.find('/') == string::npos + && s.find(':') == string::npos) { + closedir(dirp); + return true; + } + } + + closedir(dirp); + return false; +} + +//------------------------------------------------------------------------ +unsigned int Maildir::getStatusID(const string &path) const +{ + unsigned int statusid = 0; + struct stat mystat; + if (stat((path + "/new/").c_str(), &mystat) == 0) + statusid = mystat.st_ctime; + + if (stat((path + "/cur/").c_str(), &mystat) == 0) + statusid += mystat.st_ctime; + + if (stat((path + "/bincimap-cache").c_str(), &mystat) == 0) + statusid += mystat.st_ctime; + + return statusid; +} + +//------------------------------------------------------------------------ +bool Maildir::getStatus(const string &path, Status &s) const +{ + unsigned int messages = 0; + unsigned int unseen = 0; + unsigned int recent = 0; + + unsigned int readUidValidity; + unsigned int readUidNext = 1; + map<string, bool> mincache; + + const string cachefilename = path + "/bincimap-cache"; + FILE *fp = fopen(cachefilename.c_str(), "r"); + if (fp) do { + char inputBuffer[512]; + if (!fgets(inputBuffer, sizeof(inputBuffer), fp)) { + fclose(fp); + return false; + } + + // terminate the buffer + inputBuffer[sizeof(inputBuffer) - 1] = '\0'; + + char cacheFileVersionBuffer[512]; + + if (sscanf(inputBuffer, "%s %u %u", cacheFileVersionBuffer, + &readUidValidity, &readUidNext) != 3 + || strcmp(cacheFileVersionBuffer, BINC_CACHE) != 0) { + fclose(fp); + readUidValidity = 0; + readUidNext = 1; + mincache.clear(); + break; + } + + unsigned int readUID; + unsigned int readSize; + unsigned int readInternalDate; + char readUnique[512]; + while (fgets(inputBuffer, sizeof(inputBuffer), fp)) { + inputBuffer[sizeof(inputBuffer) - 1] = '\0'; + if (sscanf(inputBuffer, "%u %u %u %s", &readUID, + &readInternalDate, &readSize, readUnique) != 4) { + fclose(fp); + readUidValidity = 0; + readUidNext = 1; + mincache.clear(); + break; + } + + mincache[readUnique] = true; + } + + fclose(fp); + + s.setUidValidity(readUidValidity < 1 ? time(0) : readUidValidity); + } while (0); else { + s.setUidValidity(time(0)); + } + + // Scan new + DIR *dirp = opendir((path + "/new").c_str()); + if (dirp == 0) return false; + + struct dirent *direntp; + while ((direntp = readdir(dirp)) != 0) { + const string filename = direntp->d_name; + if (filename[0] == '.' + || filename.find(':') != string::npos + || filename.find('/') != string::npos) + continue; + + ++recent; + ++readUidNext; + ++unseen; + ++messages; + } + + closedir(dirp); + + // Scan cur + if ((dirp = opendir((path + "/cur").c_str())) == 0) + return false; + + while ((direntp = readdir(dirp)) != 0) { + const string dname = direntp->d_name; + if (dname[0] == '.') + continue; + + ++messages; + + // Add to unseen if it doesn't have the seen flag or if it has no + // flags. + const string::size_type pos = dname.find(':'); + if (pos != string::npos) { + if (mincache.find(dname.substr(0, pos)) == mincache.end()) { + ++recent; + ++readUidNext; + } + + if (dname.substr(pos).find('S') == string::npos) + ++unseen; + } else { + if (mincache.find(dname) == mincache.end()) { + ++recent; + ++readUidNext; + } + + ++unseen; + } + } + + closedir(dirp); + + s.setRecent(recent); + s.setMessages(messages); + s.setUnseen(unseen); + s.setUidNext(uidnext); + + return true; +} + +//------------------------------------------------------------------------ +unsigned int Maildir::getMaxUid(void) const +{ + MessageMap::const_iterator i = messages.end(); + if (i == messages.begin()) + return 0; + + --i; + for (;;) { + const MaildirMessage &message = i->second; + if (message.isExpunged()) { + if (i == messages.begin()) + return 0; + --i; + } else { + return message.getUID(); + } + } + + return 0; +} + +//------------------------------------------------------------------------ +unsigned int Maildir::getMaxSqnr(void) const +{ + int sqnr = messages.size(); + MessageMap::const_iterator i = messages.end(); + if (i == messages.begin()) + return 0; + + --i; + for (;;) { + const MaildirMessage &message = i->second; + if (message.isExpunged()) { + if (i == messages.begin()) + return 0; + --sqnr; + --i; + } else { + return sqnr; + } + } + + return 0; +} + +//------------------------------------------------------------------------ +unsigned int Maildir::getUidValidity(void) const +{ + return uidvalidity; +} + +//------------------------------------------------------------------------ +unsigned int Maildir::getUidNext(void) const +{ + return uidnext; +} + +//------------------------------------------------------------------------ +Message *Maildir::createMessage(const string &mbox, time_t idate) +{ + string sname = mbox + "/tmp/bincimap-XXXXXX"; + char *safename = strdup(sname.c_str()); + + int fd = mkstemp(safename); + if (fd == -1) { + setLastError("Unable to create safe name."); + return 0; + } + + string safenameStr = safename; + delete[] safename; + + MaildirMessage message(*this); + + message.setFile(fd); + message.setSafeName(safenameStr); + message.setInternalDate(idate); + + newMessages.push_back(message); + vector<MaildirMessage>::iterator i = newMessages.end(); + --i; + return &(*i); +} + +//------------------------------------------------------------------------ +bool Maildir::commitNewMessages(const string &mbox) +{ + Session &session = Session::getInstance(); + + vector<MaildirMessage>::iterator i = newMessages.begin(); + map<MaildirMessage *, string> committedMessages; + + struct timeval youngestFile = {0, 0}; + + bool abort = false; + while (!abort && i != newMessages.end()) { + MaildirMessage &m = *i; + + if (m.getInternalFlags() & MaildirMessage::Committed) { + ++i; + continue; + } + + m.setInternalFlag(MaildirMessage::Committed); + + string safeName = m.getSafeName(); + + for (int attempt = 0; !abort && attempt < 1000; ++attempt) { + struct timeval tv; + gettimeofday(&tv, 0); + youngestFile = tv; + + // Generate Maildir conformant file name + BincStream ssid; + ssid << (int) tv.tv_sec << "." + << "R" << (int) rand() + << "M" << (int) tv.tv_usec + << "P" << (int) session.getPid() + << "Q" << numDeliveries++ + << "." << session.getEnv("TCPLOCALHOST"); + + BincStream ss; + ss << mbox << "/new/" << ssid.str(); + + string fileName = ss.str(); + + if (link(safeName.c_str(), fileName.c_str()) == 0) { + unlink(safeName.c_str()); + m.setInternalDate(tv.tv_sec); + m.setUnique(ssid.str()); + m.setUID(0); + committedMessages[&m] = fileName; + break; + } + + if (errno == EEXIST) + continue; + + bincWarning << "link(" << toImapString(safeName) << ", " + << toImapString(fileName) << ") failed: " + << strerror(errno) << endl; + + session.setResponseCode("TRYCREATE"); + session.setLastError("failed, internal error."); + abort = true; + break; + } + + ++i; + } + + // abort means to make an attempt to restore the mailbox to + // its original state. + if (abort) { + // Fixme: Messages that are in committedMessages should be skipped + // here. + for (i = newMessages.begin(); i != newMessages.end(); ++i) + unlink((*i).getSafeName().c_str()); + + map<MaildirMessage *, string>::const_iterator j + = committedMessages.begin(); + while (j != committedMessages.end()) { + if (unlink(j->second.c_str()) != 0) { + if (errno == ENOENT) { + // FIXME: The message was probably moves away from new/ by + // another IMAP session. + bincWarning << "error rollbacking after failed commit to " + << toImapString(mbox) << ", failed to unlink " + << toImapString(j->second) + << ": " << strerror(errno) << endl; + } else { + bincWarning << "error rollbacking after failed commit to " + << toImapString(mbox) << ", failed to unlink " + << toImapString(j->second) + << ": " << strerror(errno) << endl; + newMessages.clear(); + return false; + } + } + + ++j; + } + + newMessages.clear(); + return false; + } + + // cover the extremely unlikely event that another concurrent + // Maildir accessor has just made a file with the same timestamp and + // random number by spinning until the timestamp has changed before + // moving the message into cur. + struct timeval tv; + gettimeofday(&tv, 0); + while (tv.tv_sec == youngestFile.tv_sec + && tv.tv_usec == youngestFile.tv_usec) { + gettimeofday(&tv, 0); + } + + map<MaildirMessage *, string>::const_iterator j + = committedMessages.begin(); + for (;j != committedMessages.end(); ++j) { + string basename = j->second.substr(j->second.rfind('/') + 1); + + int flags = j->first->getStdFlags(); + string flagStr; + if (flags & Message::F_DRAFT) flagStr += "D"; + if (flags & Message::F_FLAGGED) flagStr += "F"; + if (flags & Message::F_ANSWERED) flagStr += "R"; + if (flags & Message::F_SEEN) flagStr += "S"; + if (flags & Message::F_DELETED) flagStr += "T"; + + string dest = mbox + "/cur/" + basename + ":2," + flagStr; + if (rename(j->second.c_str(), dest.c_str()) == 0) + continue; + + if (errno != ENOENT) + bincWarning << "when setting flags on: " << j->second + << ": " << strerror(errno) << endl; + } + + committedMessages.clear(); + return true; +} + +//------------------------------------------------------------------------ +bool Maildir::rollBackNewMessages(void) +{ + vector<MaildirMessage>::const_iterator i = newMessages.begin(); + // Fixme: Messages that are in committedMessages should be skipped + // here. + for (; i != newMessages.end(); ++i) + unlink((*i).getSafeName().c_str()); + + newMessages.clear(); + return true; +} + +//------------------------------------------------------------------------ +bool Maildir::fastCopy(Message &m, Mailbox &desttype, + const std::string &destname) +{ + // At this point, fastCopy is broken because the creation time is + // equal for the two clones. The new clone must have a new creation + // time. Symlinks are a possibility, but they break if other Maildir + // accessors rename mailboxes. + // return false; + + Session &session = Session::getInstance(); + + MaildirMessage *message = dynamic_cast<MaildirMessage *>(&m); + if (!message) + return false; + + string mfilename = message->getFileName(); + if (mfilename == "") + return false; + + Maildir *mailbox = dynamic_cast<Maildir *>(&desttype); + if (!mailbox) + return false; + + for (int attempt = 0; attempt < 1000; ++attempt) { + + struct timeval tv; + gettimeofday(&tv, 0); + + // Generate Maildir conformant file name + BincStream ssid; + ssid << (int) tv.tv_sec << "." + << "R" << (int) rand() + << "M" << (int) tv.tv_usec + << "P" << (int) session.getPid() + << "Q" << numDeliveries++ + << "." << session.getEnv("TCPLOCALHOST"); + + BincStream ss; + ss << destname << "/tmp/" << ssid.str(); + + string fileName = ss.str(); + + if (link((path + "/cur/" + mfilename).c_str(), fileName.c_str()) == 0) { + MaildirMessage newmess = *message; + newmess.setSafeName(fileName); + newmess.setUnique(ssid.str()); + newmess.setInternalDate((time_t) tv.tv_sec); + newmess.setUID(0); + newMessages.push_back(newmess); + break; + } + + if (errno == EEXIST) + continue; + + bincWarning << "Warning: link(" + << toImapString(path + "/cur/" + mfilename) + << ", " << toImapString(fileName) << ") failed: " + << strerror(errno) << endl; + + session.setResponseCode("TRYCREATE"); + session.setLastError("failed, internal error."); + return false; + } + + return true; +} + +//------------------------------------------------------------------------ +MaildirMessage *Maildir::get(const std::string &id) +{ + MaildirIndexItem *item = index.find(id); + if (!item) + return 0; + + unsigned int uid = item->uid; + if (messages.find(uid) == messages.end()) + return 0; + + return &messages.find(uid)->second; +} + +//------------------------------------------------------------------------ +void Maildir::add(MaildirMessage &m) +{ + MessageMap::iterator it = messages.find(m.getUID()); + if (it != messages.end()) + messages.erase(it); + messages.insert(make_pair(m.getUID(), m)); + index.insert(m.getUnique(), m.getUID()); +} + +//------------------------------------------------------------------------ +unsigned int MaildirIndex::getSize(void) const +{ + return idx.size(); +} + +//------------------------------------------------------------------------ +void MaildirIndex::insert(const string &unique, unsigned int uid, + const string &fileName) +{ + if (idx.find(unique) == idx.end()) { + MaildirIndexItem item; + item.uid = uid; + item.fileName = fileName; + idx[unique] = item; + } else { + MaildirIndexItem &item = idx[unique]; + if (uid != 0) item.uid = uid; + if (fileName != "") item.fileName = fileName; + } +} + +//------------------------------------------------------------------------ +void MaildirIndex::remove(const string &unique) +{ + map<string, MaildirIndexItem>::iterator it = idx.find(unique); + if (it != idx.end()) + idx.erase(it); +} + +//------------------------------------------------------------------------ +MaildirIndexItem *MaildirIndex::find(const string &unique) +{ + map<string, MaildirIndexItem>::iterator it = idx.find(unique); + if (it != idx.end()) + return &it->second; + + return 0; +} + +//------------------------------------------------------------------------ +void MaildirIndex::clear(void) +{ + idx.clear(); +} + +//------------------------------------------------------------------------ +void MaildirIndex::clearUids(void) +{ + map<string, MaildirIndexItem>::iterator it = idx.begin(); + for (; it != idx.end(); ++it) + it->second.uid = 0; +} + +//------------------------------------------------------------------------ +void MaildirIndex::clearFileNames(void) +{ + map<string, MaildirIndexItem>::iterator it = idx.begin(); + for (; it != idx.end(); ++it) + it->second.fileName = ""; +} diff --git a/src/maildirmessage.cc b/src/maildirmessage.cc new file mode 100644 index 0000000..a78a5b9 --- /dev/null +++ b/src/maildirmessage.cc @@ -0,0 +1,1153 @@ +/** -------------------------------------------------------------------- + * @file maildirmessage.cc + * @brief Implementation of the MaildirMessage class. + * @author Andreas Aardal Hanssen + * @date Copyright 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> + +#include <stack> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <ctype.h> +#include <time.h> +#include <utime.h> + +#include "maildir.h" +#include "maildirmessage.h" +#include "convert.h" +#include "mime.h" +#include "iodevice.h" +#include "iofactory.h" +#include "mime-utils.h" +#include "mime-inputsource.h" + +using namespace ::std; +using namespace Binc; + +string Message::lastError; +string MaildirMessage::storage; + +namespace { + //---------------------------------------------------------------------- + void printOneHeader(IODevice &io, const MimePart *message, + const string &s_in, bool removecomments = true) + { + string tmp = ""; + HeaderItem hitem; + + if (message->h.getFirstHeader(s_in, hitem)) { + tmp = hitem.getValue(); + io << toImapString(unfold(tmp, removecomments)); + } else + io << "NIL"; + } + + //---------------------------------------------------------------------- + void printOneAddressList(IODevice &io, const MimePart *message, + const string &s_in, bool removecomments = true) + { + string tmp = ""; + HeaderItem hitem; + + if (message->h.getFirstHeader(s_in, hitem)) { + tmp = hitem.getValue(); + vector<string> addr; + splitAddr(unfold(tmp, removecomments), addr); + if (addr.size() != 0) { + io << "("; + for (vector<string>::const_iterator i = addr.begin(); + i != addr.end(); ++i) + + io << Address(*i).toParenList(); + io << ")"; + } else io << "NIL"; + } else + io << "NIL"; + } + + //---------------------------------------------------------------------- + void envelope(IODevice &io, const MimePart *message) + { + HeaderItem hitem; + io << "("; + printOneHeader(io, message, "date"); + io << " "; + printOneHeader(io, message, "subject", false); + io << " "; + printOneAddressList(io, message, "from", false); + io << " "; + printOneAddressList(io, message, + message->h.getFirstHeader("sender", hitem) + ? "sender" : "from", false); + io << " "; + printOneAddressList(io, message, + message->h.getFirstHeader("reply-to", hitem) + ? "reply-to" : "from", false); + io << " "; + printOneAddressList(io, message, "to", false); + io << " "; + printOneAddressList(io, message, "cc", false); + io << " "; + printOneAddressList(io, message, "bcc", false); + io << " "; + printOneHeader(io, message, "in-reply-to"); + io << " "; + printOneHeader(io, message, "message-id"); + io << ")"; + } + + //---------------------------------------------------------------------- + void bodyStructure(IODevice &io, const MimePart *message, bool extended) + { + HeaderItem hitem; + if (message->isMultipart() && message->members.size() > 0) { + io << "("; + + for (vector<MimePart>::const_iterator i = message->members.begin(); + i != message->members.end(); ++i) + bodyStructure(io, &(*i), extended); + + io << " "; + io << toImapString(message->getSubType()); + + if (extended) { + io << " "; + + vector<string> parameters; + vector<string> headers; + string tmp; + + string type, subtype; + + tmp = ""; + if (message->h.getFirstHeader("content-type", hitem)) { + tmp = unfold(hitem.getValue()); + trim(tmp); + + vector<string> v; + split(tmp, ";", v); + + for (vector<string>::const_iterator i = v.begin(); i != v.end(); ++i) { + string element = *i; + trim(element); + if (element.find('=') != string::npos) { + string::size_type pos = element.find('='); + string s = element.substr(0, pos); + string t = element.substr(pos + 1); + trim(s, " \""); + trim(t, " \""); + parameters.push_back(s); + parameters.push_back(t); + } + } + + if (parameters.size() != 0) { + io << "("; + for (vector<string>::const_iterator i = parameters.begin(); + i != parameters.end(); ++i) { + if (i != parameters.begin()) io << " "; + io << toImapString(*i); + } + io << ")"; + } else + io << "NIL"; + } else + io << "NIL"; + + // CONTENT-DISPOSITION + io << " "; + tmp = ""; + if (message->h.getFirstHeader("content-disposition", hitem)) { + tmp = hitem.getValue(); + trim(tmp); + + vector<string> v; + split(tmp, ";", v); + if (v.size() > 0) { + string disp = v[0]; + trim(disp); + io << "(" << toImapString(disp); + io << " "; + if (v.size() > 1) { + io << "("; + vector<string>::const_iterator i = v.begin(); + ++i; + bool wrote = false; + while (i != v.end()) { + string s = *i; + trim(s); + string::size_type pos = s.find('='); + string key = s.substr(0, pos); + string value = s.substr(pos + 1); + + trim(key); + trim(value); + + trim(key, " \""); + trim(value, " \""); + + if (!wrote) wrote = true; + else io << " "; + + io << toImapString(key); + io << " "; + io << toImapString(value); + + ++i; + } + io << ")"; + } else + io << "NIL"; + io << ")"; + } else + io << "NIL"; + } else + io << "NIL"; + + // CONTENT-LANGUAGE + io << " "; + printOneHeader(io, message, "content-language"); + } + io << ")"; + } else { + io << "("; + + vector<string> parameters; + vector<string> headers; + string tmp; + tmp = ""; + string type, subtype; + + tmp = ""; + if (message->h.getFirstHeader("content-type", hitem)) { + tmp = unfold(hitem.getValue()); + + vector<string> v; + split(tmp, ";", v); + + if (v.size() > 0) { + vector<string> b; + split(v[0], "/", b); + + if (b.size() > 0) + type = b[0]; + else + type = "text"; + + if (b.size() > 1) + subtype = b[1]; + else + subtype = "plain"; + } + + for (vector<string>::const_iterator i = v.begin(); i != v.end(); ++i) { + if (i == v.begin()) continue; + string element = *i; + trim(element); + if (element.find('=') != string::npos) { + string::size_type pos = element.find('='); + string s = element.substr(0, pos); + string t = element.substr(pos + 1); + trim(s, " \""); + trim(t, " \""); + parameters.push_back(s); + parameters.push_back(t); + } + } + + } else { + type = "text"; + subtype = "plain"; + } + + io << toImapString(type); + io << " "; + io << toImapString(subtype); + + io << " "; + if (parameters.size() != 0) { + io << "("; + for (vector<string>::const_iterator i = parameters.begin(); + i != parameters.end(); ++i) { + if (i != parameters.begin()) + io << " "; + io << toImapString(*i); + } + io << ")"; + } else + io << "NIL"; + + // CONTENT-ID + io << " "; + printOneHeader(io, message, "content-id"); + + // CONTENT-DESCRIPTION + io << " "; + printOneHeader(io, message, "content-description"); + + // CONTENT-TRANSFER-ENCODING + io << " "; + tmp = ""; + if (message->h.getFirstHeader("content-transfer-encoding", hitem)) { + tmp = hitem.getValue(); + trim(tmp); + io << toImapString(tmp); + } else + io << "\"7bit\""; + io << " "; + + // Size of body in octets + io << message->getBodyLength(); + + lowercase(type); + if (type == "text") { + io << " "; + io << message->getNofBodyLines(); + } else if (message->isMessageRFC822()) { + io << " "; + envelope(io, &message->members[0]); + io << " "; + bodyStructure(io, &message->members[0], extended); + io << " "; + io << message->getNofBodyLines(); + } + + // Extension data follows + + if (extended) { + // CONTENT-MD5 + io << " "; + printOneHeader(io, message, "content-md5"); + + // CONTENT-DISPOSITION + io << " "; + tmp = ""; + if (message->h.getFirstHeader("content-disposition", hitem)) { + tmp = hitem.getValue(); + trim(tmp); + vector<string> v; + split(tmp, ";", v); + if (v.size() > 0) { + string disp = v[0]; + trim(disp); + io << "(" << toImapString(disp); + io << " "; + if (v.size() > 1) { + io << "("; + vector<string>::const_iterator i = v.begin(); + ++i; + bool wrote = false; + while (i != v.end()) { + string s = *i; + trim(s); + string::size_type pos = s.find('='); + string key = s.substr(0, pos); + string value = s.substr(pos + 1); + trim(key); + trim(value); + trim(key, " \""); + trim(value, " \""); + if (!wrote) wrote = true; + else io << " "; + io << toImapString(key); + io << " "; + io << toImapString(value); + ++i; + } + io << ")"; + } else + io << "NIL"; + io << ")"; + } else + io << "NIL"; + } else + io << "NIL"; + + // CONTENT-LANGUAGE + io << " "; + printOneHeader(io, message, "content-language"); + + // CONTENT-LOCATION + io << " "; + printOneHeader(io, message, "content-location"); + } + + io << ")"; + } + } +} + +//------------------------------------------------------------------------ +MaildirMessage::MaildirMessage(Maildir &hom) + : fd(-1), doc(0), internalFlags(None), stdflags(F_NONE), + uid(0), size(0), unique(""), safeName(""), internaldate(0), + home(hom), customFlags(0) +{ +} + +//------------------------------------------------------------------------ +MaildirMessage::MaildirMessage(const MaildirMessage ©) + : fd(copy.fd), doc(copy.doc), internalFlags(copy.internalFlags), + stdflags(copy.stdflags), uid(copy.uid), size(copy.size), + unique(copy.unique), safeName(copy.safeName), + internaldate(copy.internaldate), home(copy.home) +{ + if (copy.customFlags) { + customFlags = new vector<string>; + *customFlags = *copy.customFlags; + } else { + customFlags = 0; + } +} + +//------------------------------------------------------------------------ +MaildirMessage::~MaildirMessage(void) +{ + delete customFlags; +} + +//------------------------------------------------------------------------ +MaildirMessage &MaildirMessage::operator =(const MaildirMessage ©) +{ + fd = copy.fd; + doc = copy.doc; + internalFlags = copy.internalFlags; + stdflags = copy.stdflags; + uid = copy.uid; + size = copy.size; + unique = copy.unique; + safeName = copy.safeName; + internaldate = copy.internaldate; + home = copy.home; + + if (copy.customFlags) { + customFlags = new vector<string>; + *customFlags = *copy.customFlags; + } else { + customFlags = 0; + } + + return *this; +} + +//------------------------------------------------------------------------ +bool MaildirMessage::operator <(const MaildirMessage &a) const +{ + return uid < a.uid; +} +//------------------------------------------------------------------------ +void MaildirMessage::close(void) +{ + if (fd != -1) { + if ((internalFlags & WasWrittenTo) && fsync(fd) != 0 + && errno != EINVAL && errno != EROFS) { + // FIXME: report this error + } + + if (::close(fd) != 0) { + // FIXME: report this error + } + + fd = -1; + } + + // The file will not be moved from tmp/ before this function has + // finished. So it's safe to assume that safeName is still valid. + if (internalFlags & WasWrittenTo) { + if (internaldate != 0) { + struct utimbuf tim = {internaldate, internaldate}; + utime(safeName.c_str(), &tim); + } else { + time_t t = time(0); + struct utimbuf tim = {t, t}; + utime(safeName.c_str(), &tim); + } + + internalFlags &= ~WasWrittenTo; + } + + + if (doc) { + doc->clear(); + delete doc; + doc = 0; + } +} + +//------------------------------------------------------------------------ +void MaildirMessage::setExpunged(void) +{ + internalFlags |= Expunged; +} + +//------------------------------------------------------------------------ +void MaildirMessage::setUnExpunged(void) +{ + internalFlags &= ~Expunged; +} + +//------------------------------------------------------------------------ +void MaildirMessage::setFlagsUnchanged(void) +{ + internalFlags &= ~FlagsChanged; +} + +//------------------------------------------------------------------------ +bool MaildirMessage::hasFlagsChanged(void) const +{ + return (internalFlags & FlagsChanged) != 0; +} + +//------------------------------------------------------------------------ +unsigned char MaildirMessage::getStdFlags(void) const +{ + return stdflags; +} + +//------------------------------------------------------------------------ +bool MaildirMessage::isExpunged(void) const +{ + return (internalFlags & Expunged) != 0; +} + +//------------------------------------------------------------------------ +unsigned int MaildirMessage::getUID(void) const +{ + return uid; +} + +//------------------------------------------------------------------------ +unsigned int MaildirMessage::getSize(bool render) const +{ + if (size == 0 && render) { + size = getDocSize(); + home.mailboxchanged = true; + } + + return size; +} + +//------------------------------------------------------------------------ +const string &MaildirMessage::getUnique(void) const +{ + return unique; +} + +//------------------------------------------------------------------------ +time_t MaildirMessage::getInternalDate(void) const +{ + return internaldate; +} + +//------------------------------------------------------------------------ +void MaildirMessage::setInternalDate(time_t t) +{ + internaldate = t; +} + +//------------------------------------------------------------------------ +void MaildirMessage::setStdFlag(unsigned char f_in) +{ + internalFlags |= FlagsChanged; + stdflags |= f_in; +} + +//------------------------------------------------------------------------ +void MaildirMessage::resetStdFlags(void) +{ + internalFlags |= FlagsChanged; + stdflags = F_NONE; +} + +//------------------------------------------------------------------------ +void MaildirMessage::setUID(unsigned int i_in) +{ + uid = i_in; +} + +//------------------------------------------------------------------------ +void MaildirMessage::setSize(unsigned int i_in) +{ + size = i_in; +} + +//------------------------------------------------------------------------ +void MaildirMessage::setUnique(const string &s_in) +{ + unique = s_in; +} + +//------------------------------------------------------------------------ +int MaildirMessage::getFile(void) const +{ + if (fd != -1) + return fd; + + const string &id = getUnique(); + MaildirIndexItem *item = home.index.find(id); + if (item) { + string fpath = home.path + "/cur/" + item->fileName; + + unsigned int oflags = O_RDONLY; +#ifdef HAVE_OLARGEFILE + oflags |= O_LARGEFILE; +#endif + while ((fd = open(fpath.c_str(), oflags)) == -1) { + if (errno == ENOENT) { + struct stat st; + if (lstat(fpath.c_str(), &st) != -1) { + bincWarning << "dangling symlink: " << fpath << endl; + return -1; + } + } else { + bincWarning << "unable to open " << fpath << ": " + << strerror(errno) << endl; + return -1; + } + + home.scanFileNames(); + if ((item = home.index.find(id)) == 0) + break; + else + fpath = home.path + "/cur/" + item->fileName; + } + + MaildirMessageCache &cache = MaildirMessageCache::getInstance(); + cache.addStatus(this, MaildirMessageCache::NotParsed); + + return fd; + } + + return -1; +} + +//------------------------------------------------------------------------ +void MaildirMessage::setFile(int fd) +{ + this->fd = fd; +} + +//------------------------------------------------------------------------ +void MaildirMessage::setSafeName(const string &name) +{ + safeName = name; +} + +//------------------------------------------------------------------------ +const string &MaildirMessage::getSafeName(void) const +{ + return safeName; +} + +//------------------------------------------------------------------------ +string MaildirMessage::getFileName(void) const +{ + MaildirIndexItem *item = home.index.find(getUnique()); + if (!item) { + home.scanFileNames(); + + if ((item = home.index.find(getUnique())) == 0) + return ""; + } + + return item->fileName; +} + +//------------------------------------------------------------------------ +int MaildirMessage::readChunk(string &chunk) +{ + if (fd == -1) { + if ((fd = getFile()) == -1) + return -1; + } + + char buffer[1024]; + ssize_t readBytes = read(fd, buffer, (size_t) sizeof(buffer)); + if (readBytes == -1) { + setLastError("Error reading from " + getFileName() + + ": " + string(strerror(errno))); + return -1; + } + + chunk = string(buffer, readBytes); + return readBytes; +} + +//------------------------------------------------------------------------ +bool MaildirMessage::appendChunk(const string &chunk) +{ + if (fd == -1) { + setLastError("Error writing to " + getSafeName() + + ": File is not open"); + return false; + } + + internalFlags |= WasWrittenTo; + + string out; + for (string::const_iterator i = chunk.begin(); i != chunk.end(); ++i) { + const char c = *i; + if (c != '\r') + out += c; + } + + ssize_t wroteBytes = 0; + for (;;) { + wroteBytes = write(fd, out.c_str(), (size_t) out.length()); + if (wroteBytes == -1) { + if (errno == EINTR) + continue; + wroteBytes = 0; + } + + break; + } + + if (wroteBytes == (ssize_t) out.length()) + return true; + + setLastError("Error writing to " + getSafeName() + + ": " + string(strerror(errno))); + return false; +} + +//------------------------------------------------------------------------ +bool MaildirMessage::parseFull(void) const +{ + MaildirMessageCache &cache = MaildirMessageCache::getInstance(); + MaildirMessageCache::ParseStatus ps = cache.getStatus(this); + if (ps == MaildirMessageCache::AllParsed && doc) + return true; + + int fd = getFile(); + if (fd == -1) + return false; + + // FIXME: parse errors + if (!doc) + doc = new MimeDocument; + doc->parseFull(fd); + + cache.addStatus(this, MaildirMessageCache::AllParsed); + + return true; +} + +//------------------------------------------------------------------------ +bool MaildirMessage::parseHeaders(void) const +{ + MaildirMessageCache &cache = MaildirMessageCache::getInstance(); + MaildirMessageCache::ParseStatus ps = cache.getStatus(this); + if ((ps == MaildirMessageCache::AllParsed + || ps == MaildirMessageCache::HeaderParsed) && doc) + return true; + + int fd = getFile(); + if (fd == -1) + return false; + + // FIXME: parse errors + if (!doc) + doc = new MimeDocument; + doc->parseOnlyHeader(fd); + + cache.addStatus(this, MaildirMessageCache::HeaderParsed); + + return true; +} + +//------------------------------------------------------------------------ +bool MaildirMessage::printBodyStructure(bool extended) const +{ + if (!parseFull()) + return false; + + bodyStructure(bincClient, doc, extended); + return true; +} + +//------------------------------------------------------------------------ +bool MaildirMessage::printEnvelope(void) const +{ + if (!parseFull()) + return false; + + envelope(bincClient, doc); + return true; +} + +//------------------------------------------------------------------------ +bool MaildirMessage::printHeader(const std::string §ion, + std::vector<std::string> headers, + bool includeHeaders, + unsigned int startOffset, + unsigned int length, + bool mime) const +{ + bincClient << storage; + storage = ""; + return true; +} + +//------------------------------------------------------------------------ +unsigned int MaildirMessage::getHeaderSize(const std::string §ion, + std::vector<std::string> headers, + bool includeHeaders, + unsigned int startOffset, + unsigned int length, + bool mime) const +{ + if (section == "") { + if (!parseHeaders()) + return 0; + } else if (!parseFull()) + return 0; + + const MimePart *part = doc->getPart(section, "", mime ? MimePart::FetchMime + : MimePart::FetchHeader); + if (!part) { + storage = ""; + return 0; + } + + int fd = getFile(); + if (fd == -1) + return 0; + + storage = ""; + part->printHeader(fd, bincClient, headers, + includeHeaders, startOffset, + length, storage); + + return storage.size(); +} + +//------------------------------------------------------------------------ +bool MaildirMessage::printBody(const std::string §ion, + unsigned int startOffset, + unsigned int length) const +{ + if (!parseFull()) + return false; + + const MimePart *part = doc->getPart(section, ""); + if (!part) { + storage = ""; + return 0; + } + + int fd = getFile(); + if (fd == -1) + return false; + + storage = ""; + part->printBody(fd, bincClient, startOffset, length); + return true; +} + +//------------------------------------------------------------------------ +unsigned int MaildirMessage::getBodySize(const std::string §ion, + unsigned int startOffset, + unsigned int length) const +{ + if (!parseFull()) + return false; + + const MimePart *part = doc->getPart(section, ""); + if (!part) { + storage = ""; + return 0; + } + + if (startOffset > part->bodylength) + return 0; + + unsigned int s = part->bodylength - startOffset; + return s < length ? s : length; +} + +//------------------------------------------------------------------------ +bool MaildirMessage::printDoc(unsigned int startOffset, + unsigned int length, bool onlyText) const +{ + if (!parseFull()) + return false; + + int fd = getFile(); + if (fd == -1) + return false; + + if (onlyText) + startOffset += doc->bodystartoffsetcrlf; + + storage = ""; + doc->printDoc(fd, bincClient, startOffset, length); + return true; +} + +//------------------------------------------------------------------------ +unsigned int MaildirMessage::getDocSize(unsigned int startOffset, + unsigned int length, + bool onlyText) const +{ + if (!parseFull()) + return false; + + unsigned int s = doc->size; + if (onlyText) s -= doc->bodystartoffsetcrlf; + + if (startOffset > s) + return 0; + + s -= startOffset; + return s < length ? s : length; +} + +//------------------------------------------------------------------------ +bool MaildirMessage::headerContains(const std::string &header, + const std::string &text) +{ + if (!parseHeaders()) + return false; + + HeaderItem hitem; + if (!doc->h.getFirstHeader(header, hitem)) + return false; + + string tmp = hitem.getValue(); + uppercase(tmp); + string tmp2 = text; + uppercase(tmp2); + return (tmp.find(tmp2) != string::npos); +} + +//------------------------------------------------------------------------ +bool MaildirMessage::bodyContains(const std::string &text) +{ + if (!parseFull()) + return false; + + // search the body part of the message.. + int fd = getFile(); + if (fd == -1) + return false; + + MimeInputSource mimeSource(fd); + + char c; + for (unsigned int i = 0; i < doc->getBodyStartOffset(); ++i) + if (!mimeSource.getChar(&c)) + break; + + char *ring = new char[text.length()]; + int pos = 0; + int length = doc->getBodyLength(); + const char *textStr = text.c_str(); + unsigned int textLength = text.length(); + while (mimeSource.getChar(&c) && length--) { + ring[pos % textLength] = toupper(c); + + if (compareStringToQueue(textStr, ring, pos + 1, textLength)) { + delete[] ring; + return true; + } + + ++pos; + } + + delete[] ring; + return false; +} + +//------------------------------------------------------------------------ +bool MaildirMessage::textContains(const std::string &text) +{ + // search the body part of the message.. + int fd = getFile(); + if (fd == -1) + return false; + + MimeInputSource mimeSource(fd); + + char c; + char *ring = new char[text.length()]; + int pos = 0; + const char *textStr = text.c_str(); + unsigned int textLength = text.length(); + while (mimeSource.getChar(&c)) { + ring[pos % textLength] = toupper(c); + + if (compareStringToQueue(textStr, ring, pos + 1, textLength)) { + delete[] ring; + return true; + } + + ++pos; + } + + delete[] ring; + return false; +} + +//------------------------------------------------------------------------ +const std::string &MaildirMessage::getHeader(const std::string &header) +{ + static string NIL = ""; + + if (!parseHeaders()) + return NIL; + + HeaderItem hitem; + if (!doc->h.getFirstHeader(header, hitem)) + return NIL; + + return hitem.getValue(); +} + +//------------------------------------------------------------------------ +MaildirMessageCache::MaildirMessageCache(void) +{ +} + +//------------------------------------------------------------------------ +MaildirMessageCache::~MaildirMessageCache(void) +{ + clear(); +} + +//------------------------------------------------------------------------ +MaildirMessageCache &MaildirMessageCache::getInstance(void) +{ + static MaildirMessageCache cache; + return cache; +} + +//------------------------------------------------------------------------ +void MaildirMessageCache::addStatus(const MaildirMessage *m, + ParseStatus s) +{ + if (statuses.find(m) == statuses.end()) { + // Insert status. Perhaps remove oldest status. + if (statuses.size() > 2) { + MaildirMessage *message = const_cast<MaildirMessage *>(parsed.front()); + + removeStatus(message); + } + + parsed.push_back(m); + } + + statuses[m] = s; +} + +//------------------------------------------------------------------------ +MaildirMessageCache::ParseStatus +MaildirMessageCache::getStatus(const MaildirMessage *m) const +{ + if (statuses.find(m) == statuses.end()) + return NotParsed; + + return statuses[m]; +} + +//------------------------------------------------------------------------ +void MaildirMessageCache::clear(void) +{ + for (deque<const MaildirMessage *>::iterator i = parsed.begin(); + i != parsed.end(); ++i) + const_cast<MaildirMessage *>(*i)->close(); + + parsed.clear(); + statuses.clear(); +} + +//------------------------------------------------------------------------ +void MaildirMessageCache::removeStatus(const MaildirMessage *m) +{ + if (statuses.find(m) == statuses.end()) + return; + + statuses.erase(statuses.find(m)); + + for (deque<const MaildirMessage *>::iterator i = parsed.begin(); + i != parsed.end(); ++i) { + if (*i == m) { + const_cast<MaildirMessage *>(*i)->close(); + parsed.erase(i); + break; + } + } +} + +//------------------------------------------------------------------------ +void MaildirMessage::setInternalFlag(unsigned char f) +{ + internalFlags |= f; +} + +//------------------------------------------------------------------------ +unsigned char MaildirMessage::getInternalFlags(void) const +{ + return internalFlags; +} + +//------------------------------------------------------------------------ +void MaildirMessage::clearInternalFlag(unsigned char f) +{ + internalFlags &= ~f; +} + +//------------------------------------------------------------------------ +void MaildirMessage::setCustomFlag(const string &flag) +{ + if (!customFlags) { + internalFlags |= FlagsChanged | CustomFlagsChanged; + customFlags = new vector<string>; + } + + for (vector<string>::const_iterator it = customFlags->begin(); + it != customFlags->end(); ++it) { + if (*it == flag) + return; + } + + internalFlags |= FlagsChanged | CustomFlagsChanged; + customFlags->push_back(flag); +} + +//------------------------------------------------------------------------ +void MaildirMessage::removeCustomFlag(const string &flag) +{ + internalFlags |= FlagsChanged | CustomFlagsChanged; + + if (!customFlags) + return; + + for (vector<string>::iterator it = customFlags->begin(); + it != customFlags->end(); ++it) { + if (*it == flag) { + customFlags->erase(it); + return; + } + } +} + +//------------------------------------------------------------------------ +void MaildirMessage::resetCustomFlags(void) +{ + internalFlags |= FlagsChanged | CustomFlagsChanged; + + delete customFlags; + customFlags = 0; +} + +//------------------------------------------------------------------------ +vector<string> MaildirMessage::getCustomFlags(void) const +{ + if (!customFlags) + return vector<string>(); + + return *customFlags; +} diff --git a/src/mime-getpart.cc b/src/mime-getpart.cc new file mode 100644 index 0000000..7ce84fd --- /dev/null +++ b/src/mime-getpart.cc @@ -0,0 +1,69 @@ +/** -------------------------------------------------------------------- + * @file mime-getpart.cc + * @brief Implementation of main mime parser components + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "mime.h" +#include "convert.h" +#include <string> +#include <vector> +#include <map> +#include <exception> +#include <iostream> + +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <errno.h> + +using namespace ::std; + +//------------------------------------------------------------------------ +const Binc::MimePart *Binc::MimePart::getPart(const string &findpart, + string genpart, FetchType fetchType) const +{ + if (findpart == genpart) + return this; + + if (isMultipart()) { + if (members.size() != 0) { + vector<MimePart>::const_iterator i = members.begin(); + int part = 1; + while (i != members.end()) { + BincStream ss; + ss << genpart; + if (genpart != "") + ss << "."; + ss << part; + + const MimePart *m; + if ((m = (*i).getPart(findpart, ss.str())) != 0) { + if (fetchType == FetchHeader && m->isMessageRFC822()) + m = &m->members[0]; + return m; + } + + ++i; + ++part; + } + } + } else if (isMessageRFC822()) { + if (members.size() == 1) { + const MimePart *m = members[0].getPart(findpart, genpart); + return m; + } else { + return 0; + } + } else { + // Singlepart + if (genpart != "") + genpart += "."; + genpart += "1"; + + if (findpart == genpart) + return this; + } + + return 0; +} diff --git a/src/mime-parsefull.cc b/src/mime-parsefull.cc new file mode 100644 index 0000000..53d07db --- /dev/null +++ b/src/mime-parsefull.cc @@ -0,0 +1,603 @@ +/** -------------------------------------------------------------------- + * @file mime-parsefull.cc + * @brief Implementation of main mime parser components + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "mime.h" +#include "mime-utils.h" +#include "mime-inputsource.h" +#include "convert.h" +#include <string> +#include <vector> +#include <map> +#include <exception> +#include <iostream> + +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <errno.h> + +Binc::MimeInputSource *mimeSource = 0; + +using namespace ::std; + +//------------------------------------------------------------------------ +void Binc::MimeDocument::parseFull(int fd) const +{ + if (allIsParsed) + return; + + allIsParsed = true; + + if (!mimeSource || mimeSource->getFileDescriptor() != fd) { + delete mimeSource; + mimeSource = new MimeInputSource(fd); + } else { + mimeSource->reset(); + } + + headerstartoffsetcrlf = 0; + headerlength = 0; + bodystartoffsetcrlf = 0; + bodylength = 0; + size = 0; + messagerfc822 = false; + multipart = false; + + int bsize = 0; + string bound; + MimePart::parseFull(bound, bsize); + + // eat any trailing junk to get the correct size + char c; + while (mimeSource->getChar(&c)); + + size = mimeSource->getOffset(); +} + +//------------------------------------------------------------------------ +static bool parseOneHeaderLine(Binc::Header *header, unsigned int *nlines) +{ + using namespace ::Binc; + char c; + bool eof = false; + char cqueue[4]; + string name; + string content; + + while (mimeSource->getChar(&c)) { + // If we encounter a \r before we got to the first ':', then + // rewind back to the start of the line and assume we're at the + // start of the body. + if (c == '\r') { + for (int i = 0; i < (int) name.length() + 1; ++i) + mimeSource->ungetChar(); + return false; + } + + // A colon marks the end of the header name + if (c == ':') break; + + // Otherwise add to the header name + name += c; + } + + cqueue[0] = '\0'; + cqueue[1] = '\0'; + cqueue[2] = '\0'; + cqueue[3] = '\0'; + + // Read until the end of the header. + bool endOfHeaders = false; + while (!endOfHeaders) { + if (!mimeSource->getChar(&c)) { + eof = true; + break; + } + + if (c == '\n') ++*nlines; + + for (int i = 0; i < 3; ++i) + cqueue[i] = cqueue[i + 1]; + cqueue[3] = c; + + if (strncmp(cqueue, "\r\n\r\n", 4) == 0) { + endOfHeaders = true; + break; + } + + // If the last character was a newline, and the first now is not + // whitespace, then rewind one character and store the current + // key,value pair. + if (cqueue[2] == '\n' && c != ' ' && c != '\t') { + if (content.length() > 2) + content.resize(content.length() - 2); + + trim(content); + header->add(name, content); + + if (c != '\r') { + mimeSource->ungetChar(); + if (c == '\n') --*nlines; + return true; + } + + mimeSource->getChar(&c); + return false; + } + + content += c; + } + + if (name != "") { + if (content.length() > 2) + content.resize(content.length() - 2); + header->add(name, content); + } + + return !(eof || endOfHeaders); +} + +//------------------------------------------------------------------------ +static void parseHeader(Binc::Header *header, unsigned int *nlines) +{ + while (parseOneHeaderLine(header, nlines)) + { } +} + +//------------------------------------------------------------------------ +static void analyzeHeader(Binc::Header *header, bool *multipart, + bool *messagerfc822, string *subtype, string *boundary) +{ + using namespace ::Binc; + + // Do simple parsing of headers to determine the + // type of message (multipart,messagerfc822 etc) + HeaderItem ctype; + if (header->getFirstHeader("content-type", ctype)) { + vector<string> types; + split(ctype.getValue(), ";", types); + + if (types.size() > 0) { + // first element should describe content type + string tmp = types[0]; + trim(tmp); + vector<string> v; + split(tmp, "/", v); + string key, value; + + key = (v.size() > 0) ? v[0] : "text"; + value = (v.size() > 1) ? v[1] : "plain"; + lowercase(key); + + if (key == "multipart") { + *multipart = true; + lowercase(value); + *subtype = value; + } else if (key == "message") { + lowercase(value); + if (value == "rfc822") + *messagerfc822 = true; + } + } + + for (vector<string>::const_iterator i = types.begin(); + i != types.end(); ++i) { + string element = *i; + trim(element); + + if (element.find("=") != string::npos) { + string::size_type pos = element.find('='); + string key = element.substr(0, pos); + string value = element.substr(pos + 1); + + lowercase(key); + trim(key); + + if (key == "boundary") { + trim(value, " \""); + *boundary = value; + } + } + } + } +} + +static void parseMessageRFC822(vector<Binc::MimePart> *members, + bool *foundendofpart, + unsigned int *bodylength, + unsigned int *nbodylines, + const string &toboundary) +{ + using namespace ::Binc; + + // message rfc822 means a completely enclosed mime document. we + // call the parser recursively, and pass on the boundary string + // that we got. when parse() finds this boundary, it returns 0. if + // it finds the end boundary (boundary + "--"), it returns != 0. + MimePart m; + + unsigned int bodystartoffsetcrlf = mimeSource->getOffset(); + + // parsefull returns the number of bytes that need to be removed + // from the body because of the terminating boundary string. + int bsize = 0; + if (m.parseFull(toboundary, bsize)) + *foundendofpart = true; + + // make sure bodylength doesn't overflow + *bodylength = mimeSource->getOffset(); + if (*bodylength >= bodystartoffsetcrlf) { + *bodylength -= bodystartoffsetcrlf; + if (*bodylength >= (unsigned int) bsize) { + *bodylength -= (unsigned int) bsize; + } else { + *bodylength = 0; + } + } else { + *bodylength = 0; + } + + *nbodylines += m.getNofLines(); + + members->push_back(m); +} + +static bool skipUntilBoundary(const string &delimiter, + unsigned int *nlines, bool *eof) +{ + int endpos = delimiter.length(); + char *delimiterqueue = 0; + int delimiterpos = 0; + const char *delimiterStr = delimiter.c_str(); + if (delimiter != "") { + delimiterqueue = new char[endpos]; + memset(delimiterqueue, 0, endpos); + } + + // first, skip to the first delimiter string. Anything between the + // header and the first delimiter string is simply ignored (it's + // usually a text message intended for non-mime clients) + char c; + + bool foundBoundary = false; + for (;;) { + if (!mimeSource->getChar(&c)) { + *eof = true; + break; + } + + if (c == '\n') ++*nlines; + + // if there is no delimiter, we just read until the end of the + // file. + if (!delimiterqueue) continue; + + delimiterqueue[delimiterpos++ % endpos] = c; + + if (compareStringToQueue(delimiterStr, delimiterqueue, + delimiterpos, endpos)) { + foundBoundary = true; + break; + } + } + + delete[] delimiterqueue; + delimiterqueue = 0; + + return foundBoundary; +} + + +static void parseMultipart(const string &boundary, + const string &toboundary, + bool *eof, + unsigned int *nlines, + int *boundarysize, + bool *foundendofpart, + unsigned int *bodylength, + vector<Binc::MimePart> *members) +{ + using namespace ::Binc; + unsigned int bodystartoffsetcrlf = mimeSource->getOffset(); + + // multipart parsing starts with skipping to the first + // boundary. then we call parse() for all parts. the last parse() + // command will return a code indicating that it found the last + // boundary of this multipart. Note that the first boundary does + // not have to start with CRLF. + string delimiter = "--" + boundary; + + skipUntilBoundary(delimiter, nlines, eof); + + if (!eof) *boundarysize = delimiter.size(); + + // Read two more characters. This may be CRLF, it may be "--" and + // it may be any other two characters. + + char a; + if (!mimeSource->getChar(&a)) *eof = true; + if (a == '\n') ++*nlines; + + char b; + if (!mimeSource->getChar(&b)) *eof = true; + if (b == '\n') ++*nlines; + + // If we find two dashes after the boundary, then this is the end + // of boundary marker. + if (!*eof) { + if (a == '-' && b == '-') { + *foundendofpart = true; + *boundarysize += 2; + + if (!mimeSource->getChar(&a)) *eof = true; + if (a == '\n') ++*nlines; + if (!mimeSource->getChar(&b)) *eof = true; + if (b == '\n') ++*nlines; + } + + if (a == '\r' && b == '\n') { + // This exception is to handle a special case where the + // delimiter of one part is not followed by CRLF, but + // immediately followed by a CRLF prefixed delimiter. + if (!mimeSource->getChar(&a) || !mimeSource->getChar(&b)) + *eof = true; + else if (a == '-' && b == '-') { + mimeSource->ungetChar(); + mimeSource->ungetChar(); + mimeSource->ungetChar(); + mimeSource->ungetChar(); + } else { + mimeSource->ungetChar(); + mimeSource->ungetChar(); + } + + *boundarysize += 2; + } else { + mimeSource->ungetChar(); + mimeSource->ungetChar(); + } + } + + // read all mime parts. + if (!*foundendofpart && !*eof) { + bool quit = false; + do { + MimePart m; + + // If parseFull returns != 0, then it encountered the multipart's + // final boundary. + int bsize = 0; + if (m.parseFull(boundary, bsize)) { + quit = true; + *boundarysize = bsize; + } + + members->push_back(m); + + } while (!quit); + } + + if (!*foundendofpart && !*eof) { + // multipart parsing starts with skipping to the first + // boundary. then we call parse() for all parts. the last parse() + // command will return a code indicating that it found the last + // boundary of this multipart. Note that the first boundary does + // not have to start with CRLF. + string delimiter = "\r\n--" + toboundary; + + skipUntilBoundary(delimiter, nlines, eof); + + if (!*eof) *boundarysize = delimiter.size(); + + // Read two more characters. This may be CRLF, it may be "--" and + // it may be any other two characters. + + char a = '\0'; + if (!mimeSource->getChar(&a)) *eof = true; + if (a == '\n') ++*nlines; + + char b = '\0'; + if (!mimeSource->getChar(&b)) *eof = true; + if (b == '\n') ++*nlines; + + // If we find two dashes after the boundary, then this is the end + // of boundary marker. + if (!*eof) { + if (a == '-' && b == '-') { + *foundendofpart = true; + *boundarysize += 2; + if (!mimeSource->getChar(&a)) + *eof = true; + if (a == '\n') + ++*nlines; + if (!mimeSource->getChar(&b)) + *eof = true; + if (b == '\n') + ++*nlines; + } + + if (a == '\r' && b == '\n') { + // This exception is to handle a special case where the + // delimiter of one part is not followed by CRLF, but + // immediately followed by a CRLF prefixed delimiter. + if (!mimeSource->getChar(&a) || !mimeSource->getChar(&b)) + *eof = true; + else if (a == '-' && b == '-') { + mimeSource->ungetChar(); + mimeSource->ungetChar(); + mimeSource->ungetChar(); + mimeSource->ungetChar(); + } else { + mimeSource->ungetChar(); + mimeSource->ungetChar(); + } + + *boundarysize += 2; + } else { + mimeSource->ungetChar(); + mimeSource->ungetChar(); + } + } + } + + // make sure bodylength doesn't overflow + *bodylength = mimeSource->getOffset(); + if (*bodylength >= bodystartoffsetcrlf) { + *bodylength -= bodystartoffsetcrlf; + if (*bodylength >= (unsigned int) *boundarysize) { + *bodylength -= (unsigned int) *boundarysize; + } else { + *bodylength = 0; + } + } else { + *bodylength = 0; + } +} + +static void parseSinglePart(const string &toboundary, + int *boundarysize, + unsigned int *nbodylines, + unsigned int *nlines, + bool *eof, bool *foundendofpart, + unsigned int *bodylength) +{ + using namespace ::Binc; + unsigned int bodystartoffsetcrlf = mimeSource->getOffset(); + + // If toboundary is empty, then we read until the end of the + // file. Otherwise we will read until we encounter toboundary. + string _toboundary; + if (toboundary != "") { + _toboundary = "\r\n--"; + _toboundary += toboundary; + } + + // if (skipUntilBoundary(_toboundary, nlines, eof)) + // *boundarysize = _toboundary.length(); + + char *boundaryqueue = 0; + int endpos = _toboundary.length(); + if (toboundary != "") { + boundaryqueue = new char[endpos]; + memset(boundaryqueue, 0, endpos); + } + int boundarypos = 0; + + *boundarysize = 0; + + const char *_toboundaryStr = _toboundary.c_str(); + string line; + bool toboundaryIsEmpty = (toboundary == ""); + char c; + while (mimeSource->getChar(&c)) { + if (c == '\n') { ++*nbodylines; ++*nlines; } + if (toboundaryIsEmpty) continue; + + // find boundary + boundaryqueue[boundarypos++ % endpos] = c; + + if (compareStringToQueue(_toboundaryStr, boundaryqueue, + boundarypos, endpos)) { + *boundarysize = _toboundary.length(); + break; + } + } + + delete[] boundaryqueue; + + if (toboundary != "") { + + char a; + if (!mimeSource->getChar(&a)) *eof = true; + if (a == '\n') ++*nlines; + + char b; + if (!mimeSource->getChar(&b)) *eof = true; + if (b == '\n') ++*nlines; + + if (a == '-' && b == '-') { + *boundarysize += 2; + *foundendofpart = true; + if (!mimeSource->getChar(&a)) *eof = true; + if (a == '\n') ++*nlines; + if (!mimeSource->getChar(&b)) *eof = true; + if (b == '\n') ++*nlines; + } + + if (a == '\r' && b == '\n') { + // This exception is to handle a special case where the + // delimiter of one part is not followed by CRLF, but + // immediately followed by a CRLF prefixed delimiter. + if (!mimeSource->getChar(&a) || !mimeSource->getChar(&b)) + *eof = true; + else if (a == '-' && b == '-') { + mimeSource->ungetChar(); + mimeSource->ungetChar(); + mimeSource->ungetChar(); + mimeSource->ungetChar(); + } else { + mimeSource->ungetChar(); + mimeSource->ungetChar(); + } + + *boundarysize += 2; + } else { + mimeSource->ungetChar(); + mimeSource->ungetChar(); + } + } + + // make sure bodylength doesn't overflow + *bodylength = mimeSource->getOffset(); + if (*bodylength >= bodystartoffsetcrlf) { + *bodylength -= bodystartoffsetcrlf; + if (*bodylength >= (unsigned int) *boundarysize) { + *bodylength -= (unsigned int) *boundarysize; + } else { + *bodylength = 0; + } + } else { + *bodylength = 0; + } + +} + +//------------------------------------------------------------------------ +int Binc::MimePart::parseFull(const string &toboundary, + int &boundarysize) const +{ + headerstartoffsetcrlf = mimeSource->getOffset(); + + // Parse the header of this mime part. + parseHeader(&h, &nlines); + + // Headerlength includes the seperating CRLF. Body starts after the + // CRLF. + headerlength = mimeSource->getOffset() - headerstartoffsetcrlf; + bodystartoffsetcrlf = mimeSource->getOffset(); + bodylength = 0; + + // Determine the type of mime part by looking at fields in the + // header. + analyzeHeader(&h, &multipart, &messagerfc822, &subtype, &boundary); + + bool eof = false; + bool foundendofpart = false; + + if (messagerfc822) { + parseMessageRFC822(&members, &foundendofpart, &bodylength, + &nbodylines, toboundary); + + } else if (multipart) { + parseMultipart(boundary, toboundary, &eof, &nlines, &boundarysize, + &foundendofpart, &bodylength, &members); + } else { + parseSinglePart(toboundary, &boundarysize, &nbodylines, &nlines, + &eof, &foundendofpart, &bodylength); + } + + return (eof || foundendofpart) ? 1 : 0; +} diff --git a/src/mime-parseonlyheader.cc b/src/mime-parseonlyheader.cc new file mode 100644 index 0000000..d36efbf --- /dev/null +++ b/src/mime-parseonlyheader.cc @@ -0,0 +1,146 @@ +/** -------------------------------------------------------------------- + * @file mime-parseonlyheader.cc + * @brief Implementation of main mime parser components + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "mime.h" +#include "mime-utils.h" +#include "mime-inputsource.h" +#include "convert.h" +#include <string> +#include <vector> +#include <map> +#include <exception> +#include <iostream> + +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <errno.h> + +using namespace ::std; + +//------------------------------------------------------------------------ +void Binc::MimeDocument::parseOnlyHeader(int fd) const +{ + if (allIsParsed || headerIsParsed) + return; + + headerIsParsed = true; + + if (!mimeSource || mimeSource->getFileDescriptor() != fd) { + delete mimeSource; + mimeSource = new MimeInputSource(fd); + } else { + mimeSource->reset(); + } + + + headerstartoffsetcrlf = 0; + headerlength = 0; + bodystartoffsetcrlf = 0; + bodylength = 0; + messagerfc822 = false; + multipart = false; + + nlines = 0; + nbodylines = 0; + + MimePart::parseOnlyHeader(""); +} + +//------------------------------------------------------------------------ +int Binc::MimePart::parseOnlyHeader(const string &toboundary) const +{ + string name; + string content; + char cqueue[4]; + memset(cqueue, 0, sizeof(cqueue)); + + headerstartoffsetcrlf = mimeSource->getOffset(); + + bool quit = false; + char c = '\0'; + + while (!quit) { + // read name + while (1) { + if (!mimeSource->getChar(&c)) { + quit = true; + break; + } + + if (c == '\n') ++nlines; + if (c == ':') break; + if (c == '\n') { + for (int i = name.length() - 1; i >= 0; --i) + mimeSource->ungetChar(); + + quit = true; + name = ""; + break; + } + + name += c; + + if (name.length() == 2 && name.substr(0, 2) == "\r\n") { + name = ""; + quit = true; + break; + } + } + + if (name.length() == 1 && name[0] == '\r') { + name = ""; + break; + } + + if (quit) break; + + while (!quit) { + if (!mimeSource->getChar(&c)) { + quit = true; + break; + } + + if (c == '\n') ++nlines; + + for (int i = 0; i < 3; ++i) + cqueue[i] = cqueue[i + 1]; + + cqueue[3] = c; + if (strncmp(cqueue, "\r\n\r\n", 4) == 0) { + quit = true; + break; + } + + if (cqueue[2] == '\n') { + // guess the mime rfc says what can not appear on the beginning + // of a line. + if (!isspace(cqueue[3])) { + if (content.length() > 2) + content.resize(content.length() - 2); + + trim(content); + h.add(name, content); + name = c; + content = ""; + break; + } + } + + content += c; + } + } + + if (name != "") { + if (content.length() > 2) + content.resize(content.length() - 2); + h.add(name, content); + } + + headerlength = mimeSource->getOffset() - headerstartoffsetcrlf; + + return 1; +} diff --git a/src/mime-printbody.cc b/src/mime-printbody.cc new file mode 100644 index 0000000..0c053d3 --- /dev/null +++ b/src/mime-printbody.cc @@ -0,0 +1,51 @@ +/** -------------------------------------------------------------------- + * @file mime-printbody.cc + * @brief Implementation of main mime parser components + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "mime.h" +#include "mime-utils.h" +#include "mime-inputsource.h" + +#include "convert.h" +#include "iodevice.h" +#include "iofactory.h" + +#include <string> +#include <vector> +#include <map> +#include <exception> +#include <iostream> + +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <errno.h> + +using namespace ::std; + +//------------------------------------------------------------------------ +void Binc::MimePart::printBody(int fd, IODevice &output, + unsigned int startoffset, + unsigned int length) const +{ + if (!mimeSource || mimeSource->getFileDescriptor() != fd) { + delete mimeSource; + mimeSource = new MimeInputSource(fd); + } + + mimeSource->reset(); + mimeSource->seek(bodystartoffsetcrlf + startoffset); + + if (startoffset + length > bodylength) + length = bodylength - startoffset; + + char c = '\0'; + for (unsigned int i = 0; i < length; ++i) { + if (!mimeSource->getChar(&c)) + break; + + output << (char)c; + } +} diff --git a/src/mime-printdoc.cc b/src/mime-printdoc.cc new file mode 100644 index 0000000..bef673b --- /dev/null +++ b/src/mime-printdoc.cc @@ -0,0 +1,47 @@ +/** -------------------------------------------------------------------- + * @file mime-printdoc.cc + * @brief Implementation of main mime parser components + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "mime.h" +#include "mime-utils.h" +#include "mime-inputsource.h" +#include "convert.h" +#include "iodevice.h" +#include "iofactory.h" + +#include <string> +#include <vector> +#include <map> +#include <exception> +#include <iostream> + +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <errno.h> + +using namespace ::std; + +//------------------------------------------------------------------------ +void Binc::MimePart::printDoc(int fd, IODevice &output, + unsigned int startoffset, + unsigned int length) const +{ + if (!mimeSource || mimeSource->getFileDescriptor() != fd) { + delete mimeSource; + mimeSource = new MimeInputSource(fd); + } + + mimeSource->reset(); + mimeSource->seek(headerstartoffsetcrlf); + + char c; + for (unsigned int i = 0; i < length; ++i) { + if (!mimeSource->getChar(&c)) + break; + + output << (char)c; + } +} diff --git a/src/mime-printheader.cc b/src/mime-printheader.cc new file mode 100644 index 0000000..84dca1e --- /dev/null +++ b/src/mime-printheader.cc @@ -0,0 +1,172 @@ +/** -------------------------------------------------------------------- + * @file mime-printheader.cc + * @brief Implementation of main mime parser components + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "mime.h" +#include "mime-utils.h" +#include "mime-inputsource.h" +#include "convert.h" +#include "iodevice.h" +#include "iofactory.h" + +#include <string> +#include <vector> +#include <map> +#include <exception> +#include <iostream> + +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <errno.h> + +using namespace ::std; + +//------------------------------------------------------------------------ +void Binc::MimePart::printHeader(int fd, IODevice &output, + vector<string> headers, bool includeheaders, + unsigned int startoffset, + unsigned int length, string &store) const +{ + if (!mimeSource || mimeSource->getFileDescriptor() != fd) { + delete mimeSource; + mimeSource = new MimeInputSource(fd); + } + + mimeSource->seek(headerstartoffsetcrlf); + + string name; + string content; + char cqueue[2]; + memset(cqueue, 0, sizeof(cqueue)); + + bool quit = false; + char c = '\0'; + + unsigned int wrotebytes = 0; + unsigned int processedbytes = 0; + bool hasHeaderSeparator = false; + + while (!quit) { + // read name + while (1) { + // allow EOF to end the header + if (!mimeSource->getChar(&c)) { + quit = true; + break; + } + + // assume this character is part of the header name. + name += c; + + // break on the first colon + if (c == ':') break; + + // break if a '\n' turned up. + if (c == '\n') { + // end of headers detected + if (name == "\r\n") { + hasHeaderSeparator = true; + quit = true; + break; + } + + // put all data back in the buffer to the beginning of this + // line. + for (int i = name.length(); i >= 0; --i) + mimeSource->ungetChar(); + + // abort printing of header. note that in this case, the + // headers will not end with a seperate \r\n. + quit = true; + name = ""; + break; + } + } + if (quit) break; + + // at this point, we have a name, that is - the start of a + // header. we'll read until the end of the header. + while (!quit) { + // allow EOF to end the header. + if (!mimeSource->getChar(&c)) { + quit = true; + break; + } + if (c == '\n') ++nlines; + + // make a fifo queue of the last 4 characters. + cqueue[0] = cqueue[1]; + cqueue[1] = c; + + // print header + if (cqueue[0] == '\n' && cqueue[1] != '\t' && cqueue[1] != ' ') { + // it wasn't a space, so put it back as it is most likely + // the start of a header name. in any case it terminates the + // content part of this header. + mimeSource->ungetChar(); + + string lowername = name; + lowercase(lowername); + trim(lowername, ": \t"); + bool foundMatch = false; + + for (vector<string>::const_iterator i = headers.begin(); + i != headers.end(); ++i) { + string nametmp = *i; + lowercase(nametmp); + if (nametmp == lowername) { + foundMatch = true; + break; + } + } + + if (foundMatch == includeheaders || headers.size() == 0) { + string out = name + content; + for (string::const_iterator i = out.begin(); i != out.end(); ++i) + if (processedbytes >= startoffset && wrotebytes < length) { + if (processedbytes >= startoffset) { + store += *i; + ++wrotebytes; + } + } else + ++processedbytes; + } + + // move on to the next header + content = ""; + name = ""; + break; + } + content += c; + } + } // end while loop + + if (name != "") { + string lowername = name; + lowercase(lowername); + trim(lowername, ": \t"); + bool foundMatch = false; + for (vector<string>::const_iterator i = headers.begin(); + i != headers.end(); ++i) { + string nametmp = *i; + lowercase(nametmp); + if (nametmp == lowername) { + foundMatch = true; + break; + } + } + + if (hasHeaderSeparator || foundMatch == includeheaders || headers.size() == 0) { + string out = name + content; + for (string::const_iterator i = out.begin(); i != out.end(); ++i) + if (processedbytes >= startoffset && wrotebytes < length) { + store += *i; + ++wrotebytes; + } else + ++processedbytes; + } + } +} diff --git a/src/mime.cc b/src/mime.cc new file mode 100644 index 0000000..972d5e5 --- /dev/null +++ b/src/mime.cc @@ -0,0 +1,134 @@ +/** -------------------------------------------------------------------- + * @file mime.cc + * @brief Implementation of main mime parser components + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "mime.h" +#include "convert.h" +#include <string> +#include <vector> +#include <map> +#include <exception> +#include <iostream> + +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <errno.h> + +using namespace ::std; + +//------------------------------------------------------------------------ +Binc::MimeDocument::MimeDocument(void) : MimePart() +{ + allIsParsed = false; + headerIsParsed = false; +} + +//------------------------------------------------------------------------ +Binc::MimeDocument::~MimeDocument(void) +{ +} + +//------------------------------------------------------------------------ +void Binc::MimeDocument::clear(void) const +{ + members.clear(); + h.clear(); + headerIsParsed = false; + allIsParsed = false; +} + +//------------------------------------------------------------------------ +void Binc::MimePart::clear(void) const +{ + members.clear(); + h.clear(); +} + +//------------------------------------------------------------------------ +Binc::MimePart::MimePart(void) +{ + size = 0; + messagerfc822 = false; + multipart = false; + + nlines = 0; + nbodylines = 0; +} + +//------------------------------------------------------------------------ +Binc::MimePart::~MimePart(void) +{ +} + +//------------------------------------------------------------------------ +Binc::HeaderItem::HeaderItem(void) +{ +} + +//------------------------------------------------------------------------ +Binc::HeaderItem::HeaderItem(const string &key, const string &value) +{ + this->key = key; + this->value = value; +} + +//------------------------------------------------------------------------ +Binc::Header::Header(void) +{ +} + +//------------------------------------------------------------------------ +Binc::Header::~Header(void) +{ +} + +//------------------------------------------------------------------------ +bool Binc::Header::getFirstHeader(const string &key, HeaderItem &dest) const +{ + string k = key; + lowercase(k); + + for (vector<HeaderItem>::const_iterator i = content.begin(); + i != content.end(); ++i) { + string tmp = (*i).getKey(); + lowercase(tmp); + + if (tmp == k) { + dest = *i; + return true; + } + } + return false; +} + +//------------------------------------------------------------------------ +bool Binc::Header::getAllHeaders(const string &key, vector<HeaderItem> &dest) const +{ + string k = key; + lowercase(k); + + for (vector<HeaderItem>::const_iterator i = content.begin(); + i != content.end(); ++i) { + string tmp = (*i).getKey(); + lowercase(tmp); + if (tmp == k) + dest.push_back(*i); + } + + return (dest.size() != 0); +} + +//------------------------------------------------------------------------ +void Binc::Header::clear(void) const +{ + content.clear(); +} + +//------------------------------------------------------------------------ +void Binc::Header::add(const string &key, const string &value) +{ + content.push_back(HeaderItem(key, value)); +} diff --git a/src/multilogdevice.cc b/src/multilogdevice.cc new file mode 100644 index 0000000..612bb33 --- /dev/null +++ b/src/multilogdevice.cc @@ -0,0 +1,86 @@ +/** -------------------------------------------------------------------- + * @file multilogdevice.cc + * @brief Implementation of the MultilogDevice class + * @author Andreas Aardal Hanssen + * @date 2003/2023 + * --------------------------------------------------------------- **/ +#include "multilogdevice.h" +#include <string> + +#include <sys/types.h> +#include <sys/select.h> +#include <sys/time.h> +#include <unistd.h> +#include <errno.h> + +using namespace ::std; +using namespace ::Binc; + +//------------------------------------------------------------------------ +MultilogDevice::MultilogDevice(int f) : IODevice(f) +{ +} + +//------------------------------------------------------------------------ +MultilogDevice::~MultilogDevice(void) +{ +} + +//------------------------------------------------------------------------ +string MultilogDevice::service(void) const +{ + return "log"; +} + +//------------------------------------------------------------------------ +bool MultilogDevice::waitForWrite(void) const +{ + fd_set writeMask; + FD_ZERO(&writeMask); + FD_SET(fileno(stderr), &writeMask); + + struct timeval tv; + tv.tv_sec = getTimeout(); + tv.tv_usec = 0; + + int result = select(fileno(stderr) + 1, 0, &writeMask, + 0, tv.tv_sec ? &tv : 0); + + return result > 0; +} + +//------------------------------------------------------------------------ +bool MultilogDevice::waitForRead(void) const +{ + return false; +} + +//------------------------------------------------------------------------ +IODevice::WriteResult MultilogDevice::write(void) +{ + for (;;) { + ssize_t wrote = ::write(fileno(stderr), outputBuffer.str().c_str(), + outputBuffer.getSize()); + + if (wrote == -1) { + if (errno == EINTR) + continue; + else + return WriteError; + } + + if ((unsigned int) wrote == outputBuffer.getSize()) { + outputBuffer.clear(); + return WriteDone; + } + + outputBuffer.popString(wrote); + return WriteWait; + } +} + +//------------------------------------------------------------------------ +bool MultilogDevice::fillInputBuffer(void) +{ + return false; +} diff --git a/src/operator-append.cc b/src/operator-append.cc new file mode 100644 index 0000000..d4650ee --- /dev/null +++ b/src/operator-append.cc @@ -0,0 +1,297 @@ +/** -------------------------------------------------------------------- + * @file operator-append.cc + * @brief Implementation of the APPEND command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <algorithm> +#include <string> + +#include <fcntl.h> + +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "mailbox.h" +#include "operators.h" +#include "recursivedescent.h" +#include "pendingupdates.h" +#include "session.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +AppendOperator::AppendOperator(void) +{ +} + +//---------------------------------------------------------------------- +AppendOperator::~AppendOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string AppendOperator::getName(void) const +{ + return "APPEND"; +} + +//---------------------------------------------------------------------- +int AppendOperator::getState(void) const +{ + return Session::AUTHENTICATED | Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult AppendOperator::process(Depot &depot, + Request &command) +{ + Session &session = Session::getInstance(); + + const string &srcmailbox = command.getMailbox(); + const string &canonmailbox = toCanonMailbox(srcmailbox); + Mailbox *mailbox = 0; + + if ((mailbox = depot.get(canonmailbox)) == 0) { + session.setResponseCode("TRYCREATE"); + session.setLastError("invalid destination mailbox " + + toImapString(srcmailbox)); + return NO; + } + + // mask all passed flags together + unsigned int newflags = (unsigned int) Message::F_NONE; + vector<string>::const_iterator f_i = command.flags.begin(); + while (f_i != command.flags.end()) { + if (*f_i == "\\Deleted") newflags |= Message::F_DELETED; + if (*f_i == "\\Answered") newflags |= Message::F_ANSWERED; + if (*f_i == "\\Seen") newflags |= Message::F_SEEN; + if (*f_i == "\\Draft") newflags |= Message::F_DRAFT; + if (*f_i == "\\Flagged") newflags |= Message::F_FLAGGED; + ++f_i; + } + + int mday, year, hour, minute, second; + char month[4]; + + struct tm mytm; + if (command.getDate() != "") { + sscanf(command.getDate().c_str(), "%2i-%3s-%4i %2i:%2i:%2i", + &mday, month, &year, &hour, &minute, &second); + + month[3] = '\0'; + string monthstr = month; + lowercase(monthstr); + mytm.tm_sec = second; + mytm.tm_min = minute; + mytm.tm_hour = hour; + mytm.tm_year = year - 1900; + mytm.tm_mday = mday; + if (monthstr == "jan") mytm.tm_mon = 0; + else if (monthstr == "feb") mytm.tm_mon = 1; + else if (monthstr == "mar") mytm.tm_mon = 2; + else if (monthstr == "apr") mytm.tm_mon = 3; + else if (monthstr == "may") mytm.tm_mon = 4; + else if (monthstr == "jun") mytm.tm_mon = 5; + else if (monthstr == "jul") mytm.tm_mon = 6; + else if (monthstr == "aug") mytm.tm_mon = 7; + else if (monthstr == "sep") mytm.tm_mon = 8; + else if (monthstr == "oct") mytm.tm_mon = 9; + else if (monthstr == "nov") mytm.tm_mon = 10; + else if (monthstr == "dec") mytm.tm_mon = 11; + mytm.tm_isdst = -1; + } + + // Read number of characters in literal. Literal is required here. + char c; + if (!bincClient.readChar(&c)) return ABORT; + + if (c != '{') { + session.setLastError("expected literal"); + return BAD; + } + + string nr; + bool literalPlus = false; + while (1) { + if (!bincClient.readChar(&c)) { + session.setLastError("unexcepted EOF"); + return BAD; + } + + if (c == '}') break; + + // Support LITERAL+ + if (c == '+' && !literalPlus) { + literalPlus = true; + continue; + } + + if (!isdigit(c)) { + session.setLastError("unexcepted non-digit character"); + return BAD; + } + + if (literalPlus) { + session.setLastError("expected '}'"); + return BAD; + } + + nr += (char) c; + } + + int nchars = atoi(nr.c_str()); + if (nchars < 0) { + session.setLastError("expected positive size of appended message"); + return BAD; + } + + if (!bincClient.readChar(&c)) return ABORT; + + if (c != '\r') { + session.setLastError("expected CR"); + return BAD; + } + + if (!bincClient.readChar(&c)) return ABORT; + + if (c != '\n') { + session.setLastError("expected LF"); + return BAD; + } + + time_t newtime = (command.getDate() != "") ? mktime(&mytm) : time(0); + if (newtime == -1) newtime = time(0); + Message *dest = mailbox->createMessage(depot.mailboxToFilename(canonmailbox), + newtime); + if (!dest) { + session.setLastError(mailbox->getLastError()); + return NO; + } + + if (!literalPlus) { + bincClient << "+ go ahead with " << nchars << " characters" << endl; + bincClient.flush(); + } + + bincClient.clearFlags(IODevice::HasInputLimit); + + while (nchars > 0) { + // Read in chunks of 8192, followed by an optional chunk at the + // end which is < 8192 bytes. + string s; + int bytesToRead = nchars > 8192 ? 8192 : nchars; + if (!bincClient.readStr(&s, bytesToRead)) { + mailbox->rollBackNewMessages(); + session.setLastError(bincClient.getLastErrorString()); + return NO; + } + + // Write the chunk to the message. + if (!dest->appendChunk(s)) { + mailbox->rollBackNewMessages(); + session.setLastError(dest->getLastError()); + return NO; + } + + // Update the message count. + nchars -= s.size(); + } + + // Read the trailing CRLF after the message data. + if (!bincClient.readChar(&c)) return ABORT; + + if (c != '\r') { + mailbox->rollBackNewMessages(); + session.setLastError("expected CR"); + return BAD; + } + + if (!bincClient.readChar(&c)) return ABORT; + + if (c != '\n') { + mailbox->rollBackNewMessages(); + session.setLastError("expected LF"); + return BAD; + } + + // Commit the message. + dest->close(); + dest->setStdFlag(newflags); + dest->setInternalDate(mktime(&mytm)); + + if (!mailbox->commitNewMessages(depot.mailboxToFilename(canonmailbox))) { + session.setLastError("failed to commit after successful APPEND: " + + mailbox->getLastError()); + return NO; + } + + if (mailbox == depot.getSelected()) { + pendingUpdates(mailbox, PendingUpdates::EXISTS + | PendingUpdates::RECENT + | PendingUpdates::FLAGS, true, false, true); + } + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult AppendOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + Operator::ParseResult res; + + if (c_in.getUidMode()) + return REJECT; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after APPEND"); + return res; + } + + string mailbox; + if ((res = expectMailbox(mailbox)) != ACCEPT) { + session.setLastError("Expected mailbox after APPEND SPACE"); + return res; + } + + c_in.setMailbox(mailbox); + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after APPEND SPACE mailbox"); + return res; + } + + if ((res = expectThisString("(")) == ACCEPT) { + if ((res = expectFlag(c_in.getFlags())) == ACCEPT) + while (1) { + if ((res = expectSPACE()) != ACCEPT) break; + if ((res = expectFlag(c_in.getFlags())) != ACCEPT) { + session.setLastError("expected a flag after the '('"); + return res; + } + } + + if ((res = expectThisString(")")) != ACCEPT) { + session.setLastError("expected a ')'"); + return res; + } + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("expected a SPACE after the flag list"); + return res; + } + } + + string date; + if ((res = expectDateTime(date)) == ACCEPT) + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("expected a SPACE after date_time"); + return res; + } + + c_in.setDate(date); + c_in.setName("APPEND"); + return ACCEPT; +} diff --git a/src/operator-authenticate.cc b/src/operator-authenticate.cc new file mode 100644 index 0000000..03f994c --- /dev/null +++ b/src/operator-authenticate.cc @@ -0,0 +1,315 @@ +/** -------------------------------------------------------------------- + * @file operator-authenticate.cc + * @brief Implementation of the AUTHENTICATE command, incl. CRAM-MD5. + * @author Andreas Aardal Hanssen, Erwin Hoffmann + * @date 2002-2005, 2023 + * ----------------------------------------------------------------- **/ +#include <string> + +#include "authenticate.h" +#include "base64.h" +#include "convert.h" +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "globals.h" +#include "operators.h" +#include "recursivedescent.h" +#include "session.h" +#include <cstring> + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +AuthenticateOperator::AuthenticateOperator(void) +{ +} + +//---------------------------------------------------------------------- +AuthenticateOperator::~AuthenticateOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string AuthenticateOperator::getName(void) const +{ + return "AUTHENTICATE"; +} + +//---------------------------------------------------------------------- +int AuthenticateOperator::getState(void) const +{ + return Session::NONAUTHENTICATED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult AuthenticateOperator::Login(string& username, string& password) +{ + Session &session = Session::getInstance(); + + bincClient << "+ " << base64encode("User Name") << endl; + bincClient.flush(); + + // Read user name + string b64usr; + for (;;) { + char c; + if (!bincClient.readChar(&c)) { + session.setLastError("unexpected EOF"); + return BAD; + } + if (c == '\n') break; + b64usr += c; + } + + if (b64usr != "" && b64usr[0] == '*') { + session.setLastError("Authentication cancelled by user"); + return NO; + } + + bincClient << "+ " << base64encode("Password") << endl; + bincClient.flush(); + + // Read password + string b64pwd; + for (;;) { + char c; + if (!bincClient.readChar(&c)) { + session.setLastError("unexpected EOF"); + return BAD; + } + if (c == '\n') break; + b64pwd += c; + } + + if (b64pwd != "" && b64pwd[0] == '*') { + session.setLastError("Authentication cancelled by user"); + return NO; + } + + username = base64decode(b64usr); + password = base64decode(b64pwd); + + return OK; +} +//------------------------------------------------------------------------ +Operator::ProcessResult AuthenticateOperator::Plain(string& username, string& password) +{ + Session &session = Session::getInstance(); + + bincClient << "+ " << endl; + bincClient.flush(); + + string b64; + for (;;) { + char c; + if (!bincClient.readChar(&c)) { + session.setLastError("unexpected EOF"); + return BAD; + } + if (c == '\n') break; + + b64 += c; + } + + if (b64.size() >= 1 && b64[0] == '*') { + session.setLastError("Authentication cancelled by user"); + return NO; + } + + string plain = base64decode(b64); + string::size_type pos = 0; + + if ((pos = plain.find('\0')) == string::npos) { + session.setLastError("Authentication failed. In PLAIN mode, " + "there must be at least two null characters " + "in the input string, but none were found"); + return NO; + } + + plain = plain.substr(pos + 1); + if ((pos = plain.find('\0')) == string::npos) { + session.setLastError("Authentication failed. In PLAIN mode, " + "there must be at least two null characters " + "in the input string, but only one was found"); + return NO; + } + + username = plain.substr(0, pos); + password = plain.substr(pos + 1); + + return OK; +} +//------------------------------------------------------------------------ +Operator::ProcessResult AuthenticateOperator::Cram(string& username, string& password, + string& challenge) +{ + Session &session = Session::getInstance(); + + // generate challenge first: <pid.time@fqdn> and deploy it to authenticator + time_t timer; + struct tm y2k = {0}; + int timestamp; + y2k.tm_hour = 0; y2k.tm_min = 0; y2k.tm_sec = 0; + y2k.tm_year = 100; y2k.tm_mon = 0; y2k.tm_mday = 1; + + time(&timer); /* get current time; same as: timer = time(NULL) */ + timestamp = difftime(timer,mktime(&y2k)); + + challenge += "<"; + challenge += to_string(session.getPid()); + challenge += "."; + challenge += to_string(timestamp); + challenge += "@"; + challenge += session.getEnv("TCPLOCALHOST"); + challenge += ">"; + + bincClient << "+ " << base64encode(challenge) << endl; + bincClient.flush(); + + // Read response + string b64; + for (;;) { + char c; + if (!bincClient.readChar(&c)) return BAD; + if (c == '\n') break; + b64 += c; + } + + // Disentangle response + string response = base64decode(b64); + string::size_type pos = 0; + + if ((pos = response.find(' ')) == string::npos) { + session.setLastError("Authentication failed. In CRAM-MD5 mode, " + "there must be a white space in the " + "input string between username and digest"); + return NO; + } + + username = response.substr(0, pos); + password = response.substr(pos + 1); + + return OK; +} +//------------------------------------------------------------------------ +Operator::ProcessResult AuthenticateOperator::process(Depot &depot, + Request &command) +{ + Session &session = Session::getInstance(); + + string authtype = command.getAuthType(); + uppercase(authtype); + + string username; + string password; + string challenge; + ProcessResult r = NOTHING; + + if (authtype == "LOGIN") { + // we only allow this type of authentication over an unencryted connection + // if it is explicitely commanded + if (!session.command.ssl + && !session.hasEnv("ALLOW_NONSSL_PLAINTEXT_LOGINS")) { + session.setLastError("Plain text password authentication is disallowd. " + "Please enable StartTLS or TLS in your mail client."); + return NO; + } + if ((r = Login(username, password)) != OK) return r; + + } else if (authtype == "PLAIN") { + // we only allow this type of authentication over an TLS encrypted connection. + if (!session.command.ssl + && !session.hasEnv("ALLOW_NONSSL_PLAINTEXT_LOGINS")) { + session.setLastError("Plain text password authentication is disallowd. " + "Please enable StartTLS or TLS in your mail client."); + return NO; + } + if ((r = Plain(username, password)) != OK) return r; + + } else if (authtype == "CRAM-MD5" ) { + // this type can be used even over unencrypted connections + if ((r = Cram(username, password, challenge)) != OK) return r; + + + } else { // Any other disallowed + session.setLastError("The authentication method " + + toImapString(authtype) + " is not supported. " + "Please try again with a different method. " + "There is built in support for \"PLAIN\" " + "and \"LOGIN\"."); + return NO; + } + + putenv(strdup(("BINCIMAP_LOGIN=AUTHENTICATE+" + command.getTag()).c_str())); + + // put the username in the environment for logging purpose + + session.setEnv("USER", username.c_str()); + + // the authenticate function calls a stub which does the actual + // authentication. the function returns 0 (success), 1 (internal + // error) or 2 (failed) + + switch (authenticate(depot, username, password, challenge)) { + case 1: + session.setLastError("An internal error occurred when you attempted " + "to log in to the IMAP server. Please contact " + "your system administrator."); + return NO; + case 2: + session.setLastError("Login failed. Either your user name " + "or your password was wrong. Please try again, " + "and if the problem persists, please contact " + "your system administrator."); + return NO; + case 3: + bincClient << "* BYE Timeout after " << IDLE_TIMEOUT + << " seconds of inactivity." << endl; + break; + case -1: + bincClient << "* BYE The server died unexpectedly. Please contact " + "your system administrator for more information." << endl; + break; + default: +// bincLog << "<" << username.c_str() << "> authenticated" << endl; + break; + } + + // auth was ok. go to logout state + session.setState(Session::LOGOUT); + return NOTHING; +} + + +//---------------------------------------------------------------------- +Operator::ParseResult AuthenticateOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) return REJECT; + + Operator::ParseResult res; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected single SPACE after AUTHENTICATE"); + return res; + } + + string authtype; + if ((res = expectAtom(authtype)) != ACCEPT) { + session.setLastError("Expected auth_type after AUTHENTICATE SPACE"); + return ERROR; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF after AUTHENTICATE SPACE auth_type"); + return res; + } + + c_in.setAuthType(authtype); + + c_in.setName("AUTHENTICATE"); + return ACCEPT; +} diff --git a/src/operator-capability.cc b/src/operator-capability.cc new file mode 100644 index 0000000..bdead58 --- /dev/null +++ b/src/operator-capability.cc @@ -0,0 +1,100 @@ +/** -------------------------------------------------------------------- + * @file bincimapd-capability.cc + * @brief Implementation of the CAPABILITY command + * @author Andreas Aardal Hanssen, Erwin Hoffmann + * @date 2002-2005, 2023 + * ----------------------------------------------------------------- **/ +#include <string> + +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "operators.h" +#include "recursivedescent.h" +#include "session.h" +#include "globals.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +CapabilityOperator::CapabilityOperator(void) +{ +} + +//---------------------------------------------------------------------- +CapabilityOperator::~CapabilityOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string CapabilityOperator::getName(void) const +{ + return "CAPABILITY"; +} + +//---------------------------------------------------------------------- +int CapabilityOperator::getState(void) const +{ + return Session::NONAUTHENTICATED + | Session::AUTHENTICATED + | Session::SELECTED; +} + +//---------------------------------------------------------------------- +void CapabilityOperator::addCapability(const string &cap) +{ + capabilities.push_back(cap); +} + +//---------------------------------------------------------------------- +Operator::ProcessResult CapabilityOperator::process(Depot &depot, + Request &command) +{ + Session &session = Session::getInstance(); + + bincClient << "* CAPABILITY " << IMAP_VERSION ; + + if (session.getState() == Session::NONAUTHENTICATED) { + if (getenv("UCSPITLS")) + if (!session.command.ssl) bincClient << " STARTTLS"; + + const string authmethods = session.getEnv("BINCIMAP_LOGIN"); + auto cram = authmethods.find("+CRAM-MD5"); + + if (session.command.ssl || session.hasEnv("ALLOW_NONSSL_PLAINTEXT_LOGINS")) { + if (cram != string::npos) bincClient << " AUTH=LOGIN AUTH=PLAIN AUTH=CRAM-MD5"; + else bincClient << " AUTH=LOGIN AUTH=PLAIN"; + } else + bincClient << " LOGINDISABLED"; + } + + bincClient << " IDLE LITERAL+ NAMESPACE CHILDREN"; + + vector<string>::const_iterator i = capabilities.begin(); + while (i != capabilities.end()) { + bincClient << " " << *i; + ++i; + } + bincClient << endl; + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult CapabilityOperator::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 CAPABILITY"); + return res; + } + + c_in.setName("CAPABILITY"); + + return ACCEPT; +} diff --git a/src/operator-check.cc b/src/operator-check.cc new file mode 100644 index 0000000..ee58d8f --- /dev/null +++ b/src/operator-check.cc @@ -0,0 +1,71 @@ +/** -------------------------------------------------------------------- + * @file operator-check.cc + * @author Implementation of the CHECK command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> + +#include "depot.h" +#include "mailbox.h" +#include "operators.h" +#include "recursivedescent.h" +#include "pendingupdates.h" +#include "session.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +CheckOperator::CheckOperator(void) +{ +} + + +//---------------------------------------------------------------------- +CheckOperator::~CheckOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string CheckOperator::getName(void) const +{ + return "CHECK"; +} + +//---------------------------------------------------------------------- +int CheckOperator::getState(void) const +{ + return Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult CheckOperator::process(Depot &depot, + Request &command) +{ + Mailbox *mailbox = depot.getSelected(); + if (mailbox != 0) + pendingUpdates(mailbox, PendingUpdates::FLAGS + | PendingUpdates::EXISTS + | PendingUpdates::RECENT, true); + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult CheckOperator::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 CHECK"); + return res; + } + + c_in.setName("CHECK"); + return ACCEPT; +} + diff --git a/src/operator-close.cc b/src/operator-close.cc new file mode 100644 index 0000000..d67dcc7 --- /dev/null +++ b/src/operator-close.cc @@ -0,0 +1,71 @@ +/** -------------------------------------------------------------------- + * @file operator-close.cc + * @brief Implementation of the CLOSE command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> + +#include "depot.h" +#include "mailbox.h" +#include "operators.h" +#include "recursivedescent.h" +#include "session.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +CloseOperator::CloseOperator(void) +{ +} + +//---------------------------------------------------------------------- +CloseOperator::~CloseOperator(void) +{ +} + +//------------------------------------------------------------------------ +const string CloseOperator::getName(void) const +{ + return "CLOSE"; +} + +//------------------------------------------------------------------------ +int CloseOperator::getState(void) const +{ + return Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult CloseOperator::process(Depot &depot, + Request &command) +{ + Mailbox *mailbox = depot.getSelected(); + mailbox->expungeMailbox(); + mailbox->closeMailbox(); + depot.resetSelected(); + + Session &session = Session::getInstance(); + session.setState(Session::AUTHENTICATED); + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult CloseOperator::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 CLOSE"); + return res; + } + + c_in.setName("CLOSE"); + return ACCEPT; +} diff --git a/src/operator-copy.cc b/src/operator-copy.cc new file mode 100644 index 0000000..cbe8767 --- /dev/null +++ b/src/operator-copy.cc @@ -0,0 +1,169 @@ +/** -------------------------------------------------------------------- + * @file operator-copy.cc + * @brief Implementation of the COPY command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> + +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "maildir.h" +#include "operators.h" +#include "recursivedescent.h" +#include "session.h" +#include "convert.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +CopyOperator::CopyOperator(void) +{ +} + +//---------------------------------------------------------------------- +CopyOperator::~CopyOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string CopyOperator::getName(void) const +{ + return "COPY"; +} + +//---------------------------------------------------------------------- +int CopyOperator::getState(void) const +{ + return Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult CopyOperator::process(Depot &depot, + Request &command) +{ + Session &session = Session::getInstance(); + + // Get the current mailbox + Mailbox *srcMailbox = depot.getSelected(); + + // Get the destination mailbox + string dmailbox = command.getMailbox(); + Mailbox *destMailbox = depot.get(toCanonMailbox(dmailbox)); + if (destMailbox == 0) { + session.setResponseCode("TRYCREATE"); + session.setLastError("invalid mailbox " + toImapString(dmailbox)); + return NO; + } + + unsigned int mode = Mailbox::SKIP_EXPUNGED; + mode |= command.getUidMode() ? Mailbox::UID_MODE : Mailbox::SQNR_MODE; + + // Copy each message in the sequence set to the destination mailbox. + bool success = true; + Mailbox::iterator i = srcMailbox->begin(command.bset, mode); + for (; success && i != srcMailbox->end(); ++i) { + Message &source = *i; + + if (srcMailbox->fastCopy(source, *destMailbox, + depot.mailboxToFilename(toCanonMailbox(dmailbox)))) + continue; + + // Have the destination mailbox create a message for us. + Message *dest + = destMailbox->createMessage(depot.mailboxToFilename(toCanonMailbox(dmailbox)), + source.getInternalDate()); + if (!dest) { + session.setLastError(destMailbox->getLastError()); + success = false; + break; + } + + // Set the flags and internal date. + dest->setStdFlag(source.getStdFlags()); + dest->setInternalDate(source.getInternalDate()); + + // Copy chunks from the source message over to the destination + // message. + string chunk; + do { + int readSize = source.readChunk(chunk); + + if (readSize == 0) break; + else if (readSize == -1) { + bincWarning << "when reading from message " + << i.getSqnr() << "/" << source.getUID() + << " in \"" << srcMailbox->getName() << "\": " + << source.getLastError() << endl; + success = false; + } else if (!dest->appendChunk(chunk)) { + bincWarning << "when writing to \"" + << dmailbox << "\": " + << dest->getLastError() << endl; + success = false; + } + } while (success); + + dest->close(); + } + + if (!success && !destMailbox->rollBackNewMessages()) { + session.setLastError("Failed to rollback after unsuccessful copy: " + + destMailbox->getLastError()); + return NO; + } + + if (success) + if (!destMailbox->commitNewMessages(depot.mailboxToFilename(toCanonMailbox(dmailbox)))) { + session.setLastError("Failed to commit after successful copy: " + + destMailbox->getLastError()); + return NO; + } + + if (!success) + session.setLastError("The transaction was unrolled. Please " + "contant your system administrator for " + "more information."); + + return success ? OK : NO; +} + +//------------------------------------------------------------------------ +Operator::ParseResult CopyOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after COPY"); + return res; + } + + if ((res = expectSet(c_in.getSet())) != ACCEPT) { + session.setLastError("Expected sequence set after COPY SPACE"); + return res; + } + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after COPY SPACE set"); + return res; + } + + string mailbox; + if ((res = expectMailbox(mailbox)) != ACCEPT) { + session.setLastError("Expected mailbox after COPY SPACE set SPACE"); + return res; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF after COPY SPACE set SPACE mailbox"); + return res; + } + + c_in.setMailbox(mailbox); + c_in.setName("COPY"); + + return ACCEPT; +} diff --git a/src/operator-create.cc b/src/operator-create.cc new file mode 100644 index 0000000..409b73f --- /dev/null +++ b/src/operator-create.cc @@ -0,0 +1,84 @@ +/** -------------------------------------------------------------------- + * @file bincimapd-create.cc + * @brief Implementation of the CREATE command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> + +#include "depot.h" +#include "mailbox.h" +#include "operators.h" +#include "imapparser.h" +#include "recursivedescent.h" +#include "session.h" +#include "convert.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +CreateOperator::CreateOperator(void) +{ +} + +//---------------------------------------------------------------------- +CreateOperator::~CreateOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string CreateOperator::getName(void) const +{ + return "CREATE"; +} + +//---------------------------------------------------------------------- +int CreateOperator::getState(void) const +{ + return Session::AUTHENTICATED | Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult CreateOperator::process(Depot &depot, + Request &command) +{ + if (depot.createMailbox(command.getMailbox())) + return OK; + else { + Session &session = Session::getInstance(); + session.setLastError(depot.getLastError()); + return NO; + } +} + +//---------------------------------------------------------------------- +Operator::ParseResult CreateOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) return REJECT; + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after CREATE"); + return res; + } + + string mailbox; + if ((res = expectMailbox(mailbox)) != ACCEPT) { + session.setLastError("Expected mailbox after CREATE SPACE"); + return res; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF after CREATE SPACE CRLF"); + return res; + } + + session.mailboxchanges = true; + + c_in.setName("CREATE"); + c_in.setMailbox(mailbox); + return ACCEPT; +} diff --git a/src/operator-delete.cc b/src/operator-delete.cc new file mode 100644 index 0000000..0365927 --- /dev/null +++ b/src/operator-delete.cc @@ -0,0 +1,84 @@ +/** -------------------------------------------------------------------- + * @file operator-delete.cc + * @briefe Implementation of the DELETE command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> + +#include "depot.h" +#include "mailbox.h" +#include "operators.h" +#include "imapparser.h" +#include "recursivedescent.h" +#include "session.h" +#include "convert.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +DeleteOperator::DeleteOperator(void) +{ +} + +//---------------------------------------------------------------------- +DeleteOperator::~DeleteOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string DeleteOperator::getName(void) const +{ + return "DELETE"; +} + +//---------------------------------------------------------------------- +int DeleteOperator::getState(void) const +{ + return Session::AUTHENTICATED | Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult DeleteOperator::process(Depot &depot, + Request &command) +{ + if (depot.deleteMailbox(command.getMailbox())) + return OK; + else { + Session &session = Session::getInstance(); + session.setLastError(depot.getLastError()); + return NO; + } +} + +//---------------------------------------------------------------------- +Operator::ParseResult DeleteOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) return REJECT; + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after DELETE"); + return res; + } + + string mailbox; + if ((res = expectMailbox(mailbox)) != ACCEPT) { + session.setLastError("Expected mailbox after DELETE SPACE"); + return res; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF after DELETE SPACE mailbox"); + return res; + } + + session.mailboxchanges = true; + + c_in.setName("DELETE"); + c_in.setMailbox(mailbox); + return ACCEPT; +} diff --git a/src/operator-examine.cc b/src/operator-examine.cc new file mode 100644 index 0000000..314641c --- /dev/null +++ b/src/operator-examine.cc @@ -0,0 +1,26 @@ +/** -------------------------------------------------------------------- + * @file operator-examine.cc + * @brief Implementation of the EXAMINE command + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "operators.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +const std::string ExamineOperator::getName(void) const +{ + return "EXAMINE"; +} + +//---------------------------------------------------------------------- +ExamineOperator::ExamineOperator(void) +{ +} + +//---------------------------------------------------------------------- +ExamineOperator::~ExamineOperator(void) +{ +} diff --git a/src/operator-expunge.cc b/src/operator-expunge.cc new file mode 100644 index 0000000..24904d5 --- /dev/null +++ b/src/operator-expunge.cc @@ -0,0 +1,73 @@ +/** -------------------------------------------------------------------- + * @file operator-expunge.cc + * @brief Implementation of the EXPUNGE command + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> + +#include "depot.h" +#include "mailbox.h" +#include "operators.h" +#include "imapparser.h" +#include "recursivedescent.h" +#include "pendingupdates.h" +#include "session.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +ExpungeOperator::ExpungeOperator(void) +{ +} + +//---------------------------------------------------------------------- +ExpungeOperator::~ExpungeOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string ExpungeOperator::getName(void) const +{ + return "EXPUNGE"; +} + +//---------------------------------------------------------------------- +int ExpungeOperator::getState(void) const +{ + return Session::SELECTED; +} + +//---------------------------------------------------------------------- +Operator::ProcessResult ExpungeOperator::process(Depot &depot, + Request &command) +{ + Mailbox *mailbox = depot.getSelected(); + mailbox->expungeMailbox(); + + pendingUpdates(mailbox, PendingUpdates::EXPUNGE + | PendingUpdates::EXISTS + | PendingUpdates::RECENT + | PendingUpdates::FLAGS, true); + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult ExpungeOperator::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"); + return res; + } + + c_in.setName("EXPUNGE"); + return ACCEPT; +} diff --git a/src/operator-fetch.cc b/src/operator-fetch.cc new file mode 100644 index 0000000..810afb5 --- /dev/null +++ b/src/operator-fetch.cc @@ -0,0 +1,641 @@ +/** -------------------------------------------------------------------- + * @file operator-fetch.cc + * @brief Implementation of the FETCH command + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> + +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "mailbox.h" +#include "operators.h" +#include "imapparser.h" +#include "pendingupdates.h" +#include "recursivedescent.h" +#include "session.h" +#include "convert.h" + +using namespace ::std; +using namespace Binc; + +namespace { + void outputFlags(const Message & message) + { + bincClient << "FLAGS "; + + bincClient << "("; + int flags = message.getStdFlags(); + vector<string> flagv; + if (flags & Message::F_SEEN) flagv.push_back("\\Seen"); + if (flags & Message::F_ANSWERED) flagv.push_back("\\Answered"); + if (flags & Message::F_DELETED) flagv.push_back("\\Deleted"); + if (flags & Message::F_DRAFT) flagv.push_back("\\Draft"); + if (flags & Message::F_RECENT) flagv.push_back("\\Recent"); + if (flags & Message::F_FLAGGED) flagv.push_back("\\Flagged"); + + for (vector<string>::const_iterator k + = flagv.begin(); k != flagv.end(); ++k) { + if (k != flagv.begin()) bincClient << " "; + bincClient << *k; + } + + vector<string> customFlags = message.getCustomFlags(); + for (vector<string>::const_iterator it = customFlags.begin(); + it != customFlags.end(); ++it) { + if (flagv.size() > 0 || it != customFlags.begin()) bincClient << " "; + bincClient << *it; + } + + bincClient << ")"; + } + +} + +//---------------------------------------------------------------------- +FetchOperator::FetchOperator(void) +{ +} + +//---------------------------------------------------------------------- +FetchOperator::~FetchOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string FetchOperator::getName(void) const +{ + return "FETCH"; +} + +//---------------------------------------------------------------------- +int FetchOperator::getState(void) const +{ + return Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult FetchOperator::process(Depot &depot, + Request &request) +{ + Session &session = Session::getInstance(); + + bool updateFlags = false; + Request req = request; + + Mailbox *mailbox = depot.getSelected(); + + // If this is a UID FETCH, check if the UID attribute is fetched. If + // it is not, then add it to the list of fetch attributes. + vector<BincImapParserFetchAtt>::const_iterator f_i; + bool uidfetched = false; + if (request.getUidMode()) { + f_i = request.fatt.begin(); + while (f_i != request.fatt.end()) { + if ((*f_i).type == "UID") { + uidfetched = true; + break; + } + f_i++; + } + + if (!uidfetched) { + BincImapParserFetchAtt b; + b.type = "UID"; + req.fatt.push_back(b); + } + } + + // Convert macros ALL, FULL and FAST + f_i = request.fatt.begin(); + while (f_i != request.fatt.end()) { + const string &type = (*f_i).type; + if (type == "ALL") { + req.fatt.push_back(BincImapParserFetchAtt("FLAGS")); + req.fatt.push_back(BincImapParserFetchAtt("INTERNALDATE")); + req.fatt.push_back(BincImapParserFetchAtt("RFC822.SIZE")); + req.fatt.push_back(BincImapParserFetchAtt("ENVELOPE")); + } else if (type == "FULL") { + req.fatt.push_back(BincImapParserFetchAtt("FLAGS")); + req.fatt.push_back(BincImapParserFetchAtt("INTERNALDATE")); + req.fatt.push_back(BincImapParserFetchAtt("RFC822.SIZE")); + req.fatt.push_back(BincImapParserFetchAtt("ENVELOPE")); + req.fatt.push_back(BincImapParserFetchAtt("BODY")); + } else if (type == "FAST") { + req.fatt.push_back(BincImapParserFetchAtt("FLAGS")); + req.fatt.push_back(BincImapParserFetchAtt("INTERNALDATE")); + req.fatt.push_back(BincImapParserFetchAtt("RFC822.SIZE")); + } + + ++f_i; + } + + int mode; + if (req.getUidMode()) + mode = Mailbox::UID_MODE; + else + mode = Mailbox::SQNR_MODE; + + Mailbox::iterator i + = mailbox->begin(req.bset, Mailbox::SKIP_EXPUNGED | mode); + + for (; i != mailbox->end(); ++i) { + Message &message = *i; + + bincClient << "* " << i.getSqnr() << " FETCH ("; + bool hasprinted = false; + f_i = req.fatt.begin(); + while (f_i != req.fatt.end()) { + BincImapParserFetchAtt fatt = *f_i; + + string prefix = ""; + if (hasprinted) prefix = " "; + + if (fatt.type == "FLAGS") { + // FLAGS + hasprinted = true; + bincClient << prefix; + + outputFlags(message); + } else if (fatt.type == "UID") { + // UID + hasprinted = true; + bincClient << prefix << "UID " << message.getUID(); + } else if (fatt.type == "RFC822.SIZE") { + // RFC822.SIZE + hasprinted = true; + bincClient << prefix << "RFC822.SIZE " << message.getSize(true); + } else if (fatt.type == "ENVELOPE") { + // ENVELOPE + hasprinted = true; + bincClient << prefix << "ENVELOPE "; + message.printEnvelope(); + } else if (fatt.type == "BODYSTRUCTURE") { + // BODYSTRUCTURE + hasprinted = true; + bincClient << prefix << "BODYSTRUCTURE "; + message.printBodyStructure(true); + } else if (fatt.type == "BODY" && !fatt.hassection) { + // BODY with no section + hasprinted = true; + session.addBody(); + bincClient << prefix << "BODY "; + message.printBodyStructure(false); + } else if (fatt.type == "INTERNALDATE") { + // INTERNALDATE + hasprinted = true; + bincClient << prefix << "INTERNALDATE "; + + time_t iDate = message.getInternalDate(); + struct tm *_tm = gmtime(&iDate); + char internal[64]; + string iDateStr; + if (strftime(internal, sizeof(internal), + "%d-%b-%Y %H:%M:%S %z", _tm) != 0) { + if (internal[0] == '0') internal[0] = ' '; + iDateStr = internal; + } else + iDateStr = "NIL"; + + bincClient << toImapString(iDateStr); + } else if (fatt.type == "BODY" || fatt.type == "BODY.PEEK") { + // BODY & BODY.PEEK + hasprinted = true; + session.addBody(); + + bincClient << prefix; + bool peek = (fatt.type == "BODY.PEEK"); + bincClient << fatt.toString(); + + bool includeheaders = true; + bool fullheader = false; + bool bodyfetch = false; + + if (fatt.section != "" || fatt.sectiontext == "" + || fatt.sectiontext == "TEXT") { + bodyfetch = true; + fullheader = true; + } + + if (fatt.sectiontext == "HEADER.FIELDS.NOT") + includeheaders = false; + + if (fatt.sectiontext == "HEADER" + || fatt.sectiontext == "HEADER.FIELDS" + || fatt.sectiontext == "HEADER.FIELDS.NOT" + || fatt.sectiontext == "MIME") { + vector<string> v; + + if (fatt.sectiontext == "MIME") { + v.push_back("content-type"); + v.push_back("content-transfer-encoding"); + v.push_back("content-disposition"); + v.push_back("content-description"); + } else + v = fatt.headerlist; + + string dummy; + unsigned int size = fullheader + ? message.getHeaderSize(fatt.section, v, true, + fatt.offsetstart, + fatt.offsetlength, + fatt.sectiontext == "MIME") + : message.getHeaderSize(fatt.section, fatt.headerlist, + includeheaders, + fatt.offsetstart, + fatt.offsetlength, + fatt.sectiontext == "MIME"); + + bincClient << "{" << size << "}\r\n"; + + if (fullheader) { + message.printHeader(fatt.section, v, true, + fatt.offsetstart, + fatt.offsetlength, + fatt.sectiontext == "MIME"); + } else { + message.printHeader(fatt.section, fatt.headerlist, + includeheaders, + fatt.offsetstart, + fatt.offsetlength, + fatt.sectiontext == "MIME"); + } + } else { + unsigned int size; + if ((fatt.sectiontext == "" || fatt.sectiontext == "TEXT") + && fatt.section == "") + size = message.getDocSize(fatt.offsetstart, + fatt.offsetlength, + fatt.sectiontext == "TEXT"); + else + size = message.getBodySize(fatt.section, + fatt.offsetstart, + fatt.offsetlength); + + bincClient << "{" << size << "}\r\n"; + + if ((fatt.sectiontext == "" || fatt.sectiontext == "TEXT") + && fatt.section == "") + message.printDoc(fatt.offsetstart, + fatt.offsetlength, + fatt.sectiontext == "TEXT"); + else + message.printBody(fatt.section, fatt.offsetstart, + fatt.offsetlength); + } + + // set the \Seen flag if .PEEK is not used. + if (!peek) + if ((message.getStdFlags() & Message::F_SEEN) == 0) + message.setStdFlag(Message::F_SEEN); + } else if (fatt.type == "RFC822") { + bincClient << prefix; + hasprinted = true; + session.addBody(); + bincClient << fatt.toString(); + unsigned int size = message.getDocSize(fatt.offsetstart, + fatt.offsetlength); + bincClient << " {" << size << "}\r\n"; + message.printDoc(fatt.offsetstart, fatt.offsetlength); + + // set the \Seen flag + if ((message.getStdFlags() & Message::F_SEEN) == 0) + message.setStdFlag(Message::F_SEEN); + + } else if (fatt.type == "RFC822.HEADER") { + bincClient << prefix; + hasprinted = true; + bincClient << fatt.toString(); + vector<string> v; + string dummy; + unsigned int size = message.getHeaderSize("", v, true, + fatt.offsetstart, + fatt.offsetlength); + bincClient << " {" << size << "}\r\n"; + message.printHeader("", v, true, fatt.offsetstart, + fatt.offsetlength); + } else if (fatt.type == "RFC822.TEXT") { + // RFC822.TEXT + bincClient << prefix; + hasprinted = true; + session.addBody(); + + bincClient << fatt.toString(); + + bool bodyfetch = false; + bodyfetch = true; + + unsigned int size; + if (fatt.sectiontext == "" && fatt.section == "") + size = message.getDocSize(fatt.offsetstart, + fatt.offsetlength, true); + else + size = message.getBodySize(fatt.section, fatt.offsetstart, + fatt.offsetlength); + + bincClient << " {" << size << "}\r\n"; + + if (fatt.sectiontext == "" && fatt.section == "") + message.printDoc(fatt.offsetstart, + fatt.offsetlength, true); + else + message.printBody(fatt.section, fatt.offsetstart, + fatt.offsetlength); + + // set the \Seen flag + if ((message.getStdFlags() & Message::F_SEEN) == 0) + message.setStdFlag(Message::F_SEEN); + + } else { + // Unrecognized fetch_att, this is stopped by the parser + // so we never get here. + } + + f_i++; + } + + // FIXME: how are parse error passed back? + + bincClient << ")" << endl; + + if (message.hasFlagsChanged()) { + updateFlags = true; + bincClient << "* " << i.getSqnr() << " FETCH ("; + outputFlags(message); + bincClient << ")" << endl; + message.setFlagsUnchanged(); + } + } + + if (updateFlags) mailbox->updateFlags(); + + pendingUpdates(mailbox, + PendingUpdates::FLAGS + | PendingUpdates::EXISTS + | PendingUpdates::EXPUNGE + | PendingUpdates::RECENT, true); + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult FetchOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after FETCH"); + return res; + } + + if ((res = expectSet(c_in.getSet())) != ACCEPT) { + session.setLastError("Expected sequence set after FETCH SPACE"); + return res; + } + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after FETCH SPACE set"); + return res; + } + + BincImapParserFetchAtt f; + + if ((res = expectThisString("ALL")) == ACCEPT) { + f.type = "ALL"; + c_in.fatt.push_back(f); + } else if ((res = expectThisString("FULL")) == ACCEPT) { + f.type = "FULL"; + c_in.fatt.push_back(f); + } else if ((res = expectThisString("FAST")) == ACCEPT) { + f.type = "FAST"; + c_in.fatt.push_back(f); + } else if ((res = expectFetchAtt(f)) == ACCEPT) { + c_in.fatt.push_back(f); + } else if ((res = expectThisString("(")) == ACCEPT) { + while (1) { + BincImapParserFetchAtt ftmp; + if ((res = expectFetchAtt(ftmp)) != ACCEPT) { + session.setLastError("Expected fetch_att"); + return res; + } + + c_in.fatt.push_back(ftmp); + + if ((res = expectSPACE()) == REJECT) break; + else if (res == ERROR) return ERROR; + } + + if ((res = expectThisString(")")) != ACCEPT) { + session.setLastError("Expected )"); + return res; + } + } else { + session.setLastError("Expected ALL, FULL, FAST, fetch_att or ("); + return res; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF"); + return res; + } + + c_in.setName("FETCH"); + return ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult +FetchOperator::expectSectionText(BincImapParserFetchAtt &f_in) const +{ + Session &session = Session::getInstance(); + + Operator::ParseResult res; + if ((res = expectThisString("HEADER")) == ACCEPT) { + f_in.sectiontext = "HEADER"; + + if ((res = expectThisString(".FIELDS")) == ACCEPT) { + f_in.sectiontext += ".FIELDS"; + + if ((res = expectThisString(".NOT")) == ACCEPT) + f_in.sectiontext += ".NOT"; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("expected SPACE"); + return res; + } + + if ((res = expectHeaderList(f_in)) != ACCEPT) { + session.setLastError("Expected header_list"); + return res; + } + } + } else if ((res = expectThisString("TEXT")) == ACCEPT) + f_in.sectiontext = "TEXT"; + else + return REJECT; + + return ACCEPT; + +} + +//---------------------------------------------------------------------- +Operator::ParseResult +FetchOperator::expectSection(BincImapParserFetchAtt &f_in) const +{ + Session &session = Session::getInstance(); + + Operator::ParseResult res; + if ((res = expectThisString("[")) != ACCEPT) + return REJECT; + + if ((res = expectSectionText(f_in)) != ACCEPT) { + unsigned int n; + if ((res = expectNZNumber(n)) == ACCEPT) { + BincStream nstr; + nstr << n; + f_in.section = nstr.str(); + + bool gotadotalready = false; + while (1) { + if ((res = expectThisString(".")) != ACCEPT) break; + + if ((res = expectNZNumber(n)) != ACCEPT) { + gotadotalready = true; + break; + } + + f_in.section += "."; + BincStream nstr; + nstr << n; + f_in.section += nstr.str(); + } + + if (gotadotalready || (res = expectThisString(".")) == ACCEPT) { + if ((res = expectThisString("MIME")) == ACCEPT) { + f_in.sectiontext = "MIME"; + } else if ((res = expectSectionText(f_in)) != ACCEPT) { + session.setLastError("Expected MIME or section_text"); + return res; + } + } + } + } + + if ((res = expectThisString("]")) != ACCEPT) { + session.setLastError("Expected ]"); + return res; + } + + return ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult +FetchOperator::expectHeaderList(BincImapParserFetchAtt &f_in) const +{ + Session &session = Session::getInstance(); + + Operator::ParseResult res; + if ((res = expectThisString("(")) != ACCEPT) + return REJECT; + + string header_fld_name; + while (1) { + if ((res = expectAstring(header_fld_name)) != ACCEPT) { + session.setLastError("Expected header_fld_name"); + return res; + } + + f_in.headerlist.push_back(header_fld_name); + + if ((res = expectSPACE()) == ACCEPT) continue; + else break; + } + + if ((res = expectThisString(")")) != ACCEPT) { + session.setLastError("Expected )"); + return res; + } + + return ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult +FetchOperator::expectOffset(BincImapParserFetchAtt &f_in) const +{ + Session &session = Session::getInstance(); + Operator::ParseResult res; + + if ((res = expectThisString("<")) != ACCEPT) return REJECT; + + unsigned int i; + if ((res = expectNumber(i)) != ACCEPT) { + session.setLastError("Expected number"); + return res; + } + + if ((res = expectThisString(".")) != ACCEPT) { + session.setLastError("Expected ."); + return res; + } + + unsigned int j; + if ((res = expectNZNumber(j)) != ACCEPT) { + session.setLastError("expected nz_number"); + return res; + } + + if ((res = expectThisString(">")) != ACCEPT) { + session.setLastError("Expected >"); + return res; + } + + f_in.offsetstart = i; + f_in.offsetlength = j; + return ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult +FetchOperator::expectFetchAtt(BincImapParserFetchAtt &f_in) const +{ + Operator::ParseResult res; + + Session &session = Session::getInstance(); + + if ((res = expectThisString("ENVELOPE")) == ACCEPT) f_in.type = "ENVELOPE"; + else if ((res = expectThisString("FLAGS")) == ACCEPT) f_in.type = "FLAGS"; + else if ((res = expectThisString("INTERNALDATE")) == ACCEPT) + f_in.type = "INTERNALDATE"; + else if ((res = expectThisString("UID")) == ACCEPT) f_in.type = "UID"; + else if ((res = expectThisString("RFC822")) == ACCEPT) { + f_in.type = "RFC822"; + if ((res = expectThisString(".HEADER")) == ACCEPT) f_in.type += ".HEADER"; + else if ((res = expectThisString(".SIZE")) == ACCEPT) f_in.type += ".SIZE"; + else if ((res = expectThisString(".TEXT")) == ACCEPT) f_in.type += ".TEXT"; + else if ((res = expectThisString(".")) == ACCEPT) { + session.setLastError("Expected RFC822, RFC822.HEADER," + " RFC822.SIZE or RFC822.TEXT"); + return ERROR; + } + + } else if ((res = expectThisString("BODY")) == ACCEPT) { + f_in.type = "BODY"; + + if ((res = expectThisString("STRUCTURE")) == ACCEPT) f_in.type += "STRUCTURE"; + else if ((res = expectThisString(".PEEK")) == ACCEPT) f_in.type += ".PEEK"; + + if ((res = expectSection(f_in)) != ACCEPT) + f_in.hassection = false; + else { + f_in.hassection = true; + if ((res = expectOffset(f_in)) == ERROR) return ERROR; + } + } else + return REJECT; + + return ACCEPT; +} diff --git a/src/operator-id.cc b/src/operator-id.cc new file mode 100644 index 0000000..842c0a3 --- /dev/null +++ b/src/operator-id.cc @@ -0,0 +1,71 @@ +/** -------------------------------------------------------------------- + * @file operator-id.cc + * @brief Operator for the ID extension. Described in RFC2971 Oct 2000. + * @author Erwin Hoffmann + * @date 22.09.2023 + * ------------------------------------------------------------------ **/ +#include <string> +#include <iostream> + +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "operators.h" +#include "recursivedescent.h" +#include "session.h" +#include "globals.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +IdOperator::IdOperator(void) +{ +} + +//---------------------------------------------------------------------- +IdOperator::~IdOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string IdOperator::getName(void) const +{ + return "ID"; +} + +//---------------------------------------------------------------------- +int IdOperator::getState(void) const +{ + return Session::NONAUTHENTICATED + | Session::AUTHENTICATED + | Session::SELECTED; +} + +//---------------------------------------------------------------------- +Operator::ProcessResult IdOperator::process(Depot &depot, + Request &command) +{ + bincClient << "* ID (\"name\" \"Binc IMAP\"" + << " \"version\" \"" << BINC_VERSION "\")" << endl; + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult IdOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) return REJECT; + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT && (res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected SPACE or CRLF"); + return res; + } + + c_in.setName("ID"); + + return ACCEPT; +} diff --git a/src/operator-id.o b/src/operator-id.o Binary files differnew file mode 100644 index 0000000..ff93eae --- /dev/null +++ b/src/operator-id.o 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; +} diff --git a/src/operator-list.cc b/src/operator-list.cc new file mode 100644 index 0000000..baa8107 --- /dev/null +++ b/src/operator-list.cc @@ -0,0 +1,252 @@ +/** -------------------------------------------------------------------- + * @file operator-list.cc + * @brief Implementation of the LIST command. + * ----------------------------------------------------------------- **/ +#include <sys/types.h> +#include <dirent.h> +#include <sys/stat.h> + +#include <string> +#include <iostream> + +#include "convert.h" +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "mailbox.h" +#include "operators.h" +#include "recursivedescent.h" +#include "regmatch.h" +#include "session.h" + +using namespace ::std; +using namespace Binc; + +namespace { + const time_t LIST_CACHE_TIMEOUT = 10; +} + +//---------------------------------------------------------------------- +ListOperator::ListOperator(void) +{ + cacheTimeout = 0; +} + +//---------------------------------------------------------------------- +ListOperator::~ListOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string ListOperator::getName(void) const +{ + return "LIST"; +} + +//---------------------------------------------------------------------- +int ListOperator::getState(void) const +{ + return Session::AUTHENTICATED | Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult ListOperator::process(Depot &depot, + Request &command) +{ + Session &session = Session::getInstance(); + const char delim = depot.getDelimiter(); + + // special case: if the mailbox argument is empty, then give a + // hard coded reply. + string wildcard; + if ((wildcard = command.getListMailbox()) == "") { + bincClient << "* LIST (\\Noselect) \"" << delim << "\" \"\"" << endl; + return OK; + } + + // remove leading or trailing delimiter in wildcard + trim(wildcard, string(&delim, 1)); + + // convert wildcard to regular expression + string regex = toRegex(wildcard, depot.getDelimiter()); + string wildcardLower = regex; + lowercase(wildcardLower); + if (wildcardLower.substr(0, 6) == "^inbox") + regex = "^[iI][nN][bB][oO][xX]" + regex.substr(6); + + // remove leading or trailing delimiter in reference + string ref = command.getMailbox(); + trim(ref, string(&delim, 1)); + wildcardLower = ref; + lowercase(wildcardLower); + if (wildcardLower.substr(0, 6) == "^inbox") + ref = "^[iI][nN][bB][oO][xX]" + ref.substr(6); + if (wildcardLower.substr(0, 5) == "inbox" + && (wildcardLower.length() == 5 || wildcardLower[5] == delim)) + ref = "INBOX" + ref.substr(5); + + // a map from mailbox name to flags + map<string, unsigned int> mailboxes; + + if (cacheTimeout == 0 || cacheTimeout < time(0) - LIST_CACHE_TIMEOUT + || session.mailboxchanges) { + session.mailboxchanges = false; + + // read through all entries in depository. + for (Depot::iterator i = depot.begin("."); i != depot.end(); ++i) { + const string path = *i; + const string mpath = depot.filenameToMailbox(path); + Mailbox *m = 0; + + // skip entries that are not identified as mailboxes + if ((m = depot.get(mpath)) == 0) + continue; + + // convert file name to mailbox name. skip it if there is no + // corresponding mailbox name. + string tmp = toCanonMailbox(depot.filenameToMailbox(path)); + trim(tmp, string(&delim, 1)); + if (tmp == "") continue; + else { + // inherit flags that were already set for this mailbox. + int flags = DIR_SELECT; + if (m->isMarked(path)) flags |= DIR_MARKED; + if (mailboxes.find(tmp) != mailboxes.end()) flags |= mailboxes[tmp]; + mailboxes[tmp] = flags; + } + + // now add all superior mailboxes with no flags set if not + // added already. + string::size_type pos = tmp.rfind(delim); + while (pos != string::npos) { + tmp = tmp.substr(0, pos); + trim(tmp, string(&delim, 1)); + + if (mailboxes.find(tmp) == mailboxes.end()) + mailboxes[tmp] = 0; + + pos = tmp.rfind(delim); + } + } + + // find leaf nodes O(N^2) + map<string, unsigned int>::iterator i; + for (i = mailboxes.begin(); i != mailboxes.end(); ++i) { + string mailbox = i->first; + mailbox += delim; + + bool leaf = true; + map<string, unsigned int>::const_iterator j = mailboxes.begin(); + for (; j != mailboxes.end(); ++j) { + string::size_type pos = j->first.rfind(delim); + if (pos == string::npos) continue; + + string base = j->first.substr(0, pos + 1); + + if (mailbox == base) { + leaf = false; + break; + } + } + + if (leaf) { + unsigned int flags = i->second; + flags |= DIR_LEAF; + i->second = flags; + } + } + + cache = mailboxes; + cacheTimeout = time(0); + } else { + mailboxes = cache; + cacheTimeout = time(0); + } + + // finally, print all mailbox entries with flags. + map<string, unsigned int>::iterator i = mailboxes.begin(); + + for (; i != mailboxes.end(); ++i) { + if (ref == "" || (ref.length() <= i->first.length() + && ref == i->first.substr(0, ref.length()))) + if (regexMatch(i->first.substr(ref.length()), regex) == 0) { + bincClient << "* LIST ("; + string sep = ""; + + int flags = i->second; + bool noselect = false; + + if (!(flags & DIR_SELECT)) { + bincClient << sep << "\\Noselect"; + sep = " "; + noselect = true; + } + + if (!noselect) { + if (flags & DIR_MARKED) + bincClient << sep << "\\Marked"; + else + bincClient << sep << "\\Unmarked"; + sep = " "; + } + + if (flags & DIR_LEAF) + bincClient << sep << "\\HasNoChildren"; + else + bincClient << sep << "\\HasChildren"; + sep = " "; + + if (flags & DIR_NOINFERIORS) + bincClient << sep << "\\Noinferiors"; + + bincClient << ") \"" << depot.getDelimiter() << "\" " + << toImapString(i->first) << endl; + } + } + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult ListOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) return REJECT; + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after LIST"); + return res; + } + + string mailbox; + if ((res = expectMailbox(mailbox)) != ACCEPT) { + session.setLastError("Expected mailbox after LIST SPACE"); + return res; + } + + c_in.setMailbox(mailbox); + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after LIST SPACE mailbox"); + return res; + } + + string listmailbox; + if ((res = expectListMailbox(listmailbox)) != ACCEPT) { + session.setLastError("Expected list_mailbox after LIST SPACE" + " mailbox SPACE"); + return res; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF after LIST SPACE mailbox" + " SPACE list_mailbox"); + return res; + } + + c_in.setListMailbox(listmailbox); + c_in.setName("LIST"); + return ACCEPT; +} diff --git a/src/operator-login.cc b/src/operator-login.cc new file mode 100644 index 0000000..a0ecacd --- /dev/null +++ b/src/operator-login.cc @@ -0,0 +1,134 @@ +/** -------------------------------------------------------------------- + * @file operator-login.cc + * @brief Implementation of the rapid LOGIN command + * @author Andreas Aardal Hanssen, Erwin Hoffmann + * @date 2002-2005, 2023 + * ----------------------------------------------------------------- **/ +#include <unistd.h> +#include <sys/types.h> +#include <signal.h> +#include <errno.h> + +#include <string> +#include <iostream> + +#include "authenticate.h" +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "globals.h" +#include "operators.h" +#include "recursivedescent.h" +#include "session.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +LoginOperator::LoginOperator(void) +{ +} + +//---------------------------------------------------------------------- +LoginOperator::~LoginOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string LoginOperator::getName(void) const +{ + return "LOGIN"; +} + +//---------------------------------------------------------------------- +int LoginOperator::getState(void) const +{ + return Session::NONAUTHENTICATED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult LoginOperator::process(Depot &depot, + Request &command) +{ + Session &session = Session::getInstance(); + + if (!session.command.ssl + && !session.hasEnv("ALLOW_NONSSL_PLAINTEXT_LOGINS")) { + session.setLastError("Plain text password authentication is disallowd. " + "Please enable StartTLS or TLS in your mail client."); + return NO; + } + + session.setEnv("BINCIMAP_LOGIN", "LOGIN+" + command.getTag()); + string challenge; + + switch (authenticate(depot, command.getUserID(), command.getPassword(), challenge)) { + case 1: + session.setLastError("An internal error occurred when you attempted " + "to log in to the IMAP server. Please contact " + "your system administrator."); + return NO; + case 2: + session.setLastError("Login failed. Either your user name " + "or your password was wrong. Please try again, " + "and if the problem persists, please contact " + "your system administrator."); + return NO; + case 3: + bincClient << "* BYE Timeout after " << IDLE_TIMEOUT + << " seconds of inactivity." << endl; + break; + case -1: + bincClient << "* BYE The server died unexpectedly. Please contact " + "your system administrator for more information." << endl; + break; + } + + // go to logout + session.setState(Session::LOGOUT); + + return NOTHING; +} + +//---------------------------------------------------------------------- +Operator::ParseResult LoginOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) return REJECT; + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected single SPACE after LOGIN"); + return res; + } + + string userid; + if ((res = expectAstring(userid)) != ACCEPT) { + session.setLastError("Expected userid after LOGIN SPACE"); + return res; + } + + c_in.setUserID(userid); + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after LOGIN SPACE userid"); + return res; + } + + string password; + if ((res = expectAstring(password)) != ACCEPT) { + session.setLastError("Expected password after LOGIN " + "SPACE userid SPACE"); + return res; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF after password"); + return res; + } + + c_in.setPassword(password); + c_in.setName("LOGIN"); + + return ACCEPT; +} diff --git a/src/operator-logout.cc b/src/operator-logout.cc new file mode 100644 index 0000000..643d412 --- /dev/null +++ b/src/operator-logout.cc @@ -0,0 +1,86 @@ +/** -------------------------------------------------------------------- + * @file operator-logout.cc + * @brief Implementation of the LOGOUT command + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> +#include <iostream> + +#include "iodevice.h" +#include "iofactory.h" + +#include "mailbox.h" +#include "recursivedescent.h" +#include "session.h" +#include "convert.h" + +#include "depot.h" +#include "operators.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +LogoutOperator::LogoutOperator(void) +{ +} + +//---------------------------------------------------------------------- +LogoutOperator::~LogoutOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string LogoutOperator::getName(void) const +{ + return "LOGOUT"; +} + +//---------------------------------------------------------------------- +int LogoutOperator::getState(void) const +{ + return Session::NONAUTHENTICATED + | Session::AUTHENTICATED + | Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult LogoutOperator::process(Depot &depot, + Request &command) +{ + bincClient << "* BYE Binc IMAP shutting down" << endl; + bincClient << command.getTag() << " OK LOGOUT completed" << endl; + bincClient.flush(); + +#ifdef BINCIMAPD + Mailbox *mailbox = 0; + if ((mailbox = depot.getSelected()) != 0) { + mailbox->closeMailbox(); + delete mailbox; + } +#endif + + Session &session = Session::getInstance(); + session.setState(Session::LOGOUT); + + return NOTHING; +} + +//---------------------------------------------------------------------- +Operator::ParseResult LogoutOperator::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"); + return res; + } + + c_in.setName("LOGOUT"); + return ACCEPT; +} diff --git a/src/operator-lsub.cc b/src/operator-lsub.cc new file mode 100644 index 0000000..f776503 --- /dev/null +++ b/src/operator-lsub.cc @@ -0,0 +1,238 @@ +/** -------------------------------------------------------------------- + * @file operator-lsub.cc + * @brief Implementation of the LSUB command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <algorithm> +#include <string> +#include <vector> +#include <iostream> + +#include "convert.h" +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "mailbox.h" +#include "operators.h" +#include "recursivedescent.h" +#include "regmatch.h" +#include "session.h" + +namespace { + const int DIR_SELECT = 0x01; + const int DIR_MARKED = 0x02; + const int DIR_NOINFERIORS = 0x04; + const int DIR_LEAF = 0x08; +} + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +LsubOperator::LsubOperator(void) +{ +} + +//---------------------------------------------------------------------- +LsubOperator::~LsubOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string LsubOperator::getName(void) const +{ + return "LSUB"; +} + +//---------------------------------------------------------------------- +int LsubOperator::getState(void) const +{ + return Session::AUTHENTICATED | Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult LsubOperator::process(Depot &depot, + Request &command) +{ + const char delim = depot.getDelimiter(); + + // remove leading or trailing delimiter in wildcard + string wildcard = command.getListMailbox(); + trim(wildcard, string(&delim, 1)); + + // convert wildcard to regular expression + string regex = toRegex(wildcard, depot.getDelimiter()); + string wildcardLower = regex; + lowercase(wildcardLower); + if (wildcardLower.substr(0, 6) == "^inbox") + regex = "^[iI][nN][bB][oO][xX]" + regex.substr(6); + + // remove leading or trailing delimiter in reference + string ref = command.getMailbox(); + trim(ref, string(&delim, 1)); + wildcardLower = ref; + lowercase(wildcardLower); + if (wildcardLower.substr(0, 5) == "inbox" + && (wildcardLower.length() == 5 || wildcardLower[5] == delim)) + ref = "INBOX" + ref.substr(5); + + // a multimap from mailbox name to flags + multimap<string, int> mailboxes; + + // read through all entries in depository. + for (Depot::iterator i = depot.begin("."); i != depot.end(); ++i) { + const string path = *i; + const string mpath = depot.filenameToMailbox(path); + Mailbox *m = 0; + + // skip entries that are not identified as mailboxes + if ((m = depot.get(mpath)) == 0) + continue; + + // convert file name to mailbox name. skip it if there is no + // corresponding mailbox name. + string tmp = toCanonMailbox(depot.filenameToMailbox(path)); + trim(tmp, string(&delim, 1)); + if (tmp == "") + continue; + else { + int flags = DIR_SELECT; + multimap<string, int>::iterator mi = mailboxes.find(tmp); + if (mi != mailboxes.end()) { + flags |= mi->second; + mailboxes.erase(mi); + } + mailboxes.insert(make_pair(tmp, flags)); + } + + // now add all superior mailboxes with no flags set if not + // added already. + string::size_type pos = tmp.rfind(delim); + while (pos != string::npos) { + tmp = tmp.substr(0, pos); + trim(tmp, string(&delim, 1)); + + multimap<string, int>::iterator mi = mailboxes.find(tmp); + if (mi == mailboxes.end()) mailboxes.insert(make_pair(tmp, 0)); + + pos = tmp.rfind(delim); + } + } + + // find leaf nodes O(N^2) + multimap<string, int>::iterator i; + for (i = mailboxes.begin(); i != mailboxes.end(); ++i) { + string mailbox = i->first; + mailbox += delim; + + bool leaf = true; + multimap<string, int>::const_iterator j; + for (j = mailboxes.begin(); j != mailboxes.end(); ++j) { + string::size_type pos = j->first.rfind(delim); + if (pos == string::npos) continue; + string base = j->first.substr(0, pos + 1); + if (mailbox == base) { + leaf = false; + break; + } + } + if (leaf) { + unsigned int flags = i->second; + flags |= DIR_LEAF; + i->second = flags; + } + } + + depot.loadSubscribes(); + + vector<string> subscribed = depot.getSubscriptions(); + sort(subscribed.begin(), subscribed.end()); + + // finally, print all mailbox entries with flags. + for (vector<string>::const_iterator j = subscribed.begin(); + j != subscribed.end(); ++j) { + if (ref == "" || (ref.length() <= (*j).length() && ref == (*j).substr(0, ref.length()))) + if (regexMatch((*j).substr(ref.length()), regex) == 0) { + int flags = 0; + for (i = mailboxes.begin(); i != mailboxes.end(); ++i) { + if (i->first == *j) { + flags = i->second; + break; + } + } + bincClient << "* LSUB ("; + string sep = ""; + bool noselect = false; + if (!(flags & DIR_SELECT)) { + bincClient << sep << "\\Noselect"; + sep = " "; + noselect = true; + } + if (!noselect) { + if (flags & DIR_MARKED) + bincClient << sep << "\\Marked"; + else + bincClient << sep << "\\Unmarked"; + sep = " "; + } + if (flags & DIR_LEAF) + bincClient << sep << "\\HasNoChildren"; + else + bincClient << sep << "\\HasChildren"; + sep = " "; + if (flags & DIR_NOINFERIORS) + bincClient << sep << "\\Noinferiors"; + bincClient << ") \"" << depot.getDelimiter() << "\" " + << toImapString(*j) << endl; + } + } + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult LsubOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) + return REJECT; + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after LSUB"); + return ERROR; + } + + string mailbox; + if ((res = expectMailbox(mailbox)) != ACCEPT) { + session.setLastError("Expected mailbox after LSUB SPACE"); + return ERROR; + } + + c_in.setMailbox(mailbox); + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after LSUB SPACE mailbox"); + return ERROR; + } + + string listmailbox; + if ((res = expectListMailbox(listmailbox)) != ACCEPT) { + session.setLastError("Expected list_mailbox after LSUB SPACE" + " mailbox SPACE"); + return ERROR; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF after LSUB SPACE" + " mailbox SPACE list_mailbox"); + return ERROR; + } + + c_in.setListMailbox(listmailbox); + c_in.setName("LSUB"); + + return ACCEPT; +} diff --git a/src/operator-namespace.cc b/src/operator-namespace.cc new file mode 100644 index 0000000..8574f91 --- /dev/null +++ b/src/operator-namespace.cc @@ -0,0 +1,79 @@ +/** -------------------------------------------------------------------- + * @file operator-namespace.cc + * @brief Operator for the NAMESPACE command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> +#include <iostream> + +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "operators.h" +#include "recursivedescent.h" +#include "session.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +NamespaceOperator::NamespaceOperator(void) +{ +} + +//---------------------------------------------------------------------- +NamespaceOperator::~NamespaceOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string NamespaceOperator::getName(void) const +{ + return "NAMESPACE"; +} + +//---------------------------------------------------------------------- +int NamespaceOperator::getState(void) const +{ + return Session::AUTHENTICATED | Session::SELECTED; +} + +//---------------------------------------------------------------------- +Operator::ProcessResult NamespaceOperator::process(Depot &depot, + Request &command) +{ + bincClient << "* NAMESPACE "; + + bincClient << "(("; // personal namespace + bincClient << toImapString(depot.getPersonalNamespace()); + bincClient << " "; + char c = depot.getDelimiter(); + bincClient << toImapString(string(&c, 1)); + bincClient << "))"; + + bincClient << " NIL"; // others' namespaces + bincClient << " NIL"; // shared namespaces + bincClient << endl; + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult NamespaceOperator::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 NAMESPACE"); + return res; + } + + c_in.setName("NAMESPACE"); + + return ACCEPT; +} diff --git a/src/operator-noop-pending.cc b/src/operator-noop-pending.cc new file mode 100644 index 0000000..f98405e --- /dev/null +++ b/src/operator-noop-pending.cc @@ -0,0 +1,44 @@ +/** -------------------------------------------------------------------- + * @file operator-noop-pending.cc + * @brief Operator for the NOOP command, with pending extension + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> +#include <iostream> + +#include "mailbox.h" +#include "pendingupdates.h" + +#include "recursivedescent.h" +#include "session.h" +#include "depot.h" +#include "operators.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +NoopPendingOperator::NoopPendingOperator(void) : NoopOperator() +{ +} + +//---------------------------------------------------------------------- +NoopPendingOperator::~NoopPendingOperator(void) +{ +} + +//---------------------------------------------------------------------- +Operator::ProcessResult NoopPendingOperator::process(Depot &depot, + Request &command) +{ + Mailbox *mailbox = depot.getSelected(); + if (mailbox) { + pendingUpdates(mailbox, PendingUpdates::EXPUNGE + | PendingUpdates::EXISTS + | PendingUpdates::RECENT + | PendingUpdates::FLAGS, true); + } + + return OK; +} diff --git a/src/operator-noop.cc b/src/operator-noop.cc new file mode 100644 index 0000000..d267513 --- /dev/null +++ b/src/operator-noop.cc @@ -0,0 +1,65 @@ +/** -------------------------------------------------------------------- + * @file operator-noop.cc + * @brief Operator for the NOOP command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ------------------------------------------------------------------ **/ +#include <string> +#include <iostream> + +#include "recursivedescent.h" +#include "session.h" +#include "depot.h" +#include "operators.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +NoopOperator::NoopOperator(void) +{ +} + +//---------------------------------------------------------------------- +NoopOperator::~NoopOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string NoopOperator::getName(void) const +{ + return "NOOP"; +} + +//---------------------------------------------------------------------- +int NoopOperator::getState(void) const +{ + return Session::NONAUTHENTICATED + | Session::AUTHENTICATED + | Session::SELECTED; +} + +//---------------------------------------------------------------------- +Operator::ProcessResult NoopOperator::process(Depot &depot, + Request &command) +{ + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult NoopOperator::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 NOOP"); + return res; + } + + c_in.setName("NOOP"); + + return ACCEPT; +} diff --git a/src/operator-rename.cc b/src/operator-rename.cc new file mode 100644 index 0000000..cc11d14 --- /dev/null +++ b/src/operator-rename.cc @@ -0,0 +1,119 @@ +/** -------------------------------------------------------------------- + * @file operator-rename.cc + * @brief Implementation of the RENAME command. + * ----------------------------------------------------------------- **/ +#include <string> +#include <iostream> + +#include <dirent.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "convert.h" +#include "depot.h" +#include "mailbox.h" +#include "operators.h" +#include "recursivedescent.h" +#include "session.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +RenameOperator::RenameOperator(void) +{ +} + +//---------------------------------------------------------------------- +RenameOperator::~RenameOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string RenameOperator::getName(void) const +{ + return "RENAME"; +} + +//---------------------------------------------------------------------- +int RenameOperator::getState(void) const +{ + return Session::AUTHENTICATED | Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult RenameOperator::process(Depot &depot, + Request &command) +{ + Session &session = Session::getInstance(); + + const string &srcmailbox = command.getMailbox(); + const string &canonmailbox = toCanonMailbox(srcmailbox); + const string &canondestmailbox = toCanonMailbox(command.getNewMailbox()); + + // renaming INBOX should actually create the destination mailbox, + // move over all the messages and then leave INBOX empty. + if (canonmailbox == "INBOX") { + session.setLastError("Sorry, renaming INBOX is not yet supported" + " by this IMAP server. Try copying the messages" + " instead"); + return NO; + } + + if (canondestmailbox == "INBOX") { + session.setLastError("It is not allowed to rename a mailbox to INBOX"); + return NO; + } + + if (depot.renameMailbox(canonmailbox, canondestmailbox)) + return OK; + else + return NO; +} + +//---------------------------------------------------------------------- +Operator::ParseResult RenameOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) + return REJECT; + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after RENAME"); + return res; + } + + string mailbox; + if ((res = expectMailbox(mailbox)) != ACCEPT) { + session.setLastError("Expected mailbox after RENAME SPACE"); + return res; + } + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after RENAME SPACE mailbox"); + return res; + } + + string newmailbox; + if ((res = expectMailbox(newmailbox)) != ACCEPT) { + session.setLastError("Expected mailbox after RENAME SPACE" + " mailbox SPACE"); + return res; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF after RENAME SPACE" + " mailbox SPACE mailbox"); + return res; + } + + session.mailboxchanges = true; + + c_in.setName("RENAME"); + c_in.setMailbox(mailbox); + c_in.setNewMailbox(newmailbox); + + return ACCEPT; +} diff --git a/src/operator-search.cc b/src/operator-search.cc new file mode 100644 index 0000000..0470eb5 --- /dev/null +++ b/src/operator-search.cc @@ -0,0 +1,898 @@ +/** -------------------------------------------------------------------- + * @file operator-search.cc + * @brief Implementation of the SEARCH command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> +#include <iostream> +#include <algorithm> + +#include <ctype.h> + +#include "convert.h" +#include "depot.h" +#include "imapparser.h" +#include "iodevice.h" +#include "iofactory.h" +#include "mailbox.h" +#include "mime.h" +#include "operators.h" +#include "recursivedescent.h" +#include "session.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +bool SearchOperator::SearchNode::convertDate(const string &date, + time_t &t, const string &delim) +{ + vector<string> parts; + split(date, delim, parts); + if (parts.size() < 3) return false; + + struct tm mold; + memset((char *) &mold, 0, sizeof(struct tm)); + mold.tm_mday = atoi(parts[0].c_str()); + mold.tm_year = atoi(parts[2].c_str()) - 1900; + + // accept mixed case months. this is more than the standard + // accepts. + string month = parts[1]; + lowercase(month); + + if (month == "jan") mold.tm_mon = 0; + else if (month == "feb") mold.tm_mon = 1; + else if (month == "mar") mold.tm_mon = 2; + else if (month == "apr") mold.tm_mon = 3; + else if (month == "may") mold.tm_mon = 4; + else if (month == "jun") mold.tm_mon = 5; + else if (month == "jul") mold.tm_mon = 6; + else if (month == "aug") mold.tm_mon = 7; + else if (month == "sep") mold.tm_mon = 8; + else if (month == "oct") mold.tm_mon = 9; + else if (month == "nov") mold.tm_mon = 10; + else if (month == "dec") mold.tm_mon = 11; + + t = mktime(&mold); + return true; +} + +//---------------------------------------------------------------------- +bool SearchOperator::SearchNode::convertDateHeader(const string &d_in, + time_t &t) +{ + string date = d_in; + string::size_type n = date.find(','); + if (n != string::npos) + date = date.substr(n + 1); + trim(date); + + bool result = convertDate(date, t, " "); + return result; +} + +//---------------------------------------------------------------------- +SearchOperator::SearchNode::SearchNode(void) +{ +} + +//---------------------------------------------------------------------- +SearchOperator::SearchNode::SearchNode(const BincImapParserSearchKey &a) +{ + init(a); +} + +//---------------------------------------------------------------------- +int SearchOperator::SearchNode::getType(void) const +{ + return type; +} + +//---------------------------------------------------------------------- +bool SearchOperator::SearchNode::match(Mailbox *mailbox, + Message *m, unsigned int seqnr, + unsigned int lastmessage, + unsigned int lastuid) const +{ + HeaderItem hitem; + string tmp; + + switch (type) { + //-------------------------------------------------------------------- + case S_ALL: + return true; + //-------------------------------------------------------------------- + case S_ANSWERED: + return (m->getStdFlags() & Message::F_ANSWERED); + //-------------------------------------------------------------------- + case S_BCC: + return m->headerContains("bcc", astring); + //-------------------------------------------------------------------- + case S_BEFORE: { + time_t mtime = m->getInternalDate(); + struct tm *mtime_ = localtime(&mtime); + mtime_->tm_sec = 0; + mtime_->tm_min = 0; + mtime_->tm_hour = 0; + mtime_->tm_wday = 0; + mtime_->tm_yday = 0; + mtime_->tm_isdst = 0; + mtime = mktime(mtime_); + + time_t atime; + if (!convertDate(date, atime)) { + bincWarning << "warning, unable to convert " << date << + " to a time_t" << endl; + return false; + } + + return mtime < atime; + } //-------------------------------------------------------------------- + case S_BODY: + return m->bodyContains(astring); + //-------------------------------------------------------------------- + case S_CC: + return m->headerContains("cc", astring); + //-------------------------------------------------------------------- + case S_DELETED: + return (m->getStdFlags() & Message::F_DELETED); + //-------------------------------------------------------------------- + case S_FLAGGED: + return (m->getStdFlags() & Message::F_FLAGGED); + //-------------------------------------------------------------------- + case S_FROM: + return m->headerContains("from", astring); + //-------------------------------------------------------------------- + case S_KEYWORD: + // the server does not support keywords + return false; + //-------------------------------------------------------------------- + case S_NEW: + return (m->getStdFlags() & Message::F_RECENT) + && !(m->getStdFlags() & Message::F_SEEN); + //-------------------------------------------------------------------- + case S_OLD: + return !(m->getStdFlags() & Message::F_RECENT); + //-------------------------------------------------------------------- + case S_ON: { + time_t mtime = m->getInternalDate(); + struct tm *mtime_ = localtime(&mtime); + mtime_->tm_sec = 0; + mtime_->tm_min = 0; + mtime_->tm_hour = 0; + mtime_->tm_wday = 0; + mtime_->tm_yday = 0; + mtime_->tm_isdst = 0; + mtime = mktime(mtime_); + + time_t atime; + if (!convertDate(date, atime)) { + bincWarning << "warning, unable to convert " << date << + " to a time_t" << endl; + return false; + } + + return mtime == atime; + } //-------------------------------------------------------------------- + case S_RECENT: + return (m->getStdFlags() & Message::F_RECENT); + //-------------------------------------------------------------------- + case S_SEEN: + return (m->getStdFlags() & Message::F_SEEN); + //-------------------------------------------------------------------- + case S_SINCE: { + time_t mtime = m->getInternalDate(); + struct tm *mtime_ = localtime(&mtime); + mtime_->tm_sec = 0; + mtime_->tm_min = 0; + mtime_->tm_hour = 0; + mtime_->tm_wday = 0; + mtime_->tm_yday = 0; + mtime_->tm_isdst = 0; + mtime = mktime(mtime_); + + time_t atime; + if (!convertDate(date, atime)) { + bincWarning << "warning, unable to convert " << date << + " to a time_t" << endl; + return false; + } + + return mtime >= atime; + } //-------------------------------------------------------------------- + case S_SUBJECT: + return m->headerContains("subject", astring); + //-------------------------------------------------------------------- + case S_TEXT: + return m->textContains(astring); + //-------------------------------------------------------------------- + case S_TO: + return m->headerContains("to", astring); + //-------------------------------------------------------------------- + case S_UNANSWERED: + return !(m->getStdFlags() & Message::F_ANSWERED); + //-------------------------------------------------------------------- + case S_UNDELETED: + return !(m->getStdFlags() & Message::F_DELETED); + //-------------------------------------------------------------------- + case S_UNFLAGGED: + return !(m->getStdFlags() & Message::F_FLAGGED); + //-------------------------------------------------------------------- + case S_UNKEYWORD: + // the server does not support keywords + return true; + //-------------------------------------------------------------------- + case S_UNSEEN: + return !(m->getStdFlags() & Message::F_SEEN); + //-------------------------------------------------------------------- + case S_DRAFT: + return (m->getStdFlags() & Message::F_DRAFT); + //-------------------------------------------------------------------- + case S_HEADER: + return m->headerContains(astring, bstring); + //-------------------------------------------------------------------- + case S_LARGER: { + return (m->getSize(true) > number); + } + //-------------------------------------------------------------------- + case S_NOT: + for (vector<SearchNode>::const_iterator i = children.begin(); + i != children.end(); ++i) + if ((*i).match(mailbox, m, seqnr, lastmessage, lastuid)) return false; + return true; + //-------------------------------------------------------------------- + case S_OR: + for (vector<SearchNode>::const_iterator i = children.begin(); + i != children.end(); ++i) + if ((*i).match(mailbox, m, seqnr, lastmessage, lastuid)) return true; + return false; + //-------------------------------------------------------------------- + case S_SENTBEFORE: { + string tmp = m->getHeader("date"); + if (tmp == "") + return false; + + lowercase(tmp); + + time_t mtime; + if (!convertDateHeader(tmp, mtime)) return false; + + if (mtime == (time_t) -1) return false; + + time_t atime; + if (!convertDate(date, atime)) { + bincWarning << "warning, unable to convert " << date << + " to a time_t" << endl; + return false; + } + + return mtime < atime; + } //-------------------------------------------------------------------- + case S_SENTON: { + string tmp = m->getHeader("date"); + if (tmp == "") return false; + + lowercase(tmp); + + time_t mtime; + if (!convertDateHeader(tmp, mtime)) return false; + + if (mtime == (time_t) -1) return false; + + time_t atime; + if (!convertDate(date, atime)) { + bincWarning << "warning, unable to convert " << date << + " to a time_t" << endl; + return false; + } + + return mtime == atime; + } //-------------------------------------------------------------------- + case S_SENTSINCE: { + string tmp = m->getHeader("date"); + if (tmp == "") return false; + + lowercase(tmp); + + time_t mtime; + if (!convertDateHeader(tmp, mtime)) return false; + + if (mtime == (time_t) -1) return false; + + time_t atime; + if (!convertDate(date, atime)) { + bincWarning << "warning, unable to convert " << date + << " to a time_t" << endl; + return false; + } + + return mtime >= atime; + } //-------------------------------------------------------------------- + case S_SMALLER: + return (m->getSize(true) < number); + //-------------------------------------------------------------------- + case S_UID: + if (!bset->isInSet(m->getUID())) + if (!(m->getUID() == lastuid && !bset->isLimited())) return false; + return true; + //-------------------------------------------------------------------- + case S_UNDRAFT: + return !(m->getStdFlags() & Message::F_DRAFT); + //-------------------------------------------------------------------- + case S_SET: + if (!bset->isInSet(seqnr)) + if (!(seqnr == lastmessage && !bset->isLimited())) return false; + return true; + //-------------------------------------------------------------------- + case S_AND: + for (vector<SearchNode>::const_iterator i = children.begin(); + i != children.end(); ++i) + if (!(*i).match(mailbox, m, seqnr, lastmessage, lastuid)) return false; + return true; + } + + return false; +} + +//---------------------------------------------------------------------- +void SearchOperator::SearchNode::init(const BincImapParserSearchKey &a) +{ + astring = a.astring; + bstring = a.bstring; + date = a.date; + number = a.number; + uppercase(astring); + uppercase(bstring); + uppercase(date); + + if (a.name == "ALL") { type = S_ALL; weight = 1; } + else if (a.name == "ANSWERED") { type = S_ANSWERED; weight = 1; } + else if (a.name == "BCC") { type = S_BCC; weight = 2; } + else if (a.name == "BEFORE") { type = S_BEFORE; weight = 2; } + else if (a.name == "BODY") { type = S_BODY; weight = 1; } + else if (a.name == "CC") { type = S_CC; weight = 2; } + else if (a.name == "DELETED") { type = S_DELETED; weight = 1; } + else if (a.name == "FLAGGED") { type = S_FLAGGED; weight = 1; } + else if (a.name == "FROM") { type = S_FROM; weight = 2; } + else if (a.name == "KEYWORD") { type = S_KEYWORD; weight = 3; } + else if (a.name == "NEW") { type = S_NEW; weight = 1; } + else if (a.name == "OLD") { type = S_OLD; weight = 1; } + else if (a.name == "ON") { type = S_ON; weight = 1; } + else if (a.name == "RECENT") { type = S_RECENT; weight = 1; } + else if (a.name == "SEEN") { type = S_SEEN; weight = 1; } + else if (a.name == "SINCE") { type = S_SINCE; weight = 1; } + else if (a.name == "SUBJECT") { type = S_SUBJECT; weight = 2; } + else if (a.name == "TEXT") { type = S_TEXT; weight = 4; } + else if (a.name == "TO") { type = S_TO; weight = 2; } + else if (a.name == "UNANSWERED") { type = S_UNANSWERED; weight = 1; } + else if (a.name == "UNDELETED") { type = S_UNDELETED; weight = 1; } + else if (a.name == "UNFLAGGED") { type = S_UNFLAGGED; weight = 1; } + else if (a.name == "UNKEYWORD") { type = S_UNKEYWORD; weight = 1; } + else if (a.name == "UNSEEN") { type = S_UNSEEN; weight = 1; } + else if (a.name == "DRAFT") { type = S_DRAFT; weight = 1; } + else if (a.name == "HEADER") { type = S_HEADER; weight = 3; } + else if (a.name == "LARGER") { type = S_LARGER; weight = 4; } + else if (a.name == "NOT") { + // ******* NOT + type = S_NOT; + weight = 1; + + vector<BincImapParserSearchKey>::const_iterator i = a.children.begin(); + while (i != a.children.end()) { + SearchNode b(*i); + weight += b.getWeight(); + children.push_back(b); + ++i; + } + + } else if (a.name == "OR") { + // ******* OR + type = S_OR; + weight = 0; + + vector<BincImapParserSearchKey>::const_iterator i = a.children.begin(); + while (i != a.children.end()) { + SearchNode b(*i); + weight += b.getWeight(); + + children.push_back(b); + ++i; + } + + } else if (a.name == "SENTBEFORE") { type = S_SENTBEFORE; weight = 1; } + else if (a.name == "SENTON") { type = S_SENTON; weight = 1; } + else if (a.name == "SENTSINCE") { type = S_SENTSINCE; weight = 1; } + else if (a.name == "SMALLER") { type = S_SMALLER; weight = 4; } + else if (a.name == "UID") { + bset = &a.getSet(); + type = S_UID; + weight = 1; + } else if (a.name == "UNDRAFT") { type = S_UNDRAFT; weight = 1; } + else if (a.type == BincImapParserSearchKey::KEY_SET) { + bset = &a.getSet(); + type = S_SET; + weight = 1; + } else if (a.type == BincImapParserSearchKey::KEY_AND) { + // ******* AND + type = S_AND; + weight = 0; + + vector<BincImapParserSearchKey>::const_iterator i = a.children.begin(); + while (i != a.children.end()) { + SearchNode b(*i); + weight += b.getWeight(); + children.push_back(b); + ++i; + } + } +} + +//---------------------------------------------------------------------- +int SearchOperator::SearchNode::getWeight(void) const +{ + return weight; +} + +//---------------------------------------------------------------------- +void SearchOperator::SearchNode::setWeight(int i) +{ + weight = i; +} + +//---------------------------------------------------------------------- +void SearchOperator::SearchNode::order(void) +{ + for (vector<SearchNode>::iterator i = children.begin(); + i != children.end(); ++i) + (*i).order(); + ::stable_sort(children.begin(), children.end(), compareNodes); +} + +//---------------------------------------------------------------------- +SearchOperator::SearchOperator(void) +{ +} + +//---------------------------------------------------------------------- +SearchOperator::~SearchOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string SearchOperator::getName(void) const +{ + return "SEARCH"; +} + +//---------------------------------------------------------------------- +int SearchOperator::getState(void) const +{ + return Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult SearchOperator::process(Depot &depot, + Request &command) +{ + Session &session = Session::getInstance(); + + Mailbox *mailbox = depot.getSelected(); + + if (command.getCharSet() != "" && command.getCharSet() != "US-ASCII") { + session.setLastError("The " + command.getCharSet() + + " charset is not supported"); + session.setResponseCode("[BADCHARSET (\"US-ASCII\")]"); + return NO; + } + + bincClient << "* SEARCH"; + bincClient.flush(); + + SearchNode s(command.searchkey); + s.order(); + + const unsigned int maxsqnr = mailbox->getMaxSqnr(); + const unsigned int maxuid = mailbox->getMaxUid(); + + Mailbox::iterator i + = mailbox->begin(SequenceSet::all(), Mailbox::SKIP_EXPUNGED); + for (; i != mailbox->end(); ++i) { + Message &message = *i; + + if (s.match(mailbox, &message, i.getSqnr(), maxsqnr, maxuid)) { + bincClient << " " + << (command.getUidMode() ? message.getUID() : i.getSqnr()); + bincClient.flush(); + } + + message.close(); + } + + bincClient << endl; + return OK; +} + +//------------------------------------------------------------------------ +Operator::ParseResult SearchOperator::parse(Request & c_in) const +{ + Session &session = Session::getInstance(); + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectThisString("CHARSET")) == ACCEPT) { + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after CHARSET"); + return res; + } + + string charset; + if ((res = expectAstring(charset)) != ACCEPT) { + session.setLastError("Expected astring after CHARSET SPACE"); + return res; + } + + c_in.setCharSet(charset); + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after CHARSET SPACE astring"); + return res; + } + } + + BincImapParserSearchKey b; + if ((res = expectSearchKey(b)) != ACCEPT) { + session.setLastError("Expected search_key"); + return res; + } + + c_in.searchkey.type = BincImapParserSearchKey::KEY_AND; + c_in.searchkey.children.push_back(b); + + while (1) { + if ((res = expectSPACE()) != ACCEPT) + break; + + BincImapParserSearchKey c; + if ((res = expectSearchKey(c)) != ACCEPT) { + session.setLastError("Expected search_key after search_key SPACE"); + return res; + } + + c_in.searchkey.children.push_back(c); + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF after search_key"); + return res; + } + + c_in.setName("SEARCH"); + return ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult +SearchOperator::expectSearchKey(BincImapParserSearchKey &s_in) const +{ + Session &session = Session::getInstance(); + Operator::ParseResult res; + + s_in.type = BincImapParserSearchKey::KEY_OTHER; + if ((res = expectThisString("ALL")) == ACCEPT) s_in.name = "ALL"; + else if ((res = expectThisString("ANSWERED")) == ACCEPT) s_in.name = "ANSWERED"; + else if ((res = expectThisString("BCC")) == ACCEPT) { + s_in.name = "BCC"; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectAstring(s_in.astring)) != ACCEPT) { + session.setLastError("Expected astring"); + return res; + } + } else if ((res = expectThisString("BEFORE")) == ACCEPT) { + s_in.name = "BEFORE"; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectDate(s_in.date)) != ACCEPT) { + session.setLastError("Expected date"); + return res; + } + } else if ((res = expectThisString("BODY")) == ACCEPT) { + s_in.name = "BODY"; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectAstring(s_in.astring)) != ACCEPT) { + session.setLastError("Expected astring"); + return res; + } + } else if ((res = expectThisString("CC")) == ACCEPT) { + s_in.name = "CC"; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectAstring(s_in.astring)) != ACCEPT) { + session.setLastError("Expected astring"); + return res; + } + } else if ((res = expectThisString("DELETED")) == ACCEPT) s_in.name = "DELETED"; + else if ((res = expectThisString("FLAGGED")) == ACCEPT) s_in.name = "FLAGGED"; + else if ((res = expectThisString("FROM")) == ACCEPT) { + s_in.name = "FROM"; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectAstring(s_in.astring)) != ACCEPT) { + session.setLastError("Expected astring"); + return res; + } + } else if ((res = expectThisString("KEYWORD")) == ACCEPT) { + s_in.name = "KEYWORD"; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectAtom(s_in.astring)) != ACCEPT) { + session.setLastError("Expected flag_keyword"); + return res; + } + } else if ((res = expectThisString("NEW")) == ACCEPT) s_in.name = "NEW"; + else if ((res = expectThisString("OLD")) == ACCEPT) s_in.name = "OLD"; + else if ((res = expectThisString("ON")) == ACCEPT) { + s_in.name = "ON"; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectDate(s_in.date)) != ACCEPT) { + session.setLastError("Expected date"); + return res; + } + } else if ((res = expectThisString("RECENT")) == ACCEPT) s_in.name = "RECENT"; + else if ((res = expectThisString("SEEN")) == ACCEPT) s_in.name = "SEEN"; + else if ((res = expectThisString("SINCE")) == ACCEPT) { + s_in.name = "SINCE"; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectDate(s_in.date)) != ACCEPT) { + session.setLastError("Expected date"); + return res; + } + } else if ((res = expectThisString("SUBJECT")) == ACCEPT) { + s_in.name = "SUBJECT"; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectAstring(s_in.astring)) != ACCEPT) { + session.setLastError("Expected astring"); + return res; + } + } else if ((res = expectThisString("TEXT")) == ACCEPT) { + s_in.name = "TEXT"; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectAstring(s_in.astring)) != ACCEPT) { + session.setLastError("Expected astring"); + return res; + } + } else if ((res = expectThisString("TO")) == ACCEPT) { + s_in.name = "TO"; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectAstring(s_in.astring)) != ACCEPT) { + session.setLastError("Expected astring"); + return res; + } + } else if ((res = expectThisString("UNANSWERED")) == ACCEPT) + s_in.name = "UNANSWERED"; + else if ((res = expectThisString("UNDELETED")) == ACCEPT) s_in.name = "UNDELETED"; + else if ((res = expectThisString("UNFLAGGED")) == ACCEPT) s_in.name = "UNFLAGGED"; + else if ((res = expectThisString("UNKEYWORD")) == ACCEPT) { + s_in.name = "UNKEYWORD"; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectAtom(s_in.astring)) != ACCEPT) { + session.setLastError("Expected flag_keyword"); + return res; + } + } else if ((res = expectThisString("UNSEEN")) == ACCEPT) s_in.name = "UNSEEN"; + else if ((res = expectThisString("DRAFT")) == ACCEPT) s_in.name = "DRAFT"; + else if ((res = expectThisString("HEADER")) == ACCEPT) { + s_in.name = "HEADER"; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectAstring(s_in.astring)) != ACCEPT) { + session.setLastError("Expected astring"); + return res; + } + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectAstring(s_in.bstring)) != ACCEPT) { + session.setLastError("Expected astring"); + return res; + } + } else if ((res = expectThisString("LARGER")) == ACCEPT) { + s_in.name = "LARGER"; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectNumber(s_in.number)) != ACCEPT) { + session.setLastError("Expected number"); + return res; + } + } else if ((res = expectThisString("NOT")) == ACCEPT) { + s_in.name = "NOT"; + s_in.type = BincImapParserSearchKey::KEY_NOT; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + BincImapParserSearchKey s; + if ((res = expectSearchKey(s)) != ACCEPT) { + session.setLastError("Expected search_key"); + return res; + } + s_in.children.push_back(s); + } else if ((res = expectThisString("OR")) == ACCEPT) { + s_in.name = "OR"; + s_in.type = BincImapParserSearchKey::KEY_OR; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + BincImapParserSearchKey s; + if ((res = expectSearchKey(s)) != ACCEPT) { + session.setLastError("Expected search_key"); + return res; + } + s_in.children.push_back(s); + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + BincImapParserSearchKey t; + if ((res = expectSearchKey(t)) != ACCEPT) { + session.setLastError("Expected search_key"); + return res; + } + s_in.children.push_back(t); + } else if ((res = expectThisString("SENTBEFORE")) == ACCEPT) { + s_in.name = "SENTBEFORE"; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectDate(s_in.date)) != ACCEPT) { + session.setLastError("Expected date"); + return res; + } + } else if ((res = expectThisString("SENTON")) == ACCEPT) { + s_in.name = "SENTON"; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectDate(s_in.date)) != ACCEPT) { + session.setLastError("Expected date"); + return res; + } + + } else if ((res = expectThisString("SENTSINCE")) == ACCEPT) { + s_in.name = "SENTSINCE"; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectDate(s_in.date)) != ACCEPT) { + session.setLastError("Expected date"); + return res; + } + } else if ((res = expectThisString("SMALLER")) == ACCEPT) { + s_in.name = "SMALLER"; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectNumber(s_in.number)) != ACCEPT) { + session.setLastError("Expected number"); + return res; + } + } else if ((res = expectThisString("UID")) == ACCEPT) { + s_in.name = "UID"; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectSet(s_in.bset)) != ACCEPT) { + session.setLastError("Expected number"); + return res; + } + } else if ((res = expectThisString("UNDRAFT")) == ACCEPT) + s_in.name = "UNDRAFT"; + else if ((res = expectSet(s_in.bset)) == ACCEPT) { + s_in.name = ""; + s_in.type = BincImapParserSearchKey::KEY_SET; + } else if ((res = expectThisString("(")) == ACCEPT) { + s_in.type = BincImapParserSearchKey::KEY_AND; + + while (1) { + BincImapParserSearchKey c; + if ((res = expectSearchKey(c)) != ACCEPT) { + session.setLastError("Expected search_key"); + return res; + } + + s_in.children.push_back(c); + + if ((res = expectSPACE()) != ACCEPT) break; + } + + if ((res = expectThisString(")")) != ACCEPT) { + session.setLastError("Expected )"); + return res; + } + } else + return REJECT; + + return ACCEPT; +} diff --git a/src/operator-select.cc b/src/operator-select.cc new file mode 100644 index 0000000..854783a --- /dev/null +++ b/src/operator-select.cc @@ -0,0 +1,158 @@ +/** -------------------------------------------------------------------- + * @file operator-select.cc + * @brief Implementation of the SELECT command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> + +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "mailbox.h" +#include "operators.h" +#include "recursivedescent.h" +#include "pendingupdates.h" +#include "session.h" +#include "convert.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +SelectOperator::SelectOperator(void) +{ +} + +//---------------------------------------------------------------------- +SelectOperator::~SelectOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string SelectOperator::getName(void) const +{ + return "SELECT"; +} + +//---------------------------------------------------------------------- +int SelectOperator::getState(void) const +{ + return Session::NONAUTHENTICATED + | Session::AUTHENTICATED + | Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult SelectOperator::process(Depot &depot, + Request &command) +{ + Session &session = Session::getInstance(); + + const bool examine = (command.getName() == "EXAMINE"); + const string &srcmailbox = command.getMailbox(); + const string &canonmailbox = toCanonMailbox(srcmailbox); + + Mailbox *mailbox = depot.getSelected(); + if (mailbox != 0) { + mailbox->closeMailbox(); + depot.resetSelected(); + mailbox = 0; + } + + mailbox = depot.get(canonmailbox); + if (mailbox == 0) { + session.setLastError(depot.getLastError()); + return NO; + } + + mailbox->setReadOnly(examine); + + if (!mailbox->selectMailbox(canonmailbox, + depot.mailboxToFilename(canonmailbox))) { + bincWarning << "selecting mailbox failed: " + << mailbox->getLastError() << endl; + session.setLastError(mailbox->getLastError()); + return NO; + } + + // find first unseen + int unseen = -1; + Mailbox::iterator i = mailbox->begin(SequenceSet::all(), + Mailbox::SKIP_EXPUNGED | Mailbox::SQNR_MODE); + for (; i != mailbox->end(); ++i) { + Message &message = *i; + + if (unseen == -1 && ((message.getStdFlags() & Message::F_SEEN) == 0)) { + unseen = i.getSqnr(); + break; + } + } + + // show pending updates with only exists and recent response. do not + // re-scan. + pendingUpdates(mailbox, PendingUpdates::EXISTS + | PendingUpdates::RECENT, false, true); + + // unseen + if (unseen != -1) + bincClient << "*" << " OK [UNSEEN " << unseen << "] Message " + << unseen << " is first unseen" << endl; + + // uidvalidity + bincClient << "*" << " OK [UIDVALIDITY " << mailbox->getUidValidity() << "]" + << endl; + + // uidnext + bincClient << "*" << " OK [UIDNEXT " << toString(mailbox->getUidNext()) << "] " + << toString(mailbox->getUidNext()) << " is the next UID" << endl; + + // flags + bincClient << "*" + << " FLAGS (\\Answered \\Flagged \\Deleted \\Recent \\Seen \\Draft \\*)" + << endl; + + // permanentflags + bincClient << "*" + << " OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted " + << "\\Seen \\Draft \\*)] Limited" + << endl; + + session.setState(Session::SELECTED); + depot.setSelected(mailbox); + + session.setResponseCode(examine ? "READ-ONLY" : "READ-WRITE"); + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult SelectOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) + return REJECT; + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE after" + c_in.getName()); + return res; + } + + string mailbox; + if ((res = expectMailbox(mailbox)) != ACCEPT) { + session.setLastError("Expected mailbox after " + c_in.getName() + + " SPACE"); + return res; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF after " + c_in.getName() + + " SPACE mailbox"); + return res; + } + + c_in.setMailbox(mailbox); + + return ACCEPT; +} diff --git a/src/operator-starttls.cc b/src/operator-starttls.cc new file mode 100644 index 0000000..fcc7b3b --- /dev/null +++ b/src/operator-starttls.cc @@ -0,0 +1,115 @@ +/** -------------------------------------------------------------------- + * @file operator-starttls.cc + * @brief Implementation of the STARTTLS command - based on sslserver + * @author Andreas Aardal Hanssen, Erwin Hoffmann + * @date 2002-2005, 2023 + * ----------------------------------------------------------------- **/ +#include <string> +#include <iostream> +#include <unistd.h> +#include <fcntl.h> + +#include "recursivedescent.h" +#include "iodevice.h" +#include "iofactory.h" +#include "session.h" +#include "depot.h" +#include "operators.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +StarttlsOperator::StarttlsOperator(void) +{ +} + +//---------------------------------------------------------------------- +StarttlsOperator::~StarttlsOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string StarttlsOperator::getName(void) const +{ + return "STARTTLS"; +} + +//---------------------------------------------------------------------- +int StarttlsOperator::getState(void) const +{ + return Session::NONAUTHENTICATED + | Session::AUTHENTICATED + | Session::SELECTED; +} + +//---------------------------------------------------------------------- +int StarttlsOperator::goStartTLS (void) const +{ + Session &session = Session::getInstance(); + + if (getenv("UCSPITLS")) { + string fdstr; + int fd; + + fdstr = session.getEnv("SSLCTLFD"); + fd = std::stoi(fdstr); + if (write(fd,"Y",1) < 1) return NOTHING; + bincClient.flush(); // flush all previous received data + + fdstr = session.getEnv("SSLREADFD"); + fd = std::stoi(fdstr); + if (fcntl(fd,F_GETFL,0) == -1) return NOTHING; + close (0); + if (fcntl(fd,F_DUPFD,0) == -1) return NOTHING; + close (fd); + + fdstr = session.getEnv("SSLWRITEFD"); + fd = std::stoi(fdstr); + if (fcntl(fd,F_GETFL,0) == -1) return NOTHING; + close (1); + if (fcntl(fd,F_DUPFD,1) == -1) return NOTHING; + close (fd); + } + + return ACCEPT; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult StarttlsOperator::process(Depot &depot, + Request &command) +{ + Session &session = Session::getInstance(); + if (session.command.ssl) { + session.setLastError("Already in TLS mode"); + return BAD; + } + + bincClient << "* ENABLED StartTLS - begin negotiation now" << endl; + bincClient << command.getTag() << " OK STARTTLS completed" << endl; + + if (goStartTLS() == ACCEPT) + session.command.ssl = true; + else + return NO; + + return NOTHING; +} + +//---------------------------------------------------------------------- +Operator::ParseResult StarttlsOperator::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"); + return res; + } + + c_in.setName("STARTTLS"); + + return ACCEPT; +} diff --git a/src/operator-status.cc b/src/operator-status.cc new file mode 100644 index 0000000..da74f16 --- /dev/null +++ b/src/operator-status.cc @@ -0,0 +1,152 @@ +/** -------------------------------------------------------------------- + * @file operator-status.cc + * @brief Implementation of the STATUS command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> +#include <iostream> + +#include <dirent.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "convert.h" +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "mailbox.h" +#include "operators.h" +#include "recursivedescent.h" +#include "session.h" +#include "status.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +StatusOperator::StatusOperator(void) +{ +} + +//---------------------------------------------------------------------- +StatusOperator::~StatusOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string StatusOperator::getName(void) const +{ + return "STATUS"; +} + +//---------------------------------------------------------------------- +int StatusOperator::getState(void) const +{ + return Session::AUTHENTICATED | Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult StatusOperator::process(Depot &depot, + Request &command) +{ + Session &session = Session::getInstance(); + + Status status; + if (!depot.getStatus(command.getMailbox(), status)) { + session.setLastError(depot.getLastError()); + return NO; + } + + bincClient << "* STATUS " << toImapString(command.getMailbox()) << " ("; + + string prefix; + for (vector<string>::const_iterator i = command.statuses.begin(); + i != command.statuses.end(); ++i) { + string tmp = *i; + uppercase(tmp); + if (tmp == "UIDNEXT") { + bincClient << prefix << "UIDNEXT " << status.getUidNext(); + prefix = " "; + } else if (tmp == "MESSAGES") { + bincClient << prefix << "MESSAGES " << status.getMessages(); + prefix = " "; + } else if (tmp == "RECENT") { + bincClient << prefix << "RECENT " << status.getRecent(); + prefix = " "; + } else if (tmp == "UIDVALIDITY") { + bincClient << prefix << "UIDVALIDITY " << status.getUidValidity(); + prefix = " "; + } else if (tmp == "UNSEEN") { + bincClient << prefix << "UNSEEN " << status.getUnseen(); + prefix = " "; + } + } + bincClient << ")" << endl; + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult StatusOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) + return REJECT; + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + string mailbox; + if ((res = expectMailbox(mailbox)) != ACCEPT) { + session.setLastError("Expected mailbox"); + return res; + } + + c_in.setMailbox(mailbox); + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectThisString("(")) != ACCEPT) { + session.setLastError("Expected ("); + return res; + } + + while (1) { + if ((res = expectThisString("MESSAGES")) == ACCEPT) + c_in.getStatuses().push_back("MESSAGES"); + else if ((res = expectThisString("RECENT")) == ACCEPT) + c_in.getStatuses().push_back("RECENT"); + else if ((res = expectThisString("UIDNEXT")) == ACCEPT) + c_in.getStatuses().push_back("UIDNEXT"); + else if ((res = expectThisString("UIDVALIDITY")) == ACCEPT) + c_in.getStatuses().push_back("UIDVALIDITY"); + else if ((res = expectThisString("UNSEEN")) == ACCEPT) + c_in.getStatuses().push_back("UNSEEN"); + else { + session.setLastError("Expected status_att"); + return res; + } + + if (expectSPACE() != ACCEPT) break; + } + + if ((res = expectThisString(")")) != ACCEPT) { + session.setLastError("Expected )"); + return res; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF"); + return ERROR; + } + + return ACCEPT; +} diff --git a/src/operator-store.cc b/src/operator-store.cc new file mode 100644 index 0000000..efb5a48 --- /dev/null +++ b/src/operator-store.cc @@ -0,0 +1,194 @@ +/** -------------------------------------------------------------------- + * @file operator-store.cc + * @brief Implementation of the STORE command + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ------------------------------------------------------------------ **/ +#include <string> +#include <iostream> + +#include "depot.h" +#include "imapparser.h" +#include "mailbox.h" +#include "operators.h" +#include "pendingupdates.h" +#include "recursivedescent.h" +#include "session.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +StoreOperator::StoreOperator(void) +{ +} + +//---------------------------------------------------------------------- +StoreOperator::~StoreOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string StoreOperator::getName(void) const +{ + return "STORE"; +} + +//---------------------------------------------------------------------- +int StoreOperator::getState(void) const +{ + return Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult StoreOperator::process(Depot &depot, + Request &command) +{ + Mailbox *mailbox = depot.getSelected(); + + // mask all passed flags together + unsigned int newflags = (unsigned int) Message::F_NONE; + vector<string> newCustomFlags; + vector<string>::const_iterator f_i = command.flags.begin(); + while (f_i != command.flags.end()) { + if (*f_i == "\\Deleted") newflags |= Message::F_DELETED; + else if (*f_i == "\\Answered") newflags |= Message::F_ANSWERED; + else if (*f_i == "\\Seen") newflags |= Message::F_SEEN; + else if (*f_i == "\\Draft") newflags |= Message::F_DRAFT; + else if (*f_i == "\\Flagged") newflags |= Message::F_FLAGGED; + else newCustomFlags.push_back(*f_i); + + ++f_i; + } + + // pass through all messages + unsigned int mode + = command.getUidMode() ? Mailbox::UID_MODE : Mailbox::SQNR_MODE; + + Mailbox::iterator i + = mailbox->begin(command.bset, Mailbox::SKIP_EXPUNGED | mode); + + for (; i != mailbox->end(); ++i) { + Message &message = *i; + + // get and reset the old flags + unsigned int flags = (unsigned int) message.getStdFlags(); + + bool recent = (flags & Message::F_RECENT) != 0; + flags &= (~Message::F_RECENT); + + // add, remove or set flags + switch (command.getMode()[0]) { + case '+': + flags |= newflags; + for (vector<string>::const_iterator it = newCustomFlags.begin(); + it != newCustomFlags.end(); ++it) + message.setCustomFlag(*it); + break; + case '-': + flags &= ~newflags; + for (vector<string>::const_iterator it = newCustomFlags.begin(); + it != newCustomFlags.end(); ++it) + message.removeCustomFlag(*it); + break; + default: + flags = newflags; + message.resetCustomFlags(); + for (vector<string>::const_iterator it = newCustomFlags.begin(); + it != newCustomFlags.end(); ++it) + message.setCustomFlag(*it); + break; + }; + + // set new flags, even if they weren't changed. + if (recent) flags |= Message::F_RECENT; + message.resetStdFlags(); + message.setStdFlag(flags); + } + + // commit flag changes to mailbox (might change mailbox) + mailbox->updateFlags(); + + // check mailbox for updates, and report them + if (command.getMode().find(".SILENT") != string::npos) + pendingUpdates(mailbox, PendingUpdates::EXISTS + | PendingUpdates::RECENT, false, false, false, + command.getUidMode()); + else + pendingUpdates(mailbox, PendingUpdates::EXISTS + | PendingUpdates::RECENT + | PendingUpdates::EXPUNGE + | PendingUpdates::FLAGS, false, false, false, + command.getUidMode()); + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult StoreOperator::parse(Request & c_in) const +{ + Session &session = Session::getInstance(); + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + if ((res = expectSet(c_in.getSet())) != ACCEPT) { + session.setLastError("Expected Set"); + return res; + } + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + string mode; + if ((res = expectThisString("+")) == ACCEPT) + mode = "+"; + else if ((res = expectThisString("-")) == ACCEPT) + mode = "-"; + + if ((res = expectThisString("FLAGS")) != ACCEPT) { + session.setLastError("Expected FLAGS"); + return res; + } else + mode += "FLAGS"; + + if ((res = expectThisString(".SILENT")) == ACCEPT) mode += ".SILENT"; + + c_in.setMode(mode); + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + bool paren = false; + if ((res = expectThisString("(")) == ACCEPT) paren = true; + + if ((res = expectFlag(c_in.getFlags())) == ACCEPT) + while (1) { + if ((res = expectSPACE()) != ACCEPT) break; + + if ((res = expectFlag(c_in.getFlags())) != ACCEPT) { + session.setLastError("Expected flag after SPACE"); + return res; + } + } + + if (paren) + if ((res = expectThisString(")")) != ACCEPT) { + session.setLastError("Expected )"); + return res; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF"); + return res; + } + + return ACCEPT; +} diff --git a/src/operator-subscribe.cc b/src/operator-subscribe.cc new file mode 100644 index 0000000..a4e77df --- /dev/null +++ b/src/operator-subscribe.cc @@ -0,0 +1,84 @@ +/** -------------------------------------------------------------------- + * @file operator-subscribe.cc + * @brief Implementation of the SUBSCRIBE command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> +#include <iostream> +#include "convert.h" +#include <sys/stat.h> + +#include "recursivedescent.h" + +#include "session.h" +#include "depot.h" +#include "operators.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +SubscribeOperator::SubscribeOperator(void) +{ +} + +//---------------------------------------------------------------------- +SubscribeOperator::~SubscribeOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string SubscribeOperator::getName(void) const +{ + return "SUBSCRIBE"; +} + +//---------------------------------------------------------------------- +int SubscribeOperator::getState(void) const +{ + return Session::AUTHENTICATED | Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult SubscribeOperator::process(Depot &depot, + Request &command) +{ + const string &srcmailbox = command.getMailbox(); + const string &canonmailbox = toCanonMailbox(srcmailbox); + + depot.loadSubscribes(); + depot.subscribeTo(canonmailbox); + depot.saveSubscribes(); + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult SubscribeOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) + return REJECT; + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + string mailbox; + if ((res = expectMailbox(mailbox)) != ACCEPT) { + session.setLastError("Expected mailbox"); + return res; + } + c_in.setMailbox(mailbox); + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF"); + return res; + } + + return ACCEPT; +} diff --git a/src/operator-unsubscribe.cc b/src/operator-unsubscribe.cc new file mode 100644 index 0000000..7ef2bf4 --- /dev/null +++ b/src/operator-unsubscribe.cc @@ -0,0 +1,91 @@ +/** -------------------------------------------------------------------- + * @file operator-unsubscribe.cc + * @brief Implementation of the UNSUBSCRIBE command. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <string> +#include <vector> +#include <iostream> +#include "convert.h" +#include <sys/stat.h> + +#include "recursivedescent.h" + +#include "session.h" +#include "depot.h" +#include "operators.h" + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +UnsubscribeOperator::UnsubscribeOperator(void) +{ +} + +//---------------------------------------------------------------------- +UnsubscribeOperator::~UnsubscribeOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string UnsubscribeOperator::getName(void) const +{ + return "UNSUBSCRIBE"; +} + +//---------------------------------------------------------------------- +int UnsubscribeOperator::getState(void) const +{ + return Session::AUTHENTICATED | Session::SELECTED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult UnsubscribeOperator::process(Depot &depot, + Request &command) +{ + const string &mailbox = command.getMailbox(); + const string &canonmailbox = toCanonMailbox(mailbox); + + depot.loadSubscribes(); + if (!depot.unsubscribeTo(canonmailbox)) { + Session &session = Session::getInstance(); + session.setLastError("Not subscribed to " + toImapString(mailbox)); + return NO; + } + + depot.saveSubscribes(); + + return OK; +} + +//---------------------------------------------------------------------- +Operator::ParseResult UnsubscribeOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) + return REJECT; + + Operator::ParseResult res; + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected SPACE"); + return res; + } + + string mailbox; + if ((res = expectMailbox(mailbox)) != ACCEPT) { + session.setLastError("Expected mailbox"); + return res; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF"); + return res; + } + + c_in.setMailbox(mailbox); + + return ACCEPT; +} diff --git a/src/pendingupdates.cc b/src/pendingupdates.cc new file mode 100644 index 0000000..337c21a --- /dev/null +++ b/src/pendingupdates.cc @@ -0,0 +1,265 @@ +/** -------------------------------------------------------------------- + * @file pendingupdates.cc + * @brief <---> + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include <iostream> +#include <string> +#include <vector> + +#include "iodevice.h" +#include "iofactory.h" +#include "mailbox.h" +#include "message.h" +#include "pendingupdates.h" +#include "session.h" + +using namespace ::std; +using namespace Binc; + +//------------------------------------------------------------------------ +PendingUpdates::PendingUpdates(void) : expunges(), flagupdates() +{ + recent = 0; + exists = 0; + + newrecent = false; + newexists = false; +} + +//------------------------------------------------------------------------ +PendingUpdates::~PendingUpdates(void) +{ +} + +//------------------------------------------------------------------------ +void PendingUpdates::addExpunged(unsigned int uid) +{ + expunges.push_back(uid); +} + +//------------------------------------------------------------------------ +void PendingUpdates::addFlagUpdates(unsigned int sqnr, unsigned int uid, + unsigned int flags, const vector<string> &cflags) +{ + flagupdates[sqnr] = flags; + sqnrtouid[sqnr] = uid; + sqnrtocflags[sqnr] = cflags; +} + +//------------------------------------------------------------------------ +void PendingUpdates::setExists(unsigned int n) +{ + exists = n; + newexists = true; +} + +//------------------------------------------------------------------------ +void PendingUpdates::setRecent(unsigned int n) +{ + recent = n; + newrecent = true; +} + +//------------------------------------------------------------------------ +unsigned int PendingUpdates::getExists(void) const +{ + return exists; +} + +//------------------------------------------------------------------------ +unsigned int PendingUpdates::getRecent(void) const +{ + return recent; +} + +//------------------------------------------------------------------------ +bool PendingUpdates::newExists(void) const +{ + return newexists; +} + +//------------------------------------------------------------------------ +bool PendingUpdates::newRecent(void) const +{ + return newrecent; +} + +//------------------------------------------------------------------------ +PendingUpdates::expunged_const_iterator::expunged_const_iterator(void) +{ +} + +//------------------------------------------------------------------------ +PendingUpdates::expunged_const_iterator::expunged_const_iterator(vector<unsigned int>::iterator i) : internal(i) +{ +} + +//------------------------------------------------------------------------ +unsigned int PendingUpdates::expunged_const_iterator::operator * (void) const +{ + return *internal; +} + +//------------------------------------------------------------------------ +void PendingUpdates::expunged_const_iterator::operator ++ (void) +{ + ++internal; +} + +//------------------------------------------------------------------------ +bool PendingUpdates::expunged_const_iterator::operator == (expunged_const_iterator i) const +{ + return internal == i.internal; +} + +//------------------------------------------------------------------------ +bool PendingUpdates::expunged_const_iterator::operator != (expunged_const_iterator i) const +{ + return internal != i.internal; +} + +//------------------------------------------------------------------------ +PendingUpdates::expunged_const_iterator PendingUpdates::beginExpunged(void) +{ + return expunged_const_iterator(expunges.begin()); +} + +//------------------------------------------------------------------------ +PendingUpdates::expunged_const_iterator PendingUpdates::endExpunged(void) +{ + return expunged_const_iterator(expunges.end()); +} + +//------------------------------------------------------------------------ +PendingUpdates::flagupdates_const_iterator::flagupdates_const_iterator(void) +{ +} + +//------------------------------------------------------------------------ +PendingUpdates::flagupdates_const_iterator::flagupdates_const_iterator( + map<unsigned int, unsigned int>::iterator i, + map<unsigned int, vector<string> > *j, + map<unsigned int, unsigned int> *sqnrmap) : internal(i), sqnrtocflags(j) +{ + sqnrtouid = sqnrmap; +} + +//------------------------------------------------------------------------ +void PendingUpdates::flagupdates_const_iterator::operator ++ (void) +{ + ++internal; +} + +//------------------------------------------------------------------------ +bool PendingUpdates::flagupdates_const_iterator::operator != (flagupdates_const_iterator i) const +{ + return internal != i.internal; +} + +//------------------------------------------------------------------------ +PendingUpdates::flagupdates_const_iterator PendingUpdates::beginFlagUpdates(void) +{ + return flagupdates_const_iterator(flagupdates.begin(), &sqnrtocflags, &sqnrtouid); +} + +//------------------------------------------------------------------------ +PendingUpdates::flagupdates_const_iterator PendingUpdates::endFlagUpdates(void) +{ + return flagupdates_const_iterator(flagupdates.end(), &sqnrtocflags, &sqnrtouid); +} + +//------------------------------------------------------------------------ +unsigned int PendingUpdates::flagupdates_const_iterator::first(void) const +{ + return internal->first; +} + +//------------------------------------------------------------------------ +unsigned int PendingUpdates::flagupdates_const_iterator::second(void) const +{ + return internal->second; +} + +//------------------------------------------------------------------------ +vector<string> PendingUpdates::flagupdates_const_iterator::getCustomFlags(void) const +{ + return (*sqnrtocflags)[internal->first]; +} + +//------------------------------------------------------------------------ +unsigned int PendingUpdates::flagupdates_const_iterator::getUID(void) const +{ + return (*sqnrtouid)[internal->first]; +} + +//-------------------------------------------------------------------- +bool Binc::pendingUpdates(Mailbox *mailbox, int type, bool rescan, bool showAll, + bool forceScan, bool uidfetchflags) +{ + Session &session = Session::getInstance(); + + if (mailbox == 0) + return true; + + PendingUpdates p; + if (!mailbox->getUpdates(rescan, type, p, forceScan)) { + session.setLastError(mailbox->getLastError()); + return false; + } + + if (type & PendingUpdates::EXPUNGE) { + PendingUpdates::expunged_const_iterator i = p.beginExpunged(); + PendingUpdates::expunged_const_iterator e = p.endExpunged(); + + while (i != e) { + bincClient << "* " << *i << " EXPUNGE" << endl; + ++i; + } + } + + if (((type & PendingUpdates::EXISTS) && p.newExists()) || showAll) + bincClient << "* " << p.getExists() << " EXISTS" << endl; + + if (((type & PendingUpdates::RECENT) && p.newRecent() || showAll)) + bincClient << "* " << p.getRecent() << " RECENT" << endl; + + if (type & PendingUpdates::FLAGS) { + PendingUpdates::flagupdates_const_iterator i = p.beginFlagUpdates(); + PendingUpdates::flagupdates_const_iterator e = p.endFlagUpdates(); + + while (i != e) { + int flags = i.second(); + + vector<string> flagv; + if (flags & Message::F_SEEN) flagv.push_back("\\Seen"); + if (flags & Message::F_ANSWERED) flagv.push_back("\\Answered"); + if (flags & Message::F_DELETED) flagv.push_back("\\Deleted"); + if (flags & Message::F_DRAFT) flagv.push_back("\\Draft"); + if (flags & Message::F_RECENT) flagv.push_back("\\Recent"); + if (flags & Message::F_FLAGGED) flagv.push_back("\\Flagged"); + + bincClient << "* " << i.first() << " FETCH (FLAGS ("; + for (vector<string>::const_iterator k + = flagv.begin(); k != flagv.end(); ++k) { + if (k != flagv.begin()) bincClient << " "; + bincClient << *k; + } + + vector<string> customFlags = i.getCustomFlags(); + for (vector<string>::const_iterator it = customFlags.begin(); + it != customFlags.end(); ++it) { + if (flagv.size() > 0 || it != customFlags.begin()) bincClient << " "; + bincClient << *it; + } + + bincClient << ")"; + if (uidfetchflags) bincClient << " UID " << i.getUID(); + bincClient << ")" << endl; + ++i; + } + } + + return true; +} diff --git a/src/recursivedescent.cc b/src/recursivedescent.cc new file mode 100644 index 0000000..be9c560 --- /dev/null +++ b/src/recursivedescent.cc @@ -0,0 +1,1055 @@ +/** -------------------------------------------------------------------- + * @file recursivedescent.cc + * @brief Implementation of a recursive descent IMAP command parser. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "imapparser.h" +#include "recursivedescent.h" +#include "iodevice.h" +#include "iofactory.h" +#include "convert.h" +#include "session.h" + +#include <stdio.h> +#include <ctype.h> +#include <stack> +#include <iostream> +#include <iomanip> + +using namespace ::std; +using namespace Binc; + +stack<int> Binc::inputBuffer; +int Binc::charnr = 0; + + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectThisString(const string &s_in) +{ + Session &session = Session::getInstance(); + + string tmp; + + bool match = true; + for (string::const_iterator i = s_in.begin(); i != s_in.end(); ++i) { + + char c; + if (!bincClient.readChar(&c)) { + session.setLastError(bincClient.getLastErrorString()); + + if (bincClient.getLastError() == IODevice::Timeout) return Operator::TIMEOUT; + return Operator::ERROR; + } + + tmp += c; + + if (toupper(*i) != toupper(c)) { + match = false; + break; + } + } + + if (!match) { + bincClient.unreadStr(tmp); + return Operator::REJECT; + } else + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectDateTime(string &s_in) +{ + Session &session = Session::getInstance(); + + if (expectThisString("\"") != Operator::ACCEPT) + return Operator::REJECT; + + unsigned int digit1, digit2; + if (expectSPACE() == Operator::ACCEPT) { + digit1 = 0; + Operator::ParseResult res; + if ((res = expectDigit(digit2)) != Operator::ACCEPT) { + session.setLastError("expected digit (day) after \" and a SPACE."); + return res; + } + } else { + Operator::ParseResult res; + if ((res = expectDigit(digit1)) != Operator::ACCEPT) { + session.setLastError("expected first digit of day"); + return res; + } + if ((res = expectDigit(digit2)) != Operator::ACCEPT) { + session.setLastError("expected second digit of day"); + return res; + } + } + + int day = digit1 * 10 + digit2; + + BincStream daystr; + + if (day < 10) + daystr << '0'; + daystr << day; + + s_in += daystr.str(); + + Operator::ParseResult res; + if ((res = expectThisString("-")) != Operator::ACCEPT) { + session.setLastError("expected -"); + return res; + } + + s_in += "-"; + + /* month */ + if ((res = expectThisString("Jan")) == Operator::ACCEPT) s_in += "Jan"; + else if ((res = expectThisString("Feb")) == Operator::ACCEPT) s_in += "Feb"; + else if ((res = expectThisString("Mar")) == Operator::ACCEPT) s_in += "Mar"; + else if ((res = expectThisString("Apr")) == Operator::ACCEPT) s_in += "Apr"; + else if ((res = expectThisString("May")) == Operator::ACCEPT) s_in += "May"; + else if ((res = expectThisString("Jun")) == Operator::ACCEPT) s_in += "Jun"; + else if ((res = expectThisString("Jul")) == Operator::ACCEPT) s_in += "Jul"; + else if ((res = expectThisString("Aug")) == Operator::ACCEPT) s_in += "Aug"; + else if ((res = expectThisString("Sep")) == Operator::ACCEPT) s_in += "Sep"; + else if ((res = expectThisString("Oct")) == Operator::ACCEPT) s_in += "Oct"; + else if ((res = expectThisString("Nov")) == Operator::ACCEPT) s_in += "Nov"; + else if ((res = expectThisString("Dec")) == Operator::ACCEPT) s_in += "Dec"; + else { + session.setLastError("expected month"); + return res; + } + + if ((res = expectThisString("-")) != Operator::ACCEPT) { + session.setLastError("expected -"); + return res; + } + + s_in += "-"; + + /* year */ + unsigned int year, c; + if ((res = expectDigit(year)) != Operator::ACCEPT) { + session.setLastError("expected digit (first digit of year)"); + return res; + } + + if ((res = expectDigit(c)) != Operator::ACCEPT) { + session.setLastError("expected digit (second digit of year)"); + return res; + } + + year = (year * 10) + c; + + if ((res = expectDigit(c)) != Operator::ACCEPT) { + session.setLastError("expected digit (third digit of year)"); + return res; + } + + year = (year * 10) + c; + + if ((res = expectDigit(c)) != Operator::ACCEPT) { + session.setLastError("expected digit (last digit of year)"); + return res; + } + + year = (year * 10) + c; + + BincStream yearstr; + + yearstr << year; + + s_in += yearstr.str(); + + if ((res = expectSPACE()) != Operator::ACCEPT) { + session.setLastError("expected SPACE"); + return res; + } + + s_in += " "; + + if ((res = expectTime(s_in)) != Operator::ACCEPT) { + session.setLastError("expected time"); + return res; + } + + if ((res = expectSPACE()) != Operator::ACCEPT) { + session.setLastError("expected SPACE"); + return res; + } + + s_in += " "; + + if ((res = expectZone(s_in)) != Operator::ACCEPT) { + session.setLastError("expected zone"); + return res; + } + + if ((res = expectThisString("\"")) != Operator::ACCEPT) { + session.setLastError("expected \""); + return res; + } + + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectTime(string &s_in) +{ + Session &session = Session::getInstance(); + + unsigned int c, t; + Operator::ParseResult res; + if ((res = expectDigit(t)) != Operator::ACCEPT) + return res; + + if ((res = expectDigit(c)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + t = (t * 10) + c; + + BincStream tstr; + + tstr << t; + + s_in += tstr.str(); + + if ((res = expectThisString(":")) != Operator::ACCEPT) { + session.setLastError("expected colon"); + return res; + } + + s_in += ":"; + + if ((res = expectDigit(t)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + if ((res = expectDigit(c)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + t = (t * 10) + c; + + tstr.clear(); + + tstr << t; + + s_in += tstr.str(); + + if ((res = expectThisString(":")) != Operator::ACCEPT) { + session.setLastError("expected colon"); + return res; + } + + s_in += ":"; + + if ((res = expectDigit(t)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + if ((res = expectDigit(c)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + t = (t * 10) + c; + + tstr.clear(); + + tstr << t; + + s_in += tstr.str(); + + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectZone(string &s_in) +{ + Session &session = Session::getInstance(); + + Operator::ParseResult res; + if ((res = expectThisString("-")) == Operator::ACCEPT) + s_in += "-"; + else if ((res = expectThisString("+")) == Operator::ACCEPT) + s_in += "+"; + else + return res; + + unsigned int c, t; + if ((res = expectDigit(t)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + if ((res = expectDigit(c)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + t = (t * 10) + c; + + if ((res = expectDigit(c)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + t = (t * 10) + c; + + if ((res = expectDigit(c)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + t = (t * 10) + c; + + BincStream tstr; + + tstr << t; + + s_in += tstr.str(); + + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectListWildcards(int &c_in) +{ + Operator::ParseResult res; + if ((res = expectThisString("%")) == Operator::ACCEPT) { + c_in = '%'; + return Operator::ACCEPT; + } else if ((res = expectThisString("*")) == Operator::ACCEPT) { + c_in = '*'; + return Operator::ACCEPT; + } else + return res; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectListMailbox(string &s_in) +{ + Operator::ParseResult res; + if ((res = expectString(s_in)) == Operator::ACCEPT) + return Operator::ACCEPT; + + int c; + if ((res = expectAtomChar(c)) == Operator::ACCEPT + || (res = expectListWildcards(c)) == Operator::ACCEPT + || (res = expectThisString("]")) == Operator::ACCEPT) { + do { + s_in += (char) c; + if ((res = expectAtomChar(c)) != Operator::ACCEPT + && (res = expectListWildcards(c)) != Operator::ACCEPT + && (res = expectThisString("]")) != Operator::ACCEPT) + return Operator::ACCEPT; + } while (1); + } + + bincClient.unreadStr(s_in); + + return res; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectFlag(vector<string> &v_in) +{ + Session &session = Session::getInstance(); + + Operator::ParseResult res; + string flag; + if ((res = expectThisString("\\Answered")) == Operator::ACCEPT) + v_in.push_back("\\Answered"); + else if ((res = expectThisString("\\Flagged")) == Operator::ACCEPT) + v_in.push_back("\\Flagged"); + else if ((res = expectThisString("\\Deleted")) == Operator::ACCEPT) + v_in.push_back("\\Deleted"); + else if ((res = expectThisString("\\Seen")) == Operator::ACCEPT) + v_in.push_back("\\Seen"); + else if ((res = expectThisString("\\Draft")) == Operator::ACCEPT) + v_in.push_back("\\Draft"); + else if ((res = expectThisString("\\Answered")) == Operator::ACCEPT) + v_in.push_back("\\Answered"); + else { + if ((res = expectThisString("\\")) == Operator::ACCEPT) { + if ((res = expectAtom(flag)) == Operator::ACCEPT) + v_in.push_back("\\" + flag); + else { + session.setLastError("expected atom"); + return res; + } + + } else if (expectAtom(flag) == Operator::ACCEPT) { + v_in.push_back(flag); + } else + return res; + } + + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectDate(string &s_in) +{ + Session &session = Session::getInstance(); + + Operator::ParseResult res; + bool quoted = false; + if ((res = expectThisString("\"")) == Operator::ACCEPT) + quoted = true; + + /* day */ + unsigned int day, c; + if ((res = expectDigit(c)) == Operator::ACCEPT) { + day = c; + if ((res = expectDigit(c)) == Operator::ACCEPT) + day = (day * 10) + c; + + BincStream daystr; + + daystr << day; + + s_in += daystr.str(); + } else { + session.setLastError("expected digit"); + return res; + } + + /* - */ + if ((res = expectThisString("-")) != Operator::ACCEPT) { + session.setLastError("expected -"); + return res; + } + + s_in += '-'; + + /* month */ + if ((res = expectThisString("Jan")) == Operator::ACCEPT) s_in += "Jan"; + else if ((res = expectThisString("Feb")) == Operator::ACCEPT) s_in += "Feb"; + else if ((res = expectThisString("Mar")) == Operator::ACCEPT) s_in += "Mar"; + else if ((res = expectThisString("Apr")) == Operator::ACCEPT) s_in += "Apr"; + else if ((res = expectThisString("May")) == Operator::ACCEPT) s_in += "May"; + else if ((res = expectThisString("Jun")) == Operator::ACCEPT) s_in += "Jun"; + else if ((res = expectThisString("Jul")) == Operator::ACCEPT) s_in += "Jul"; + else if ((res = expectThisString("Aug")) == Operator::ACCEPT) s_in += "Aug"; + else if ((res = expectThisString("Sep")) == Operator::ACCEPT) s_in += "Sep"; + else if ((res = expectThisString("Oct")) == Operator::ACCEPT) s_in += "Oct"; + else if ((res = expectThisString("Nov")) == Operator::ACCEPT) s_in += "Nov"; + else if ((res = expectThisString("Dec")) == Operator::ACCEPT) s_in += "Dec"; + else { + session.setLastError("expected month"); + return res; + } + + /* - */ + if ((res = expectThisString("-")) != Operator::ACCEPT) { + session.setLastError("expected -"); + return res; + } + + s_in += '-'; + + /* year */ + unsigned int year; + if ((res = expectDigit(year)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + if ((res = expectDigit(c)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + year = (year * 10) + c; + + if ((res = expectDigit(c)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + year = (year * 10) + c; + + if ((res = expectDigit(c)) != Operator::ACCEPT) { + session.setLastError("expected digit"); + return res; + } + + year = (year * 10) + c; + + BincStream yearstr; + + yearstr << year; + + s_in += yearstr.str(); + + if (quoted) + if ((res = expectThisString("\"")) != Operator::ACCEPT) { + session.setLastError("expected \""); + return res; + } + + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectCRLF(void) +{ + Operator::ParseResult res; + if ((res = expectCR()) == Operator::ACCEPT + && (res = expectLF()) == Operator::ACCEPT) + return Operator::ACCEPT; + else + return res; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectCR(void) +{ + Session &session = Session::getInstance(); + + char c; + if (!bincClient.readChar(&c)) { + session.setLastError(bincClient.getLastErrorString()); + + if (bincClient.getLastError() == IODevice::Timeout) + return Operator::TIMEOUT; + return Operator::ERROR; + } + + if (c == 0x0d) + return Operator::ACCEPT; + else { + bincClient.unreadChar(c); + return Operator::REJECT; + } +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectLF(void) +{ + Session &session = Session::getInstance(); + + char c; + if (!bincClient.readChar(&c)) { + session.setLastError(bincClient.getLastErrorString()); + + if (bincClient.getLastError() == IODevice::Timeout) + return Operator::TIMEOUT; + return Operator::ERROR; + } + + if (c == 0x0a) + return Operator::ACCEPT; + else { + bincClient.unreadChar(c); + return Operator::REJECT; + } +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectTagChar(int &c_in) +{ + Session &session = Session::getInstance(); + + char c; + if (!bincClient.readChar(&c)) { + session.setLastError(bincClient.getLastErrorString()); + + if (bincClient.getLastError() == IODevice::Timeout) + return Operator::TIMEOUT; + return Operator::ERROR; + } + + switch (c) { + case 041: case 043: case 044: case 046: case 047: case 054: + case 055: case 056: case 057: case 060: case 061: case 062: + case 063: case 064: case 065: case 066: case 067: case 070: + case 071: case 072: case 073: case 074: case 075: case 076: + case 077: case 0100: case 0101: case 0102: case 0103: case 0104: + case 0105: case 0106: case 0107: case 0110: case 0111: case 0112: + case 0113: case 0114: case 0115: case 0116: case 0117: case 0120: + case 0121: case 0122: case 0123: case 0124: case 0125: case 0126: + case 0127: case 0130: case 0131: case 0132: case 0133: case 0135: + case 0136: case 0137: case 0140: case 0141: case 0142: case 0143: + case 0144: case 0145: case 0146: case 0147: case 0150: case 0151: + case 0152: case 0153: case 0154: case 0155: case 0156: case 0157: + case 0160: case 0161: case 0162: case 0163: case 0164: case 0165: + case 0166: case 0167: case 0170: case 0171: case 0172: case 0174: + case 0175: case 0176: + c_in = c; + return Operator::ACCEPT; + default: + break; + } + + bincClient.unreadChar(c); + + return Operator::REJECT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectTag(string &s_in) +{ + string tag; + int tagchar; + + int eres = expectTagChar(tagchar); + if (eres == Operator::REJECT) + return Operator::REJECT; + else if (eres == Operator::ERROR) + return Operator::ERROR; + else if (eres == Operator::TIMEOUT) + return Operator::TIMEOUT; + else { + tag += tagchar; + + bool done = false; + + while (!done) { + switch (expectTagChar(tagchar)) { + case Operator::ACCEPT: + tag += tagchar; + break; + case Operator::REJECT: + done = true; + break; + case Operator::ERROR: + return Operator::ERROR; + case Operator::TIMEOUT: + return Operator::TIMEOUT; + } + } + } + + s_in = tag; + + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectSPACE(void) +{ + Session &session = Session::getInstance(); + + char c; + if (!bincClient.readChar(&c)) { + session.setLastError(bincClient.getLastErrorString()); + + if (bincClient.getLastError() == IODevice::Timeout) + return Operator::TIMEOUT; + return Operator::ERROR; + } + + if (c == ' ') + return Operator::ACCEPT; + else { + bincClient.unreadChar(c); + return Operator::REJECT; + } +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectMailbox(string &s_in) +{ + return expectAstring(s_in); +} + +//---------------------------------------------------------------------- +// FIXME: This rule is wrong. +Operator::ParseResult Binc::expectAstring(string &s_in) +{ + Operator::ParseResult res; + if ((res = expectAtom(s_in)) == Operator::ACCEPT) + return Operator::ACCEPT; + + if ((res = expectString(s_in)) == Operator::ACCEPT) + return Operator::ACCEPT; + + return res; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectAtomChar(int &c_in) +{ + Session &session = Session::getInstance(); + + char c; + if (!bincClient.readChar(&c)) { + session.setLastError(bincClient.getLastErrorString()); + + if (bincClient.getLastError() == IODevice::Timeout) + return Operator::TIMEOUT; + return Operator::ERROR; + } + + switch (c) { + case 041: case 043: case 044: case 046: case 047: case 053: + case 054: case 055: case 056: case 057: case 060: case 061: + case 062: case 063: case 064: case 065: case 066: case 067: + case 070: case 071: case 072: case 073: case 074: case 075: + case 076: case 077: case 0100: case 0101: case 0102: case 0103: + case 0104: case 0105: case 0106: case 0107: case 0110: case 0111: + case 0112: case 0113: case 0114: case 0115: case 0116: case 0117: + case 0120: case 0121: case 0122: case 0123: case 0124: case 0125: + case 0126: case 0127: case 0130: case 0131: case 0132: case 0133: + case 0135: case 0136: case 0137: case 0140: case 0141: case 0142: + case 0143: case 0144: case 0145: case 0146: case 0147: case 0150: + case 0151: case 0152: case 0153: case 0154: case 0155: case 0156: + case 0157: case 0160: case 0161: case 0162: case 0163: case 0164: + case 0165: case 0166: case 0167: case 0170: case 0171: case 0172: + case 0174: case 0175: case 0176: + c_in = c; + return Operator::ACCEPT; + default: + break; + } + + bincClient.unreadChar(c); + return Operator::REJECT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectAtom(string &s_in) +{ + string atom; + int atomchar; + + Operator::ParseResult res; + while ((res = expectAtomChar(atomchar)) == Operator::ACCEPT) + atom += atomchar; + + if (atom == "") { + bincClient.unreadStr(atom); + return res; + } else + s_in = atom; + + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectString(string &s_in) +{ + Operator::ParseResult res; + if ((res = expectQuoted(s_in)) == Operator::ACCEPT) + return Operator::ACCEPT; + + if ((res = expectLiteral(s_in)) == Operator::ACCEPT) + return Operator::ACCEPT; + + return res; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectQuoted(string &s_in) +{ + string quoted; + int quotedchar; + Operator::ParseResult res; + + if ((res = expectThisString("\"")) != Operator::ACCEPT) + return res; + + while ((res = expectQuotedChar(quotedchar)) == Operator::ACCEPT) + quoted += quotedchar; + + if ((res = expectThisString("\"")) != Operator::ACCEPT) { + bincClient.unreadStr("\"" + quoted); + return res; + } + + s_in = quoted; + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectQuotedChar(int &c_in) +{ + Session &session = Session::getInstance(); + + char c; + if (!bincClient.readChar(&c)) { + session.setLastError(bincClient.getLastErrorString()); + + if (bincClient.getLastError() == IODevice::Timeout) + return Operator::TIMEOUT; + return Operator::ERROR; + } + + switch (c) { + case 01: case 02: case 03: case 04: case 05: case 06: case 07: + case 010: case 011: case 013: case 014: case 016: case 017: + case 020: case 021: case 022: case 023: case 024: case 025: case 026: case 027: + case 030: case 031: case 032: case 033: case 034: case 035: case 036: case 037: + case 040: case 041: case 043: case 044: case 045: case 046: case 047: + case 050: case 051: case 052: case 053: case 054: case 055: case 056: case 057: + case 060: case 061: case 062: case 063: case 064: case 065: case 066: case 067: + case 070: case 071: case 072: case 073: case 074: case 075: case 076: case 077: + case 0100: case 0101: case 0102: case 0103: case 0104: case 0105: case 0106: case 0107: + case 0110: case 0111: case 0112: case 0113: case 0114: case 0115: case 0116: case 0117: + case 0120: case 0121: case 0122: case 0123: case 0124: case 0125: case 0126: case 0127: + case 0130: case 0131: case 0132: case 0133: case 0135: case 0136: case 0137: + case 0140: case 0141: case 0142: case 0143: case 0144: case 0145: case 0146: case 0147: + case 0150: case 0151: case 0152: case 0153: case 0154: case 0155: case 0156: case 0157: + case 0160: case 0161: case 0162: case 0163: case 0164: case 0165: case 0166: case 0167: + case 0170: case 0171: case 0172: case 0173: case 0174: case 0175: case 0176: case 0177: + c_in = c; + return Operator::ACCEPT; + case '\\': { + char d; + if (!bincClient.readChar(&d)) { + session.setLastError(bincClient.getLastErrorString()); + + if (bincClient.getLastError() == IODevice::Timeout) return Operator::TIMEOUT; + return Operator::ERROR; + } + + if (d == '\"' || d == '\\') { + c_in = d; + return Operator::ACCEPT; + } else { + bincClient.unreadChar(d); + bincClient.unreadChar(c); + return Operator::REJECT; + } + } + default: + break; + } + + bincClient.unreadChar(c); + return Operator::REJECT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectLiteral(string &s_in) +{ + Session &session = Session::getInstance(); + + string literal; + Operator::ParseResult res; + + if ((res = expectThisString("{")) != Operator::ACCEPT) return res; + + unsigned int nchar; + + if ((res = expectNumber(nchar)) != Operator::ACCEPT) { + session.setLastError("expected number"); + return res; + } + + // rfc2088 describes the non-synchronizing literal, or LITERAL+, as + // sent by the client with an extra '+' appended after the octet + // count. + bool literalPlus = false; + if ((res = expectThisString("+")) == Operator::ACCEPT) + literalPlus = true; + + if ((res = expectThisString("}")) != Operator::ACCEPT) { + session.setLastError("expected }"); + return res; + } + + if ((res = expectCRLF()) != Operator::ACCEPT) { + session.setLastError("expected CRLF"); + return Operator::ERROR; + } + + // Only send the reply if the client did not send a LITERAL+ + // request. + if (!literalPlus) { + bincClient << "+ ok, send " << nchar << " bytes of data." << endl; + bincClient.flush(); + } + + for (unsigned int i = 0; i < nchar; ++i) { + char c; + if (!bincClient.readChar(&c)) { + session.setLastError(bincClient.getLastErrorString()); + if (bincClient.getLastError() == IODevice::Timeout) return Operator::TIMEOUT; + return Operator::ERROR; + } + + literal += c; + } + + s_in = literal; + + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectNumber(unsigned int &i_in) +{ + i_in = 0; + unsigned int n; + Operator::ParseResult res; + + while ((res = expectDigit(n)) == Operator::ACCEPT) { + if (i_in == 0) + i_in = n; + else + i_in = (i_in * 10) + n; + } + + if (res == Operator::TIMEOUT) + return res; + + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectDigit(unsigned int &i_in) +{ + Session &session = Session::getInstance(); + + char c; + if (!bincClient.readChar(&c)) { + session.setLastError(bincClient.getLastErrorString()); + + if (bincClient.getLastError() == IODevice::Timeout) + return Operator::TIMEOUT; + return Operator::ERROR; + } + + if (c == '0') { + i_in = 0; + return Operator::ACCEPT; + } else + bincClient.unreadChar(c); + + if (expectDigitNZ(i_in) != Operator::ACCEPT) + return Operator::REJECT; + + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectDigitNZ(unsigned int &i_in) +{ + Session &session = Session::getInstance(); + + char c; + if (!bincClient.readChar(&c)) { + session.setLastError(bincClient.getLastErrorString()); + + if (bincClient.getLastError() == IODevice::Timeout) + return Operator::TIMEOUT; + return Operator::ERROR; + } + + switch (c) { + case '1': i_in = 1; break; + case '2': i_in = 2; break; + case '3': i_in = 3; break; + case '4': i_in = 4; break; + case '5': i_in = 5; break; + case '6': i_in = 6; break; + case '7': i_in = 7; break; + case '8': i_in = 8; break; + case '9': i_in = 9; break; + case -1: + session.setLastError(bincClient.getLastErrorString()); + return Operator::ERROR; + case -2: + return Operator::TIMEOUT; + default: + bincClient.unreadChar(c); + return Operator::REJECT; + } + + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectSet(SequenceSet &s_in) +{ + Session &session = Session::getInstance(); + unsigned int seqnum = (unsigned int) -1; + + Operator::ParseResult res; + + /* if a set does not start with a sequencenum, then it's not a + * set. :-) seqnum == -1 means '*'. */ + if ((res = expectSequenceNum(seqnum)) != Operator::ACCEPT) + return res; + + /* the first number is always a part of the set */ + s_in.addNumber(seqnum); + + /* if _after_ a set there is a ':', then there will always be a + * sequencenum after the colon. if not, it's a syntax error. a + * colon delimits two numbers in a range. */ + if ((res = expectThisString(":")) == Operator::ACCEPT) { + unsigned int seqnum2 = (unsigned int) -1; + if ((res = expectSequenceNum(seqnum2)) != Operator::ACCEPT) { + session.setLastError("expected sequencenum"); + return res; + } + + s_in.addRange(seqnum, seqnum2); + } + + /* if _after_ a set there is a ',', then there will always be + * a set after the comma. if not, it's a syntax error. */ + if ((res = expectThisString(",")) == Operator::ACCEPT) + if ((res = expectSet(s_in)) != Operator::ACCEPT) { + session.setLastError("expected set"); + return res; + } + + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectSequenceNum(unsigned int &i_in) +{ + Session &session = Session::getInstance(); + + char c; + if (!bincClient.readChar(&c)) { + session.setLastError(bincClient.getLastErrorString()); + + if (bincClient.getLastError() == IODevice::Timeout) + return Operator::TIMEOUT; + return Operator::ERROR; + } + + if (c == '*') { + i_in = (unsigned int) -1; + return Operator::ACCEPT; + } else + bincClient.unreadChar(c); + + if (expectNZNumber(i_in) != Operator::ACCEPT) + return Operator::REJECT; + else + return Operator::ACCEPT; +} + +//---------------------------------------------------------------------- +Operator::ParseResult Binc::expectNZNumber(unsigned int &i_in) +{ + unsigned int c; + Operator::ParseResult res; + + if ((res = expectDigitNZ(c)) != Operator::ACCEPT) + return res; + + i_in = c; + while ((res = expectDigit(c)) == Operator::ACCEPT) + i_in = (i_in * 10) + c; + + if (res == Operator::TIMEOUT) + return res; + + return Operator::ACCEPT; +} diff --git a/src/regmatch.cc b/src/regmatch.cc new file mode 100644 index 0000000..c433087 --- /dev/null +++ b/src/regmatch.cc @@ -0,0 +1,31 @@ +/** -------------------------------------------------------------------- + * @file regex.cc + * @brief Implementation of miscellaneous regexp functions + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "regmatch.h" +#include <string> + +#include <sys/types.h> +#include <regex.h> +#include <stdio.h> + +using namespace ::std; + +//------------------------------------------------------------------------ +int Binc::regexMatch(const string &data_in, const string &p_in) +{ + regex_t r; + regmatch_t rm[2]; + + if (regcomp(&r, p_in.c_str(), REG_EXTENDED | REG_NOSUB) != 0) + return 2; + + int n = regexec(&r, data_in.c_str(), data_in.length(), rm, 0); + regfree(&r); + if (n == 0) + return 0; + else + return 2; +} diff --git a/src/session-initialize-bincimap-up.cc b/src/session-initialize-bincimap-up.cc new file mode 100644 index 0000000..e6a817f --- /dev/null +++ b/src/session-initialize-bincimap-up.cc @@ -0,0 +1,137 @@ +/** -------------------------------------------------------------------- + * @file session-initialize-bincimap-up.cc + * @brief bincimap-up requires sslserver + * @author Andreas Aardal Hanssen, Erwin Hoffmann + * @date 2002-2005, 2023 + * ----------------------------------------------------------------- **/ +#include <syslog.h> +#include <ctype.h> + +#include "broker.h" +#include "convert.h" +#include "globals.h" +#include "iodevice.h" +#include "iofactory.h" +#include "multilogdevice.h" +#include "syslogdevice.h" +#include "stdiodevice.h" +#include "session.h" +#include "tools.h" + +#include <fcntl.h> +#include <string> +#include <map> + +using namespace ::std; +using namespace Binc; + +extern char **environ; + +//---------------------------------------------------------------------- +bool Session::initialize(int argc, char *argv[]) +{ + IOFactory &ioFactory = IOFactory::getInstance(); + + IODevice *stdioDevice = new StdIODevice(IODevice::IsEnabled); + stdioDevice->setFlags(IODevice::HasOutputLimit); + stdioDevice->setMaxOutputBufferSize(TRANSFER_BUFFER_SIZE); + ioFactory.addDevice(stdioDevice); + + Session &session = Session::getInstance(); + + IOFactory::getLogger().clearFlags(IODevice::FlushesOnEndl); + + // Read command line arguments + if (!session.parseCommandLine(argc, argv)) + return false; + + // Show help if asked for it + if (session.command.help) { + printf("%s\n", session.args.usageString().c_str()); + return false; + } + + // Show version if asked for it + if (session.command.version) { + printf("Binc IMAP v" BINC_VERSION"\n"); + return false; + } + + // Let the command line args override the global settings. + session.assignCommandLineArgs(); + + // for log input + string ip = getenv("TCP6REMOTEIP") ? getenv("TCP6REMOTEIP") : + getenv("TCPREMOTEIP") ? getenv("TCPREMOTEIP") : "?"; + session.setIP(ip); + + string logtype = session.getEnv("LOG_TYPE"); + lowercase(logtype); + trim(logtype); + if (logtype == "" || logtype == "multilog") { + MultilogDevice *device = new MultilogDevice(IODevice::IsEnabled + | IODevice::FlushesOnEndl); + ioFactory.addDevice(device); + } else if (logtype == "syslog") { + const string f = session.getEnv("SYSLOG_FACILITY"); + int facility; + + if (isdigit(f[0])) { + facility = atoi(f); + } else { + if (f == "LOG_USER") facility = LOG_USER; + else if (f == "LOG_LOCAL0") facility = LOG_LOCAL0; + else if (f == "LOG_LOCAL1") facility = LOG_LOCAL1; + else if (f == "LOG_LOCAL2") facility = LOG_LOCAL2; + else if (f == "LOG_LOCAL3") facility = LOG_LOCAL3; + else if (f == "LOG_LOCAL4") facility = LOG_LOCAL4; + else if (f == "LOG_LOCAL5") facility = LOG_LOCAL5; + else if (f == "LOG_LOCAL6") facility = LOG_LOCAL6; + else if (f == "LOG_LOCAL7") facility = LOG_LOCAL7; + else facility = LOG_DAEMON; + } + + SyslogDevice *device = new SyslogDevice(IODevice::IsEnabled + | IODevice::FlushesOnEndl, + "bincimap-up", + LOG_NDELAY | LOG_PID, + facility); + ioFactory.addDevice(device); + } + + // Now that we know the log type, we can flush. + IOFactory::getLogger().setFlags(IODevice::FlushesOnEndl); + IOFactory::getLogger().setOutputLevelLimit(IODevice::InfoLevel); + + + // imaps (port 993) -- requires sslserver with option -e + + int stls = 0; + if (getenv("SSL_SESSION_ID")) { + session.command.ssl = true; + stls = -1; + // else we will do starttls - requires new FDs + } else if (getenv("UCSPITLS")) { + string ucspitls = session.getEnv("UCSPITLS"); + if (ucspitls == "+") stls = 1; + if (ucspitls == "-") stls = 0; + if (ucspitls == "!") stls = 2; + } + + BrokerFactory &brokerfactory = BrokerFactory::getInstance(); + + brokerfactory.assign("AUTHENTICATE", new AuthenticateOperator()); + brokerfactory.assign("CAPABILITY", new CapabilityOperator()); + brokerfactory.assign("LOGIN", new LoginOperator()); + brokerfactory.assign("LOGOUT", new LogoutOperator()); + brokerfactory.assign("NOOP", new NoopOperator()); + brokerfactory.assign("ID", new IdOperator()); + if (stls > 0) brokerfactory.assign("STARTTLS", new StarttlsOperator()); + + bincClient.setTimeout(AUTH_TIMEOUT); + + session.setState(Session::NONAUTHENTICATED); + + // If the depot was not initialized properly, return false. + return true; +} diff --git a/src/session-initialize-bincimapd.cc b/src/session-initialize-bincimapd.cc new file mode 100644 index 0000000..18d43b1 --- /dev/null +++ b/src/session-initialize-bincimapd.cc @@ -0,0 +1,213 @@ +/** -------------------------------------------------------------------- + * @file session-initialize-bincimapd.cc + * @brief <---> + * @author Andreas Aardal Hanssen, Erwin Hoffmann + * @date 2002-2005, 2023 + * -------------------------------------------------------------------- + */ +#include <unistd.h> +#include <syslog.h> +#include <errno.h> + +#include "broker.h" +#include "maildir.h" +#include "depot.h" +#include "globals.h" +#include "iodevice.h" +#include "iofactory.h" +#include "multilogdevice.h" +#include "session.h" +#include "stdiodevice.h" +#include "syslogdevice.h" +#include "tools.h" +#include "convert.h" +#include "imapserver.h" + +#include <string> +#include <map> +#include <signal.h> + +using namespace ::std; +using namespace Binc; + +extern char **environ; + +//---------------------------------------------------------------------- +bool Session::initialize(int argc, char *argv[]) +{ + IOFactory &ioFactory = IOFactory::getInstance(); + + IODevice *stdioDevice = new StdIODevice(IODevice::IsEnabled + | IODevice::HasInputLimit + | IODevice::HasTimeout); + stdioDevice->setFlags(IODevice::HasOutputLimit); + stdioDevice->setMaxOutputBufferSize(TRANSFER_BUFFER_SIZE); + ioFactory.addDevice(stdioDevice); + + Session &session = Session::getInstance(); + + // Read command line arguments + if (!session.parseCommandLine(argc, argv)) + return false; + + // Show help if asked for it + if (session.command.help) { + printf("%s\n", session.args.usageString().c_str()); + return false; + } + + // Show version if asked for it + if (session.command.version) { + printf("Binc IMAP v" BINC_VERSION"\n"); + return false; + } + + // Assign command line arguments to global config. + session.assignCommandLineArgs(); + + // log settings + string ip = getenv("TCP6REMOTEIP") ? getenv("TCP6REMOTEIP") : + getenv("TCPREMOTEIP") ? getenv("TCPREMOTEIP") : "?"; + session.setIP(ip); + + string logtype = session.getEnv("LOG_TYPE"); + lowercase(logtype); + trim(logtype); + if (logtype == "" || logtype == "multilog" ) { + ioFactory.addDevice(new MultilogDevice(IODevice::IsEnabled)); + } else if (logtype == "syslog") { + const string f = session.getEnv("SYSLOG_FACILITY"); + + int facility; + + if (isdigit(f[0])) facility = atoi(f); + else { + if (f == "LOG_USER") facility = LOG_USER; + else if (f == "LOG_LOCAL0") facility = LOG_LOCAL0; + else if (f == "LOG_LOCAL1") facility = LOG_LOCAL1; + else if (f == "LOG_LOCAL2") facility = LOG_LOCAL2; + else if (f == "LOG_LOCAL3") facility = LOG_LOCAL3; + else if (f == "LOG_LOCAL4") facility = LOG_LOCAL4; + else if (f == "LOG_LOCAL5") facility = LOG_LOCAL5; + else if (f == "LOG_LOCAL6") facility = LOG_LOCAL6; + else if (f == "LOG_LOCAL7") facility = LOG_LOCAL7; + else facility = LOG_DAEMON; + } + + session.setEnv("SYSLOG_FACILITY", toString(facility)); + + ioFactory.addDevice(new SyslogDevice(IODevice::IsEnabled, + "bincimapd", + LOG_NDELAY | LOG_PID, + facility)); + } + + // Now that we know the log type, we can flush. + IOFactory::getLogger().flush(); + IOFactory::getLogger().setFlags(IODevice::FlushesOnEndl); + IOFactory::getLogger().setOutputLevelLimit(IODevice::InfoLevel); + + string pid = to_string(session.getPid()); + + char *logindetails = getenv("BINCIMAP_LOGIN"); + if (logindetails == 0) { + bincLog + << "bincimapd: pid " << pid + << " BINCIMAP_LOGIN missing from environment (are you sure you invoked " + << argv[0] << " properly?)\n"; + return false; + } + + DepotFactory &depotfactory = DepotFactory::getInstance(); + depotfactory.assign(new IMAPdirDepot()); + depotfactory.assign(new MaildirPPDepot()); + + string depottype = session.getEnv("DEPOT"); + if (depottype == "") depottype = "Maildir++"; + + if ((depot = depotfactory.get(depottype)) == 0) { + bincLog << "bincimapd: pid " << pid + << " Found no Depot for: " << depottype + << ". Please check your configurations file under the Mailbox section\n"; + bincLog.flush(); + return false; + } + + depot->assign(new Maildir()); + depot->setDefaultType("Maildir"); + + // Configurable delimiter to ease crossover from other IMAPservers + string delimiter = session.getEnv("DELIMITER"); + if (delimiter != "") depot->setDelimiter(delimiter[0]); + + BrokerFactory &brokerfactory = BrokerFactory::getInstance(); + + brokerfactory.assign("APPEND", new AppendOperator()); + brokerfactory.assign("CAPABILITY", new CapabilityOperator()); + brokerfactory.assign("CHECK", new CheckOperator()); + brokerfactory.assign("CLOSE", new CloseOperator()); + brokerfactory.assign("COPY", new CopyOperator()); + brokerfactory.assign("CREATE", new CreateOperator()); + brokerfactory.assign("DELETE", new DeleteOperator()); + brokerfactory.assign("EXAMINE", new ExamineOperator()); + brokerfactory.assign("EXPUNGE", new ExpungeOperator()); + brokerfactory.assign("FETCH", new FetchOperator()); + brokerfactory.assign("IDLE", new IdleOperator()); + brokerfactory.assign("ID", new IdOperator()); + brokerfactory.assign("LIST", new ListOperator()); + brokerfactory.assign("LOGOUT", new LogoutOperator()); + brokerfactory.assign("LSUB", new LsubOperator()); + brokerfactory.assign("NAMESPACE", new NamespaceOperator()); + brokerfactory.assign("NOOP", new NoopPendingOperator()); + brokerfactory.assign("RENAME", new RenameOperator()); + brokerfactory.assign("SEARCH", new SearchOperator()); + brokerfactory.assign("SELECT", new SelectOperator()); + brokerfactory.assign("STATUS", new StatusOperator()); + brokerfactory.assign("STORE", new StoreOperator()); + brokerfactory.assign("SUBSCRIBE", new SubscribeOperator()); + brokerfactory.assign("UNSUBSCRIBE", new UnsubscribeOperator()); + + // automatically create depot directory if it's not there already + string path; + if (session.args.getUnqualifiedArgs().size() > 0) + path = session.args.getUnqualifiedArgs()[0]; + if (path == "") path = "."; + else if (chdir(path.c_str()) != 0) { + mkdir(path.c_str(), 0777); + if (chdir(path.c_str()) != 0) { + bincLog << "bincimapd: pid" << pid + << " Error entering depot " + toImapString(path) + ": " + << strerror(errno) << endl; + return false; + } + } + + // automatically create INBOX if it's not there already + if (depot->get("INBOX") == 0 && !depot->createMailbox("INBOX")) { + bincLog << "bincimapd: pid " << pid + << " " << depot->getLastError() << endl; + return false; + } + + // load subscription list + depot->loadSubscribes(); + + session.setState(Session::AUTHENTICATED); + + const string details = logindetails; + string::size_type det = details.find('+'); + if (det == string::npos) { + bincLog << "bincimapd: pid " << pid + << " Invalid content of BINCIMAP_LOGIN - did you invoke " + << argv[0] << " correctly?" << endl; + return false; + } + + const string tag = details.substr(det + 1); + const string command = details.substr(0, det); + bincClient << tag << " OK " << command << " completed" << endl; + bincClient.flush(); + bincClient.setTimeout(IDLE_TIMEOUT); + + return true; +} diff --git a/src/session.cc b/src/session.cc new file mode 100644 index 0000000..849c1d6 --- /dev/null +++ b/src/session.cc @@ -0,0 +1,241 @@ +/** -------------------------------------------------------------------- + * @file session.cc + * @brief Implementation of the Session class. + * ------------------------------------------------------------------ **/ +#include <unistd.h> +#include <syslog.h> + +#include "argparser.h" +#include "convert.h" +#include "globals.h" +#include "session.h" +#include "tools.h" +#include <string> +#include <map> + +using namespace ::std; +using namespace Binc; + +extern char **environ; + +//---------------------------------------------------------------------- +Session::Session(void) +{ + readbytes = 0; + writebytes = 0; + statements = 0; + bodies = 0; + mailboxchanges = true; + logfacility = LOG_DAEMON; +} + +//---------------------------------------------------------------------- +Session &Session::getInstance(void) +{ + static Session session; + return session; +} + +//---------------------------------------------------------------------- +const int Session::getState(void) const +{ + return state; +} + +//---------------------------------------------------------------------- +void Session::setState(int n) +{ + state = n; +} + +//---------------------------------------------------------------------- +const string &Session::getUserID(void) const +{ + return userid; +} + +//---------------------------------------------------------------------- +void Session::setUserID(const string &s) +{ + userid = s; +} + +//---------------------------------------------------------------------- +const string &Session::getIP(void) const +{ + return ip; +} + +//---------------------------------------------------------------------- +void Session::setIP(const string &s) +{ + ip = s; +} + +//---------------------------------------------------------------------- +void Session::setLogFacility(int facility) +{ + logfacility = facility; +} + +//---------------------------------------------------------------------- +int Session::getLogFacility(void) const +{ + return logfacility; +} + +//---------------------------------------------------------------------- +void Session::addBody(void) +{ + ++bodies; +} + +//---------------------------------------------------------------------- +void Session::addStatement(void) +{ + ++statements; +} + +//---------------------------------------------------------------------- +void Session::addReadBytes(int i) +{ + readbytes += i; +} + +//---------------------------------------------------------------------- +void Session::addWriteBytes(int i) +{ + writebytes += i; +} + +//---------------------------------------------------------------------- +int Session::getBodies(void) const +{ + return bodies; +} + +//---------------------------------------------------------------------- +int Session::getStatements(void) const +{ + return statements; +} + +//---------------------------------------------------------------------- +int Session::getWriteBytes(void) const +{ + return writebytes; +} + +//---------------------------------------------------------------------- +int Session::getReadBytes(void) const +{ + return readbytes; +} + +//---------------------------------------------------------------------- +bool Session::parseCommandLine(int argc, char * argv[]) +{ + args.addOptional("h|help", "Display this help screen", true); + args.addOptional("version", "Display the version of Binc IMAP", true); + args.addOptional("a|allow-plain", "Allow authentication when not TLS protected", true); + args.addOptional("v|show-version", "Enable verbose IMAP greeting", false); + args.addOptional("l|log-type", "Sets the method used for logging", false); + args.addOptional("d|depot", "Sets the depot type", false); + args.addOptional("D|delimiter", "Sets the mailbox delimiter", false); + + if (!args.parse(argc, argv)) { + setLastError("Command line error, " + args.errorString()); + return false; + } + + command.help = args["help"] == "yes" ? true : false; + command.version = args["version"] == "yes" ? true : false; + + unparsedArgs = argv + args.argc(); + + return true; +} + +//---------------------------------------------------------------------- +void Session::assignCommandLineArgs(void) +{ + if (args.hasArg("allow-plain")) + setEnv("ALLOW_NONSSL_PLAINTEXT_LOGINS", "yes"); + + if (args.hasArg("show-version")) + setEnv("SHOW_VERSION_IN_GREETING", "yes"); + + if (args.hasArg("log-type")) + setEnv("LOG_TYPE", args["log-type"]); + + if (args.hasArg("depot")) + setEnv("DEPOT", args["depot"]); + + if (args.hasArg("delimiter")) + setEnv("DELIMITER", args["delimiter"]); +} + +//---------------------------------------------------------------------- +const string &Session::getLastError(void) const +{ + return lastError; +} + +//---------------------------------------------------------------------- +void Session::setLastError(const string &error) const +{ + lastError = error; +} + +//---------------------------------------------------------------------- +const string &Session::getResponseCode(void) const +{ + return responseCode; +} + +//---------------------------------------------------------------------- +void Session::setResponseCode(const string &code) const +{ + responseCode = "[" + code + "] "; +} + +//---------------------------------------------------------------------- +void Session::clearResponseCode(void) const +{ + responseCode = ""; +} + +//---------------------------------------------------------------------- +pid_t Session::getPid(void) +{ + if (pid == 0) + pid = getpid(); + + return pid; +} + +//---------------------------------------------------------------------- +int Session::timeout() const +{ + return state == NONAUTHENTICATED ? AUTH_TIMEOUT : IDLE_TIMEOUT; +} + +//---------------------------------------------------------------------- +bool Session::hasEnv(const string &key) const +{ + return getenv(key.c_str()) != 0; +} + +//---------------------------------------------------------------------- +string Session::getEnv(const string &key) +{ + char *c = getenv(key.c_str()); + return c ? c : ""; +} + +//---------------------------------------------------------------------- +void Session::setEnv(const string &key, const string &value) +{ + string env = key + "=" + value; + putenv(strdup(env.c_str())); +} diff --git a/src/status.cc b/src/status.cc new file mode 100644 index 0000000..b9d4f17 --- /dev/null +++ b/src/status.cc @@ -0,0 +1,20 @@ +/** -------------------------------------------------------------------- + * @file status.cc + * @brief Implementation of the Status class. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ----------------------------------------------------------------- **/ +#include "status.h" + +using namespace ::std; +using namespace Binc; + +//------------------------------------------------------------------------ +Status::Status(void) +{ +} + +//------------------------------------------------------------------------ +Status::~Status(void) +{ +} diff --git a/src/stdiodevice.cc b/src/stdiodevice.cc new file mode 100644 index 0000000..9eb5f9c --- /dev/null +++ b/src/stdiodevice.cc @@ -0,0 +1,111 @@ +/** -------------------------------------------------------------------- + * @file stdiodevice.cc + * @brief Implementation of the StdIODevice class + * @author Andreas Aardal Hanssen + * @date 2003/2023 + * ---------------------------------------------------------------- **/ +#include "stdiodevice.h" +#include <string> + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/time.h> +#include <unistd.h> +#include <errno.h> + +using namespace ::std; +using namespace ::Binc; + +//------------------------------------------------------------------------ +StdIODevice::StdIODevice(int f) : IODevice(f) +{ +} + +//------------------------------------------------------------------------ +StdIODevice::~StdIODevice(void) +{ +} + +//------------------------------------------------------------------------ +string StdIODevice::service(void) const +{ + return "client"; +} + +//------------------------------------------------------------------------ +bool StdIODevice::canRead(void) const +{ + size_t bytes; + return ioctl(fileno(stdin), FIONREAD, (char *) &bytes) > 0; +} + +//------------------------------------------------------------------------ +bool StdIODevice::waitForWrite(void) const +{ + fd_set writeMask; + FD_ZERO(&writeMask); + FD_SET(fileno(stdout), &writeMask); + + struct timeval tv; + tv.tv_sec = timeout; + tv.tv_usec = 0; + + int result = select(fileno(stdout) + 1, 0, &writeMask, 0, timeout ? &tv : 0); + if (result == 0) error = Timeout; + return result > 0; +} + +//------------------------------------------------------------------------ +bool StdIODevice::waitForRead(void) const +{ + fd_set readMask; + FD_ZERO(&readMask); + FD_SET(fileno(stdin), &readMask); + + struct timeval tv; + tv.tv_sec = timeout; + tv.tv_usec = 0; + + int result = select(fileno(stdin) + 1, &readMask, 0, 0, timeout ? &tv : 0); + if (result == 0) error = Timeout; + return result > 0; +} + +//------------------------------------------------------------------------ +IODevice::WriteResult StdIODevice::write(void) +{ + for (;;) { + ssize_t wrote = ::write(fileno(stdout), outputBuffer.str().c_str(), + outputBuffer.getSize()); + + if (wrote == -1) { + if (errno == EINTR) + continue; + else + return WriteError; + } + + outputBuffer.popString(wrote); + + if (wrote == (ssize_t) outputBuffer.getSize()) + return WriteDone; + return WriteWait; + } +} + +//------------------------------------------------------------------------ +bool StdIODevice::fillInputBuffer(void) +{ + if (!waitForRead()) + return false; + + char buf[4096]; + ssize_t red = read(fileno(stdin), buf, sizeof(buf) - 1); + if (red <= 0) + return false; + + buf[red] = '\0'; + inputBuffer << buf; + return true; +} diff --git a/src/syslogdevice.cc b/src/syslogdevice.cc new file mode 100644 index 0000000..927416e --- /dev/null +++ b/src/syslogdevice.cc @@ -0,0 +1,75 @@ +/** -------------------------------------------------------------------- + * @file syslogdevice.cc + * @brief Implementation of the SyslogDevice class. + * @author Andreas Aardal Hanssen + * @date 2002, 2003 + * ----------------------------------------------------------------- **/ +#include "syslogdevice.h" +#include <string> + +#include <syslog.h> + +using namespace ::std; +using namespace ::Binc; + +//------------------------------------------------------------------------ +string SyslogDevice::ident; + +//------------------------------------------------------------------------ +SyslogDevice::SyslogDevice(int f, const char *i, int o, int fa) + : IODevice(f), option(o), facility(fa), priority(LOG_INFO) +{ + ident = i; + openlog(ident.c_str(), option, facility); +} + +//------------------------------------------------------------------------ +SyslogDevice::~SyslogDevice(void) +{ + closelog(); +} + +//------------------------------------------------------------------------ +string SyslogDevice::service(void) const +{ + return "log"; +} + +//------------------------------------------------------------------------ +bool SyslogDevice::waitForWrite(void) const +{ + return true; +} + +//------------------------------------------------------------------------ +bool SyslogDevice::waitForRead(void) const +{ + return false; +} + +//------------------------------------------------------------------------ +IODevice::WriteResult SyslogDevice::write(void) +{ + string out; + string::const_iterator i = outputBuffer.str().begin(); + string::const_iterator ie = outputBuffer.str().end(); + + for (; i != ie; ++i) { + if (*i == '\n') { + syslog(priority, out.c_str(), out.size()); + out = ""; + } else if (*i != '\r') + out += *i; + } + + if (out != "") syslog(priority, out.c_str(), out.size()); + + outputBuffer.clear(); + return WriteDone; +} + +//------------------------------------------------------------------------ +bool SyslogDevice::fillInputBuffer(void) +{ + return false; +} diff --git a/src/tools.cc b/src/tools.cc new file mode 100644 index 0000000..24c3757 --- /dev/null +++ b/src/tools.cc @@ -0,0 +1,44 @@ +/** -------------------------------------------------------------------- + * @file tools.cc + * @brief Implementation of miscellaneous tools. + * @author Andreas Aardal Hanssen + * @date 2002-2005 + * ------------------------------------------------------------------ **/ +#include <errno.h> +#include <cstring> + +#include "tools.h" + +using namespace ::std; +using namespace Binc; + +//------------------------------------------------------------------------ +Tools::Tools(void) +{ +} + +//------------------------------------------------------------------------ +Tools &Tools::getInstance(void) +{ + static Tools tools; + return tools; +} + +//------------------------------------------------------------------------ +void Tools::setenv(const string &key, const string &value) const +{ + char *c = strdup((key + "=" + value).c_str()); + putenv(c); +} + +//------------------------------------------------------------------------ +string Tools::getenv(const string &key) const +{ + static const string NIL = ""; + + const char *c = ::getenv((char *)key.c_str()); + if (c == 0) + return NIL; + else + return string(c); +} |