diff options
Diffstat (limited to '')
70 files changed, 21330 insertions, 0 deletions
diff --git a/src/lib-imap-client/Makefile.am b/src/lib-imap-client/Makefile.am new file mode 100644 index 0000000..8fe8a8d --- /dev/null +++ b/src/lib-imap-client/Makefile.am @@ -0,0 +1,53 @@ +noinst_LTLIBRARIES = libimap_client.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dns \ + -I$(top_srcdir)/src/lib-sasl \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap + +libimap_client_la_SOURCES = \ + imapc-client.c \ + imapc-connection.c \ + imapc-msgmap.c + +headers = \ + imapc-client.h \ + imapc-client-private.h \ + imapc-connection.h \ + imapc-msgmap.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers) + +test_programs = \ + test-imapc-client + +noinst_PROGRAMS = $(test_programs) + +test_deps = \ + $(noinst_LTLIBRARIES) \ + ../lib-ssl-iostream/libssl_iostream.la \ + ../lib-sasl/libsasl.la \ + ../lib-imap/libimap.la \ + ../lib-mail/libmail.la \ + ../lib-charset/libcharset.la \ + ../lib-dns/libdns.la \ + ../lib-test/libtest.la \ + ../lib/liblib.la + +test_libs = \ + $(test_deps) \ + $(MODULE_LIBS) + +test_imapc_client_SOURCES = test-imapc-client.c +test_imapc_client_LDADD = $(test_libs) +test_imapc_client_DEPENDENCIES = $(test_deps) + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done diff --git a/src/lib-imap-client/Makefile.in b/src/lib-imap-client/Makefile.in new file mode 100644 index 0000000..d3fe475 --- /dev/null +++ b/src/lib-imap-client/Makefile.in @@ -0,0 +1,880 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = $(am__EXEEXT_1) +subdir = src/lib-imap-client +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = test-imapc-client$(EXEEXT) +PROGRAMS = $(noinst_PROGRAMS) +LTLIBRARIES = $(noinst_LTLIBRARIES) +libimap_client_la_LIBADD = +am_libimap_client_la_OBJECTS = imapc-client.lo imapc-connection.lo \ + imapc-msgmap.lo +libimap_client_la_OBJECTS = $(am_libimap_client_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +am_test_imapc_client_OBJECTS = test-imapc-client.$(OBJEXT) +test_imapc_client_OBJECTS = $(am_test_imapc_client_OBJECTS) +am__DEPENDENCIES_1 = +am__DEPENDENCIES_2 = $(test_deps) $(am__DEPENDENCIES_1) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/imapc-client.Plo \ + ./$(DEPDIR)/imapc-connection.Plo ./$(DEPDIR)/imapc-msgmap.Plo \ + ./$(DEPDIR)/test-imapc-client.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libimap_client_la_SOURCES) $(test_imapc_client_SOURCES) +DIST_SOURCES = $(libimap_client_la_SOURCES) \ + $(test_imapc_client_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkginc_libdir)" +HEADERS = $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LTLIBRARIES = libimap_client.la +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dns \ + -I$(top_srcdir)/src/lib-sasl \ + -I$(top_srcdir)/src/lib-ssl-iostream \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap + +libimap_client_la_SOURCES = \ + imapc-client.c \ + imapc-connection.c \ + imapc-msgmap.c + +headers = \ + imapc-client.h \ + imapc-client-private.h \ + imapc-connection.h \ + imapc-msgmap.h + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = $(headers) +test_programs = \ + test-imapc-client + +test_deps = \ + $(noinst_LTLIBRARIES) \ + ../lib-ssl-iostream/libssl_iostream.la \ + ../lib-sasl/libsasl.la \ + ../lib-imap/libimap.la \ + ../lib-mail/libmail.la \ + ../lib-charset/libcharset.la \ + ../lib-dns/libdns.la \ + ../lib-test/libtest.la \ + ../lib/liblib.la + +test_libs = \ + $(test_deps) \ + $(MODULE_LIBS) + +test_imapc_client_SOURCES = test-imapc-client.c +test_imapc_client_LDADD = $(test_libs) +test_imapc_client_DEPENDENCIES = $(test_deps) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-imap-client/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib-imap-client/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libimap_client.la: $(libimap_client_la_OBJECTS) $(libimap_client_la_DEPENDENCIES) $(EXTRA_libimap_client_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libimap_client_la_OBJECTS) $(libimap_client_la_LIBADD) $(LIBS) + +test-imapc-client$(EXEEXT): $(test_imapc_client_OBJECTS) $(test_imapc_client_DEPENDENCIES) $(EXTRA_test_imapc_client_DEPENDENCIES) + @rm -f test-imapc-client$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imapc_client_OBJECTS) $(test_imapc_client_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-msgmap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imapc-client.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/imapc-client.Plo + -rm -f ./$(DEPDIR)/imapc-connection.Plo + -rm -f ./$(DEPDIR)/imapc-msgmap.Plo + -rm -f ./$(DEPDIR)/test-imapc-client.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/imapc-client.Plo + -rm -f ./$(DEPDIR)/imapc-connection.Plo + -rm -f ./$(DEPDIR)/imapc-msgmap.Plo + -rm -f ./$(DEPDIR)/test-imapc-client.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkginc_libHEADERS + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ + check-local clean clean-generic clean-libtool \ + clean-noinstLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-pkginc_libHEADERS install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-pkginc_libHEADERS + +.PRECIOUS: Makefile + + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib-imap-client/imapc-client-private.h b/src/lib-imap-client/imapc-client-private.h new file mode 100644 index 0000000..98c4e8e --- /dev/null +++ b/src/lib-imap-client/imapc-client-private.h @@ -0,0 +1,63 @@ +#ifndef IMAPC_CLIENT_PRIVATE_H +#define IMAPC_CLIENT_PRIVATE_H + +#include "imapc-client.h" + +#define IMAPC_CLIENT_IDLE_SEND_DELAY_MSECS 100 + +struct imapc_client_connection { + struct imapc_connection *conn; + struct imapc_client *client; + struct imapc_client_mailbox *box; +}; + +struct imapc_client { + pool_t pool; + int refcount; + + struct event *event; + struct imapc_client_settings set; + struct ssl_iostream_context *ssl_ctx; + + imapc_untagged_callback_t *untagged_callback; + void *untagged_context; + + imapc_state_change_callback_t *state_change_callback; + void *state_change_context; + + imapc_command_callback_t *login_callback; + void *login_context; + + ARRAY(struct imapc_client_connection *) conns; + bool logging_out; + + struct ioloop *ioloop; + bool stop_on_state_finish; +}; + +struct imapc_client_mailbox { + struct imapc_client *client; + struct imapc_connection *conn; + struct imapc_msgmap *msgmap; + struct timeout *to_send_idle; + + void (*reopen_callback)(void *context); + void *reopen_context; + + void *untagged_box_context; + + bool reconnect_ok; + bool reconnecting; + bool closing; +}; + +extern unsigned int imapc_client_cmd_tag_counter; + +void imapc_client_ref(struct imapc_client *client); +void imapc_client_unref(struct imapc_client **client); + +void imapc_command_set_mailbox(struct imapc_command *cmd, + struct imapc_client_mailbox *box); +void imapc_client_try_stop(struct imapc_client *client); + +#endif diff --git a/src/lib-imap-client/imapc-client.c b/src/lib-imap-client/imapc-client.c new file mode 100644 index 0000000..5806a80 --- /dev/null +++ b/src/lib-imap-client/imapc-client.c @@ -0,0 +1,584 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "ioloop.h" +#include "safe-mkstemp.h" +#include "iostream-ssl.h" +#include "imapc-msgmap.h" +#include "imapc-connection.h" +#include "imapc-client-private.h" + +#include <unistd.h> + +const char *imapc_command_state_names[] = { + "OK", "NO", "BAD", "(auth failed)", "(disconnected)" + +}; + +const struct imapc_capability_name imapc_capability_names[] = { + { "SASL-IR", IMAPC_CAPABILITY_SASL_IR }, + { "LITERAL+", IMAPC_CAPABILITY_LITERALPLUS }, + { "QRESYNC", IMAPC_CAPABILITY_QRESYNC }, + { "IDLE", IMAPC_CAPABILITY_IDLE }, + { "UIDPLUS", IMAPC_CAPABILITY_UIDPLUS }, + { "AUTH=PLAIN", IMAPC_CAPABILITY_AUTH_PLAIN }, + { "STARTTLS", IMAPC_CAPABILITY_STARTTLS }, + { "X-GM-EXT-1", IMAPC_CAPABILITY_X_GM_EXT_1 }, + { "CONDSTORE", IMAPC_CAPABILITY_CONDSTORE }, + { "NAMESPACE", IMAPC_CAPABILITY_NAMESPACE }, + { "UNSELECT", IMAPC_CAPABILITY_UNSELECT }, + { "ESEARCH", IMAPC_CAPABILITY_ESEARCH }, + { "WITHIN", IMAPC_CAPABILITY_WITHIN }, + { "QUOTA", IMAPC_CAPABILITY_QUOTA }, + { "ID", IMAPC_CAPABILITY_ID }, + { "SAVEDATE", IMAPC_CAPABILITY_SAVEDATE }, + + { "IMAP4REV1", IMAPC_CAPABILITY_IMAP4REV1 }, + { NULL, 0 } +}; + +unsigned int imapc_client_cmd_tag_counter = 0; + +static void +default_untagged_callback(const struct imapc_untagged_reply *reply ATTR_UNUSED, + void *context ATTR_UNUSED) +{ +} + +struct imapc_client * +imapc_client_init(const struct imapc_client_settings *set, + struct event *event_parent) +{ + struct imapc_client *client; + const char *error; + pool_t pool; + + i_assert(set->connect_retry_count == 0 || + set->connect_retry_interval_msecs > 0); + + pool = pool_alloconly_create("imapc client", 1024); + client = p_new(pool, struct imapc_client, 1); + client->pool = pool; + client->refcount = 1; + client->event = event_create(event_parent); + + client->set.debug = set->debug; + client->set.host = p_strdup(pool, set->host); + client->set.port = set->port; + client->set.master_user = p_strdup_empty(pool, set->master_user); + client->set.username = p_strdup(pool, set->username); + client->set.password = p_strdup(pool, set->password); + client->set.sasl_mechanisms = p_strdup(pool, set->sasl_mechanisms); + client->set.session_id_prefix = p_strdup(pool, set->session_id_prefix); + client->set.use_proxyauth = set->use_proxyauth; + client->set.dns_client_socket_path = + p_strdup(pool, set->dns_client_socket_path); + client->set.temp_path_prefix = + p_strdup(pool, set->temp_path_prefix); + client->set.rawlog_dir = p_strdup(pool, set->rawlog_dir); + client->set.max_idle_time = set->max_idle_time; + client->set.connect_timeout_msecs = set->connect_timeout_msecs != 0 ? + set->connect_timeout_msecs : + IMAPC_DEFAULT_CONNECT_TIMEOUT_MSECS; + client->set.connect_retry_count = set->connect_retry_count; + client->set.connect_retry_interval_msecs = set->connect_retry_interval_msecs; + client->set.cmd_timeout_msecs = set->cmd_timeout_msecs != 0 ? + set->cmd_timeout_msecs : IMAPC_DEFAULT_COMMAND_TIMEOUT_MSECS; + client->set.max_line_length = set->max_line_length != 0 ? + set->max_line_length : IMAPC_DEFAULT_MAX_LINE_LENGTH; + client->set.throttle_set = set->throttle_set; + + if (client->set.throttle_set.init_msecs == 0) + client->set.throttle_set.init_msecs = IMAPC_THROTTLE_DEFAULT_INIT_MSECS; + if (client->set.throttle_set.max_msecs == 0) + client->set.throttle_set.max_msecs = IMAPC_THROTTLE_DEFAULT_MAX_MSECS; + if (client->set.throttle_set.shrink_min_msecs == 0) + client->set.throttle_set.shrink_min_msecs = IMAPC_THROTTLE_DEFAULT_SHRINK_MIN_MSECS; + + if (set->ssl_mode != IMAPC_CLIENT_SSL_MODE_NONE) { + client->set.ssl_mode = set->ssl_mode; + ssl_iostream_settings_init_from(pool, &client->set.ssl_set, &set->ssl_set); + client->set.ssl_set.verbose_invalid_cert = !client->set.ssl_set.allow_invalid_cert; + if (ssl_iostream_client_context_cache_get(&client->set.ssl_set, + &client->ssl_ctx, + &error) < 0) { + i_error("imapc(%s:%u): Couldn't initialize SSL context: %s", + set->host, set->port, error); + } + } + client->untagged_callback = default_untagged_callback; + + p_array_init(&client->conns, pool, 8); + return client; +} + +void imapc_client_ref(struct imapc_client *client) +{ + i_assert(client->refcount > 0); + + client->refcount++; +} + +void imapc_client_unref(struct imapc_client **_client) +{ + struct imapc_client *client = *_client; + + *_client = NULL; + + i_assert(client->refcount > 0); + if (--client->refcount > 0) + return; + + if (client->ssl_ctx != NULL) + ssl_iostream_context_unref(&client->ssl_ctx); + event_unref(&client->event); + pool_unref(&client->pool); +} + +void imapc_client_disconnect(struct imapc_client *client) +{ + struct imapc_client_connection *const *conns, *conn; + unsigned int i, count; + + conns = array_get(&client->conns, &count); + for (i = count; i > 0; i--) { + conn = conns[i-1]; + array_delete(&client->conns, i-1, 1); + + i_assert(imapc_connection_get_mailbox(conn->conn) == NULL); + imapc_connection_deinit(&conn->conn); + i_free(conn); + } +} + +void imapc_client_deinit(struct imapc_client **_client) +{ + struct imapc_client *client = *_client; + + imapc_client_disconnect(client); + imapc_client_unref(_client); +} + +void imapc_client_register_untagged(struct imapc_client *client, + imapc_untagged_callback_t *callback, + void *context) +{ + client->untagged_callback = callback; + client->untagged_context = context; +} + +static void imapc_client_run_pre(struct imapc_client *client) +{ + struct imapc_client_connection *conn; + struct ioloop *prev_ioloop = current_ioloop; + + i_assert(client->ioloop == NULL); + + client->ioloop = io_loop_create(); + io_loop_set_running(client->ioloop); + + array_foreach_elem(&client->conns, conn) { + imapc_connection_ioloop_changed(conn->conn); + if (imapc_connection_get_state(conn->conn) == IMAPC_CONNECTION_STATE_DISCONNECTED) + imapc_connection_connect(conn->conn); + } + + if (io_loop_is_running(client->ioloop)) + io_loop_run(client->ioloop); + io_loop_set_current(prev_ioloop); +} + +static void imapc_client_run_post(struct imapc_client *client) +{ + struct imapc_client_connection *conn; + struct ioloop *ioloop = client->ioloop; + + client->ioloop = NULL; + array_foreach_elem(&client->conns, conn) + imapc_connection_ioloop_changed(conn->conn); + + io_loop_set_current(ioloop); + io_loop_destroy(&ioloop); +} + +void imapc_client_run(struct imapc_client *client) +{ + imapc_client_run_pre(client); + imapc_client_run_post(client); +} + +void imapc_client_stop(struct imapc_client *client) +{ + if (client->ioloop != NULL) + io_loop_stop(client->ioloop); +} + +void imapc_client_try_stop(struct imapc_client *client) +{ + struct imapc_client_connection *conn; + array_foreach_elem(&client->conns, conn) + if (imapc_connection_get_state(conn->conn) != IMAPC_CONNECTION_STATE_DISCONNECTED) + return; + imapc_client_stop(client); +} + +bool imapc_client_is_running(struct imapc_client *client) +{ + return client->ioloop != NULL; +} + +static void imapc_client_login_callback(const struct imapc_command_reply *reply, + void *context) +{ + struct imapc_client_connection *conn = context; + struct imapc_client *client = conn->client; + struct imapc_client_mailbox *box = conn->box; + + if (box != NULL && box->reconnecting) { + box->reconnecting = FALSE; + + if (reply->state == IMAPC_COMMAND_STATE_OK) { + /* reopen the mailbox */ + box->reopen_callback(box->reopen_context); + } else { + imapc_connection_abort_commands(box->conn, NULL, FALSE); + } + } + + /* call the login callback only once */ + if (client->login_callback != NULL) { + imapc_command_callback_t *callback = client->login_callback; + void *context = client->login_context; + + client->login_callback = NULL; + client->login_context = NULL; + callback(reply, context); + } +} + +static struct imapc_client_connection * +imapc_client_add_connection(struct imapc_client *client) +{ + struct imapc_client_connection *conn; + + conn = i_new(struct imapc_client_connection, 1); + conn->client = client; + conn->conn = imapc_connection_init(client, imapc_client_login_callback, + conn); + array_push_back(&client->conns, &conn); + return conn; +} + +static struct imapc_connection * +imapc_client_find_connection(struct imapc_client *client) +{ + struct imapc_client_connection *const *connp; + + /* FIXME: stupid algorithm */ + if (array_count(&client->conns) == 0) + return imapc_client_add_connection(client)->conn; + connp = array_front(&client->conns); + return (*connp)->conn; +} + +struct imapc_command * +imapc_client_cmd(struct imapc_client *client, + imapc_command_callback_t *callback, void *context) +{ + struct imapc_connection *conn; + + conn = imapc_client_find_connection(client); + return imapc_connection_cmd(conn, callback, context); +} + +static struct imapc_client_connection * +imapc_client_get_unboxed_connection(struct imapc_client *client) +{ + struct imapc_client_connection *const *conns; + unsigned int i, count; + + conns = array_get(&client->conns, &count); + for (i = 0; i < count; i++) { + if (conns[i]->box == NULL) + return conns[i]; + } + return imapc_client_add_connection(client); +} + + +void imapc_client_login(struct imapc_client *client) +{ + struct imapc_client_connection *conn; + + i_assert(client->login_callback != NULL); + i_assert(array_count(&client->conns) == 0); + + conn = imapc_client_add_connection(client); + imapc_connection_connect(conn->conn); +} + +struct imapc_logout_ctx { + struct imapc_client *client; + unsigned int logout_count; +}; + +static void +imapc_client_logout_callback(const struct imapc_command_reply *reply ATTR_UNUSED, + void *context) +{ + struct imapc_logout_ctx *ctx = context; + + i_assert(ctx->logout_count > 0); + + if (--ctx->logout_count == 0) + imapc_client_stop(ctx->client); +} + +void imapc_client_logout(struct imapc_client *client) +{ + struct imapc_logout_ctx ctx = { .client = client }; + struct imapc_client_connection *conn; + struct imapc_command *cmd; + + client->logging_out = TRUE; + + /* send LOGOUT to all connections */ + array_foreach_elem(&client->conns, conn) { + if (imapc_connection_get_state(conn->conn) == IMAPC_CONNECTION_STATE_DISCONNECTED) + continue; + imapc_connection_set_no_reconnect(conn->conn); + ctx.logout_count++; + cmd = imapc_connection_cmd(conn->conn, + imapc_client_logout_callback, &ctx); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN | + IMAPC_COMMAND_FLAG_LOGOUT); + imapc_command_send(cmd, "LOGOUT"); + } + + /* wait for LOGOUT to finish */ + while (ctx.logout_count > 0) + imapc_client_run(client); + + /* we should have disconnected all clients already, but if there were + any timeouts there may be some clients left. */ + imapc_client_disconnect(client); +} + +struct imapc_client_mailbox * +imapc_client_mailbox_open(struct imapc_client *client, + void *untagged_box_context) +{ + struct imapc_client_mailbox *box; + struct imapc_client_connection *conn; + + box = i_new(struct imapc_client_mailbox, 1); + box->client = client; + box->untagged_box_context = untagged_box_context; + conn = imapc_client_get_unboxed_connection(client); + conn->box = box; + box->conn = conn->conn; + box->msgmap = imapc_msgmap_init(); + /* if we get disconnected before the SELECT is finished, allow + one reconnect retry. */ + box->reconnect_ok = TRUE; + return box; +} + +void imapc_client_mailbox_set_reopen_cb(struct imapc_client_mailbox *box, + void (*callback)(void *context), + void *context) +{ + box->reopen_callback = callback; + box->reopen_context = context; +} + +bool imapc_client_mailbox_can_reconnect(struct imapc_client_mailbox *box) +{ + /* the reconnect_ok flag attempts to avoid infinite reconnection loops + to a server that keeps disconnecting us (e.g. some of the commands + we send keeps crashing it always) */ + return box->reopen_callback != NULL && box->reconnect_ok; +} + +void imapc_client_mailbox_reconnect(struct imapc_client_mailbox *box, + const char *errmsg) +{ + imapc_connection_try_reconnect(box->conn, errmsg, 0, FALSE); +} + +void imapc_client_mailbox_close(struct imapc_client_mailbox **_box) +{ + struct imapc_client_mailbox *box = *_box; + struct imapc_client_connection *conn; + + box->closing = TRUE; + + /* cancel any pending commands */ + imapc_connection_unselect(box, FALSE); + + if (box->reconnecting) { + /* need to abort the reconnection so it won't try to access + the box */ + imapc_connection_disconnect(box->conn); + } + + /* set this only after unselect, which may cancel some commands that + reference this box */ + *_box = NULL; + + array_foreach_elem(&box->client->conns, conn) { + if (conn->box == box) { + conn->box = NULL; + break; + } + } + + imapc_msgmap_deinit(&box->msgmap); + timeout_remove(&box->to_send_idle); + i_free(box); +} + +struct imapc_command * +imapc_client_mailbox_cmd(struct imapc_client_mailbox *box, + imapc_command_callback_t *callback, void *context) +{ + struct imapc_command *cmd; + + i_assert(!box->closing); + + cmd = imapc_connection_cmd(box->conn, callback, context); + imapc_command_set_mailbox(cmd, box); + return cmd; +} + +struct imapc_msgmap * +imapc_client_mailbox_get_msgmap(struct imapc_client_mailbox *box) +{ + return box->msgmap; +} + +static void imapc_client_mailbox_idle_send(struct imapc_client_mailbox *box) +{ + timeout_remove(&box->to_send_idle); + if (imapc_client_mailbox_is_opened(box)) + imapc_connection_idle(box->conn); +} + +void imapc_client_mailbox_idle(struct imapc_client_mailbox *box) +{ + /* send the IDLE with a delay to avoid unnecessary IDLEs that are + immediately aborted */ + if (box->to_send_idle == NULL && imapc_client_mailbox_is_opened(box)) { + box->to_send_idle = + timeout_add_short(IMAPC_CLIENT_IDLE_SEND_DELAY_MSECS, + imapc_client_mailbox_idle_send, box); + } + /* we're done with all work at this point. */ + box->reconnect_ok = TRUE; +} + +bool imapc_client_mailbox_is_opened(struct imapc_client_mailbox *box) +{ + struct imapc_client_mailbox *selected_box; + + if (box->closing || + imapc_connection_get_state(box->conn) != IMAPC_CONNECTION_STATE_DONE) + return FALSE; + + selected_box = imapc_connection_get_mailbox(box->conn); + if (selected_box != box) { + if (selected_box != NULL) + i_error("imapc: Selected mailbox changed unexpectedly"); + return FALSE; + } + return TRUE; +} + +static bool +imapc_client_get_any_capabilities(struct imapc_client *client, + enum imapc_capability *capabilities_r) +{ + struct imapc_client_connection *conn; + + array_foreach_elem(&client->conns, conn) { + if (imapc_connection_get_state(conn->conn) == IMAPC_CONNECTION_STATE_DONE) { + *capabilities_r = imapc_connection_get_capabilities(conn->conn); + return TRUE; + } + } + return FALSE; +} + +int imapc_client_get_capabilities(struct imapc_client *client, + enum imapc_capability *capabilities_r) +{ + /* try to find a connection that is already logged in */ + if (imapc_client_get_any_capabilities(client, capabilities_r)) + return 0; + + /* if there are no connections yet, create one */ + if (array_count(&client->conns) == 0) + (void)imapc_client_add_connection(client); + + /* wait for any of the connections to login */ + client->stop_on_state_finish = TRUE; + imapc_client_run(client); + client->stop_on_state_finish = FALSE; + if (imapc_client_get_any_capabilities(client, capabilities_r)) + return 0; + + /* failed */ + return -1; +} + +int imapc_client_create_temp_fd(struct imapc_client *client, + const char **path_r) +{ + string_t *path; + int fd; + + if (client->set.temp_path_prefix == NULL) { + i_error("imapc: temp_path_prefix not set, " + "can't create temp file"); + return -1; + } + + path = t_str_new(128); + str_append(path, client->set.temp_path_prefix); + fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1); + if (fd == -1) { + i_error("safe_mkstemp(%s) failed: %m", str_c(path)); + return -1; + } + + /* we just want the fd, unlink it */ + if (i_unlink(str_c(path)) < 0) { + /* shouldn't happen.. */ + i_close_fd(&fd); + return -1; + } + *path_r = str_c(path); + return fd; +} + +void imapc_client_register_state_change_callback(struct imapc_client *client, + imapc_state_change_callback_t *cb, + void *context) +{ + i_assert(client->state_change_callback == NULL); + i_assert(client->state_change_context == NULL); + + client->state_change_callback = cb; + client->state_change_context = context; +} + +void +imapc_client_set_login_callback(struct imapc_client *client, + imapc_command_callback_t *callback, void *context) +{ + client->login_callback = callback; + client->login_context = context; +} + diff --git a/src/lib-imap-client/imapc-client.h b/src/lib-imap-client/imapc-client.h new file mode 100644 index 0000000..5c81be6 --- /dev/null +++ b/src/lib-imap-client/imapc-client.h @@ -0,0 +1,250 @@ +#ifndef IMAPC_CLIENT_H +#define IMAPC_CLIENT_H + +#include "net.h" +#include "iostream-ssl.h" + +/* IMAP RFC defines this to be at least 30 minutes. */ +#define IMAPC_DEFAULT_MAX_IDLE_TIME (60*29) + +enum imapc_command_state { + IMAPC_COMMAND_STATE_OK = 0, + IMAPC_COMMAND_STATE_NO, + IMAPC_COMMAND_STATE_BAD, + /* Authentication to IMAP server failed (NO or BAD) */ + IMAPC_COMMAND_STATE_AUTH_FAILED, + /* Client was unexpectedly disconnected. */ + IMAPC_COMMAND_STATE_DISCONNECTED +}; +extern const char *imapc_command_state_names[]; + +enum imapc_capability { + IMAPC_CAPABILITY_SASL_IR = 0x01, + IMAPC_CAPABILITY_LITERALPLUS = 0x02, + IMAPC_CAPABILITY_QRESYNC = 0x04, + IMAPC_CAPABILITY_IDLE = 0x08, + IMAPC_CAPABILITY_UIDPLUS = 0x10, + IMAPC_CAPABILITY_AUTH_PLAIN = 0x20, + IMAPC_CAPABILITY_STARTTLS = 0x40, + IMAPC_CAPABILITY_X_GM_EXT_1 = 0x80, + IMAPC_CAPABILITY_CONDSTORE = 0x100, + IMAPC_CAPABILITY_NAMESPACE = 0x200, + IMAPC_CAPABILITY_UNSELECT = 0x400, + IMAPC_CAPABILITY_ESEARCH = 0x800, + IMAPC_CAPABILITY_WITHIN = 0x1000, + IMAPC_CAPABILITY_QUOTA = 0x2000, + IMAPC_CAPABILITY_ID = 0x4000, + IMAPC_CAPABILITY_SAVEDATE = 0x8000, + + IMAPC_CAPABILITY_IMAP4REV1 = 0x40000000 +}; +struct imapc_capability_name { + const char *name; + enum imapc_capability capability; +}; +extern const struct imapc_capability_name imapc_capability_names[]; + +enum imapc_command_flags { + /* The command changes the selected mailbox (SELECT, EXAMINE) */ + IMAPC_COMMAND_FLAG_SELECT = 0x01, + /* The command is sent to server before login (or is the login + command itself). Non-prelogin commands will be queued until login + is successful. */ + IMAPC_COMMAND_FLAG_PRELOGIN = 0x02, + /* Allow command to be automatically retried if disconnected before it + finishes. */ + IMAPC_COMMAND_FLAG_RETRIABLE = 0x04, + /* This is the LOGOUT command. Use a small timeout for it. */ + IMAPC_COMMAND_FLAG_LOGOUT = 0x08, + /* Command is being resent after a reconnection. */ + IMAPC_COMMAND_FLAG_RECONNECTED = 0x10 +}; + +enum imapc_client_ssl_mode { + IMAPC_CLIENT_SSL_MODE_NONE, + IMAPC_CLIENT_SSL_MODE_IMMEDIATE, + IMAPC_CLIENT_SSL_MODE_STARTTLS +}; + +#define IMAPC_DEFAULT_CONNECT_TIMEOUT_MSECS (1000*30) +#define IMAPC_DEFAULT_COMMAND_TIMEOUT_MSECS (1000*60*5) +#define IMAPC_DEFAULT_MAX_LINE_LENGTH (SIZE_MAX) + +struct imapc_throttling_settings { + unsigned int init_msecs; + unsigned int max_msecs; + unsigned int shrink_min_msecs; +}; + +struct imapc_client_settings { + const char *host; + in_port_t port; + + const char *master_user; + const char *username; + const char *password; + /* Space-separated list of SASL mechanisms to try (in the specified + order). The default is to use only LOGIN command or SASL PLAIN. */ + const char *sasl_mechanisms; + bool use_proxyauth; /* Use Sun/Oracle PROXYAUTH command */ + unsigned int max_idle_time; + /* If ID capability is advertised, send a unique "x-session-ext-id", + which begins with this prefix. */ + const char *session_id_prefix; + + const char *dns_client_socket_path; + const char *temp_path_prefix; + struct ssl_iostream_settings ssl_set; + + enum imapc_client_ssl_mode ssl_mode; + + const char *rawlog_dir; + bool debug; + + /* Timeout for logging in. 0 = default. */ + unsigned int connect_timeout_msecs; + /* Number of retries, -1 = infinity */ + unsigned int connect_retry_count; + /* Interval between retries, must be > 0 if retries > 0 */ + unsigned int connect_retry_interval_msecs; + + /* Timeout for IMAP commands. Reset every time more data is being + sent or received. 0 = default. */ + unsigned int cmd_timeout_msecs; + + /* Maximum allowed line length (not including literals read as + streams). 0 = unlimited. */ + size_t max_line_length; + + struct imapc_throttling_settings throttle_set; +}; + +struct imapc_command_reply { + enum imapc_command_state state; + /* "[RESP TEXT]" produces key=RESP, value=TEXT. + "[RESP]" produces key=RESP, value=NULL + otherwise both are NULL */ + const char *resp_text_key, *resp_text_value; + /* The full tagged reply, including [RESP TEXT]. */ + const char *text_full; + /* Tagged reply text without [RESP TEXT] */ + const char *text_without_resp; +}; + +struct imapc_arg_file { + /* file descriptor containing the value */ + int fd; + + /* parent_arg.list[list_idx] points to the IMAP_ARG_LITERAL_SIZE + argument */ + const struct imap_arg *parent_arg; + unsigned int list_idx; +}; + +struct imapc_untagged_reply { + /* name of the untagged reply, e.g. EXISTS */ + const char *name; + /* number at the beginning of the reply, or 0 if there wasn't any. + Set for EXISTS, EXPUNGE, etc. */ + uint32_t num; + /* the rest of the reply can be read from these args. */ + const struct imap_arg *args; + /* arguments whose contents are stored into files. only + "FETCH (BODY[" arguments can be here. */ + const struct imapc_arg_file *file_args; + unsigned int file_args_count; + + /* "* OK [RESP TEXT]" produces key=RESP, value=TEXT. + "* OK [RESP]" produces key=RESP, value=NULL + otherwise both are NULL */ + const char *resp_text_key, *resp_text_value; + + /* If this reply occurred while a mailbox was selected, this contains + the mailbox's untagged_context. */ + void *untagged_box_context; +}; + +enum imapc_state_change_event { + IMAPC_STATE_CHANGE_AUTH_OK, + IMAPC_STATE_CHANGE_AUTH_FAILED, +}; + +/* Called when tagged reply is received for command. */ +typedef void imapc_command_callback_t(const struct imapc_command_reply *reply, + void *context); +/* Called each time untagged input is received. */ +typedef void imapc_untagged_callback_t(const struct imapc_untagged_reply *reply, + void *context); +typedef void imapc_state_change_callback_t(void *context, + enum imapc_state_change_event event, + const char *error); + +struct imapc_client * +imapc_client_init(const struct imapc_client_settings *set, + struct event *event_parent); +void imapc_client_disconnect(struct imapc_client *client); +void imapc_client_deinit(struct imapc_client **client); + +/* Set login callback, must be set before calling other commands. + This is called only for the first login, not for any reconnects or if there + are multiple connections created. */ +void +imapc_client_set_login_callback(struct imapc_client *client, + imapc_command_callback_t *callback, void *context); +/* Explicitly login to server (also done automatically). */ +void imapc_client_login(struct imapc_client *client); +/* Send a LOGOUT and wait for disconnection. */ +void imapc_client_logout(struct imapc_client *client); + +struct imapc_command * +imapc_client_cmd(struct imapc_client *client, + imapc_command_callback_t *callback, void *context); +void imapc_command_set_flags(struct imapc_command *cmd, + enum imapc_command_flags flags); +bool imapc_command_connection_is_selected(struct imapc_command *cmd); +void imapc_command_send(struct imapc_command *cmd, const char *cmd_str); +void imapc_command_sendf(struct imapc_command *cmd, const char *cmd_fmt, ...) + ATTR_FORMAT(2, 3); +void imapc_command_sendvf(struct imapc_command *cmd, + const char *cmd_fmt, va_list args) ATTR_FORMAT(2, 0); +const char *imapc_command_get_tag(struct imapc_command *cmd); +void imapc_command_abort(struct imapc_command **cmd); + +void imapc_client_register_untagged(struct imapc_client *client, + imapc_untagged_callback_t *callback, + void *context); + +void imapc_client_run(struct imapc_client *client); +void imapc_client_stop(struct imapc_client *client); +bool imapc_client_is_running(struct imapc_client *client); + +struct imapc_client_mailbox * +imapc_client_mailbox_open(struct imapc_client *client, + void *untagged_box_context); +void imapc_client_mailbox_set_reopen_cb(struct imapc_client_mailbox *box, + void (*callback)(void *context), + void *context); +void imapc_client_mailbox_close(struct imapc_client_mailbox **box); +bool imapc_client_mailbox_can_reconnect(struct imapc_client_mailbox *box); +void imapc_client_mailbox_reconnect(struct imapc_client_mailbox *box, + const char *errmsg); +struct imapc_command * +imapc_client_mailbox_cmd(struct imapc_client_mailbox *box, + imapc_command_callback_t *callback, void *context); +struct imapc_msgmap * +imapc_client_mailbox_get_msgmap(struct imapc_client_mailbox *box); + +void imapc_client_mailbox_idle(struct imapc_client_mailbox *box); +bool imapc_client_mailbox_is_opened(struct imapc_client_mailbox *box); + +int imapc_client_get_capabilities(struct imapc_client *client, + enum imapc_capability *capabilities_r); + +int imapc_client_create_temp_fd(struct imapc_client *client, + const char **path_r); + +void imapc_client_register_state_change_callback(struct imapc_client *client, + imapc_state_change_callback_t *cb, + void *context); + +#endif diff --git a/src/lib-imap-client/imapc-connection.c b/src/lib-imap-client/imapc-connection.c new file mode 100644 index 0000000..f025403 --- /dev/null +++ b/src/lib-imap-client/imapc-connection.c @@ -0,0 +1,2557 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "net.h" +#include "istream.h" +#include "ostream.h" +#include "base64.h" +#include "write-full.h" +#include "str.h" +#include "time-util.h" +#include "dns-lookup.h" +#include "dsasl-client.h" +#include "iostream-rawlog.h" +#include "iostream-ssl.h" +#include "imap-quote.h" +#include "imap-util.h" +#include "imap-parser.h" +#include "imapc-client-private.h" +#include "imapc-connection.h" + +#include <unistd.h> +#include <ctype.h> + +#define IMAPC_COMMAND_STATE_AUTHENTICATE_CONTINUE 10000 +#define IMAPC_MAX_INLINE_LITERAL_SIZE (1024*32) +/* If LOGOUT reply takes longer than this, disconnect. */ +#define IMAPC_LOGOUT_TIMEOUT_MSECS 5000 + +enum imapc_input_state { + IMAPC_INPUT_STATE_NONE = 0, + IMAPC_INPUT_STATE_PLUS, + IMAPC_INPUT_STATE_UNTAGGED, + IMAPC_INPUT_STATE_UNTAGGED_NUM, + IMAPC_INPUT_STATE_TAGGED +}; + +struct imapc_command_stream { + unsigned int pos; + uoff_t size; + struct istream *input; +}; + +struct imapc_command { + pool_t pool; + buffer_t *data; + unsigned int send_pos; + unsigned int tag; + + enum imapc_command_flags flags; + struct imapc_connection *conn; + /* If non-NULL, points to the mailbox where this command should be + executed */ + struct imapc_client_mailbox *box; + + ARRAY(struct imapc_command_stream) streams; + + imapc_command_callback_t *callback; + void *context; + + /* This is the AUTHENTICATE command */ + bool authenticate:1; + /* This is the IDLE command */ + bool idle:1; + /* Waiting for '+' literal reply before we can continue */ + bool wait_for_literal:1; + /* Command is fully sent to server */ + bool sent:1; +}; +ARRAY_DEFINE_TYPE(imapc_command, struct imapc_command *); + +struct imapc_connection_literal { + char *temp_path; + int fd; + uoff_t bytes_left; + + const struct imap_arg *parent_arg; + unsigned int list_idx; +}; + +struct imapc_connection { + struct imapc_client *client; + char *name; + int refcount; + + int fd; + struct io *io; + struct istream *input, *raw_input; + struct ostream *output, *raw_output; + struct imap_parser *parser; + struct timeout *to; + struct timeout *to_output; + struct dns_lookup *dns_lookup; + struct dsasl_client *sasl_client; + + struct ssl_iostream *ssl_iostream; + + int (*input_callback)(struct imapc_connection *conn); + enum imapc_input_state input_state; + unsigned int cur_tag; + uint32_t cur_num; + struct timeval last_connect; + unsigned int reconnect_count; + + /* If QRESYNC isn't used, this is set immediately after issuing + SELECT/EXAMINE. We could differentiate better whether a mailbox is + "being selected" vs "fully selected", but that code is already in + the imapc-storage side so it would have to be moved or duplicated + here. And since nothing actually cares about this distinction (yet), + don't bother with it for now. This is set to NULL when the mailbox + is closed from imapc-storage point of view, even if the server is + still in selected state (see selected_on_server). */ + struct imapc_client_mailbox *selected_box; + /* If QRESYNC is used, this is set when SELECT/EXAMINE is issued. + If the server is already in selected state, the selected_box is most + likely already NULL at this point, because imapc-storage has closed + it. */ + struct imapc_client_mailbox *qresync_selecting_box; + enum imapc_connection_state state; + char *disconnect_reason; + + enum imapc_capability capabilities; + char **capabilities_list; + + imapc_command_callback_t *login_callback; + void *login_context; + + /* commands pending in queue to be sent */ + ARRAY_TYPE(imapc_command) cmd_send_queue; + /* commands that have been sent, waiting for their tagged reply */ + ARRAY_TYPE(imapc_command) cmd_wait_list; + /* commands that were already sent, but were aborted since (due to + unselecting mailbox). */ + ARRAY_TYPE(seq_range) aborted_cmd_tags; + unsigned int reconnect_command_count; + + unsigned int ips_count, prev_connect_idx; + struct ip_addr *ips; + + struct imapc_connection_literal literal; + ARRAY(struct imapc_arg_file) literal_files; + + unsigned int throttle_msecs; + unsigned int throttle_shrink_msecs; + unsigned int last_successful_throttle_msecs; + bool throttle_pending; + struct timeval throttle_end_timeval; + struct timeout *to_throttle, *to_throttle_shrink; + + bool reconnecting:1; + bool reconnect_waiting:1; + bool reconnect_ok:1; + bool idling:1; + bool idle_stopping:1; + bool idle_plus_waiting:1; + bool select_waiting_reply:1; + /* TRUE if IMAP server is in SELECTED state. select_box may be NULL + though, if we already closed the mailbox from client point of + view. */ + bool selected_on_server:1; +}; + +static void imapc_connection_capability_cb(const struct imapc_command_reply *reply, + void *context); +static int imapc_connection_output(struct imapc_connection *conn); +static int imapc_connection_ssl_init(struct imapc_connection *conn); +static void imapc_command_free(struct imapc_command *cmd); +static void imapc_command_send_more(struct imapc_connection *conn); +static void +imapc_login_callback(struct imapc_connection *conn, + const struct imapc_command_reply *reply); + +static void +imapc_auth_ok(struct imapc_connection *conn) +{ + if (conn->client->set.debug) + i_debug("imapc(%s): Authenticated successfully", conn->name); + + if (conn->client->state_change_callback == NULL) + return; + + conn->client->state_change_callback(conn->client->state_change_context, + IMAPC_STATE_CHANGE_AUTH_OK, NULL); +} + +static void +imapc_auth_failed(struct imapc_connection *conn, const struct imapc_command_reply *_reply, + const char *error) +{ + struct imapc_command_reply reply = *_reply; + + reply.text_without_resp = reply.text_full = + t_strdup_printf("Authentication failed: %s", error); + if (reply.state != IMAPC_COMMAND_STATE_DISCONNECTED) { + reply.state = IMAPC_COMMAND_STATE_AUTH_FAILED; + i_error("imapc(%s): %s", conn->name, reply.text_full); + } + imapc_login_callback(conn, &reply); + + if (conn->client->state_change_callback == NULL) + return; + + conn->client->state_change_callback(conn->client->state_change_context, + IMAPC_STATE_CHANGE_AUTH_FAILED, + error); +} + +struct imapc_connection * +imapc_connection_init(struct imapc_client *client, + imapc_command_callback_t *login_callback, + void *login_context) +{ + struct imapc_connection *conn; + + conn = i_new(struct imapc_connection, 1); + conn->refcount = 1; + conn->client = client; + conn->login_callback = login_callback; + conn->login_context = login_context; + conn->fd = -1; + conn->name = i_strdup_printf("%s:%u", client->set.host, + client->set.port); + conn->literal.fd = -1; + conn->reconnect_ok = (client->set.connect_retry_count>0); + i_array_init(&conn->cmd_send_queue, 8); + i_array_init(&conn->cmd_wait_list, 32); + i_array_init(&conn->literal_files, 4); + i_array_init(&conn->aborted_cmd_tags, 8); + + if (client->set.debug) + i_debug("imapc(%s): Created new connection", conn->name); + + imapc_client_ref(client); + return conn; +} + +static void imapc_connection_ref(struct imapc_connection *conn) +{ + i_assert(conn->refcount > 0); + + conn->refcount++; +} + +static void imapc_connection_unref(struct imapc_connection **_conn) +{ + struct imapc_connection *conn = *_conn; + + i_assert(conn->refcount > 0); + + *_conn = NULL; + if (--conn->refcount > 0) + return; + + i_assert(conn->disconnect_reason == NULL); + + if (conn->capabilities_list != NULL) + p_strsplit_free(default_pool, conn->capabilities_list); + array_free(&conn->cmd_send_queue); + array_free(&conn->cmd_wait_list); + array_free(&conn->literal_files); + array_free(&conn->aborted_cmd_tags); + imapc_client_unref(&conn->client); + i_free(conn->ips); + i_free(conn->name); + i_free(conn); +} + +void imapc_connection_deinit(struct imapc_connection **_conn) +{ + imapc_connection_disconnect(*_conn); + imapc_connection_unref(_conn); +} + +void imapc_connection_ioloop_changed(struct imapc_connection *conn) +{ + if (conn->io != NULL) + conn->io = io_loop_move_io(&conn->io); + if (conn->to != NULL) + conn->to = io_loop_move_timeout(&conn->to); + if (conn->to_throttle != NULL) + conn->to_throttle = io_loop_move_timeout(&conn->to_throttle); + if (conn->to_throttle_shrink != NULL) + conn->to_throttle_shrink = io_loop_move_timeout(&conn->to_throttle_shrink); + if (conn->output != NULL) + o_stream_switch_ioloop(conn->output); + if (conn->dns_lookup != NULL) + dns_lookup_switch_ioloop(conn->dns_lookup); + + if (conn->client->ioloop == NULL && conn->to_output != NULL) { + /* we're only once moving the to_output to the main ioloop, + since timeout moves currently also reset the timeout. + (the rest of the times this is a no-op) */ + conn->to_output = io_loop_move_timeout(&conn->to_output); + } +} + +static const char *imapc_command_get_readable(struct imapc_command *cmd) +{ + string_t *str = t_str_new(256); + const unsigned char *data = cmd->data->data; + unsigned int i; + + for (i = 0; i < cmd->data->used; i++) { + if (data[i] != '\r' && data[i] != '\n') + str_append_c(str, data[i]); + } + return str_c(str); +} + +static void +imapc_connection_abort_commands_array(ARRAY_TYPE(imapc_command) *cmd_array, + ARRAY_TYPE(imapc_command) *dest_array, + struct imapc_client_mailbox *only_box, + bool keep_retriable) +{ + struct imapc_command *cmd; + unsigned int i; + + for (i = 0; i < array_count(cmd_array); ) { + cmd = array_idx_elem(cmd_array, i); + + if (cmd->box != only_box && only_box != NULL) + i++; + else if (keep_retriable && + (cmd->flags & IMAPC_COMMAND_FLAG_RETRIABLE) != 0) { + cmd->send_pos = 0; + cmd->wait_for_literal = 0; + cmd->flags |= IMAPC_COMMAND_FLAG_RECONNECTED; + i++; + } else { + array_delete(cmd_array, i, 1); + array_push_back(dest_array, &cmd); + } + } +} + +void imapc_connection_abort_commands(struct imapc_connection *conn, + struct imapc_client_mailbox *only_box, + bool keep_retriable) +{ + struct imapc_command *cmd; + ARRAY_TYPE(imapc_command) tmp_array; + struct imapc_command_reply reply; + + t_array_init(&tmp_array, 8); + imapc_connection_abort_commands_array(&conn->cmd_wait_list, &tmp_array, + only_box, keep_retriable); + imapc_connection_abort_commands_array(&conn->cmd_send_queue, &tmp_array, + only_box, keep_retriable); + + if (array_count(&conn->cmd_wait_list) > 0 && only_box == NULL) { + /* need to move all the waiting commands to send queue */ + array_append_array(&conn->cmd_wait_list, + &conn->cmd_send_queue); + array_clear(&conn->cmd_send_queue); + array_append_array(&conn->cmd_send_queue, + &conn->cmd_wait_list); + array_clear(&conn->cmd_wait_list); + } + + /* abort the commands. we'll do it here later so that if the + callback recurses us back here we don't crash */ + i_zero(&reply); + reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; + if (only_box != NULL) { + reply.text_without_resp = reply.text_full = + "Unselecting mailbox"; + } else { + reply.text_without_resp = reply.text_full = + "Disconnected from server"; + } + array_foreach_elem(&tmp_array, cmd) { + if (cmd->sent && conn->state == IMAPC_CONNECTION_STATE_DONE) { + /* We're not disconnected, so the reply will still + come. Remember that it needs to be ignored. */ + seq_range_array_add(&conn->aborted_cmd_tags, cmd->tag); + } + cmd->callback(&reply, cmd->context); + imapc_command_free(cmd); + } + if (array_count(&conn->cmd_wait_list) == 0) + timeout_remove(&conn->to); +} + +static void +imapc_login_callback(struct imapc_connection *conn, + const struct imapc_command_reply *reply) +{ + if (conn->login_callback != NULL) + conn->login_callback(reply, conn->login_context); +} + +static void imapc_connection_set_state(struct imapc_connection *conn, + enum imapc_connection_state state) +{ + struct imapc_command_reply reply; + + conn->state = state; + + switch (state) { + case IMAPC_CONNECTION_STATE_DISCONNECTED: + i_zero(&reply); + reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; + reply.text_full = "Disconnected from server"; + if (conn->disconnect_reason != NULL) { + reply.text_full = t_strdup_printf("%s: %s", + reply.text_full, conn->disconnect_reason); + i_free_and_null(conn->disconnect_reason); + } + reply.text_without_resp = reply.text_full; + if (!conn->reconnecting) { + imapc_login_callback(conn, &reply); + i_free(conn->ips); + conn->ips_count = 0; + } + array_clear(&conn->aborted_cmd_tags); + conn->idling = FALSE; + conn->idle_plus_waiting = FALSE; + conn->idle_stopping = FALSE; + + conn->select_waiting_reply = FALSE; + conn->qresync_selecting_box = NULL; + conn->selected_box = NULL; + conn->selected_on_server = FALSE; + /* fall through */ + case IMAPC_CONNECTION_STATE_DONE: + /* if we came from imapc_client_get_capabilities(), stop so + it can finish up and not just hang indefinitely. */ + if (conn->client->stop_on_state_finish && !conn->reconnecting) + imapc_client_stop(conn->client); + break; + default: + break; + } +} + +static void imapc_connection_lfiles_free(struct imapc_connection *conn) +{ + struct imapc_arg_file *lfile; + + array_foreach_modifiable(&conn->literal_files, lfile) { + if (close(lfile->fd) < 0) + i_error("imapc: close(literal file) failed: %m"); + } + array_clear(&conn->literal_files); +} + +static void +imapc_connection_literal_reset(struct imapc_connection_literal *literal) +{ + i_close_fd_path(&literal->fd, literal->temp_path); + i_free_and_null(literal->temp_path); + + i_zero(literal); + literal->fd = -1; +} + +void imapc_connection_disconnect_full(struct imapc_connection *conn, + bool reconnecting) +{ + /* timeout may be set also in disconnected state */ + timeout_remove(&conn->to); + conn->reconnecting = reconnecting; + + if (conn->state == IMAPC_CONNECTION_STATE_DISCONNECTED) { + i_assert(array_count(&conn->cmd_wait_list) == 0); + if (conn->reconnect_command_count == 0) + imapc_connection_abort_commands(conn, NULL, + reconnecting); + return; + } + + if (conn->client->set.debug) + i_debug("imapc(%s): Disconnected", conn->name); + + if (conn->dns_lookup != NULL) + dns_lookup_abort(&conn->dns_lookup); + imapc_connection_lfiles_free(conn); + imapc_connection_literal_reset(&conn->literal); + timeout_remove(&conn->to_output); + timeout_remove(&conn->to_throttle); + timeout_remove(&conn->to_throttle_shrink); + if (conn->parser != NULL) + imap_parser_unref(&conn->parser); + io_remove(&conn->io); + ssl_iostream_destroy(&conn->ssl_iostream); + if (conn->fd != -1) { + i_stream_destroy(&conn->input); + o_stream_destroy(&conn->output); + net_disconnect(conn->fd); + conn->fd = -1; + } + + /* get capabilities again after reconnection. this is especially + important because post-login capabilities often do not contain AUTH= + capabilities. */ + conn->capabilities = 0; + if (conn->capabilities_list != NULL) { + p_strsplit_free(default_pool, conn->capabilities_list); + conn->capabilities_list = NULL; + } + + imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DISCONNECTED); + imapc_connection_abort_commands(conn, NULL, reconnecting); + + if (!reconnecting) { + imapc_client_try_stop(conn->client); + } +} + +void imapc_connection_set_no_reconnect(struct imapc_connection *conn) +{ + conn->reconnect_ok = FALSE; +} + +void imapc_connection_disconnect(struct imapc_connection *conn) +{ + imapc_connection_disconnect_full(conn, FALSE); +} + +static void imapc_connection_set_disconnected(struct imapc_connection *conn) +{ + imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DISCONNECTED); + imapc_connection_abort_commands(conn, NULL, FALSE); +} + +static bool imapc_connection_can_reconnect(struct imapc_connection *conn) +{ + if (conn->client->logging_out) + return FALSE; + if (conn->client->set.connect_retry_count == 0 || + (conn->client->set.connect_retry_count < UINT_MAX && + conn->reconnect_count >= conn->client->set.connect_retry_count)) + return FALSE; + + if (conn->selected_box != NULL) + return imapc_client_mailbox_can_reconnect(conn->selected_box); + else { + return conn->reconnect_command_count == 0 && + conn->reconnect_ok; + } +} + +static void imapc_connection_reconnect(struct imapc_connection *conn) +{ + conn->reconnect_ok = FALSE; + conn->reconnect_waiting = FALSE; + + if (conn->selected_box != NULL) { + i_assert(!conn->selected_box->reconnecting); + conn->selected_box->reconnecting = TRUE; + /* if we fail again, avoid reconnecting immediately. if the + server is broken we could just get into an infinitely + failing reconnection loop. */ + conn->selected_box->reconnect_ok = FALSE; + } + imapc_connection_disconnect_full(conn, TRUE); + imapc_connection_connect(conn); +} + +void imapc_connection_try_reconnect(struct imapc_connection *conn, + const char *errstr, + unsigned int delay_msecs, + bool connect_error) +{ + /* Try the next IP address only for connect() problems. */ + if (conn->prev_connect_idx + 1 < conn->ips_count && connect_error) { + i_warning("imapc(%s): %s - trying the next IP", conn->name, errstr); + conn->reconnect_ok = TRUE; + imapc_connection_disconnect_full(conn, TRUE); + imapc_connection_connect(conn); + return; + } + + if (!imapc_connection_can_reconnect(conn)) { + i_error("imapc(%s): %s - disconnecting", conn->name, errstr); + imapc_connection_disconnect(conn); + } else { + conn->reconnecting = TRUE; + i_warning("imapc(%s): %s - reconnecting (delay %u ms)", conn->name, errstr, delay_msecs); + if (delay_msecs == 0) + imapc_connection_reconnect(conn); + else { + imapc_connection_disconnect_full(conn, TRUE); + conn->to = timeout_add(delay_msecs, imapc_connection_reconnect, conn); + conn->reconnect_count++; + conn->reconnect_waiting = TRUE; + } + } +} + +static void ATTR_FORMAT(2, 3) +imapc_connection_input_error(struct imapc_connection *conn, + const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + i_error("imapc(%s): Server sent invalid input: %s", + conn->name, t_strdup_vprintf(fmt, va)); + imapc_connection_disconnect(conn); + va_end(va); +} + +static bool last_arg_is_fetch_body(const struct imap_arg *args, + const struct imap_arg **parent_arg_r, + unsigned int *idx_r) +{ + const struct imap_arg *list; + const char *name; + unsigned int count; + + if (args[0].type == IMAP_ARG_ATOM && + imap_arg_atom_equals(&args[1], "FETCH") && + imap_arg_get_list_full(&args[2], &list, &count) && count >= 2 && + list[count].type == IMAP_ARG_LITERAL_SIZE && + imap_arg_get_atom(&list[count-1], &name) && + strncasecmp(name, "BODY[", 5) == 0) { + *parent_arg_r = &args[2]; + *idx_r = count; + return TRUE; + } + return FALSE; +} + +static int +imapc_connection_read_literal_init(struct imapc_connection *conn, uoff_t size, + const struct imap_arg *args) +{ + const char *path; + const struct imap_arg *parent_arg; + unsigned int idx; + + i_assert(conn->literal.fd == -1); + + if (size <= IMAPC_MAX_INLINE_LITERAL_SIZE || + !last_arg_is_fetch_body(args, &parent_arg, &idx)) { + /* read the literal directly into parser */ + return 0; + } + + conn->literal.fd = imapc_client_create_temp_fd(conn->client, &path); + if (conn->literal.fd == -1) + return -1; + conn->literal.temp_path = i_strdup(path); + conn->literal.bytes_left = size; + conn->literal.parent_arg = parent_arg; + conn->literal.list_idx = idx; + return 1; +} + +static int imapc_connection_read_literal(struct imapc_connection *conn) +{ + struct imapc_arg_file *lfile; + const unsigned char *data; + size_t size; + + if (conn->literal.bytes_left == 0) + return 1; + + data = i_stream_get_data(conn->input, &size); + if (size > conn->literal.bytes_left) + size = conn->literal.bytes_left; + if (size > 0) { + if (write_full(conn->literal.fd, data, size) < 0) { + i_error("imapc(%s): write(%s) failed: %m", + conn->name, conn->literal.temp_path); + imapc_connection_disconnect(conn); + return -1; + } + i_stream_skip(conn->input, size); + conn->literal.bytes_left -= size; + } + if (conn->literal.bytes_left > 0) + return 0; + + /* finished */ + lfile = array_append_space(&conn->literal_files); + lfile->fd = conn->literal.fd; + lfile->parent_arg = conn->literal.parent_arg; + lfile->list_idx = conn->literal.list_idx; + + conn->literal.fd = -1; + imapc_connection_literal_reset(&conn->literal); + return 1; +} + +static int +imapc_connection_read_line_more(struct imapc_connection *conn, + const struct imap_arg **imap_args_r) +{ + uoff_t literal_size; + int ret; + + if ((ret = imapc_connection_read_literal(conn)) <= 0) + return ret; + + ret = imap_parser_read_args(conn->parser, 0, + IMAP_PARSE_FLAG_LITERAL_SIZE | + IMAP_PARSE_FLAG_ATOM_ALLCHARS | + IMAP_PARSE_FLAG_LITERAL8 | + IMAP_PARSE_FLAG_SERVER_TEXT, imap_args_r); + if (ret == -2) { + /* need more data */ + return 0; + } + if (ret < 0) { + enum imap_parser_error parser_error; + const char *err_msg = imap_parser_get_error(conn->parser, &parser_error); + if (parser_error != IMAP_PARSE_ERROR_BAD_SYNTAX) + imapc_connection_input_error(conn, "Error parsing input: %s", err_msg); + else + i_error("Error parsing input: %s", err_msg); + return -1; + } + + if (imap_parser_get_literal_size(conn->parser, &literal_size)) { + if (imapc_connection_read_literal_init(conn, literal_size, + *imap_args_r) <= 0) { + imap_parser_read_last_literal(conn->parser); + return 2; + } + return imapc_connection_read_line_more(conn, imap_args_r); + } + return 1; +} + +static int +imapc_connection_read_line(struct imapc_connection *conn, + const struct imap_arg **imap_args_r) +{ + const unsigned char *data; + size_t size; + int ret; + + while ((ret = imapc_connection_read_line_more(conn, imap_args_r)) == 2) + ; + + if (ret > 0) { + data = i_stream_get_data(conn->input, &size); + if (size >= 2 && data[0] == '\r' && data[1] == '\n') + i_stream_skip(conn->input, 2); + else if (size >= 1 && data[0] == '\n') + i_stream_skip(conn->input, 1); + else + i_panic("imapc: Missing LF from input line"); + } else if (ret < 0) { + data = i_stream_get_data(conn->input, &size); + unsigned char *lf = memchr(data, '\n', size); + if (lf != NULL) + i_stream_skip(conn->input, (lf - data) + 1); + } + return ret; +} + +static int +imapc_connection_parse_capability(struct imapc_connection *conn, + const char *value) +{ + const char *const *tmp; + unsigned int i; + + if (conn->client->set.debug) { + i_debug("imapc(%s): Server capabilities: %s", + conn->name, value); + } + + conn->capabilities = 0; + if (conn->capabilities_list != NULL) + p_strsplit_free(default_pool, conn->capabilities_list); + conn->capabilities_list = p_strsplit(default_pool, value, " "); + + for (tmp = t_strsplit(value, " "); *tmp != NULL; tmp++) { + for (i = 0; imapc_capability_names[i].name != NULL; i++) { + const struct imapc_capability_name *cap = + &imapc_capability_names[i]; + + if (strcasecmp(*tmp, cap->name) == 0) { + conn->capabilities |= cap->capability; + break; + } + } + } + + if ((conn->capabilities & IMAPC_CAPABILITY_IMAP4REV1) == 0) { + imapc_connection_input_error(conn, + "CAPABILITY list is missing IMAP4REV1"); + return -1; + } + return 0; +} + +static int +imapc_connection_handle_resp_text_code(struct imapc_connection *conn, + const char *key, const char *value) +{ + if (strcasecmp(key, "CAPABILITY") == 0) { + if (imapc_connection_parse_capability(conn, value) < 0) + return -1; + } + if (strcasecmp(key, "CLOSED") == 0) { + /* QRESYNC: SELECTing another mailbox */ + if (conn->qresync_selecting_box != NULL) { + conn->selected_box = conn->qresync_selecting_box; + conn->qresync_selecting_box = NULL; + } else { + conn->selected_on_server = FALSE; + } + } + return 0; +} + +static int +imapc_connection_handle_resp_text(struct imapc_connection *conn, + const char *text, + const char **key_r, const char **value_r) +{ + const char *p, *value; + + i_assert(text[0] == '['); + + p = strchr(text, ']'); + if (p == NULL) { + imapc_connection_input_error(conn, "Missing ']' in resp-text"); + return -1; + } + text = t_strdup_until(text + 1, p); + value = strchr(text, ' '); + if (value != NULL) { + *key_r = t_strdup_until(text, value); + *value_r = value + 1; + } else { + *key_r = text; + *value_r = ""; + } + return imapc_connection_handle_resp_text_code(conn, *key_r, *value_r); +} + +static int +imapc_connection_handle_imap_resp_text(struct imapc_connection *conn, + const struct imap_arg *args, + const char **key_r, const char **value_r) +{ + const char *text; + + if (args->type != IMAP_ARG_ATOM) + return 0; + + text = imap_args_to_str(args); + if (*text != '[') { + if (*text == '\0') { + imapc_connection_input_error(conn, + "Missing text in resp-text"); + return -1; + } + return 0; + } + return imapc_connection_handle_resp_text(conn, text, key_r, value_r); +} + +static bool need_literal(const char *str) +{ + unsigned int i; + + for (i = 0; str[i] != '\0'; i++) { + unsigned char c = str[i]; + + if ((c & 0x80) != 0 || c == '\r' || c == '\n') + return TRUE; + } + return FALSE; +} + +static void imapc_connection_input_reset(struct imapc_connection *conn) +{ + conn->input_state = IMAPC_INPUT_STATE_NONE; + conn->cur_tag = 0; + conn->cur_num = 0; + if (conn->parser != NULL) + imap_parser_reset(conn->parser); + imapc_connection_lfiles_free(conn); +} + +static void +imapc_connection_auth_finish(struct imapc_connection *conn, + const struct imapc_command_reply *reply) +{ + if (reply->state != IMAPC_COMMAND_STATE_OK) { + imapc_auth_failed(conn, reply, reply->text_full); + imapc_connection_disconnect(conn); + return; + } + + imapc_auth_ok(conn); + + i_assert(array_count(&conn->cmd_wait_list) == 0); + timeout_remove(&conn->to); + imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DONE); + imapc_login_callback(conn, reply); + + imapc_command_send_more(conn); +} + +static void imapc_connection_login_cb(const struct imapc_command_reply *reply, + void *context) +{ + struct imapc_connection *conn = context; + + imapc_connection_auth_finish(conn, reply); +} + +static void +imapc_connection_proxyauth_login_cb(const struct imapc_command_reply *reply, + void *context) +{ + struct imapc_connection *conn = context; + const struct imapc_client_settings *set = &conn->client->set; + struct imapc_command *cmd; + + if (reply->state == IMAPC_COMMAND_STATE_OK) { + cmd = imapc_connection_cmd(conn, imapc_connection_login_cb, + conn); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); + imapc_command_sendf(cmd, "PROXYAUTH %s", set->username); + imapc_command_send_more(conn); + } else { + imapc_connection_auth_finish(conn, reply); + } +} + +static void +imapc_connection_authenticate_cb(const struct imapc_command_reply *reply, + void *context) +{ + struct imapc_connection *conn = context; + const unsigned char *sasl_output; + size_t input_len, sasl_output_len; + buffer_t *buf; + const char *error; + + if ((int)reply->state != IMAPC_COMMAND_STATE_AUTHENTICATE_CONTINUE) { + dsasl_client_free(&conn->sasl_client); + imapc_connection_auth_finish(conn, reply); + return; + } + + input_len = strlen(reply->text_full); + buf = t_buffer_create(MAX_BASE64_DECODED_SIZE(input_len)); + if (base64_decode(reply->text_full, input_len, NULL, buf) < 0) { + imapc_auth_failed(conn, reply, + t_strdup_printf("Server sent non-base64 input for AUTHENTICATE: %s", + reply->text_full)); + } else if (dsasl_client_input(conn->sasl_client, buf->data, buf->used, &error) < 0) { + imapc_auth_failed(conn, reply, error); + } else if (dsasl_client_output(conn->sasl_client, &sasl_output, + &sasl_output_len, &error) < 0) { + imapc_auth_failed(conn, reply, error); + } else { + string_t *imap_output = + t_str_new(MAX_BASE64_ENCODED_SIZE(sasl_output_len)+2); + base64_encode(sasl_output, sasl_output_len, imap_output); + str_append(imap_output, "\r\n"); + o_stream_nsend(conn->output, str_data(imap_output), + str_len(imap_output)); + return; + } + imapc_connection_disconnect(conn); +} + +static bool imapc_connection_have_auth(struct imapc_connection *conn, + const char *mech_name) +{ + char *const *capa; + + for (capa = conn->capabilities_list; *capa != NULL; capa++) { + if (strncasecmp(*capa, "AUTH=", 5) == 0 && + strcasecmp((*capa)+5, mech_name) == 0) + return TRUE; + } + return FALSE; +} + +static int +imapc_connection_get_sasl_mech(struct imapc_connection *conn, + const struct dsasl_client_mech **mech_r, + const char **error_r) +{ + const struct imapc_client_settings *set = &conn->client->set; + const char *const *mechanisms = + t_strsplit_spaces(set->sasl_mechanisms, ", "); + + /* find one of the specified SASL mechanisms */ + for (; *mechanisms != NULL; mechanisms++) { + if (imapc_connection_have_auth(conn, *mechanisms)) { + *mech_r = dsasl_client_mech_find(*mechanisms); + if (*mech_r != NULL) + return 0; + + *error_r = t_strdup_printf( + "Support for SASL method '%s' is missing", *mechanisms); + return -1; + } + } + *error_r = t_strdup_printf("IMAP server doesn't support any of the requested SASL mechanisms: %s", + set->sasl_mechanisms); + return -1; +} + +static void imapc_connection_authenticate(struct imapc_connection *conn) +{ + const struct imapc_client_settings *set = &conn->client->set; + struct imapc_command *cmd; + struct dsasl_client_settings sasl_set; + const struct dsasl_client_mech *sasl_mech = NULL; + const char *error; + + if (conn->client->set.debug) { + if (set->master_user == NULL) { + i_debug("imapc(%s): Authenticating as %s", + conn->name, set->username); + } else { + i_debug("imapc(%s): Authenticating as %s for user %s", + conn->name, set->master_user, set->username); + } + } + + if (set->sasl_mechanisms != NULL && set->sasl_mechanisms[0] != '\0') { + if (imapc_connection_get_sasl_mech(conn, &sasl_mech, &error) < 0) { + struct imapc_command_reply reply; + i_zero(&reply); + reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; + reply.text_full = ""; + imapc_auth_failed(conn, &reply, error); + imapc_connection_disconnect(conn); + return; + } + } + + if (set->use_proxyauth && set->master_user != NULL) { + /* We can use LOGIN command */ + cmd = imapc_connection_cmd(conn, imapc_connection_proxyauth_login_cb, + conn); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); + imapc_command_sendf(cmd, "LOGIN %s %s", + set->master_user, set->password); + return; + } + if (sasl_mech == NULL && + ((set->master_user == NULL && + !need_literal(set->username) && !need_literal(set->password)) || + (conn->capabilities & IMAPC_CAPABILITY_AUTH_PLAIN) == 0)) { + /* We can use LOGIN command */ + cmd = imapc_connection_cmd(conn, imapc_connection_login_cb, + conn); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); + imapc_command_sendf(cmd, "LOGIN %s %s", + set->username, set->password); + return; + } + + i_zero(&sasl_set); + if (set->master_user == NULL) + sasl_set.authid = set->username; + else { + sasl_set.authid = set->master_user; + sasl_set.authzid = set->username; + } + sasl_set.password = set->password; + + if (sasl_mech == NULL) + sasl_mech = &dsasl_client_mech_plain; + conn->sasl_client = dsasl_client_new(sasl_mech, &sasl_set); + + cmd = imapc_connection_cmd(conn, imapc_connection_authenticate_cb, conn); + cmd->authenticate = TRUE; + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); + + if ((conn->capabilities & IMAPC_CAPABILITY_SASL_IR) != 0) { + const unsigned char *sasl_output; + size_t sasl_output_len; + string_t *sasl_output_base64; + const char *error; + + if (dsasl_client_output(conn->sasl_client, &sasl_output, + &sasl_output_len, &error) < 0) { + i_error("imapc(%s): Failed to create initial SASL reply: %s", + conn->name, error); + imapc_connection_disconnect(conn); + return; + } + sasl_output_base64 = t_str_new(MAX_BASE64_ENCODED_SIZE(sasl_output_len)); + base64_encode(sasl_output, sasl_output_len, sasl_output_base64); + + imapc_command_sendf(cmd, "AUTHENTICATE %1s %1s", + dsasl_client_mech_get_name(sasl_mech), + str_c(sasl_output_base64)); + } else { + imapc_command_sendf(cmd, "AUTHENTICATE %1s", + dsasl_client_mech_get_name(sasl_mech)); + } +} + +static void +imapc_connection_starttls_cb(const struct imapc_command_reply *reply, + void *context) +{ + struct imapc_connection *conn = context; + struct imapc_command *cmd; + + if (reply->state != IMAPC_COMMAND_STATE_OK) { + imapc_connection_input_error(conn, "STARTTLS failed: %s", + reply->text_full); + return; + } + + if (imapc_connection_ssl_init(conn) < 0) + imapc_connection_disconnect(conn); + else { + /* get updated capabilities */ + cmd = imapc_connection_cmd(conn, imapc_connection_capability_cb, + conn); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); + imapc_command_send(cmd, "CAPABILITY"); + } +} + +static void +imapc_connection_id_callback(const struct imapc_command_reply *reply ATTR_UNUSED, + void *context ATTR_UNUSED) +{ +} + +static void imapc_connection_send_id(struct imapc_connection *conn) +{ + static unsigned int global_id_counter = 0; + struct imapc_command *cmd; + + if ((conn->capabilities & IMAPC_CAPABILITY_ID) == 0 || + conn->client->set.session_id_prefix == NULL) + return; + + cmd = imapc_connection_cmd(conn, imapc_connection_id_callback, conn); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); + imapc_command_send(cmd, t_strdup_printf( + "ID (\"name\" \"Dovecot\" \"x-session-ext-id\" \"%s-%u\")", + conn->client->set.session_id_prefix, ++global_id_counter)); +} + +static void imapc_connection_starttls(struct imapc_connection *conn) +{ + struct imapc_command *cmd; + + if (conn->client->set.ssl_mode == IMAPC_CLIENT_SSL_MODE_STARTTLS && + conn->ssl_iostream == NULL) { + if ((conn->capabilities & IMAPC_CAPABILITY_STARTTLS) == 0) { + i_error("imapc(%s): Requested STARTTLS, " + "but server doesn't support it", + conn->name); + imapc_connection_disconnect(conn); + return; + } + cmd = imapc_connection_cmd(conn, imapc_connection_starttls_cb, + conn); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); + imapc_command_send(cmd, "STARTTLS"); + return; + } + imapc_connection_send_id(conn); + imapc_connection_authenticate(conn); +} + +static void +imapc_connection_capability_cb(const struct imapc_command_reply *reply, + void *context) +{ + struct imapc_connection *conn = context; + + if (reply->state != IMAPC_COMMAND_STATE_OK) { + imapc_connection_input_error(conn, + "Failed to get capabilities: %s", reply->text_full); + } else if (conn->capabilities == 0) { + imapc_connection_input_error(conn, + "Capabilities not returned by server"); + } else { + imapc_connection_starttls(conn); + } +} + +static int imapc_connection_input_banner(struct imapc_connection *conn) +{ + const struct imap_arg *imap_args; + const char *key, *value; + struct imapc_command *cmd; + int ret; + + if ((ret = imapc_connection_read_line(conn, &imap_args)) <= 0) + return ret; + /* we already verified that the banner beigns with OK */ + i_assert(imap_arg_atom_equals(imap_args, "OK")); + imap_args++; + + if (imapc_connection_handle_imap_resp_text(conn, imap_args, + &key, &value) < 0) + return -1; + imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_AUTHENTICATING); + + if (conn->capabilities == 0) { + /* capabilities weren't sent in the banner. ask for them. */ + cmd = imapc_connection_cmd(conn, imapc_connection_capability_cb, + conn); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); + imapc_command_send(cmd, "CAPABILITY"); + } else { + imapc_connection_starttls(conn); + } + conn->input_callback = NULL; + imapc_connection_input_reset(conn); + return 1; +} + +static int imapc_connection_input_untagged(struct imapc_connection *conn) +{ + const struct imap_arg *imap_args; + const unsigned char *data; + size_t size; + const char *name, *value; + struct imap_parser *parser; + struct imapc_untagged_reply reply; + int ret; + + if (conn->state == IMAPC_CONNECTION_STATE_CONNECTING) { + /* input banner */ + data = i_stream_get_data(conn->input, &size); + if (size < 3 && memchr(data, '\n', size) == NULL) + return 0; + if (i_memcasecmp(data, "OK ", 3) != 0) { + imapc_connection_input_error(conn, + "Banner doesn't begin with OK: %s", + t_strcut(t_strndup(data, size), '\n')); + return -1; + } + conn->input_callback = imapc_connection_input_banner; + return 1; + } + + if ((ret = imapc_connection_read_line(conn, &imap_args)) == 0) + return 0; + else if (ret < 0) { + imapc_connection_input_reset(conn); + return 1; + } + if (!imap_arg_get_atom(&imap_args[0], &name)) { + imapc_connection_input_error(conn, "Invalid untagged reply"); + return -1; + } + imap_args++; + + if (conn->input_state == IMAPC_INPUT_STATE_UNTAGGED && + str_to_uint32(name, &conn->cur_num) == 0) { + /* <seq> <event> */ + conn->input_state = IMAPC_INPUT_STATE_UNTAGGED_NUM; + if (!imap_arg_get_atom(&imap_args[0], &name)) { + imapc_connection_input_error(conn, + "Invalid untagged reply"); + return -1; + } + imap_args++; + } + i_zero(&reply); + + if (strcasecmp(name, "OK") == 0) { + if (imapc_connection_handle_imap_resp_text(conn, imap_args, + &reply.resp_text_key, + &reply.resp_text_value) < 0) + return -1; + } else if (strcasecmp(name, "CAPABILITY") == 0) { + value = imap_args_to_str(imap_args); + if (imapc_connection_parse_capability(conn, value) < 0) + return -1; + } else if (strcasecmp(name, "BYE") == 0) { + i_free(conn->disconnect_reason); + conn->disconnect_reason = i_strdup(imap_args_to_str(imap_args)); + } + + reply.name = name; + reply.num = conn->cur_num; + reply.args = imap_args; + reply.file_args = array_get(&conn->literal_files, + &reply.file_args_count); + + if (conn->selected_box != NULL) { + reply.untagged_box_context = + conn->selected_box->untagged_box_context; + } + + /* the callback may disconnect and destroy the parser */ + parser = conn->parser; + imap_parser_ref(parser); + conn->client->untagged_callback(&reply, conn->client->untagged_context); + imap_parser_unref(&parser); + imapc_connection_input_reset(conn); + return 1; +} + +static int imapc_connection_input_plus(struct imapc_connection *conn) +{ + struct imapc_command *const *cmds; + unsigned int cmds_count; + const char *line; + + if ((line = i_stream_next_line(conn->input)) == NULL) + return 0; + + cmds = array_get(&conn->cmd_send_queue, &cmds_count); + if (conn->idle_plus_waiting) { + /* "+ idling" reply for IDLE command */ + conn->idle_plus_waiting = FALSE; + conn->idling = TRUE; + /* no timing out while IDLEing */ + if (conn->to != NULL && !conn->idle_stopping) + timeout_remove(&conn->to); + } else if (cmds_count > 0 && cmds[0]->wait_for_literal) { + /* reply for literal */ + cmds[0]->wait_for_literal = FALSE; + imapc_command_send_more(conn); + } else { + cmds = array_get(&conn->cmd_wait_list, &cmds_count); + if (cmds_count > 0 && cmds[0]->authenticate) { + /* continue AUTHENTICATE */ + struct imapc_command_reply reply; + + i_zero(&reply); + reply.state = (enum imapc_command_state)IMAPC_COMMAND_STATE_AUTHENTICATE_CONTINUE; + reply.text_full = line; + cmds[0]->callback(&reply, cmds[0]->context); + } else { + imapc_connection_input_error(conn, "Unexpected '+': %s", line); + return -1; + } + } + + imapc_connection_input_reset(conn); + return 1; +} + +static void +imapc_connection_throttle_shrink_timeout(struct imapc_connection *conn) +{ + if (conn->throttle_msecs <= 1) + conn->throttle_msecs = 0; + else + conn->throttle_msecs = conn->throttle_msecs*3 / 4; + + if (conn->throttle_shrink_msecs <= conn->client->set.throttle_set.shrink_min_msecs) + conn->throttle_shrink_msecs = 0; + else + conn->throttle_shrink_msecs = conn->throttle_shrink_msecs*3 / 4; + + timeout_remove(&conn->to_throttle_shrink); + if (conn->throttle_shrink_msecs > 0) { + conn->to_throttle_shrink = + timeout_add(conn->throttle_shrink_msecs, + imapc_connection_throttle_shrink_timeout, conn); + } +} + +static void +imapc_connection_throttle(struct imapc_connection *conn, + const struct imapc_command_reply *reply) +{ + timeout_remove(&conn->to_throttle); + + /* If GMail returns [THROTTLED], start slowing down commands. + Unfortunately this isn't a nice resp-text-code, but just + appended at the end of the line (although we kind of support + it as resp-text-code also in here if it's uppercased). */ + if (strstr(reply->text_full, "[THROTTLED]") != NULL) { + if (conn->throttle_msecs == 0) + conn->throttle_msecs = conn->client->set.throttle_set.init_msecs; + else if (conn->throttle_msecs < conn->last_successful_throttle_msecs) + conn->throttle_msecs = conn->last_successful_throttle_msecs; + else { + conn->throttle_msecs *= 2; + if (conn->throttle_msecs > conn->client->set.throttle_set.max_msecs) + conn->throttle_msecs = conn->client->set.throttle_set.max_msecs; + } + if (conn->throttle_shrink_msecs == 0) + conn->throttle_shrink_msecs = conn->client->set.throttle_set.shrink_min_msecs; + else + conn->throttle_shrink_msecs *= 2; + if (conn->to_throttle_shrink != NULL) + timeout_reset(conn->to_throttle_shrink); + } else { + if (conn->throttle_shrink_msecs > 0 && + conn->to_throttle_shrink == NULL) { + conn->to_throttle_shrink = + timeout_add(conn->throttle_shrink_msecs, + imapc_connection_throttle_shrink_timeout, conn); + } + conn->last_successful_throttle_msecs = conn->throttle_msecs; + } + + if (conn->throttle_msecs > 0) { + conn->throttle_end_timeval = ioloop_timeval; + timeval_add_msecs(&conn->throttle_end_timeval, + conn->throttle_msecs); + conn->throttle_pending = TRUE; + } +} + +static void +imapc_command_reply_free(struct imapc_command *cmd, + const struct imapc_command_reply *reply) +{ + cmd->callback(reply, cmd->context); + imapc_command_free(cmd); +} + +static int imapc_connection_input_tagged(struct imapc_connection *conn) +{ + struct imapc_command *const *cmds, *cmd = NULL; + unsigned int i, count; + char *line, *linep; + const char *p; + struct imapc_command_reply reply; + + line = i_stream_next_line(conn->input); + if (line == NULL) + return 0; + /* make sure reply texts stays valid if input stream gets freed */ + line = t_strdup_noconst(line); + + i_zero(&reply); + + linep = strchr(line, ' '); + if (linep == NULL) + reply.text_full = ""; + else { + *linep = '\0'; + reply.text_full = linep + 1; + } + + if (strcasecmp(line, "ok") == 0) + reply.state = IMAPC_COMMAND_STATE_OK; + else if (strcasecmp(line, "no") == 0) + reply.state = IMAPC_COMMAND_STATE_NO; + else if (strcasecmp(line, "bad") == 0) + reply.state = IMAPC_COMMAND_STATE_BAD; + else { + imapc_connection_input_error(conn, + "Invalid state in tagged reply: %u %s %s", + conn->cur_tag, line, reply.text_full); + return -1; + } + + if (reply.text_full[0] == '[') { + /* get resp-text */ + if (imapc_connection_handle_resp_text(conn, reply.text_full, + &reply.resp_text_key, + &reply.resp_text_value) < 0) + return -1; + + p = i_strchr_to_next(reply.text_full, ']'); + i_assert(p != NULL); + reply.text_without_resp = p; + if (reply.text_without_resp[0] == ' ') + reply.text_without_resp++; + } else { + reply.text_without_resp = reply.text_full; + } + /* if we've pipelined multiple commands, handle [THROTTLED] reply + from only one of them */ + if (!conn->throttle_pending) + imapc_connection_throttle(conn, &reply); + + /* find the command. it's either the first command in send queue + (literal failed) or somewhere in wait list. */ + cmds = array_get(&conn->cmd_send_queue, &count); + if (count > 0 && cmds[0]->tag == conn->cur_tag) { + cmd = cmds[0]; + array_pop_front(&conn->cmd_send_queue); + } else { + cmds = array_get(&conn->cmd_wait_list, &count); + for (i = 0; i < count; i++) { + if (cmds[i]->tag == conn->cur_tag) { + cmd = cmds[i]; + array_delete(&conn->cmd_wait_list, i, 1); + break; + } + } + } + if (array_count(&conn->cmd_wait_list) == 0 && + array_count(&conn->cmd_send_queue) == 0 && + conn->state == IMAPC_CONNECTION_STATE_DONE && conn->to != NULL) + timeout_remove(&conn->to); + + if (cmd == NULL) { + if (seq_range_exists(&conn->aborted_cmd_tags, conn->cur_tag)) { + /* sent command was already aborted - ignore it */ + seq_range_array_remove(&conn->aborted_cmd_tags, + conn->cur_tag); + imapc_connection_input_reset(conn); + return 1; + } + imapc_connection_input_error(conn, + "Unknown tag in a reply: %u %s %s", + conn->cur_tag, line, reply.text_full); + return -1; + } + if ((cmd->flags & IMAPC_COMMAND_FLAG_SELECT) != 0) + conn->select_waiting_reply = FALSE; + + if (reply.state == IMAPC_COMMAND_STATE_BAD) { + i_error("imapc(%s): Command '%s' failed with BAD: %u %s", + conn->name, imapc_command_get_readable(cmd), + conn->cur_tag, reply.text_full); + imapc_connection_disconnect(conn); + } + + if (reply.state == IMAPC_COMMAND_STATE_NO && + (cmd->flags & IMAPC_COMMAND_FLAG_SELECT) != 0 && + conn->selected_box != NULL) { + /* EXAMINE/SELECT failed: mailbox is no longer selected */ + imapc_connection_unselect(conn->selected_box, TRUE); + } + + if (conn->reconnect_command_count > 0 && + (cmd->flags & IMAPC_COMMAND_FLAG_RECONNECTED) != 0) { + i_assert(conn->reconnect_command_count > 0); + if (--conn->reconnect_command_count == 0) { + /* we've received replies for all the commands started + before reconnection. if we get disconnected now, we + can safely reconnect without worrying about infinite + reconnect loops. */ + if (conn->selected_box != NULL) + conn->selected_box->reconnect_ok = TRUE; + } + } + if (conn->reconnect_command_count == 0) { + /* we've successfully received replies to some commands. */ + conn->reconnect_ok = TRUE; + } + imapc_connection_input_reset(conn); + imapc_command_reply_free(cmd, &reply); + imapc_command_send_more(conn); + return 1; +} + +static int imapc_connection_input_one(struct imapc_connection *conn) +{ + const char *tag; + int ret = -1; + + if (conn->input_callback != NULL) + return conn->input_callback(conn); + + switch (conn->input_state) { + case IMAPC_INPUT_STATE_NONE: + tag = imap_parser_read_word(conn->parser); + if (tag == NULL) + return 0; + + if (strcmp(tag, "*") == 0) { + conn->input_state = IMAPC_INPUT_STATE_UNTAGGED; + conn->cur_num = 0; + ret = imapc_connection_input_untagged(conn); + } else if (strcmp(tag, "+") == 0) { + conn->input_state = IMAPC_INPUT_STATE_PLUS; + ret = imapc_connection_input_plus(conn); + } else { + conn->input_state = IMAPC_INPUT_STATE_TAGGED; + if (str_to_uint(tag, &conn->cur_tag) < 0 || + conn->cur_tag == 0) { + imapc_connection_input_error(conn, + "Invalid command tag: %s", tag); + ret = -1; + } else { + ret = imapc_connection_input_tagged(conn); + } + } + break; + case IMAPC_INPUT_STATE_PLUS: + ret = imapc_connection_input_plus(conn); + break; + case IMAPC_INPUT_STATE_UNTAGGED: + case IMAPC_INPUT_STATE_UNTAGGED_NUM: + ret = imapc_connection_input_untagged(conn); + break; + case IMAPC_INPUT_STATE_TAGGED: + ret = imapc_connection_input_tagged(conn); + break; + } + return ret; +} + +static void imapc_connection_input(struct imapc_connection *conn) +{ + const char *errstr; + string_t *str; + ssize_t ret = 0; + + /* we need to read as much as we can with SSL streams to avoid + hanging */ + imapc_connection_ref(conn); + while (conn->input != NULL && (ret = i_stream_read(conn->input)) > 0) + imapc_connection_input_pending(conn); + + if (ret < 0 && conn->client->logging_out && + conn->disconnect_reason != NULL) { + /* expected disconnection */ + imapc_connection_disconnect(conn); + } else if (ret < 0) { + /* disconnected or buffer full */ + str = t_str_new(128); + if (conn->disconnect_reason != NULL) { + str_printfa(str, "Server disconnected with message: %s", + conn->disconnect_reason); + } else if (ret == -2) { + str_printfa(str, "Server sent too large input " + "(buffer full at %zu)", + i_stream_get_data_size(conn->input)); + } else if (conn->ssl_iostream == NULL) { + errstr = conn->input->stream_errno == 0 ? "EOF" : + i_stream_get_error(conn->input); + str_printfa(str, "Server disconnected unexpectedly: %s", + errstr); + } else { + errstr = ssl_iostream_get_last_error(conn->ssl_iostream); + if (errstr == NULL) { + errstr = conn->input->stream_errno == 0 ? "EOF" : + i_stream_get_error(conn->input); + } + str_printfa(str, "Server disconnected unexpectedly: %s", + errstr); + } + imapc_connection_try_reconnect(conn, str_c(str), 0, FALSE); + } + imapc_connection_unref(&conn); +} + +static int imapc_connection_ssl_handshaked(const char **error_r, void *context) +{ + struct imapc_connection *conn = context; + const char *error; + + if (ssl_iostream_check_cert_validity(conn->ssl_iostream, + conn->client->set.host, &error) == 0) { + if (conn->client->set.debug) { + i_debug("imapc(%s): SSL handshake successful", + conn->name); + } + return 0; + } else if (conn->client->set.ssl_set.allow_invalid_cert) { + if (conn->client->set.debug) { + i_debug("imapc(%s): SSL handshake successful, " + "ignoring invalid certificate: %s", + conn->name, error); + } + return 0; + } else { + *error_r = error; + return -1; + } +} + +static int imapc_connection_ssl_init(struct imapc_connection *conn) +{ + const char *error; + + if (conn->client->ssl_ctx == NULL) { + i_error("imapc(%s): No SSL context", conn->name); + return -1; + } + + if (conn->client->set.debug) + i_debug("imapc(%s): Starting SSL handshake", conn->name); + + if (conn->raw_input != conn->input) { + /* recreate rawlog after STARTTLS */ + i_stream_ref(conn->raw_input); + o_stream_ref(conn->raw_output); + i_stream_destroy(&conn->input); + o_stream_destroy(&conn->output); + conn->input = conn->raw_input; + conn->output = conn->raw_output; + } + + io_remove(&conn->io); + if (io_stream_create_ssl_client(conn->client->ssl_ctx, + conn->client->set.host, + &conn->client->set.ssl_set, + &conn->input, &conn->output, + &conn->ssl_iostream, &error) < 0) { + i_error("imapc(%s): Couldn't initialize SSL client: %s", + conn->name, error); + return -1; + } + conn->io = io_add_istream(conn->input, imapc_connection_input, conn); + ssl_iostream_set_handshake_callback(conn->ssl_iostream, + imapc_connection_ssl_handshaked, + conn); + if (ssl_iostream_handshake(conn->ssl_iostream) < 0) { + i_error("imapc(%s): SSL handshake failed: %s", conn->name, + ssl_iostream_get_last_error(conn->ssl_iostream)); + return -1; + } + + if (*conn->client->set.rawlog_dir != '\0') { + iostream_rawlog_create(conn->client->set.rawlog_dir, + &conn->input, &conn->output); + } + + imap_parser_set_streams(conn->parser, conn->input, NULL); + return 0; +} + +static int imapc_connection_connected(struct imapc_connection *conn) +{ + const struct ip_addr *ip = &conn->ips[conn->prev_connect_idx]; + struct ip_addr local_ip; + in_port_t local_port; + int err; + + i_assert(conn->io == NULL); + + err = net_geterror(conn->fd); + if (err != 0) { + imapc_connection_try_reconnect(conn, t_strdup_printf( + "connect(%s, %u) failed: %s", + net_ip2addr(ip), conn->client->set.port, + strerror(err)), conn->client->set.connect_retry_interval_msecs, TRUE); + return -1; + } + if (net_getsockname(conn->fd, &local_ip, &local_port) < 0) + local_port = 0; + i_info("imapc(%s): Connected to %s:%u (local %s:%u)", conn->name, + net_ip2addr(ip), conn->client->set.port, + net_ip2addr(&local_ip), local_port); + conn->io = io_add(conn->fd, IO_READ, imapc_connection_input, conn); + o_stream_set_flush_callback(conn->output, imapc_connection_output, + conn); + + if (conn->client->set.ssl_mode == IMAPC_CLIENT_SSL_MODE_IMMEDIATE) { + if (imapc_connection_ssl_init(conn) < 0) + imapc_connection_disconnect(conn); + } + return imapc_connection_output(conn); +} + +static void imapc_connection_timeout(struct imapc_connection *conn) +{ + const struct ip_addr *ip = &conn->ips[conn->prev_connect_idx]; + const char *errstr; + bool connect_error = FALSE; + + switch (conn->state) { + case IMAPC_CONNECTION_STATE_CONNECTING: + errstr = t_strdup_printf("connect(%s, %u) timed out after %u seconds", + net_ip2addr(ip), conn->client->set.port, + conn->client->set.connect_timeout_msecs/1000); + connect_error = TRUE; + break; + case IMAPC_CONNECTION_STATE_AUTHENTICATING: + errstr = t_strdup_printf("Authentication timed out after %u seconds", + conn->client->set.connect_timeout_msecs/1000); + break; + default: + i_unreached(); + } + imapc_connection_try_reconnect(conn, errstr, 0, connect_error); +} + +static void +imapc_noop_callback(const struct imapc_command_reply *reply ATTR_UNUSED, + void *context ATTR_UNUSED) +{ +} + +static void +imapc_reidle_callback(const struct imapc_command_reply *reply ATTR_UNUSED, + void *context) +{ + struct imapc_connection *conn = context; + + imapc_connection_idle(conn); +} + +static void imapc_connection_reset_idle(struct imapc_connection *conn) +{ + struct imapc_command *cmd; + + if (conn->idling) + cmd = imapc_connection_cmd(conn, imapc_reidle_callback, conn); + else if (array_count(&conn->cmd_wait_list) == 0) + cmd = imapc_connection_cmd(conn, imapc_noop_callback, NULL); + else { + /* IMAP command reply is taking a long time */ + return; + } + imapc_command_send(cmd, "NOOP"); +} + +static void imapc_connection_connect_next_ip(struct imapc_connection *conn) +{ + const struct ip_addr *ip = NULL; + unsigned int i; + int fd; + + i_assert(conn->client->set.max_idle_time > 0); + + for (i = 0; i<conn->ips_count;) { + conn->prev_connect_idx = (conn->prev_connect_idx+1) % conn->ips_count; + ip = &conn->ips[conn->prev_connect_idx]; + fd = net_connect_ip(ip, conn->client->set.port, NULL); + if (fd != -1) + break; + + /* failed to connect to one of the IPs immediately + (e.g. IPv6 address without connectivity). try all IPs + before failing completely. */ + i_error("net_connect_ip(%s:%u) failed: %m", + net_ip2addr(ip), conn->client->set.port); + if (conn->prev_connect_idx+1 == conn->ips_count) { + imapc_connection_try_reconnect(conn, "No more IP address(es) to try", + conn->client->set.connect_retry_interval_msecs, TRUE); + return; + } + } + + i_assert(ip != NULL); + + conn->fd = fd; + conn->input = conn->raw_input = + i_stream_create_fd(fd, conn->client->set.max_line_length); + conn->output = conn->raw_output = o_stream_create_fd(fd, SIZE_MAX); + o_stream_set_no_error_handling(conn->output, TRUE); + + if (*conn->client->set.rawlog_dir != '\0' && + conn->client->set.ssl_mode != IMAPC_CLIENT_SSL_MODE_IMMEDIATE) { + iostream_rawlog_create(conn->client->set.rawlog_dir, + &conn->input, &conn->output); + } + + o_stream_set_flush_pending(conn->output, TRUE); + o_stream_set_flush_callback(conn->output, imapc_connection_connected, + conn); + conn->parser = imap_parser_create(conn->input, NULL, + conn->client->set.max_line_length); + conn->to = timeout_add(conn->client->set.connect_timeout_msecs, + imapc_connection_timeout, conn); + conn->to_output = timeout_add(conn->client->set.max_idle_time*1000, + imapc_connection_reset_idle, conn); + if (conn->client->set.debug) { + i_debug("imapc(%s): Connecting to %s:%u", conn->name, + net_ip2addr(ip), conn->client->set.port); + } +} + +static void +imapc_connection_dns_callback(const struct dns_lookup_result *result, + struct imapc_connection *conn) +{ + conn->dns_lookup = NULL; + + if (result->ret != 0) { + i_error("imapc(%s): dns_lookup(%s) failed: %s", + conn->name, conn->client->set.host, result->error); + imapc_connection_set_disconnected(conn); + return; + } + + i_assert(result->ips_count > 0); + conn->ips_count = result->ips_count; + conn->ips = i_new(struct ip_addr, conn->ips_count); + memcpy(conn->ips, result->ips, sizeof(*conn->ips) * conn->ips_count); + conn->prev_connect_idx = conn->ips_count - 1; + + imapc_connection_connect_next_ip(conn); +} + +void imapc_connection_connect(struct imapc_connection *conn) +{ + struct dns_lookup_settings dns_set; + struct ip_addr ip, *ips; + unsigned int ips_count; + int ret; + + if (conn->fd != -1 || conn->dns_lookup != NULL) + return; + if (conn->reconnect_waiting) { + /* wait for the reconnection delay to finish before + doing anything. */ + return; + } + + conn->reconnecting = FALSE; + /* if we get disconnected before we've finished all the pending + commands, don't reconnect */ + conn->reconnect_command_count = array_count(&conn->cmd_wait_list) + + array_count(&conn->cmd_send_queue); + + imapc_connection_input_reset(conn); + conn->last_connect = ioloop_timeval; + + if (conn->client->set.debug) { + i_debug("imapc(%s): Looking up IP address " + "(reconnect_ok=%s, last_connect=%ld)", conn->name, + (conn->reconnect_ok ? "true" : "false"), + (long)conn->last_connect.tv_sec); + } + + i_zero(&dns_set); + dns_set.dns_client_socket_path = + conn->client->set.dns_client_socket_path; + dns_set.timeout_msecs = conn->client->set.connect_timeout_msecs; + dns_set.event_parent = conn->client->event; + + imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_CONNECTING); + if (conn->ips_count > 0) { + /* do nothing */ + } else if (net_addr2ip(conn->client->set.host, &ip) == 0) { + conn->ips_count = 1; + conn->ips = i_new(struct ip_addr, conn->ips_count); + conn->ips[0] = ip; + } else if (*dns_set.dns_client_socket_path == '\0') { + ret = net_gethostbyname(conn->client->set.host, + &ips, &ips_count); + if (ret != 0) { + i_error("imapc(%s): net_gethostbyname(%s) failed: %s", + conn->name, conn->client->set.host, + net_gethosterror(ret)); + imapc_connection_set_disconnected(conn); + return; + } + conn->ips_count = ips_count; + conn->ips = i_new(struct ip_addr, ips_count); + memcpy(conn->ips, ips, ips_count * sizeof(*ips)); + } else { + (void)dns_lookup(conn->client->set.host, &dns_set, + imapc_connection_dns_callback, conn, + &conn->dns_lookup); + return; + } + imapc_connection_connect_next_ip(conn); +} + +void imapc_connection_input_pending(struct imapc_connection *conn) +{ + int ret = 1; + + if (conn->input == NULL) + return; + + if (conn->to != NULL && !conn->idle_stopping) + timeout_reset(conn->to); + + o_stream_cork(conn->output); + while (ret > 0 && conn->input != NULL) { + T_BEGIN { + ret = imapc_connection_input_one(conn); + } T_END; + } + + if (conn->output != NULL) + o_stream_uncork(conn->output); +} + +static struct imapc_command * +imapc_command_begin(imapc_command_callback_t *callback, void *context) +{ + struct imapc_command *cmd; + pool_t pool; + + i_assert(callback != NULL); + + pool = pool_alloconly_create("imapc command", 2048); + cmd = p_new(pool, struct imapc_command, 1); + cmd->pool = pool; + cmd->callback = callback; + cmd->context = context; + + /* use a globally unique tag counter so looking at rawlogs is + somewhat easier */ + if (++imapc_client_cmd_tag_counter == 0) + imapc_client_cmd_tag_counter++; + cmd->tag = imapc_client_cmd_tag_counter; + return cmd; +} + +static void imapc_command_free(struct imapc_command *cmd) +{ + struct imapc_command_stream *stream; + + if (array_is_created(&cmd->streams)) { + array_foreach_modifiable(&cmd->streams, stream) + i_stream_unref(&stream->input); + } + pool_unref(&cmd->pool); +} + +const char *imapc_command_get_tag(struct imapc_command *cmd) +{ + return t_strdup_printf("%u", cmd->tag); +} + +void imapc_command_abort(struct imapc_command **_cmd) +{ + struct imapc_command *cmd = *_cmd; + + *_cmd = NULL; + imapc_command_free(cmd); +} + +static void imapc_command_timeout(struct imapc_connection *conn) +{ + struct imapc_command *const *cmds; + unsigned int count; + + cmds = array_get(&conn->cmd_wait_list, &count); + i_assert(count > 0); + + imapc_connection_try_reconnect(conn, t_strdup_printf( + "Command '%s' timed out", imapc_command_get_readable(cmds[0])), 0, FALSE); +} + +static bool +parse_sync_literal(const unsigned char *data, unsigned int pos, + unsigned int *value_r) +{ + unsigned int value = 0, mul = 1; + + /* data should contain "{size}\r\n" and pos points after \n */ + if (pos <= 4 || data[pos-1] != '\n' || data[pos-2] != '\r' || + data[pos-3] != '}' || !i_isdigit(data[pos-4])) + return FALSE; + pos -= 4; + + do { + value += (data[pos] - '0') * mul; + mul = mul*10; + pos--; + } while (pos > 0 && i_isdigit(data[pos])); + + if (pos == 0 || data[pos] != '{') + return FALSE; + + *value_r = value; + return TRUE; +} + +static void imapc_command_send_finished(struct imapc_connection *conn, + struct imapc_command *cmd) +{ + struct imapc_command *const *cmdp; + + i_assert(conn->to != NULL); + + if (cmd->idle) + conn->idle_plus_waiting = TRUE; + cmd->sent = TRUE; + + /* everything sent. move command to wait list. */ + cmdp = array_front(&conn->cmd_send_queue); + i_assert(*cmdp == cmd); + array_pop_front(&conn->cmd_send_queue); + array_push_back(&conn->cmd_wait_list, &cmd); + + /* send the next command in queue */ + imapc_command_send_more(conn); +} + +static struct imapc_command_stream * +imapc_command_get_sending_stream(struct imapc_command *cmd) +{ + struct imapc_command_stream *stream; + + if (!array_is_created(&cmd->streams) || array_count(&cmd->streams) == 0) + return NULL; + + stream = array_front_modifiable(&cmd->streams); + if (stream->pos != cmd->send_pos) + return NULL; + return stream; +} + +static int imapc_command_try_send_stream(struct imapc_connection *conn, + struct imapc_command *cmd) +{ + struct imapc_command_stream *stream; + enum ostream_send_istream_result res; + + stream = imapc_command_get_sending_stream(cmd); + if (stream == NULL) + return -2; + + /* we're sending the stream now */ + o_stream_set_max_buffer_size(conn->output, 0); + res = o_stream_send_istream(conn->output, stream->input); + o_stream_set_max_buffer_size(conn->output, SIZE_MAX); + + switch (res) { + case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: + break; + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: + i_unreached(); + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: + i_assert(stream->input->v_offset < stream->size); + return 0; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: + i_error("imapc: read(%s) failed: %s", + i_stream_get_name(stream->input), + i_stream_get_error(stream->input)); + return -1; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: + /* disconnected */ + return -1; + } + i_assert(stream->input->v_offset == stream->size); + + /* finished with the stream */ + i_stream_unref(&stream->input); + array_pop_front(&cmd->streams); + + i_assert(cmd->send_pos != cmd->data->used); + return 1; +} + +static void imapc_connection_set_selecting(struct imapc_client_mailbox *box) +{ + struct imapc_connection *conn = box->conn; + + i_assert(conn->qresync_selecting_box == NULL); + + if (conn->selected_on_server && + (conn->capabilities & IMAPC_CAPABILITY_QRESYNC) != 0) { + /* server will send a [CLOSED] once selected mailbox is + closed */ + conn->qresync_selecting_box = box; + } else { + /* we'll have to assume that all the future untagged messages + are for the mailbox we're selecting */ + conn->selected_box = box; + conn->selected_on_server = TRUE; + } + conn->select_waiting_reply = TRUE; +} + +static bool imapc_connection_is_throttled(struct imapc_connection *conn) +{ + timeout_remove(&conn->to_throttle); + + if (conn->throttle_msecs == 0) { + /* we haven't received [THROTTLED] recently */ + return FALSE; + } + if (array_count(&conn->cmd_wait_list) > 0) { + /* wait until we have received the existing commands' tagged + replies to see if we're still throttled */ + return TRUE; + } + if (timeval_cmp(&ioloop_timeval, &conn->throttle_end_timeval) >= 0) { + /* we reached the throttle timeout - send the next command */ + conn->throttle_pending = FALSE; + return FALSE; + } + + /* we're still being throttled - wait for it to end */ + conn->to_throttle = timeout_add_absolute(&conn->throttle_end_timeval, + imapc_command_send_more, conn); + return TRUE; +} + +static void imapc_command_send_more(struct imapc_connection *conn) +{ + struct imapc_command *const *cmds, *cmd; + struct imapc_command_reply reply; + const unsigned char *p, *data; + unsigned int count, size; + size_t seek_pos, start_pos, end_pos; + int ret; + + if (imapc_connection_is_throttled(conn)) + return; + + cmds = array_get(&conn->cmd_send_queue, &count); + if (count == 0) + return; + cmd = cmds[0]; + + if ((cmd->flags & IMAPC_COMMAND_FLAG_PRELOGIN) == 0 && + conn->state != IMAPC_CONNECTION_STATE_DONE) { + /* wait until we're fully connected */ + return; + } + if ((cmd->flags & IMAPC_COMMAND_FLAG_LOGOUT) != 0 && + array_count(&conn->cmd_wait_list) > 0) { + /* wait until existing commands have finished */ + return; + } + if (conn->select_waiting_reply) { + /* wait for SELECT to finish */ + return; + } + if (cmd->wait_for_literal) { + /* wait until we received '+' */ + return; + } + + i_assert(cmd->send_pos < cmd->data->used); + + if (cmd->box == NULL) { + /* non-mailbox command */ + } else if (cmd->send_pos == 0 && + (cmd->flags & IMAPC_COMMAND_FLAG_SELECT) != 0) { + /* SELECT/EXAMINE command */ + imapc_connection_set_selecting(cmd->box); + } else if (!imapc_client_mailbox_is_opened(cmd->box)) { + if (cmd->box->reconnecting) { + /* wait for SELECT/EXAMINE */ + return; + } + /* shouldn't normally happen */ + i_zero(&reply); + reply.text_without_resp = reply.text_full = "Mailbox not open"; + reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; + + array_pop_front(&conn->cmd_send_queue); + imapc_command_reply_free(cmd, &reply); + imapc_command_send_more(conn); + return; + } + + /* add timeout for commands if there's not one yet + (pre-login has its own timeout) */ + if ((cmd->flags & IMAPC_COMMAND_FLAG_LOGOUT) != 0) { + /* LOGOUT has a shorter timeout */ + timeout_remove(&conn->to); + conn->to = timeout_add(IMAPC_LOGOUT_TIMEOUT_MSECS, + imapc_command_timeout, conn); + } else if (conn->to == NULL) { + conn->to = timeout_add(conn->client->set.cmd_timeout_msecs, + imapc_command_timeout, conn); + } + + timeout_reset(conn->to_output); + if ((ret = imapc_command_try_send_stream(conn, cmd)) == 0) + return; + if (ret == -1) { + i_zero(&reply); + reply.text_without_resp = reply.text_full = "Mailbox not open"; + reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; + + array_pop_front(&conn->cmd_send_queue); + imapc_command_reply_free(cmd, &reply); + imapc_command_send_more(conn); + return; + } + + seek_pos = cmd->send_pos; + if (seek_pos != 0 && ret == -2) { + /* skip over the literal. we can also get here from + AUTHENTICATE command, which doesn't use a literal */ + if (parse_sync_literal(cmd->data->data, seek_pos, &size)) { + seek_pos += size; + i_assert(seek_pos <= cmd->data->used); + } + } + + do { + start_pos = seek_pos; + p = memchr(CONST_PTR_OFFSET(cmd->data->data, seek_pos), '\n', + cmd->data->used - seek_pos); + i_assert(p != NULL); + + seek_pos = p - (const unsigned char *)cmd->data->data + 1; + /* keep going for LITERAL+ command */ + } while (start_pos + 3 < seek_pos && + p[-1] == '\r' && p[-2] == '}' && p[-3] == '+'); + end_pos = seek_pos; + + data = CONST_PTR_OFFSET(cmd->data->data, cmd->send_pos); + size = end_pos - cmd->send_pos; + o_stream_nsend(conn->output, data, size); + cmd->send_pos = end_pos; + + if (cmd->send_pos == cmd->data->used) { + i_assert(!array_is_created(&cmd->streams) || + array_count(&cmd->streams) == 0); + imapc_command_send_finished(conn, cmd); + } else { + cmd->wait_for_literal = TRUE; + } +} + +static void imapc_connection_send_idle_done(struct imapc_connection *conn) +{ + if ((conn->idling || conn->idle_plus_waiting) && !conn->idle_stopping) { + conn->idle_stopping = TRUE; + o_stream_nsend_str(conn->output, "DONE\r\n"); + if (conn->to == NULL) { + conn->to = timeout_add(conn->client->set.cmd_timeout_msecs, + imapc_command_timeout, conn); + } + } +} + +static void imapc_connection_cmd_send(struct imapc_command *cmd) +{ + struct imapc_connection *conn = cmd->conn; + struct imapc_command *const *cmds; + unsigned int i, count; + + imapc_connection_send_idle_done(conn); + + i_assert((cmd->flags & IMAPC_COMMAND_FLAG_RECONNECTED) == 0); + + if ((cmd->flags & IMAPC_COMMAND_FLAG_PRELOGIN) != 0 && + conn->state == IMAPC_CONNECTION_STATE_AUTHENTICATING) { + /* pre-login commands get inserted before everything else */ + array_push_front(&conn->cmd_send_queue, &cmd); + imapc_command_send_more(conn); + return; + } + + /* add the command just before retried commands */ + cmds = array_get(&conn->cmd_send_queue, &count); + for (i = count; i > 0; i--) { + if ((cmds[i-1]->flags & IMAPC_COMMAND_FLAG_RECONNECTED) == 0) + break; + } + array_insert(&conn->cmd_send_queue, i, &cmd, 1); + imapc_command_send_more(conn); +} + +static int imapc_connection_output(struct imapc_connection *conn) +{ + struct imapc_command *const *cmds; + unsigned int count; + int ret; + + if (conn->to != NULL) + timeout_reset(conn->to); + + if ((ret = o_stream_flush(conn->output)) < 0) + return 1; + + imapc_connection_ref(conn); + cmds = array_get(&conn->cmd_send_queue, &count); + if (count > 0) { + if (imapc_command_get_sending_stream(cmds[0]) != NULL && + !cmds[0]->wait_for_literal) { + /* we're sending a stream. send more. */ + imapc_command_send_more(conn); + } + } + imapc_connection_unref(&conn); + return ret; +} + +struct imapc_command * +imapc_connection_cmd(struct imapc_connection *conn, + imapc_command_callback_t *callback, void *context) +{ + struct imapc_command *cmd; + + cmd = imapc_command_begin(callback, context); + cmd->conn = conn; + return cmd; +} + +void imapc_command_set_flags(struct imapc_command *cmd, + enum imapc_command_flags flags) +{ + cmd->flags = flags; +} + +void imapc_command_set_mailbox(struct imapc_command *cmd, + struct imapc_client_mailbox *box) +{ + cmd->box = box; +} + +bool imapc_command_connection_is_selected(struct imapc_command *cmd) +{ + return cmd->conn->selected_box != NULL || + cmd->conn->qresync_selecting_box != NULL; +} + +void imapc_command_send(struct imapc_command *cmd, const char *cmd_str) +{ + size_t len = strlen(cmd_str); + + cmd->data = str_new(cmd->pool, 6 + len + 2); + str_printfa(cmd->data, "%u %s\r\n", cmd->tag, cmd_str); + imapc_connection_cmd_send(cmd); +} + +void imapc_command_sendf(struct imapc_command *cmd, const char *cmd_fmt, ...) +{ + va_list args; + + va_start(args, cmd_fmt); + imapc_command_sendvf(cmd, cmd_fmt, args); + va_end(args); +} + +void imapc_command_sendvf(struct imapc_command *cmd, + const char *cmd_fmt, va_list args) +{ + unsigned int i; + + cmd->data = str_new(cmd->pool, 128); + str_printfa(cmd->data, "%u ", cmd->tag); + + for (i = 0; cmd_fmt[i] != '\0'; i++) { + if (cmd_fmt[i] != '%') { + str_append_c(cmd->data, cmd_fmt[i]); + continue; + } + + switch (cmd_fmt[++i]) { + case '\0': + i_unreached(); + case 'u': { + unsigned int arg = va_arg(args, unsigned int); + + str_printfa(cmd->data, "%u", arg); + break; + } + case 'p': { + struct istream *input = va_arg(args, struct istream *); + struct imapc_command_stream *s; + uoff_t size; + + if (!array_is_created(&cmd->streams)) + p_array_init(&cmd->streams, cmd->pool, 2); + if (i_stream_get_size(input, TRUE, &size) < 0) + size = 0; + str_printfa(cmd->data, "{%"PRIuUOFF_T"}\r\n", size); + s = array_append_space(&cmd->streams); + s->pos = str_len(cmd->data); + s->size = size; + s->input = input; + i_stream_ref(input); + break; + } + case 's': { + const char *arg = va_arg(args, const char *); + + if (!need_literal(arg)) + imap_append_quoted(cmd->data, arg); + else if ((cmd->conn->capabilities & + IMAPC_CAPABILITY_LITERALPLUS) != 0) { + str_printfa(cmd->data, "{%zu+}\r\n%s", + strlen(arg), arg); + } else { + str_printfa(cmd->data, "{%zu}\r\n%s", + strlen(arg), arg); + } + break; + } + case '1': { + /* %1s - no quoting */ + const char *arg = va_arg(args, const char *); + + i++; + i_assert(cmd_fmt[i] == 's'); + str_append(cmd->data, arg); + break; + } + } + } + str_append(cmd->data, "\r\n"); + + imapc_connection_cmd_send(cmd); +} + +enum imapc_connection_state +imapc_connection_get_state(struct imapc_connection *conn) +{ + return conn->state; +} + +enum imapc_capability +imapc_connection_get_capabilities(struct imapc_connection *conn) +{ + return conn->capabilities; +} + +void imapc_connection_unselect(struct imapc_client_mailbox *box, + bool via_tagged_reply) +{ + struct imapc_connection *conn = box->conn; + + if (conn->select_waiting_reply) { + /* Mailbox closing was requested before SELECT/EXAMINE + replied. The connection state is now unknown and + shouldn't be used anymore. */ + imapc_connection_disconnect(conn); + } else if (conn->qresync_selecting_box == NULL && + conn->selected_box == NULL) { + /* There is no mailbox selected currently. */ + i_assert(!via_tagged_reply); + } else { + /* Mailbox was closed in a known state. Either due to + SELECT/EXAMINE failing (via_tagged_reply) or by + imapc-storage after the mailbox was already fully + selected. */ + i_assert(conn->qresync_selecting_box == box || + conn->selected_box == box); + conn->qresync_selecting_box = NULL; + conn->selected_box = NULL; + if (via_tagged_reply) + conn->selected_on_server = FALSE; + else { + /* We didn't actually send UNSELECT command, so don't + touch selected_on_server state. */ + } + } + + imapc_connection_send_idle_done(conn); + imapc_connection_abort_commands(conn, box, FALSE); +} + +struct imapc_client_mailbox * +imapc_connection_get_mailbox(struct imapc_connection *conn) +{ + if (conn->qresync_selecting_box != NULL) + return conn->qresync_selecting_box; + return conn->selected_box; +} + +static void +imapc_connection_idle_callback(const struct imapc_command_reply *reply ATTR_UNUSED, + void *context) +{ + struct imapc_connection *conn = context; + + conn->idling = FALSE; + conn->idle_plus_waiting = FALSE; + conn->idle_stopping = FALSE; +} + +void imapc_connection_idle(struct imapc_connection *conn) +{ + struct imapc_command *cmd; + + if (array_count(&conn->cmd_send_queue) != 0 || + array_count(&conn->cmd_wait_list) != 0 || + conn->idling || conn->idle_plus_waiting || + (conn->capabilities & IMAPC_CAPABILITY_IDLE) == 0) + return; + + cmd = imapc_connection_cmd(conn, imapc_connection_idle_callback, conn); + cmd->idle = TRUE; + imapc_command_send(cmd, "IDLE"); +} diff --git a/src/lib-imap-client/imapc-connection.h b/src/lib-imap-client/imapc-connection.h new file mode 100644 index 0000000..20c249e --- /dev/null +++ b/src/lib-imap-client/imapc-connection.h @@ -0,0 +1,64 @@ +#ifndef IMAPC_CONNECTION_H +#define IMAPC_CONNECTION_H + +#include "imapc-client.h" + +/* [THROTTLED] handling behavior */ +#define IMAPC_THROTTLE_DEFAULT_INIT_MSECS 50 +#define IMAPC_THROTTLE_DEFAULT_MAX_MSECS (16*1000) +#define IMAPC_THROTTLE_DEFAULT_SHRINK_MIN_MSECS 500 + +struct imapc_client; +struct imapc_connection; + +enum imapc_connection_state { + /* No connection */ + IMAPC_CONNECTION_STATE_DISCONNECTED = 0, + /* Trying to connect */ + IMAPC_CONNECTION_STATE_CONNECTING, + /* Connected, trying to authenticate */ + IMAPC_CONNECTION_STATE_AUTHENTICATING, + /* Authenticated, ready to accept commands */ + IMAPC_CONNECTION_STATE_DONE +}; + +struct imapc_connection * +imapc_connection_init(struct imapc_client *client, + imapc_command_callback_t *login_callback, + void *login_context); +void imapc_connection_deinit(struct imapc_connection **conn); + +void imapc_connection_connect(struct imapc_connection *conn); +void imapc_connection_set_no_reconnect(struct imapc_connection *conn); +void imapc_connection_disconnect(struct imapc_connection *conn); +void imapc_connection_disconnect_full(struct imapc_connection *conn, + bool reconnecting); +void imapc_connection_try_reconnect(struct imapc_connection *conn, + const char *errstr, + unsigned int delay_msecs, + bool connect_error); +void imapc_connection_abort_commands(struct imapc_connection *conn, + struct imapc_client_mailbox *only_box, + bool keep_retriable) ATTR_NULL(2); +void imapc_connection_ioloop_changed(struct imapc_connection *conn); +void imapc_connection_input_pending(struct imapc_connection *conn); + +struct imapc_command * +imapc_connection_cmd(struct imapc_connection *conn, + imapc_command_callback_t *callback, void *context) + ATTR_NULL(3); + +void imapc_connection_unselect(struct imapc_client_mailbox *box, + bool via_tagged_reply); + +enum imapc_connection_state +imapc_connection_get_state(struct imapc_connection *conn); +enum imapc_capability +imapc_connection_get_capabilities(struct imapc_connection *conn); + +struct imapc_client_mailbox * +imapc_connection_get_mailbox(struct imapc_connection *conn); + +void imapc_connection_idle(struct imapc_connection *conn); + +#endif diff --git a/src/lib-imap-client/imapc-msgmap.c b/src/lib-imap-client/imapc-msgmap.c new file mode 100644 index 0000000..6280a24 --- /dev/null +++ b/src/lib-imap-client/imapc-msgmap.c @@ -0,0 +1,89 @@ +/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "imapc-msgmap.h" +#include "sort.h" + +struct imapc_msgmap { + ARRAY_TYPE(uint32_t) uids; + uint32_t uid_next; +}; + +struct imapc_msgmap *imapc_msgmap_init(void) +{ + struct imapc_msgmap *msgmap; + + msgmap = i_new(struct imapc_msgmap, 1); + i_array_init(&msgmap->uids, 128); + msgmap->uid_next = 1; + return msgmap; +} + +void imapc_msgmap_deinit(struct imapc_msgmap **_msgmap) +{ + struct imapc_msgmap *msgmap = *_msgmap; + + *_msgmap = NULL; + + array_free(&msgmap->uids); + i_free(msgmap); +} + +uint32_t imapc_msgmap_count(struct imapc_msgmap *msgmap) +{ + return array_count(&msgmap->uids); +} + +uint32_t imapc_msgmap_uidnext(struct imapc_msgmap *msgmap) +{ + return msgmap->uid_next; +} + +uint32_t imapc_msgmap_rseq_to_uid(struct imapc_msgmap *msgmap, uint32_t rseq) +{ + const uint32_t *uidp; + + uidp = array_idx(&msgmap->uids, rseq-1); + return *uidp; +} + +bool imapc_msgmap_uid_to_rseq(struct imapc_msgmap *msgmap, + uint32_t uid, uint32_t *rseq_r) +{ + const uint32_t *p, *first; + + p = array_bsearch(&msgmap->uids, &uid, uint32_cmp); + if (p == NULL) { + *rseq_r = 0; + return FALSE; + } + + first = array_front(&msgmap->uids); + *rseq_r = (p - first) + 1; + return TRUE; +} + +void imapc_msgmap_append(struct imapc_msgmap *msgmap, + uint32_t rseq, uint32_t uid) +{ + i_assert(rseq == imapc_msgmap_count(msgmap) + 1); + i_assert(uid >= msgmap->uid_next); + + msgmap->uid_next = uid + 1; + array_push_back(&msgmap->uids, &uid); +} + +void imapc_msgmap_expunge(struct imapc_msgmap *msgmap, uint32_t rseq) +{ + i_assert(rseq > 0); + i_assert(rseq <= imapc_msgmap_count(msgmap)); + + array_delete(&msgmap->uids, rseq-1, 1); +} + +void imapc_msgmap_reset(struct imapc_msgmap *msgmap) +{ + array_clear(&msgmap->uids); + msgmap->uid_next = 1; +} diff --git a/src/lib-imap-client/imapc-msgmap.h b/src/lib-imap-client/imapc-msgmap.h new file mode 100644 index 0000000..934bf97 --- /dev/null +++ b/src/lib-imap-client/imapc-msgmap.h @@ -0,0 +1,18 @@ +#ifndef IMAPC_MSGMAP_H +#define IMAPC_MSGMAP_H + +struct imapc_msgmap *imapc_msgmap_init(void); +void imapc_msgmap_deinit(struct imapc_msgmap **msgmap); + +uint32_t imapc_msgmap_count(struct imapc_msgmap *msgmap); +uint32_t imapc_msgmap_uidnext(struct imapc_msgmap *msgmap); +uint32_t imapc_msgmap_rseq_to_uid(struct imapc_msgmap *msgmap, uint32_t rseq); +bool imapc_msgmap_uid_to_rseq(struct imapc_msgmap *msgmap, + uint32_t uid, uint32_t *rseq_r); + +void imapc_msgmap_append(struct imapc_msgmap *msgmap, + uint32_t rseq, uint32_t uid); +void imapc_msgmap_expunge(struct imapc_msgmap *msgmap, uint32_t rseq); +void imapc_msgmap_reset(struct imapc_msgmap *msgmap); + +#endif diff --git a/src/lib-imap-client/test-imapc-client.c b/src/lib-imap-client/test-imapc-client.c new file mode 100644 index 0000000..c8953f0 --- /dev/null +++ b/src/lib-imap-client/test-imapc-client.c @@ -0,0 +1,901 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hostpid.h" +#include "net.h" +#include "istream.h" +#include "ostream.h" +#include "ioloop.h" +#include "unlink-directory.h" +#include "sleep.h" +#include "test-common.h" +#include "test-subprocess.h" +#include "imapc-client-private.h" + +#include <stdio.h> +#include <unistd.h> + +#define SERVER_KILL_TIMEOUT_SECS 20 + +#define IMAPC_COMMAND_STATE_INVALID (enum imapc_command_state)-1 + +typedef void test_server_init_t(void); +typedef void test_client_init_t(void); + +struct test_server { + in_port_t port; + pid_t pid; + + int fd_listen, fd; + struct istream *input; + struct ostream *output; +}; + +static struct ip_addr bind_ip; +static struct test_server server; +static struct imapc_client *imapc_client; +static enum imapc_command_state imapc_login_last_reply; +static ARRAY(enum imapc_command_state) imapc_cmd_last_replies; +static bool debug = FALSE; + +static void main_deinit(void); + +/* + * Test client + */ + +static struct imapc_client_settings test_imapc_default_settings = { + .host = "127.0.0.1", + .username = "testuser", + .password = "testpass", + + .dns_client_socket_path = "", + .temp_path_prefix = ".test-tmp/", + .rawlog_dir = "", + + .connect_timeout_msecs = 5000, + .connect_retry_count = 3, + .connect_retry_interval_msecs = 10, + + .max_idle_time = 10000, +}; + +static enum imapc_command_state test_imapc_cmd_last_reply_pop(void) +{ + const enum imapc_command_state *replies; + enum imapc_command_state reply; + unsigned int count; + + replies = array_get(&imapc_cmd_last_replies, &count); + if (count == 0) + return IMAPC_COMMAND_STATE_INVALID; + reply = replies[0]; + array_pop_front(&imapc_cmd_last_replies); + return reply; +} + +static bool test_imapc_cmd_last_reply_expect(enum imapc_command_state state) +{ + if (array_count(&imapc_cmd_last_replies) == 0) + imapc_client_run(imapc_client); + return test_imapc_cmd_last_reply_pop() == state; +} + +static void imapc_login_callback(const struct imapc_command_reply *reply, + void *context ATTR_UNUSED) +{ + if (debug) { + i_debug("Login reply: %s %s", + imapc_command_state_names[reply->state], + reply->text_full); + } + imapc_login_last_reply = reply->state; + imapc_client_stop(imapc_client); +} + +static void imapc_command_callback(const struct imapc_command_reply *reply, + void *context ATTR_UNUSED) +{ + if (debug) { + i_debug("Command reply: %s %s", + imapc_command_state_names[reply->state], + reply->text_full); + } + array_push_back(&imapc_cmd_last_replies, &reply->state); + imapc_client_stop(imapc_client); +} + +static void imapc_reopen_callback(void *context) +{ + struct imapc_client_mailbox *box = context; + struct imapc_command *cmd; + + cmd = imapc_client_mailbox_cmd(box, imapc_command_callback, NULL); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_SELECT); + imapc_command_send(cmd, "SELECT"); +} + +/* + * Test server + */ + +static bool +test_imapc_server_expect_full(struct test_server *server, + const char *expected_line) +{ + const char *line = i_stream_read_next_line(server->input); + + if (debug) + i_debug("Received: %s", (line == NULL ? "<EOF>" : line)); + + if (line == NULL) { + printf("imapc client disconnected unexpectedly: %s\n", + i_stream_get_error(server->input)); + return FALSE; + } else if (strcmp(line, expected_line) != 0) { + printf("imapc client sent '%s' when expecting '%s'\n", + line, expected_line); + return FALSE; + } else { + return TRUE; + } +} + +static bool test_imapc_server_expect(const char *expected_line) +{ + return test_imapc_server_expect_full(&server, expected_line); +} + +static void +test_server_wait_connection(struct test_server *server, bool send_banner) +{ + if (debug) + i_debug("Waiting for connection"); + + server->fd = net_accept(server->fd_listen, NULL, NULL); + i_assert(server->fd >= 0); + + if (debug) + i_debug("Client connected"); + + fd_set_nonblock(server->fd, FALSE); + server->input = i_stream_create_fd(server->fd, SIZE_MAX); + server->output = o_stream_create_fd(server->fd, SIZE_MAX); + o_stream_set_no_error_handling(server->output, TRUE); + + if (send_banner) { + o_stream_nsend_str(server->output, + "* OK [CAPABILITY IMAP4rev1 UNSELECT QUOTA] ready\r\n"); + } +} + +static void test_server_disconnect(struct test_server *server) +{ + if (debug) + i_debug("Disconnecting client"); + + i_stream_unref(&server->input); + o_stream_unref(&server->output); + i_close_fd(&server->fd); +} + +static void test_server_disconnect_and_wait(bool send_banner) +{ + test_server_disconnect(&server); + test_server_wait_connection(&server, send_banner); +} + +/* + * Test processes + */ + +static int test_open_server_fd(in_port_t *bind_port) +{ + int fd = net_listen(&bind_ip, bind_port, 128); + if (debug) + i_debug("server listening on %u", *bind_port); + if (fd == -1) { + i_fatal("listen(%s:%u) failed: %m", + net_ip2addr(&bind_ip), *bind_port); + } + fd_set_nonblock(fd, FALSE); + return fd; +} + +static int test_run_server(test_server_init_t *server_test) +{ + struct ioloop *ioloop; + + i_set_failure_prefix("SERVER: "); + + if (debug) + i_debug("PID=%s", my_pid); + + ioloop = io_loop_create(); + if (server_test != NULL) + server_test(); + test_server_disconnect(&server); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); + + i_close_fd(&server.fd_listen); + main_deinit(); + return 0; +} + +static void +test_run_client(const struct imapc_client_settings *client_set, + test_client_init_t *client_test) +{ + struct ioloop *ioloop; + + i_set_failure_prefix("CLIENT: "); + + if (debug) + i_debug("PID=%s", my_pid); + + i_sleep_msecs(100); /* wait a little for server setup */ + + ioloop = io_loop_create(); + imapc_client = imapc_client_init(client_set, NULL); + client_test(); + imapc_client_logout(imapc_client); + test_assert(array_count(&imapc_cmd_last_replies) == 0); + if (imapc_client != NULL) + imapc_client_deinit(&imapc_client); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); +} + +static void +test_run_client_server(const struct imapc_client_settings *client_set, + test_client_init_t *client_test, + test_server_init_t *server_test) +{ + struct imapc_client_settings client_set_copy = *client_set; + const char *error; + + imapc_client_cmd_tag_counter = 0; + imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID; + t_array_init(&imapc_cmd_last_replies, 4); + + i_zero(&server); + server.pid = (pid_t)-1; + server.fd = -1; + server.fd_listen = test_open_server_fd(&server.port); + client_set_copy.port = server.port; + + if (mkdir(client_set->temp_path_prefix, 0700) < 0 && errno != EEXIST) + i_fatal("mkdir(%s) failed: %m", client_set->temp_path_prefix); + + if (server_test != NULL) { + /* Fork server */ + test_subprocess_fork(test_run_server, server_test, TRUE); + } + i_close_fd(&server.fd_listen); + + /* Run client */ + test_run_client(&client_set_copy, client_test); + + i_unset_failure_prefix(); + test_subprocess_kill_all(SERVER_KILL_TIMEOUT_SECS); + if (unlink_directory(client_set->temp_path_prefix, + UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0) + i_fatal("%s", error); +} + +/* + * imapc connect failed + */ + +static void test_imapc_connect_failed_client(void) +{ + imapc_client_set_login_callback(imapc_client, + imapc_login_callback, NULL); + imapc_client_login(imapc_client); + /* connection refused & one reconnect */ + test_expect_errors(2); + imapc_client_run(imapc_client); + test_expect_no_more_errors(); + test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_DISCONNECTED); +} + +static void test_imapc_connect_failed(void) +{ + struct imapc_client_settings set = test_imapc_default_settings; + + test_begin("imapc connect failed"); + test_run_client_server(&set, test_imapc_connect_failed_client, NULL); + test_end(); +} + +/* + * imapc banner hang + */ + +static void test_imapc_banner_hangs_client(void) +{ + imapc_client_set_login_callback(imapc_client, + imapc_login_callback, NULL); + imapc_client_login(imapc_client); + test_expect_errors(2); + imapc_client_run(imapc_client); + test_expect_no_more_errors(); + test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_DISCONNECTED); +} + +static void test_imapc_banner_hangs_server(void) +{ + struct test_server server2 = { .fd_listen = server.fd_listen }; + + test_server_wait_connection(&server, FALSE); + test_server_wait_connection(&server2, FALSE); + test_assert(i_stream_read_next_line(server2.input) == NULL); + test_server_disconnect(&server2); +} + +static void test_imapc_banner_hangs(void) +{ + struct imapc_client_settings set = test_imapc_default_settings; + set.connect_timeout_msecs = 500; + + test_begin("imapc banner hangs"); + test_run_client_server(&set, test_imapc_banner_hangs_client, + test_imapc_banner_hangs_server); + test_end(); +} + +/* + * imapc login hangs + */ + +static void test_imapc_login_hangs_client(void) +{ + imapc_client_set_login_callback(imapc_client, + imapc_login_callback, NULL); + imapc_client_login(imapc_client); + /* run the first login */ + test_expect_error_string("Authentication timed out"); + imapc_client_run(imapc_client); + test_expect_no_more_errors(); + /* imapc_login_callback() has stopped us. run the second reconnect + login. */ + test_expect_error_string("Authentication timed out"); + imapc_client_run(imapc_client); + test_expect_no_more_errors(); + test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_DISCONNECTED); +} + +static void test_imapc_login_hangs_server(void) +{ + struct test_server server2 = { .fd_listen = server.fd_listen }; + + test_server_wait_connection(&server, TRUE); + test_assert(test_imapc_server_expect( + "1 LOGIN \"testuser\" \"testpass\"")); + + test_server_wait_connection(&server2, TRUE); + test_assert(test_imapc_server_expect_full( + &server2, "2 LOGIN \"testuser\" \"testpass\"")); + + test_assert(i_stream_read_next_line(server2.input) == NULL); + test_server_disconnect(&server2); +} + +static void test_imapc_login_hangs(void) +{ + struct imapc_client_settings set = test_imapc_default_settings; + set.connect_timeout_msecs = 500; + + test_begin("imapc login hangs"); + test_run_client_server(&set, test_imapc_login_hangs_client, + test_imapc_login_hangs_server); + test_end(); +} + +/* + * imapc login fails + */ + +static void test_imapc_login_fails_client(void) +{ + imapc_client_set_login_callback(imapc_client, + imapc_login_callback, NULL); + imapc_client_login(imapc_client); + test_expect_error_string("Authentication failed: Test login failed"); + imapc_client_run(imapc_client); + test_expect_no_more_errors(); + test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_AUTH_FAILED); +} + +static void test_imapc_login_fails_server(void) +{ + test_server_wait_connection(&server, TRUE); + test_assert(test_imapc_server_expect( + "1 LOGIN \"testuser\" \"testpass\"")); + o_stream_nsend_str(server.output, "1 NO Test login failed\r\n"); +} + +static void test_imapc_login_fails(void) +{ + struct imapc_client_settings set = test_imapc_default_settings; + + test_begin("imapc login fails"); + test_run_client_server(&set, test_imapc_login_fails_client, + test_imapc_login_fails_server); + test_end(); +} + +/* + * imapc reconnect + */ + +static void test_imapc_reconnect_client(void) +{ + struct imapc_command *cmd; + + /* login to server */ + imapc_client_set_login_callback(imapc_client, + imapc_login_callback, NULL); + imapc_client_login(imapc_client); + imapc_client_run(imapc_client); + test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_OK); + imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID; + + /* disconnect */ + cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); + imapc_command_send(cmd, "DISCONNECT"); + test_expect_error_string("reconnecting"); + imapc_client_run(imapc_client); + test_expect_no_more_errors(); + test_assert(test_imapc_cmd_last_reply_pop() == + IMAPC_COMMAND_STATE_DISCONNECTED); + + /* we should be reconnected now. try a command. */ + cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); + imapc_command_send(cmd, "NOOP"); + imapc_client_run(imapc_client); + test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_INVALID); + test_assert(test_imapc_cmd_last_reply_pop() == IMAPC_COMMAND_STATE_OK); +} + +static void test_imapc_reconnect_server(void) +{ + test_server_wait_connection(&server, TRUE); + test_assert(test_imapc_server_expect( + "1 LOGIN \"testuser\" \"testpass\"")); + o_stream_nsend_str(server.output, "1 OK \r\n"); + + test_assert(test_imapc_server_expect("2 DISCONNECT")); + test_server_disconnect_and_wait(TRUE); + + test_assert(test_imapc_server_expect( + "4 LOGIN \"testuser\" \"testpass\"")); + o_stream_nsend_str(server.output, "4 OK \r\n"); + test_assert(test_imapc_server_expect("3 NOOP")); + o_stream_nsend_str(server.output, "3 OK \r\n"); + + test_assert(test_imapc_server_expect("5 LOGOUT")); + o_stream_nsend_str(server.output, "5 OK \r\n"); + + test_assert(i_stream_read_next_line(server.input) == NULL); +} + +static void test_imapc_reconnect(void) +{ + struct imapc_client_settings set = test_imapc_default_settings; + + test_begin("imapc reconnect"); + test_run_client_server(&set, test_imapc_reconnect_client, + test_imapc_reconnect_server); + test_end(); +} + +/* + * imapc reconnect resend commands + */ + +static void test_imapc_reconnect_resend_cmds_client(void) +{ + struct imapc_command *cmd; + + /* login to server */ + imapc_client_set_login_callback(imapc_client, + imapc_login_callback, NULL); + imapc_client_login(imapc_client); + imapc_client_run(imapc_client); + test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_OK); + imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID; + + /* send two commands */ + cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); + imapc_command_send(cmd, "RETRY1"); + cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); + imapc_command_send(cmd, "RETRY2"); + + /* disconnect & reconnect automatically */ + cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); + imapc_command_send(cmd, "DISCONNECT"); + test_expect_error_string("reconnecting"); + imapc_client_run(imapc_client); + test_expect_no_more_errors(); + test_assert(test_imapc_cmd_last_reply_expect( + IMAPC_COMMAND_STATE_DISCONNECTED)); + + /* continue reconnection */ + test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK)); + test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK)); +} + +static void test_imapc_reconnect_resend_cmds_server(void) +{ + test_server_wait_connection(&server, TRUE); + test_assert(test_imapc_server_expect( + "1 LOGIN \"testuser\" \"testpass\"")); + o_stream_nsend_str(server.output, "1 OK \r\n"); + + test_assert(test_imapc_server_expect("2 RETRY1")); + test_assert(test_imapc_server_expect("3 RETRY2")); + test_assert(test_imapc_server_expect("4 DISCONNECT")); + test_server_disconnect_and_wait(TRUE); + + test_assert(test_imapc_server_expect( + "5 LOGIN \"testuser\" \"testpass\"")); + o_stream_nsend_str(server.output, "5 OK \r\n"); + test_assert(test_imapc_server_expect("2 RETRY1")); + o_stream_nsend_str(server.output, "2 OK \r\n"); + test_assert(test_imapc_server_expect("3 RETRY2")); + o_stream_nsend_str(server.output, "3 OK \r\n"); + + test_assert(test_imapc_server_expect("6 LOGOUT")); + o_stream_nsend_str(server.output, "6 OK \r\n"); + + test_assert(i_stream_read_next_line(server.input) == NULL); +} + +static void test_imapc_reconnect_resend_commands(void) +{ + struct imapc_client_settings set = test_imapc_default_settings; + + test_begin("imapc reconnect resend commands"); + test_run_client_server(&set, test_imapc_reconnect_resend_cmds_client, + test_imapc_reconnect_resend_cmds_server); + test_end(); +} + +/* + * imapc reconnect resend commands failed + */ + +static void test_imapc_reconnect_resend_cmds_failed_client(void) +{ + struct imapc_command *cmd; + + /* login to server */ + imapc_client_set_login_callback(imapc_client, + imapc_login_callback, NULL); + imapc_client_login(imapc_client); + imapc_client_run(imapc_client); + test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_OK); + imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID; + + /* send two commands */ + cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); + imapc_command_send(cmd, "RETRY1"); + cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); + imapc_command_send(cmd, "RETRY2"); + + /* disconnect & try to reconnect automatically */ + cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); + imapc_command_send(cmd, "DISCONNECT"); + test_expect_error_string("reconnecting"); + imapc_client_run(imapc_client); + test_expect_no_more_errors(); + test_assert(test_imapc_cmd_last_reply_expect( + IMAPC_COMMAND_STATE_DISCONNECTED)); + test_expect_error_string("timed out"); + test_assert(test_imapc_cmd_last_reply_expect( + IMAPC_COMMAND_STATE_DISCONNECTED)); + test_expect_no_more_errors(); + test_assert(test_imapc_cmd_last_reply_expect( + IMAPC_COMMAND_STATE_DISCONNECTED)); +} + +static void test_imapc_reconnect_resend_cmds_failed_server(void) +{ + test_server_wait_connection(&server, TRUE); + test_assert(test_imapc_server_expect( + "1 LOGIN \"testuser\" \"testpass\"")); + o_stream_nsend_str(server.output, "1 OK \r\n"); + + test_assert(test_imapc_server_expect("2 RETRY1")); + test_assert(test_imapc_server_expect("3 RETRY2")); + test_assert(test_imapc_server_expect("4 DISCONNECT")); + test_server_disconnect(&server); + + i_sleep_intr_secs(60); +} + +static void test_imapc_reconnect_resend_commands_failed(void) +{ + struct imapc_client_settings set = test_imapc_default_settings; + set.connect_timeout_msecs = 500; + + test_begin("imapc reconnect resend commands failed"); + test_run_client_server(&set, + test_imapc_reconnect_resend_cmds_failed_client, + test_imapc_reconnect_resend_cmds_failed_server); + test_end(); +} + +/* + * imapc reconnect mailbox + */ + +static void test_imapc_reconnect_mailbox_client(void) +{ + struct imapc_command *cmd; + struct imapc_client_mailbox *box; + + /* login to server */ + imapc_client_set_login_callback(imapc_client, + imapc_login_callback, NULL); + imapc_client_login(imapc_client); + imapc_client_run(imapc_client); + test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_OK); + imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID; + + /* select a mailbox */ + box = imapc_client_mailbox_open(imapc_client, NULL); + imapc_client_mailbox_set_reopen_cb(box, imapc_reopen_callback, box); + + cmd = imapc_client_mailbox_cmd(box, imapc_command_callback, NULL); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_SELECT); + imapc_command_send(cmd, "SELECT"); + imapc_client_run(imapc_client); + test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK)); + + /* send a command */ + cmd = imapc_client_mailbox_cmd(box, imapc_command_callback, NULL); + imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); + imapc_command_send(cmd, "RETRY"); + + /* disconnect & reconnect automatically */ + cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); + imapc_command_send(cmd, "DISCONNECT"); + test_expect_error_string("reconnecting"); + imapc_client_run(imapc_client); + test_expect_no_more_errors(); + test_assert(test_imapc_cmd_last_reply_expect( + IMAPC_COMMAND_STATE_DISCONNECTED)); + + /* continue reconnection */ + test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK)); + test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK)); + + imapc_client_mailbox_close(&box); +} + +static void test_imapc_reconnect_mailbox_server(void) +{ + test_server_wait_connection(&server, TRUE); + test_assert(test_imapc_server_expect( + "1 LOGIN \"testuser\" \"testpass\"")); + o_stream_nsend_str(server.output, "1 OK \r\n"); + + test_assert(test_imapc_server_expect("2 SELECT")); + o_stream_nsend_str(server.output, "2 OK \r\n"); + + test_assert(test_imapc_server_expect("3 RETRY")); + test_assert(test_imapc_server_expect("4 DISCONNECT")); + test_server_disconnect_and_wait(TRUE); + + test_assert(test_imapc_server_expect( + "5 LOGIN \"testuser\" \"testpass\"")); + o_stream_nsend_str(server.output, "5 OK \r\n"); + test_assert(test_imapc_server_expect("6 SELECT")); + o_stream_nsend_str(server.output, "6 OK \r\n"); + test_assert(test_imapc_server_expect("3 RETRY")); + o_stream_nsend_str(server.output, "3 OK \r\n"); + + test_assert(test_imapc_server_expect("7 LOGOUT")); + o_stream_nsend_str(server.output, "7 OK \r\n"); + + test_assert(i_stream_read_next_line(server.input) == NULL); +} + +static void test_imapc_reconnect_mailbox(void) +{ + struct imapc_client_settings set = test_imapc_default_settings; + + test_begin("imapc reconnect mailbox"); + test_run_client_server(&set, test_imapc_reconnect_mailbox_client, + test_imapc_reconnect_mailbox_server); + test_end(); +} + +/* + * imapc_client_get_capabilities() + */ + +static void test_imapc_client_get_capabilities_client(void) +{ + enum imapc_capability capabilities; + + test_assert(imapc_client_get_capabilities(imapc_client, &capabilities) == 0); + test_assert(capabilities == (IMAPC_CAPABILITY_IMAP4REV1 | + IMAPC_CAPABILITY_UNSELECT | + IMAPC_CAPABILITY_QUOTA)); +} + +static void test_imapc_client_get_capabilities_server(void) +{ + test_server_wait_connection(&server, TRUE); + test_assert(test_imapc_server_expect( + "1 LOGIN \"testuser\" \"testpass\"")); + o_stream_nsend_str(server.output, "1 OK \r\n"); + + test_assert(test_imapc_server_expect("2 LOGOUT")); + o_stream_nsend_str(server.output, "2 OK \r\n"); + + test_assert(i_stream_read_next_line(server.input) == NULL); +} + +static void test_imapc_client_get_capabilities(void) +{ + struct imapc_client_settings set = test_imapc_default_settings; + + test_begin("imapc_client_get_capabilities()"); + test_run_client_server(&set, test_imapc_client_get_capabilities_client, + test_imapc_client_get_capabilities_server); + test_end(); +} + +/* + * imapc_client_get_capabilities() reconnected + */ + +static void test_imapc_client_get_capabilities_reconnected_client(void) +{ + enum imapc_capability capabilities; + + test_expect_error_string("Server disconnected unexpectedly"); + test_assert(imapc_client_get_capabilities(imapc_client, + &capabilities) == 0); + test_assert(capabilities == (IMAPC_CAPABILITY_IMAP4REV1 | + IMAPC_CAPABILITY_UNSELECT | + IMAPC_CAPABILITY_QUOTA)); + test_expect_no_more_errors(); +} + +static void test_imapc_client_get_capabilities_reconnected_server(void) +{ + test_server_wait_connection(&server, TRUE); + test_server_disconnect_and_wait(TRUE); + + test_assert(test_imapc_server_expect( + "2 LOGIN \"testuser\" \"testpass\"")); + o_stream_nsend_str(server.output, "2 OK \r\n"); + + test_assert(test_imapc_server_expect("3 LOGOUT")); + o_stream_nsend_str(server.output, "3 OK \r\n"); + + test_assert(i_stream_read_next_line(server.input) == NULL); +} + +static void test_imapc_client_get_capabilities_reconnected(void) +{ + struct imapc_client_settings set = test_imapc_default_settings; + + test_begin("imapc_client_get_capabilities() reconnected"); + + test_run_client_server( + &set, test_imapc_client_get_capabilities_reconnected_client, + test_imapc_client_get_capabilities_reconnected_server); + test_end(); +} + +/* + * imapc_client_get_capabilities() disconnected + */ + +static void test_imapc_client_get_capabilities_disconnected_client(void) +{ + enum imapc_capability capabilities; + + test_expect_errors(2); + test_assert(imapc_client_get_capabilities(imapc_client, + &capabilities) < 0); + test_expect_no_more_errors(); +} + +static void test_imapc_client_get_capabilities_disconnected_server(void) +{ + test_server_wait_connection(&server, TRUE); + test_server_disconnect_and_wait(TRUE); +} + +static void test_imapc_client_get_capabilities_disconnected(void) +{ + struct imapc_client_settings set = test_imapc_default_settings; + + test_begin("imapc_client_get_capabilities() disconnected"); + + test_run_client_server( + &set, test_imapc_client_get_capabilities_disconnected_client, + test_imapc_client_get_capabilities_disconnected_server); + test_end(); +} + +/* + * Main + */ + +static void main_init(void) +{ + /* nothing yet */ +} + +static void main_deinit(void) +{ + /* nothing yet; also called from sub-processes */ +} + +int main(int argc ATTR_UNUSED, char *argv[]) +{ + int c; + int ret; + + static void (*const test_functions[])(void) = { + test_imapc_connect_failed, + test_imapc_banner_hangs, + test_imapc_login_hangs, + test_imapc_login_fails, + test_imapc_reconnect, + test_imapc_reconnect_resend_commands, + test_imapc_reconnect_resend_commands_failed, + test_imapc_reconnect_mailbox, + test_imapc_client_get_capabilities, + test_imapc_client_get_capabilities_reconnected, + test_imapc_client_get_capabilities_disconnected, + NULL + }; + + lib_init(); + main_init(); + + while ((c = getopt(argc, argv, "D")) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + default: + i_fatal("Usage: %s [-D]", argv[0]); + } + } + + test_subprocesses_init(debug); + test_imapc_default_settings.debug = debug; + + /* listen on localhost */ + i_zero(&bind_ip); + bind_ip.family = AF_INET; + bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK); + + ret = test_run(test_functions); + + test_subprocesses_deinit(); + main_deinit(); + lib_deinit(); + + return ret; +} diff --git a/src/lib-imap-storage/Makefile.am b/src/lib-imap-storage/Makefile.am new file mode 100644 index 0000000..b084653 --- /dev/null +++ b/src/lib-imap-storage/Makefile.am @@ -0,0 +1,24 @@ +noinst_LTLIBRARIES = libimap-storage.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-charset \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-imap + +libimap_storage_la_SOURCES = \ + imap-msgpart.c \ + imap-msgpart-url.c \ + imap-metadata.c + +headers = \ + imap-msgpart.h \ + imap-msgpart-url.h \ + imap-metadata.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers) + diff --git a/src/lib-imap-storage/Makefile.in b/src/lib-imap-storage/Makefile.in new file mode 100644 index 0000000..7f8f26b --- /dev/null +++ b/src/lib-imap-storage/Makefile.in @@ -0,0 +1,825 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/lib-imap-storage +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libimap_storage_la_LIBADD = +am_libimap_storage_la_OBJECTS = imap-msgpart.lo imap-msgpart-url.lo \ + imap-metadata.lo +libimap_storage_la_OBJECTS = $(am_libimap_storage_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/imap-metadata.Plo \ + ./$(DEPDIR)/imap-msgpart-url.Plo ./$(DEPDIR)/imap-msgpart.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libimap_storage_la_SOURCES) +DIST_SOURCES = $(libimap_storage_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkginc_libdir)" +HEADERS = $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LTLIBRARIES = libimap-storage.la +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-charset \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-imap + +libimap_storage_la_SOURCES = \ + imap-msgpart.c \ + imap-msgpart-url.c \ + imap-metadata.c + +headers = \ + imap-msgpart.h \ + imap-msgpart-url.h \ + imap-metadata.h + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = $(headers) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-imap-storage/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib-imap-storage/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libimap-storage.la: $(libimap_storage_la_OBJECTS) $(libimap_storage_la_DEPENDENCIES) $(EXTRA_libimap_storage_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libimap_storage_la_OBJECTS) $(libimap_storage_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-metadata.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-msgpart-url.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-msgpart.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/imap-metadata.Plo + -rm -f ./$(DEPDIR)/imap-msgpart-url.Plo + -rm -f ./$(DEPDIR)/imap-msgpart.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/imap-metadata.Plo + -rm -f ./$(DEPDIR)/imap-msgpart-url.Plo + -rm -f ./$(DEPDIR)/imap-msgpart.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkginc_libHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-pkginc_libHEADERS install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-pkginc_libHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib-imap-storage/imap-metadata.c b/src/lib-imap-storage/imap-metadata.c new file mode 100644 index 0000000..eb791a4 --- /dev/null +++ b/src/lib-imap-storage/imap-metadata.c @@ -0,0 +1,314 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-storage.h" + +#include "imap-metadata.h" + +struct imap_metadata_transaction { + struct mailbox *box; + struct mailbox_transaction_context *trans; + + enum mail_error error; + char *error_string; + + bool server:1; + bool validated_only:1; +}; + +bool imap_metadata_verify_entry_name(const char *name, + const char **client_error_r) +{ + unsigned int i; + bool ok; + + if (name[0] != '/') { + *client_error_r = "Entry name must begin with '/'"; + return FALSE; + } + for (i = 0; name[i] != '\0'; i++) { + switch (name[i]) { + case '/': + if (i > 0 && name[i-1] == '/') { + *client_error_r = "Entry name can't contain consecutive '/'"; + return FALSE; + } + if (name[i+1] == '\0') { + *client_error_r = "Entry name can't end with '/'"; + return FALSE; + } + break; + case '*': + *client_error_r = "Entry name can't contain '*'"; + return FALSE; + case '%': + *client_error_r = "Entry name can't contain '%'"; + return FALSE; + default: + if (name[i] <= 0x19) { + *client_error_r = "Entry name can't contain control chars"; + return FALSE; + } + break; + } + } + T_BEGIN { + const char *prefix, *p = strchr(name+1, '/'); + + prefix = p == NULL ? name : t_strdup_until(name, p); + ok = strcasecmp(prefix, IMAP_METADATA_PRIVATE_PREFIX) == 0 || + strcasecmp(prefix, IMAP_METADATA_SHARED_PREFIX) == 0; + } T_END; + if (!ok) { + *client_error_r = "Entry name must begin with /private or /shared"; + return FALSE; + } + return TRUE; +} + +static void +imap_metadata_transaction_set_error(struct imap_metadata_transaction *imtrans, + enum mail_error error, const char *string) +{ + i_free(imtrans->error_string); + imtrans->error_string = i_strdup(string); + imtrans->error = error; +} + +static bool +imap_metadata_entry2key(struct imap_metadata_transaction *imtrans, + const char *entry, enum mail_attribute_type *type_r, + const char **key_r) +{ + const char *key_prefix = (imtrans->server ? + MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER : NULL); + + /* names are case-insensitive so we'll always lowercase them */ + entry = t_str_lcase(entry); + + if (str_begins(entry, IMAP_METADATA_PRIVATE_PREFIX)) { + *key_r = entry + strlen(IMAP_METADATA_PRIVATE_PREFIX); + *type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE; + } else { + i_assert(str_begins(entry, IMAP_METADATA_SHARED_PREFIX)); + *key_r = entry + strlen(IMAP_METADATA_SHARED_PREFIX); + *type_r = MAIL_ATTRIBUTE_TYPE_SHARED; + } + if ((*key_r)[0] == '\0') { + /* /private or /shared prefix has no value itself */ + } else { + i_assert((*key_r)[0] == '/'); + *key_r += 1; + } + + if (imtrans->validated_only) + *type_r |= MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED; + + if (str_begins(*key_r, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT)) { + /* Dovecot's internal attribute (mailbox or server). + don't allow accessing this. */ + return FALSE; + } + /* Add the server-prefix (after checking for the above internal + attribute). */ + if (key_prefix != NULL) + *key_r = t_strconcat(key_prefix, *key_r, NULL); + return TRUE; +} + +static int +imap_metadata_get_mailbox_transaction(struct imap_metadata_transaction *imtrans) +{ + if (imtrans->trans != NULL) + return 0; + + if (imtrans->box == NULL || mailbox_open(imtrans->box) < 0) + return -1; + imtrans->trans = mailbox_transaction_begin(imtrans->box, + MAILBOX_TRANSACTION_FLAG_EXTERNAL, __func__); + return 0; +} + +int imap_metadata_set(struct imap_metadata_transaction *imtrans, + const char *entry, const struct mail_attribute_value *value) +{ + enum mail_attribute_type type; + const char *key; + + if (!imap_metadata_entry2key(imtrans, entry, &type, &key)) { + imap_metadata_transaction_set_error(imtrans, MAIL_ERROR_PARAMS, + "Internal mailbox attributes cannot be accessed"); + return -1; + } + + if (imap_metadata_get_mailbox_transaction(imtrans) < 0) + return -1; + return (value->value == NULL && value->value_stream == NULL ? + mailbox_attribute_unset(imtrans->trans, type, key) : + mailbox_attribute_set(imtrans->trans, type, key, value)); +} + +int imap_metadata_unset(struct imap_metadata_transaction *imtrans, + const char *entry) +{ + struct mail_attribute_value value; + + i_zero(&value); + return imap_metadata_set(imtrans, entry, &value); +} + +int imap_metadata_get(struct imap_metadata_transaction *imtrans, + const char *entry, struct mail_attribute_value *value_r) +{ + enum mail_attribute_type type; + const char *key; + + i_zero(value_r); + if (!imap_metadata_entry2key(imtrans, entry, &type, &key)) + return 0; + return mailbox_attribute_get(imtrans->box, type, key, value_r); +} + +int imap_metadata_get_stream(struct imap_metadata_transaction *imtrans, + const char *entry, struct mail_attribute_value *value_r) +{ + enum mail_attribute_type type; + const char *key; + + i_zero(value_r); + if (!imap_metadata_entry2key(imtrans, entry, &type, &key)) + return 0; + return mailbox_attribute_get_stream(imtrans->box, type, key, value_r); +} + +struct imap_metadata_iter { + struct mailbox_attribute_iter *iter; +}; + +struct imap_metadata_iter * +imap_metadata_iter_init(struct imap_metadata_transaction *imtrans, + const char *entry) +{ + struct imap_metadata_iter *iter; + enum mail_attribute_type type; + const char *key; + + iter = i_new(struct imap_metadata_iter, 1); + if (imap_metadata_entry2key(imtrans, entry, &type, &key)) { + const char *prefix = + key[0] == '\0' ? "" : t_strconcat(key, "/", NULL); + iter->iter = mailbox_attribute_iter_init(imtrans->box, type, + prefix); + } + return iter; +} + +const char *imap_metadata_iter_next(struct imap_metadata_iter *iter) +{ + if (iter->iter == NULL) + return NULL; + return mailbox_attribute_iter_next(iter->iter); +} + +int imap_metadata_iter_deinit(struct imap_metadata_iter **_iter) +{ + struct imap_metadata_iter *iter = *_iter; + int ret; + + *_iter = NULL; + + if (iter->iter == NULL) + ret = 0; + else + ret = mailbox_attribute_iter_deinit(&iter->iter); + i_free(iter); + return ret; +} + +struct imap_metadata_transaction * +imap_metadata_transaction_begin(struct mailbox *box) +{ + struct imap_metadata_transaction *imtrans; + + imtrans = i_new(struct imap_metadata_transaction, 1); + imtrans->box = box; + return imtrans; +} + +struct imap_metadata_transaction * +imap_metadata_transaction_begin_server(struct mail_user *user) +{ + struct mail_namespace *ns; + struct mailbox *box; + struct imap_metadata_transaction *imtrans; + + ns = mail_namespace_find_inbox(user->namespaces); + /* Server metadata shouldn't depend on INBOX's ACLs, so ignore them. */ + box = mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_IGNORE_ACLS | + MAILBOX_FLAG_ATTRIBUTE_SESSION); + imtrans = imap_metadata_transaction_begin(box); + imtrans->server = TRUE; + return imtrans; +} + +void imap_metadata_transaction_validated_only(struct imap_metadata_transaction *imtrans, + bool set) +{ + imtrans->validated_only = set; +} + +static void +imap_metadata_transaction_finish(struct imap_metadata_transaction **_imtrans) +{ + struct imap_metadata_transaction *imtrans = *_imtrans; + + if (imtrans->server) + mailbox_free(&imtrans->box); + + i_free(imtrans->error_string); + i_free(imtrans); + *_imtrans = NULL; +} + +int imap_metadata_transaction_commit( + struct imap_metadata_transaction **_imtrans, + enum mail_error *error_code_r, const char **client_error_r) +{ + struct imap_metadata_transaction *imtrans = *_imtrans; + int ret = 0; + + if (imtrans->trans != NULL) { + const char *error = NULL; + ret = mailbox_transaction_commit(&imtrans->trans); + if (ret < 0) + error = mailbox_get_last_error(imtrans->box, error_code_r); + if (client_error_r != NULL) + *client_error_r = error; + } + imap_metadata_transaction_finish(_imtrans); + return ret; +} + +void imap_metadata_transaction_rollback( + struct imap_metadata_transaction **_imtrans) +{ + struct imap_metadata_transaction *imtrans = *_imtrans; + + if (imtrans->trans != NULL) + mailbox_transaction_rollback(&imtrans->trans); + imap_metadata_transaction_finish(_imtrans); +} + +const char * +imap_metadata_transaction_get_last_error( + struct imap_metadata_transaction *imtrans, + enum mail_error *error_code_r) +{ + if (imtrans->error != MAIL_ERROR_NONE) { + if (error_code_r != NULL) + *error_code_r = imtrans->error; + return imtrans->error_string; + } + i_assert(imtrans->box != NULL); + return mailbox_get_last_error(imtrans->box, error_code_r); +} diff --git a/src/lib-imap-storage/imap-metadata.h b/src/lib-imap-storage/imap-metadata.h new file mode 100644 index 0000000..ef88e61 --- /dev/null +++ b/src/lib-imap-storage/imap-metadata.h @@ -0,0 +1,61 @@ +#ifndef IMAP_METADATA_H +#define IMAP_METADATA_H + +#define IMAP_METADATA_PRIVATE_PREFIX "/private" +#define IMAP_METADATA_SHARED_PREFIX "/shared" + +struct imap_metadata_iter; +struct imap_metadata_transaction; + +/* Checks whether IMAP metadata entry name is valid */ +bool imap_metadata_verify_entry_name( + const char *name, const char **client_error_r); + +/* Set IMAP metadata entry to value. */ +int imap_metadata_set(struct imap_metadata_transaction *imtrans, + const char *entry, const struct mail_attribute_value *value); +/* Delete IMAP metadata entry. This is just a wrapper to + imap_metadata_set() with value->value=NULL. */ +int imap_metadata_unset(struct imap_metadata_transaction *imtrans, + const char *entry); +/* Returns value for IMAP metadata entry. Returns 1 if value was returned, + 0 if value wasn't found (set to NULL), -1 if error */ +int imap_metadata_get(struct imap_metadata_transaction *imtrans, + const char *entry, struct mail_attribute_value *value_r); +/* Same as imap_metadata_get(), but the returned value may be either an + input stream or a string. */ +int imap_metadata_get_stream(struct imap_metadata_transaction *imtrans, + const char *entry, struct mail_attribute_value *value_r); + +/* Iterate through IMAP metadata entries names under the specified entry. */ +struct imap_metadata_iter * +imap_metadata_iter_init(struct imap_metadata_transaction *imtrans, + const char *entry); +/* Returns the next IMAP metadata entry name or NULL if there are no more + entries. */ +const char *imap_metadata_iter_next(struct imap_metadata_iter *iter); +int imap_metadata_iter_deinit(struct imap_metadata_iter **_iter); + +struct imap_metadata_transaction * +imap_metadata_transaction_begin(struct mailbox *box); +struct imap_metadata_transaction * +imap_metadata_transaction_begin_mailbox(struct mail_user *user, + const char *mailbox); +struct imap_metadata_transaction * +imap_metadata_transaction_begin_server(struct mail_user *user); + +/* Allow access only to validated attributes within this transaction. */ +void imap_metadata_transaction_validated_only(struct imap_metadata_transaction *imtrans, + bool set); + +int imap_metadata_transaction_commit( + struct imap_metadata_transaction **_imtrans, + enum mail_error *error_code_r, const char **client_error_r); +void imap_metadata_transaction_rollback( + struct imap_metadata_transaction **_imtrans); +const char * +imap_metadata_transaction_get_last_error( + struct imap_metadata_transaction *imtrans, + enum mail_error *error_code_r); + +#endif diff --git a/src/lib-imap-storage/imap-msgpart-url.c b/src/lib-imap-storage/imap-msgpart-url.c new file mode 100644 index 0000000..2469dd3 --- /dev/null +++ b/src/lib-imap-storage/imap-msgpart-url.c @@ -0,0 +1,287 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "istream.h" +#include "message-parser.h" +#include "mail-storage.h" +#include "mail-namespace.h" +#include "imap-url.h" +#include "imap-msgpart.h" +#include "imap-msgpart-url.h" + +struct imap_msgpart_url { + char *mailbox; + uint32_t uidvalidity; + uint32_t uid; + char *section; + uoff_t partial_offset, partial_size; + + struct imap_msgpart *part; + + struct mail_user *user; + struct mailbox *selected_box; + struct mailbox *box; + struct mailbox_transaction_context *trans; + struct mail *mail; + + struct imap_msgpart_open_result result; + + bool decode_cte_to_binary:1; +}; + +int imap_msgpart_url_create(struct mail_user *user, const struct imap_url *url, + struct imap_msgpart_url **mpurl_r, + const char **client_error_r) +{ + const char *section = url->section == NULL ? "" : url->section; + struct imap_msgpart_url *mpurl; + struct imap_msgpart *msgpart; + + if (url->mailbox == NULL || url->uid == 0 || + url->search_program != NULL) { + *client_error_r = "Invalid messagepart IMAP URL"; + return -1; + } + if (imap_msgpart_parse(section, &msgpart) < 0) { + *client_error_r = "Invalid section"; + return -1; + } + + mpurl = i_new(struct imap_msgpart_url, 1); + mpurl->part = msgpart; + mpurl->user = user; + mpurl->mailbox = i_strdup(url->mailbox); + mpurl->uidvalidity = url->uidvalidity; + mpurl->uid = url->uid; + if (url->section != NULL) + mpurl->section = i_strdup(url->section); + mpurl->partial_offset = url->partial_offset; + mpurl->partial_size = url->partial_size; + + imap_msgpart_set_partial(msgpart, url->partial_offset, + url->partial_size == 0 ? + UOFF_T_MAX : url->partial_size); + + *mpurl_r = mpurl; + return 0; +} + +int imap_msgpart_url_parse(struct mail_user *user, struct mailbox *selected_box, + const char *urlstr, struct imap_msgpart_url **mpurl_r, + const char **client_error_r) +{ + struct mailbox_status box_status; + struct imap_url base_url, *url; + const char *error; + + /* build base url */ + i_zero(&base_url); + if (selected_box != NULL) { + mailbox_get_open_status(selected_box, STATUS_UIDVALIDITY, + &box_status); + base_url.mailbox = mailbox_get_vname(selected_box); + base_url.uidvalidity = box_status.uidvalidity; + } + + /* parse url */ + if (imap_url_parse(urlstr, &base_url, + IMAP_URL_PARSE_REQUIRE_RELATIVE, &url, &error) < 0) { + *client_error_r = t_strconcat("Invalid IMAP URL: ", error, NULL); + return 0; + } + if (url->mailbox == NULL) { + *client_error_r = "Mailbox-relative IMAP URL, but no mailbox selected"; + return 0; + } + if (imap_msgpart_url_create(user, url, mpurl_r, client_error_r) < 0) + return -1; + (*mpurl_r)->selected_box = selected_box; + return 1; +} + +struct mailbox *imap_msgpart_url_get_mailbox(struct imap_msgpart_url *mpurl) +{ + return mpurl->box; +} + +int imap_msgpart_url_open_mailbox(struct imap_msgpart_url *mpurl, + struct mailbox **box_r, enum mail_error *error_code_r, + const char **client_error_r) +{ + struct mailbox_status box_status; + enum mailbox_flags flags = MAILBOX_FLAG_READONLY; + struct mail_namespace *ns; + struct mailbox *box; + + if (mpurl->box != NULL) { + *box_r = mpurl->box; + *error_code_r = MAIL_ERROR_NONE; + return 1; + } + + /* find mailbox namespace */ + ns = mail_namespace_find(mpurl->user->namespaces, mpurl->mailbox); + + /* open mailbox */ + if (mpurl->selected_box != NULL && + mailbox_equals(mpurl->selected_box, ns, mpurl->mailbox)) + box = mpurl->selected_box; + else + box = mailbox_alloc(ns->list, mpurl->mailbox, flags); + if (mailbox_open(box) < 0) { + *client_error_r = mail_storage_get_last_error(mailbox_get_storage(box), + error_code_r); + if (box != mpurl->selected_box) + mailbox_free(&box); + return *error_code_r == MAIL_ERROR_TEMP ? -1 : 0; + } + + /* verify UIDVALIDITY */ + mailbox_get_open_status(box, STATUS_UIDVALIDITY, &box_status); + if (mpurl->uidvalidity > 0 && + box_status.uidvalidity != mpurl->uidvalidity) { + *client_error_r = "Invalid UIDVALIDITY"; + *error_code_r = MAIL_ERROR_EXPUNGED; + if (box != mpurl->selected_box) + mailbox_free(&box); + return 0; + } + mpurl->box = box; + *box_r = box; + return 1; +} + +int imap_msgpart_url_open_mail(struct imap_msgpart_url *mpurl, + struct mail **mail_r, + const char **client_error_r) +{ + struct mailbox_transaction_context *t; + struct mailbox *box; + enum mail_error error_code; + struct mail *mail; + int ret; + + if (mpurl->mail != NULL) { + *mail_r = mpurl->mail; + return 1; + } + + /* open mailbox if it is not yet open */ + if ((ret = imap_msgpart_url_open_mailbox(mpurl, &box, &error_code, + client_error_r)) <= 0) + return ret; + + /* start transaction */ + t = mailbox_transaction_begin(box, 0, __func__); + mail = mail_alloc(t, MAIL_FETCH_MESSAGE_PARTS | + MAIL_FETCH_IMAP_BODYSTRUCTURE, NULL); + + /* find the message */ + if (!mail_set_uid(mail, mpurl->uid)) { + *client_error_r = "Message not found"; + mail_free(&mail); + mailbox_transaction_rollback(&t); + return 0; + } + + mpurl->trans = t; + mpurl->mail = mail; + *mail_r = mail; + return 1; +} + +struct imap_msgpart * +imap_msgpart_url_get_part(struct imap_msgpart_url *mpurl) +{ + return mpurl->part; +} + +void imap_msgpart_url_set_decode_to_binary(struct imap_msgpart_url *mpurl) +{ + imap_msgpart_set_decode_to_binary(mpurl->part); +} + +int imap_msgpart_url_read_part(struct imap_msgpart_url *mpurl, + struct imap_msgpart_open_result *result_r, + const char **client_error_r) +{ + struct mail *mail; + int ret; + + if (mpurl->result.input != NULL) { + i_stream_seek(mpurl->result.input, 0); + *result_r = mpurl->result; + return 1; + } + + /* open mail if it is not yet open */ + ret = imap_msgpart_url_open_mail(mpurl, &mail, client_error_r); + if (ret <= 0) + return ret; + + /* open the referenced part as a stream */ + ret = imap_msgpart_open(mail, mpurl->part, result_r); + if (ret < 0) { + *client_error_r = mailbox_get_last_error(mpurl->box, NULL); + return ret; + } + + mpurl->result = *result_r; + return 1; +} + +int imap_msgpart_url_verify(struct imap_msgpart_url *mpurl, + const char **client_error_r) +{ + struct mail *mail; + int ret; + + if (mpurl->result.input != NULL) + return 1; + + /* open mail if it is not yet open */ + ret = imap_msgpart_url_open_mail(mpurl, &mail, client_error_r); + return ret; +} + +int imap_msgpart_url_get_bodypartstructure(struct imap_msgpart_url *mpurl, + const char **bpstruct_r, + const char **client_error_r) +{ + struct mail *mail; + int ret; + + /* open mail if it is not yet open */ + ret = imap_msgpart_url_open_mail(mpurl, &mail, client_error_r); + if (ret <= 0) + return ret; + + ret = imap_msgpart_bodypartstructure(mail, mpurl->part, bpstruct_r); + if (ret < 0) + *client_error_r = mailbox_get_last_error(mpurl->box, NULL); + else if (ret == 0) + *client_error_r = "Message part not found"; + return ret; +} + +void imap_msgpart_url_free(struct imap_msgpart_url **_mpurl) +{ + struct imap_msgpart_url *mpurl = *_mpurl; + + *_mpurl = NULL; + + i_stream_unref(&mpurl->result.input); + if (mpurl->part != NULL) + imap_msgpart_free(&mpurl->part); + if (mpurl->mail != NULL) + mail_free(&mpurl->mail); + if (mpurl->trans != NULL) + mailbox_transaction_rollback(&mpurl->trans); + if (mpurl->box != NULL && mpurl->box != mpurl->selected_box) + mailbox_free(&mpurl->box); + if (mpurl->section != NULL) + i_free(mpurl->section); + i_free(mpurl->mailbox); + i_free(mpurl); +} diff --git a/src/lib-imap-storage/imap-msgpart-url.h b/src/lib-imap-storage/imap-msgpart-url.h new file mode 100644 index 0000000..5bc72c2 --- /dev/null +++ b/src/lib-imap-storage/imap-msgpart-url.h @@ -0,0 +1,50 @@ +#ifndef IMAP_MSGPART_URL_H +#define IMAP_MSGPART_URL_H + +#include "imap-msgpart.h" + +struct imap_url; +struct imap_msgpart; +struct imap_msgpart_url; + +/* Functions returning int return 1 on success, 0 if URL doesn't point to + valid mail, -1 on storage error. */ + +int imap_msgpart_url_create(struct mail_user *user, const struct imap_url *url, + struct imap_msgpart_url **url_r, + const char **client_error_r); +int imap_msgpart_url_parse(struct mail_user *user, struct mailbox *selected_box, + const char *urlstr, struct imap_msgpart_url **url_r, + const char **client_error_r); + +int imap_msgpart_url_open_mailbox(struct imap_msgpart_url *mpurl, + struct mailbox **box_r, enum mail_error *error_code_r, + const char **client_error_r); +struct mailbox *imap_msgpart_url_get_mailbox(struct imap_msgpart_url *mpurl); +int imap_msgpart_url_open_mail(struct imap_msgpart_url *mpurl, + struct mail **mail_r, + const char **client_error_r); + +struct imap_msgpart * +imap_msgpart_url_get_part(struct imap_msgpart_url *mpurl); + +/* Decode MIME parts with Content-Transfer-Encoding: base64/quoted-printable + to binary data (IMAP BINARY extension). If something can't be decoded, fails + with storage error set to MAIL_ERROR_CONVERSION. */ +void imap_msgpart_url_set_decode_to_binary(struct imap_msgpart_url *mpurl); + +/* stream_r is set to NULL when part has zero length, e.g. when partial offset + is larger than the size of the referenced part */ +int imap_msgpart_url_read_part(struct imap_msgpart_url *mpurl, + struct imap_msgpart_open_result *result_r, + const char **client_error_r); + +int imap_msgpart_url_get_bodypartstructure(struct imap_msgpart_url *mpurl, + const char **bpstruct_r, + const char **client_error_r); + +int imap_msgpart_url_verify(struct imap_msgpart_url *mpurl, + const char **client_error_r); +void imap_msgpart_url_free(struct imap_msgpart_url **mpurl); + +#endif diff --git a/src/lib-imap-storage/imap-msgpart.c b/src/lib-imap-storage/imap-msgpart.c new file mode 100644 index 0000000..3bce117 --- /dev/null +++ b/src/lib-imap-storage/imap-msgpart.c @@ -0,0 +1,860 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "array.h" +#include "istream.h" +#include "istream-crlf.h" +#include "istream-nonuls.h" +#include "istream-base64.h" +#include "istream-header-filter.h" +#include "istream-qp.h" +#include "ostream.h" +#include "message-parser.h" +#include "message-decoder.h" +#include "mail-storage-private.h" +#include "mail-namespace.h" +#include "imap-bodystructure.h" +#include "imap-parser.h" +#include "imap-msgpart.h" + +enum fetch_type { + FETCH_FULL, + FETCH_MIME, + FETCH_MIME_BODY, + FETCH_HEADER, + FETCH_HEADER_FIELDS, + FETCH_HEADER_FIELDS_NOT, + FETCH_BODY +}; + +struct imap_msgpart { + pool_t pool; + + /* "" for root, otherwise e.g. "1.2.3". the .MIME, .HEADER, etc. + suffix not included */ + const char *section_number; + enum fetch_type fetch_type; + enum mail_fetch_field wanted_fields; + + /* HEADER.FIELDS[.NOT] (list of headers) */ + struct mailbox_header_lookup_ctx *header_ctx; + const char *const *headers; + + /* which part of the message part to fetch (default: 0..UOFF_T_MAX) */ + uoff_t partial_offset, partial_size; + + bool decode_cte_to_binary:1; +}; + +struct imap_msgpart_open_ctx { + /* from matching message_part, set after opening: */ + uoff_t physical_pos; + struct message_size mime_hdr_size; + struct message_size mime_body_size; +}; + +static struct imap_msgpart *imap_msgpart_type(enum fetch_type fetch_type) +{ + struct imap_msgpart *msgpart; + pool_t pool; + + pool = pool_alloconly_create("imap msgpart", sizeof(*msgpart)+32); + msgpart = p_new(pool, struct imap_msgpart, 1); + msgpart->pool = pool; + msgpart->partial_size = UOFF_T_MAX; + msgpart->fetch_type = fetch_type; + msgpart->section_number = ""; + if (fetch_type == FETCH_HEADER || fetch_type == FETCH_FULL) + msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER; + if (fetch_type == FETCH_BODY || fetch_type == FETCH_FULL) + msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY; + return msgpart; +} + +struct imap_msgpart *imap_msgpart_full(void) +{ + return imap_msgpart_type(FETCH_FULL); +} + +struct imap_msgpart *imap_msgpart_header(void) +{ + return imap_msgpart_type(FETCH_HEADER); +} + +struct imap_msgpart *imap_msgpart_body(void) +{ + return imap_msgpart_type(FETCH_BODY); +} + +static struct message_part * +imap_msgpart_find(struct message_part *parts, const char *section) +{ + struct message_part *part = parts; + const char *path; + unsigned int num; + + path = section; + while (*path >= '0' && *path <= '9' && part != NULL) { + /* get part number, we have already verified its validity */ + num = 0; + while (*path != '\0' && *path != '.') { + i_assert(*path >= '0' && *path <= '9'); + + num = num*10 + (*path - '0'); + path++; + } + + if (*path == '.') + path++; + + if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) { + /* find the part */ + part = part->children; + for (; num > 1 && part != NULL; num--) + part = part->next; + } else { + /* only 1 allowed with non-multipart messages. + if the child isn't message/rfc822, the path must be + finished after this. */ + if (num != 1) + part = NULL; + else if (*path != '\0' && + (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0) + part = NULL; + } + + if (part != NULL && + (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0 && + (*path >= '0' && *path <= '9')) { + /* if we continue inside the message/rfc822, skip this + body part */ + part = part->children; + } + } + i_assert(part == NULL || *path == '\0'); + return part; +} + +static int +imap_msgpart_get_header_fields(pool_t pool, const char *header_list, + ARRAY_TYPE(const_string) *fields) +{ + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args, *hdr_list; + unsigned int list_count; + unsigned int i; + int result = 0; + + input = i_stream_create_from_data(header_list, strlen(header_list)); + parser = imap_parser_create(input, NULL, SIZE_MAX); + + if (imap_parser_finish_line(parser, 0, 0, &args) > 0 && + imap_arg_get_list_full(args, &hdr_list, &list_count) && + args[1].type == IMAP_ARG_EOL && + list_count > 0) { + const char *value; + + p_array_init(fields, pool, list_count); + for (i = 0; i < list_count; i++) { + if (!imap_arg_get_astring(&hdr_list[i], &value)) { + result = -1; + break; + } + + value = p_strdup(pool, t_str_ucase(value)); + array_push_back(fields, &value); + } + /* istream-header-filter requires headers to be sorted */ + array_sort(fields, i_strcasecmp_p); + } else { + result = -1; + } + + imap_parser_unref(&parser); + i_stream_unref(&input); + return result; +} + +static int +imap_msgpart_parse_header_fields(struct imap_msgpart *msgpart, + const char *header_list) +{ + ARRAY_TYPE(const_string) fields; + + if (header_list[0] == ' ') + header_list++; + + /* HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */ + if (imap_msgpart_get_header_fields(msgpart->pool, header_list, + &fields) < 0) + return -1; + + array_append_zero(&fields); + msgpart->headers = array_front(&fields); + return 0; +} + +int imap_msgpart_parse(const char *section, struct imap_msgpart **msgpart_r) +{ + struct imap_msgpart *msgpart; + pool_t pool; + unsigned int i; + bool next_digit; + int ret; + + pool = pool_alloconly_create("imap msgpart", 1024); + msgpart = *msgpart_r = p_new(pool, struct imap_msgpart, 1); + msgpart->pool = pool; + msgpart->partial_size = UOFF_T_MAX; + + /* get the section number */ + next_digit = TRUE; + for (i = 0; section[i] != '\0'; i++) { + if (section[i] >= '0' && section[i] <= '9') { + next_digit = FALSE; + } else if (!next_digit && section[i] == '.') { + next_digit = TRUE; + } else { + break; + } + } + if (i == 0) { + /* [], [HEADER], etc. */ + msgpart->section_number = ""; + } else if (section[i] == '\0') { + /* [1.2.3] */ + if (i > 0 && section[i-1] == '.') { + pool_unref(&pool); + return -1; + } + msgpart->section_number = p_strdup(pool, section); + section = ""; + } else { + /* [1.2.3.MIME], [1.2.3.HEADER], etc */ + if (section[i-1] != '.') { + pool_unref(&pool); + return -1; + } + msgpart->section_number = p_strndup(pool, section, i-1); + section += i; + } + + if (*section == '\0') { + msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY; + if (*msgpart->section_number == '\0') { + /* BODY[] - header+body */ + msgpart->fetch_type = FETCH_FULL; + msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER; + } else { + /* BODY[1] - body only */ + msgpart->fetch_type = FETCH_MIME_BODY; + } + return 0; + } + section = t_str_ucase(section); + + if (strcmp(section, "MIME") == 0) { + if (msgpart->section_number[0] == '\0') + return -1; + msgpart->fetch_type = FETCH_MIME; + msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY; + } else if (strcmp(section, "TEXT") == 0) { + /* body (for root or for message/rfc822) */ + msgpart->fetch_type = FETCH_BODY; + msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY; + } else if (str_begins(section, "HEADER")) { + /* header (for root or for message/rfc822) */ + if (section[6] == '\0') { + msgpart->fetch_type = FETCH_HEADER; + ret = 0; + } else if (str_begins(section, "HEADER.FIELDS.NOT")) { + msgpart->fetch_type = FETCH_HEADER_FIELDS_NOT; + ret = imap_msgpart_parse_header_fields(msgpart, + section+17); + } else if (str_begins(section, "HEADER.FIELDS")) { + msgpart->fetch_type = FETCH_HEADER_FIELDS; + ret = imap_msgpart_parse_header_fields(msgpart, + section+13); + } else { + ret = -1; + } + if (ret < 0) { + imap_msgpart_free(&msgpart); + return -1; + } + if (msgpart->fetch_type == FETCH_HEADER_FIELDS) { + /* we may be able to get this from cache, don't give a + wanted_fields hint */ + } else if (*msgpart->section_number == '\0') + msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER; + else + msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY; + } else { + imap_msgpart_free(&msgpart); + return -1; + } + return 0; +} + +void imap_msgpart_free(struct imap_msgpart **_msgpart) +{ + struct imap_msgpart *msgpart = *_msgpart; + + *_msgpart = NULL; + + imap_msgpart_close_mailbox(msgpart); + pool_unref(&msgpart->pool); +} + +bool imap_msgpart_contains_body(const struct imap_msgpart *msgpart) +{ + switch (msgpart->fetch_type) { + case FETCH_HEADER: + case FETCH_HEADER_FIELDS: + case FETCH_HEADER_FIELDS_NOT: + return FALSE; + case FETCH_FULL: + case FETCH_MIME: + case FETCH_MIME_BODY: + case FETCH_BODY: + break; + } + return TRUE; +} + +void imap_msgpart_set_decode_to_binary(struct imap_msgpart *msgpart) +{ + msgpart->decode_cte_to_binary = TRUE; +} + +void imap_msgpart_set_partial(struct imap_msgpart *msgpart, + uoff_t offset, uoff_t size) +{ + msgpart->partial_offset = offset; + msgpart->partial_size = size; +} + +uoff_t imap_msgpart_get_partial_offset(struct imap_msgpart *msgpart) +{ + return msgpart->partial_offset; +} + +uoff_t imap_msgpart_get_partial_size(struct imap_msgpart *msgpart) +{ + return msgpart->partial_size; +} + +enum mail_fetch_field imap_msgpart_get_fetch_data(struct imap_msgpart *msgpart) +{ + return msgpart->wanted_fields; +} + +void imap_msgpart_get_wanted_headers(struct imap_msgpart *msgpart, + ARRAY_TYPE(const_string) *headers) +{ + unsigned int i; + + if (msgpart->fetch_type != FETCH_HEADER_FIELDS) + return; + + for (i = 0; msgpart->headers[i] != NULL; i++) + array_push_back(headers, &msgpart->headers[i]); +} + +static int +imap_msgpart_get_partial_header(struct mail *mail, struct istream *mail_input, + const struct imap_msgpart *msgpart, + uoff_t *virtual_size_r, bool *have_crlfs_r, + struct imap_msgpart_open_result *result_r) +{ + const char *const *hdr_fields = msgpart->headers; + unsigned int hdr_count = str_array_length(hdr_fields); + struct message_size hdr_size; + struct istream *input; + bool has_nuls; + + if (msgpart->fetch_type != FETCH_HEADER_FIELDS) { + i_assert(msgpart->fetch_type == FETCH_HEADER_FIELDS_NOT); + input = i_stream_create_header_filter(mail_input, + HEADER_FILTER_EXCLUDE | + HEADER_FILTER_HIDE_BODY, + hdr_fields, hdr_count, + *null_header_filter_callback, + NULL); + } else if (msgpart->section_number[0] != '\0') { + /* fetching partial headers for a message/rfc822 part. */ + input = i_stream_create_header_filter(mail_input, + HEADER_FILTER_INCLUDE | + HEADER_FILTER_HIDE_BODY, + hdr_fields, hdr_count, + *null_header_filter_callback, + NULL); + } else { + /* mail_get_header_stream() already filtered out the + unwanted headers. */ + input = mail_input; + i_stream_ref(input); + } + + if (message_get_header_size(input, &hdr_size, &has_nuls) < 0) { + mail_set_critical(mail, + "read(%s) failed: %s", i_stream_get_name(input), + i_stream_get_error(input)); + i_stream_unref(&input); + return -1; + } + i_stream_seek(input, 0); + result_r->input = input; + result_r->size = hdr_size.virtual_size; + result_r->size_field = 0; + *virtual_size_r = hdr_size.virtual_size; + *have_crlfs_r = hdr_size.physical_size == hdr_size.virtual_size; + return 0; +} + +static struct istream * +imap_msgpart_crlf_seek(struct mail *mail, struct istream *input, + const struct imap_msgpart *msgpart) +{ + struct mail_msgpart_partial_cache *cache = &mail->box->partial_cache; + struct istream *crlf_input, *errinput; + uoff_t physical_start = input->v_offset; + uoff_t virtual_skip = msgpart->partial_offset; + bool cr_skipped; + + if (virtual_skip == 0) { + /* no need to seek */ + } else if (mail->uid > 0 && cache->uid == mail->uid && + cache->physical_start == physical_start && + cache->virtual_pos < virtual_skip) { + /* use cache */ + i_stream_seek(input, physical_start + cache->physical_pos); + virtual_skip -= cache->virtual_pos; + } + if (message_skip_virtual(input, virtual_skip, &cr_skipped) < 0) { + errinput = i_stream_create_error_str(errno, "%s", i_stream_get_error(input)); + i_stream_set_name(errinput, i_stream_get_name(input)); + i_stream_unref(&input); + return errinput; + } + + if (mail->uid > 0 && + (msgpart->partial_offset != 0 || + msgpart->partial_size != UOFF_T_MAX) && !input->eof) { + /* update cache */ + cache->uid = mail->uid; + cache->physical_start = physical_start; + cache->physical_pos = input->v_offset - physical_start; + cache->virtual_pos = msgpart->partial_offset; + if (cr_skipped) { + /* the physical_pos points to virtual CRLF, but + virtual_pos already skipped CR. that can't work, + so seek back the virtual CR */ + cache->virtual_pos--; + } + } + crlf_input = i_stream_create_crlf(input); + if (cr_skipped) + i_stream_skip(crlf_input, 1); + i_stream_unref(&input); + return crlf_input; +} + +static void +imap_msgpart_get_partial(struct mail *mail, const struct imap_msgpart *msgpart, + bool convert_nuls, bool use_partial_cache, + uoff_t virtual_size, bool have_crlfs, + struct imap_msgpart_open_result *result) +{ + struct istream *input2; + uoff_t bytes_left; + + /* input is already seeked to the beginning of the wanted data */ + + if (msgpart->partial_offset >= virtual_size) { + /* can't seek past the MIME part */ + i_stream_unref(&result->input); + result->input = i_stream_create_from_data("", 0); + result->size = 0; + return; + } + + if (have_crlfs) { + /* input has CRLF linefeeds, we can quickly seek to + wanted position */ + i_stream_skip(result->input, msgpart->partial_offset); + } else { + /* input has LF linefeeds. it can be slow to seek to wanted + position, so try to do caching whenever possible */ + i_assert(use_partial_cache); + result->input = imap_msgpart_crlf_seek(mail, result->input, + msgpart); + } + + bytes_left = virtual_size - msgpart->partial_offset; + if (msgpart->partial_size <= bytes_left) { + /* limit output to specified number of bytes */ + result->size = msgpart->partial_size; + } else { + /* send all bytes */ + result->size = bytes_left; + } + + if (!mail->has_no_nuls && convert_nuls) { + /* IMAP literals must not contain NULs. change them to + 0x80 characters. */ + input2 = i_stream_create_nonuls(result->input, '\x80'); + i_stream_unref(&result->input); + result->input = input2; + } + input2 = i_stream_create_limit(result->input, result->size); + i_stream_unref(&result->input); + result->input = input2; +} + +static int +imap_msgpart_find_part(struct mail *mail, const struct imap_msgpart *msgpart, + struct message_part **part_r) +{ + struct message_part *parts, *part = NULL; + + if (*msgpart->section_number == '\0') { + *part_r = NULL; + return 1; + } + + if (mail_get_parts(mail, &parts) < 0) + return -1; + part = imap_msgpart_find(parts, msgpart->section_number); + if (part == NULL) { + /* MIME part not found. */ + *part_r = NULL; + return 0; + } + + switch (msgpart->fetch_type) { + case FETCH_MIME: + /* What to do if this is a message/rfc822? Does it have + MIME headers or not? Possibilities are: a) no, return + empty string (UW-IMAP does this), b) return the same as + HEADER. Dovecot has done b) for a long time and it's not + very clear which one is correct, so we'll just continue + with b) */ + case FETCH_FULL: + case FETCH_MIME_BODY: + break; + case FETCH_HEADER: + case FETCH_HEADER_FIELDS: + case FETCH_HEADER_FIELDS_NOT: + case FETCH_BODY: + /* fetching message/rfc822 part's header/body */ + if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0) { + *part_r = NULL; + return 0; + } + i_assert(part->children != NULL && + part->children->next == NULL); + part = part->children; + break; + } + *part_r = part; + return 1; +} + +static int +imap_msgpart_open_normal(struct mail *mail, struct imap_msgpart *msgpart, + const struct message_part *part, + uoff_t *virtual_size_r, bool *have_crlfs_r, + struct imap_msgpart_open_result *result_r) +{ + struct message_size hdr_size, body_size, part_size; + struct istream *input = NULL; + bool unknown_crlfs = FALSE; + + i_zero(&hdr_size); + i_zero(&body_size); + i_zero(&part_size); + + if (*msgpart->section_number != '\0') { + /* find the MIME part */ + i_assert(part != NULL); + + if (mail_get_stream_because(mail, NULL, NULL, "MIME part", &input) < 0) + return -1; + + i_stream_seek(input, part->physical_pos); + hdr_size = part->header_size; + body_size = part->body_size; + } else switch (msgpart->fetch_type) { + case FETCH_FULL: + /* fetch the whole message */ + if (mail_get_stream_because(mail, NULL, NULL, "full mail", &input) < 0 || + mail_get_virtual_size(mail, &body_size.virtual_size) < 0) + return -1; + result_r->size_field = MAIL_FETCH_VIRTUAL_SIZE; + + i_assert(mail->lookup_abort == MAIL_LOOKUP_ABORT_NEVER); + mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL; + if (mail_get_physical_size(mail, &body_size.physical_size) < 0) + unknown_crlfs = TRUE; + mail->lookup_abort = MAIL_LOOKUP_ABORT_NEVER; + break; + case FETCH_MIME: + case FETCH_MIME_BODY: + i_unreached(); + case FETCH_HEADER: + case FETCH_HEADER_FIELDS_NOT: + /* fetch the message's header */ + if (mail_get_hdr_stream(mail, &hdr_size, &input) < 0) + return -1; + result_r->size_field = MAIL_FETCH_MESSAGE_PARTS; + break; + case FETCH_HEADER_FIELDS: + /* try to lookup the headers from cache */ + if (msgpart->header_ctx == NULL) { + msgpart->header_ctx = + mailbox_header_lookup_init(mail->box, + msgpart->headers); + } + if (mail_get_header_stream(mail, msgpart->header_ctx, + &input) < 0) + return -1; + result_r->size_field = 0; + break; + case FETCH_BODY: + /* fetch the message's body */ + if (mail_get_stream_because(mail, &hdr_size, &body_size, + "mail body", &input) < 0) + return -1; + result_r->size_field = MAIL_FETCH_MESSAGE_PARTS; + break; + } + + if (msgpart->headers != NULL) { + /* return specific headers */ + return imap_msgpart_get_partial_header(mail, input, msgpart, + virtual_size_r, + have_crlfs_r, result_r); + } + + switch (msgpart->fetch_type) { + case FETCH_FULL: + part_size.physical_size += body_size.physical_size; + part_size.virtual_size += body_size.virtual_size; + /* fall through */ + case FETCH_MIME: + case FETCH_HEADER: + part_size.physical_size += hdr_size.physical_size; + part_size.virtual_size += hdr_size.virtual_size; + break; + case FETCH_HEADER_FIELDS: + case FETCH_HEADER_FIELDS_NOT: + i_unreached(); + case FETCH_BODY: + case FETCH_MIME_BODY: + i_stream_skip(input, hdr_size.physical_size); + part_size.physical_size += body_size.physical_size; + part_size.virtual_size += body_size.virtual_size; + break; + } + + result_r->input = input; + i_stream_ref(input); + *virtual_size_r = part_size.virtual_size; + *have_crlfs_r = !unknown_crlfs && + part_size.virtual_size == part_size.physical_size; + return 0; +} + +int imap_msgpart_open(struct mail *mail, struct imap_msgpart *msgpart, + struct imap_msgpart_open_result *result_r) +{ + struct message_part *part; + uoff_t virtual_size; + bool include_hdr, binary, use_partial_cache, have_crlfs; + int ret; + + i_zero(result_r); + + if ((ret = imap_msgpart_find_part(mail, msgpart, &part)) < 0) + return -1; + if (ret == 0) { + /* MIME part not found. return an empty part. */ + result_r->input = i_stream_create_from_data("", 0); + return 0; + } + + if (msgpart->decode_cte_to_binary && + (msgpart->fetch_type == FETCH_FULL || + msgpart->fetch_type == FETCH_BODY || + msgpart->fetch_type == FETCH_MIME_BODY)) { + /* binary fetch */ + include_hdr = msgpart->fetch_type == FETCH_FULL; + if (part == NULL) { + if (mail_get_parts(mail, &part) < 0) + return -1; + } + if (mail_get_binary_stream(mail, part, include_hdr, + &virtual_size, &binary, + &result_r->input) < 0) + return -1; + have_crlfs = TRUE; + use_partial_cache = FALSE; + } else { + if (imap_msgpart_open_normal(mail, msgpart, part, &virtual_size, + &have_crlfs, result_r) < 0) + return -1; + binary = FALSE; + use_partial_cache = TRUE; + } + + if (binary && msgpart->decode_cte_to_binary) + result_r->binary_decoded_input_has_nuls = TRUE; + + imap_msgpart_get_partial(mail, msgpart, !binary, use_partial_cache, + virtual_size, have_crlfs, result_r); + return 0; +} + +int imap_msgpart_size(struct mail *mail, struct imap_msgpart *msgpart, + uoff_t *size_r) +{ + struct imap_msgpart_open_result result; + struct message_part *part; + bool include_hdr; + unsigned int lines; + int ret; + + if (!msgpart->decode_cte_to_binary || + (msgpart->fetch_type != FETCH_FULL && + msgpart->fetch_type != FETCH_BODY && + msgpart->fetch_type != FETCH_MIME_BODY)) { + /* generic implementation */ + if (imap_msgpart_open(mail, msgpart, &result) < 0) + return -1; + i_stream_unref(&result.input); + *size_r = result.size; + return 0; + } + + /* binary-optimized implementation: */ + if ((ret = imap_msgpart_find_part(mail, msgpart, &part)) < 0) + return -1; + if (ret == 0) { + /* MIME part not found. return an empty part. */ + *size_r = 0; + return 0; + } + if (part == NULL) { + if (mail_get_parts(mail, &part) < 0) + return -1; + } + include_hdr = msgpart->fetch_type == FETCH_FULL; + return mail_get_binary_size(mail, part, include_hdr, size_r, &lines); +} + +static int +imap_msgpart_parse_bodystructure(struct mail *mail, + struct message_part *all_parts) +{ + struct mail_private *pmail = (struct mail_private *)mail; + const char *bodystructure, *error; + + if (mail_get_special(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE, + &bodystructure) < 0) + return -1; + if (all_parts->context != NULL) { + /* we just parsed the bodystructure */ + return 0; + } + + if (imap_bodystructure_parse(bodystructure, pmail->data_pool, + all_parts, &error) < 0) { + mail_set_cache_corrupted(mail, + MAIL_FETCH_IMAP_BODYSTRUCTURE, t_strdup_printf( + "Invalid message_part/BODYSTRUCTURE %s: %s", + bodystructure, error)); + return -1; + } + return 0; +} + +static int +imap_msgpart_vsizes_to_binary(struct mail *mail, const struct message_part *part, + struct message_part **binpart_r) +{ + struct message_part **pos; + uoff_t size; + unsigned int lines; + + if (mail_get_binary_size(mail, part, FALSE, &size, &lines) < 0) + return -1; + + *binpart_r = t_new(struct message_part, 1); + **binpart_r = *part; + (*binpart_r)->body_size.virtual_size = size; + (*binpart_r)->body_size.lines = lines; + + pos = &(*binpart_r)->children; + for (part = part->children; part != NULL; part = part->next) { + if (imap_msgpart_vsizes_to_binary(mail, part, pos) < 0) + return -1; + pos = &(*pos)->next; + } + return 0; +} + +int imap_msgpart_bodypartstructure(struct mail *mail, + struct imap_msgpart *msgpart, + const char **bpstruct_r) +{ + struct message_part *all_parts, *part; + string_t *bpstruct; + const char *error; + int ret; + + /* if we start parsing the body in here, make sure we also parse the + BODYSTRUCTURE */ + mail_add_temp_wanted_fields(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE, NULL); + + if ((ret = imap_msgpart_find_part(mail, msgpart, &part)) < 0) + return -1; + if (ret == 0) { + /* MIME part not found. */ + *bpstruct_r = NULL; + return 0; + } + + if (mail_get_parts(mail, &all_parts) < 0) + return -1; + if (all_parts->context == NULL) { + if (imap_msgpart_parse_bodystructure(mail, all_parts) < 0) + return -1; + } + if (part == NULL) + part = all_parts; + + if (msgpart->decode_cte_to_binary) + ret = imap_msgpart_vsizes_to_binary(mail, part, &part); + + if (ret >= 0) { + bpstruct = t_str_new(256); + if (imap_bodystructure_write(part, bpstruct, TRUE, &error) < 0) { + error = t_strdup_printf( + "Invalid message_part/BODYSTRUCTURE: %s", error); + mail_set_cache_corrupted(mail, MAIL_FETCH_MESSAGE_PARTS, + error); + return -1; + } + *bpstruct_r = str_c(bpstruct); + } + return ret < 0 ? -1 : 1; +} + + +void imap_msgpart_close_mailbox(struct imap_msgpart *msgpart) +{ + mailbox_header_lookup_unref(&msgpart->header_ctx); +} diff --git a/src/lib-imap-storage/imap-msgpart.h b/src/lib-imap-storage/imap-msgpart.h new file mode 100644 index 0000000..92b56c6 --- /dev/null +++ b/src/lib-imap-storage/imap-msgpart.h @@ -0,0 +1,68 @@ +#ifndef IMAP_MSGPART_H +#define IMAP_MSGPART_H + +struct imap_msgpart; + +struct imap_msgpart_open_result { + /* message contents with CRLF linefeeds */ + struct istream *input; + /* size of input */ + uoff_t size; + /* if size was looked up using cache and it ends up being wrong, + this field can be used to log about cache corruption */ + enum mail_fetch_field size_field; + /* TRUE if BINARY decoded content contains NUL characters */ + bool binary_decoded_input_has_nuls; +}; + +struct imap_msgpart *imap_msgpart_full(void); +struct imap_msgpart *imap_msgpart_header(void); +struct imap_msgpart *imap_msgpart_body(void); +/* Parse section into imap_msgpart. Returns 0 and msgpart_r on success, + -1 if the section isn't valid. The same imap_msgpart can be used to open + multiple messages. */ +int imap_msgpart_parse(const char *section, struct imap_msgpart **msgpart_r); +void imap_msgpart_free(struct imap_msgpart **msgpart); + +/* Returns TRUE if the msgpart might return at least part of the message body. + Or alternatively: If FALSE is returned, the msgpart will never return + anything except (part of) the message header. MIME headers are counted + as part of the message body. */ +bool imap_msgpart_contains_body(const struct imap_msgpart *msgpart); +/* Decode MIME parts with Content-Transfer-Encoding: base64/quoted-printable + to binary data (IMAP BINARY extension). If something can't be decoded, fails + with storage error set to MAIL_ERROR_CONVERSION. */ +void imap_msgpart_set_decode_to_binary(struct imap_msgpart *msgpart); + +/* Set the fetch to be partial. For unlimited size use UOFF_T_MAX. */ +void imap_msgpart_set_partial(struct imap_msgpart *msgpart, + uoff_t offset, uoff_t size); +uoff_t imap_msgpart_get_partial_offset(struct imap_msgpart *msgpart); +uoff_t imap_msgpart_get_partial_size(struct imap_msgpart *msgpart); +/* Return wanted_fields mask. */ +enum mail_fetch_field imap_msgpart_get_fetch_data(struct imap_msgpart *msgpart); +/* Append all the specifically requested headers to the headers array + (no deduplication is done) */ +void imap_msgpart_get_wanted_headers(struct imap_msgpart *msgpart, + ARRAY_TYPE(const_string) *headers); + +/* Open message part refenced by IMAP section as istream. Returns 0 if + successful, -1 if storage error. Returned istream is initially referenced, + so i_stream_unref() must be called for it. */ +int imap_msgpart_open(struct mail *mail, struct imap_msgpart *msgpart, + struct imap_msgpart_open_result *result_r); +/* Return msgpart's size without actually opening the stream (if possible). */ +int imap_msgpart_size(struct mail *mail, struct imap_msgpart *msgpart, + uoff_t *size_r); + +/* Return msgpart's IMAP BODYPARTSTRUCTURE */ +int imap_msgpart_bodypartstructure(struct mail *mail, + struct imap_msgpart *msgpart, + const char **bpstruct_r); + +/* Header context is automatically created by imap_msgpart_open() and destroyed + by imap_msgpart_free(), but if you want to use the same imap_msgpart across + multiple mailboxes, you need to close the part before closing the mailbox. */ +void imap_msgpart_close_mailbox(struct imap_msgpart *msgpart); + +#endif diff --git a/src/lib-imap-urlauth/Makefile.am b/src/lib-imap-urlauth/Makefile.am new file mode 100644 index 0000000..6734b72 --- /dev/null +++ b/src/lib-imap-urlauth/Makefile.am @@ -0,0 +1,29 @@ +noinst_LTLIBRARIES = libimap-urlauth.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-charset \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-imap-storage + +libimap_urlauth_la_SOURCES = \ + imap-urlauth.c \ + imap-urlauth-fetch.c \ + imap-urlauth-backend.c \ + imap-urlauth-connection.c + +headers = \ + imap-urlauth.h \ + imap-urlauth-private.h \ + imap-urlauth-fetch.h \ + imap-urlauth-backend.h \ + imap-urlauth-connection.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers) + diff --git a/src/lib-imap-urlauth/Makefile.in b/src/lib-imap-urlauth/Makefile.in new file mode 100644 index 0000000..9ff6efa --- /dev/null +++ b/src/lib-imap-urlauth/Makefile.in @@ -0,0 +1,835 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/lib-imap-urlauth +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libimap_urlauth_la_LIBADD = +am_libimap_urlauth_la_OBJECTS = imap-urlauth.lo imap-urlauth-fetch.lo \ + imap-urlauth-backend.lo imap-urlauth-connection.lo +libimap_urlauth_la_OBJECTS = $(am_libimap_urlauth_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/imap-urlauth-backend.Plo \ + ./$(DEPDIR)/imap-urlauth-connection.Plo \ + ./$(DEPDIR)/imap-urlauth-fetch.Plo \ + ./$(DEPDIR)/imap-urlauth.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libimap_urlauth_la_SOURCES) +DIST_SOURCES = $(libimap_urlauth_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkginc_libdir)" +HEADERS = $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LTLIBRARIES = libimap-urlauth.la +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-charset \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-imap-storage + +libimap_urlauth_la_SOURCES = \ + imap-urlauth.c \ + imap-urlauth-fetch.c \ + imap-urlauth-backend.c \ + imap-urlauth-connection.c + +headers = \ + imap-urlauth.h \ + imap-urlauth-private.h \ + imap-urlauth-fetch.h \ + imap-urlauth-backend.h \ + imap-urlauth-connection.h + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = $(headers) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-imap-urlauth/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib-imap-urlauth/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libimap-urlauth.la: $(libimap_urlauth_la_OBJECTS) $(libimap_urlauth_la_DEPENDENCIES) $(EXTRA_libimap_urlauth_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libimap_urlauth_la_OBJECTS) $(libimap_urlauth_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-urlauth-backend.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-urlauth-connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-urlauth-fetch.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-urlauth.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/imap-urlauth-backend.Plo + -rm -f ./$(DEPDIR)/imap-urlauth-connection.Plo + -rm -f ./$(DEPDIR)/imap-urlauth-fetch.Plo + -rm -f ./$(DEPDIR)/imap-urlauth.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/imap-urlauth-backend.Plo + -rm -f ./$(DEPDIR)/imap-urlauth-connection.Plo + -rm -f ./$(DEPDIR)/imap-urlauth-fetch.Plo + -rm -f ./$(DEPDIR)/imap-urlauth.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkginc_libHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-pkginc_libHEADERS install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-pkginc_libHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib-imap-urlauth/imap-urlauth-backend.c b/src/lib-imap-urlauth/imap-urlauth-backend.c new file mode 100644 index 0000000..de2b9c9 --- /dev/null +++ b/src/lib-imap-urlauth/imap-urlauth-backend.c @@ -0,0 +1,174 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "hex-binary.h" +#include "randgen.h" +#include "mail-user.h" +#include "mail-storage.h" +#include "mailbox-list-iter.h" +#include "imap-urlauth-private.h" +#include "imap-urlauth-backend.h" + +#define IMAP_URLAUTH_KEY MAILBOX_ATTRIBUTE_PREFIX_DOVECOT"imap-urlauth" + +static int +imap_urlauth_backend_trans_set_mailbox_key(struct mailbox *box, + unsigned char mailbox_key_r[IMAP_URLAUTH_KEY_LEN], + const char **client_error_r, + enum mail_error *error_code_r) +{ + struct mail_attribute_value urlauth_key; + const char *mailbox_key_hex = NULL; + int ret; + + if (mailbox_open(box) < 0) { + *client_error_r = mailbox_get_last_error(box, error_code_r); + return -1; + } + + struct mailbox_transaction_context *t = + mailbox_transaction_begin(box, + MAILBOX_TRANSACTION_FLAG_EXTERNAL, + __func__); + + /* create new key */ + random_fill(mailbox_key_r, IMAP_URLAUTH_KEY_LEN); + mailbox_key_hex = binary_to_hex(mailbox_key_r, + IMAP_URLAUTH_KEY_LEN); + i_zero(&urlauth_key); + urlauth_key.value = mailbox_key_hex; + ret = mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_PRIVATE, + IMAP_URLAUTH_KEY, &urlauth_key); + + if (mailbox_transaction_commit(&t) < 0) { + *client_error_r = mailbox_get_last_error(box, error_code_r); + ret = -1; + } + + return ret; +} + +static int +imap_urlauth_backend_trans_get_mailbox_key(struct mailbox *box, + bool create, + unsigned char mailbox_key_r[IMAP_URLAUTH_KEY_LEN], + const char **client_error_r, + enum mail_error *error_code_r) +{ + struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box)); + struct mail_attribute_value urlauth_key; + const char *mailbox_key_hex = NULL; + buffer_t key_buf; + int ret; + + *client_error_r = "Internal server error"; + *error_code_r = MAIL_ERROR_TEMP; + + ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_PRIVATE, + IMAP_URLAUTH_KEY, &urlauth_key); + if (ret < 0) + return -1; + + e_debug(user->event, "imap-urlauth: %skey found for mailbox %s", + (ret > 0 ? "" : "no "), mailbox_get_vname(box)); + + if (ret == 0) { + if (!create) + return 0; + + ret = imap_urlauth_backend_trans_set_mailbox_key(box, + mailbox_key_r, + client_error_r, + error_code_r); + + if (ret < 0) + return -1; + e_debug(user->event, "imap-urlauth: created key for mailbox %s", + mailbox_get_vname(box)); + } else { + /* read existing key */ + buffer_create_from_data(&key_buf, mailbox_key_r, + IMAP_URLAUTH_KEY_LEN); + mailbox_key_hex = urlauth_key.value; + if (strlen(mailbox_key_hex) != 2*IMAP_URLAUTH_KEY_LEN || + hex_to_binary(mailbox_key_hex, &key_buf) < 0 || + key_buf.used != IMAP_URLAUTH_KEY_LEN) { + i_error("imap-urlauth: key found for mailbox %s is invalid", + mailbox_get_vname(box)); + return -1; + } + } + return 1; +} + +int imap_urlauth_backend_get_mailbox_key(struct mailbox *box, bool create, + unsigned char mailbox_key_r[IMAP_URLAUTH_KEY_LEN], + const char **client_error_r, + enum mail_error *error_code_r) +{ + int ret; + + ret = imap_urlauth_backend_trans_get_mailbox_key(box, create, + mailbox_key_r, + client_error_r, + error_code_r); + return ret; +} + +int imap_urlauth_backend_reset_mailbox_key(struct mailbox *box) +{ + struct mailbox_transaction_context *t; + int ret; + + t = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL, + __func__); + ret = mailbox_attribute_unset(t, MAIL_ATTRIBUTE_TYPE_PRIVATE, + IMAP_URLAUTH_KEY); + if (mailbox_transaction_commit(&t) < 0) + ret = -1; + return ret; +} + +static int imap_urlauth_backend_mailbox_reset_key(struct mailbox *box) +{ + const char *errstr; + enum mail_error error; + + if (mailbox_open(box) < 0) { + errstr = mailbox_get_last_internal_error(box, &error); + if (error == MAIL_ERROR_NOTFOUND || error == MAIL_ERROR_PERM) + return 0; + i_error("urlauth key reset: Couldn't open mailbox %s: %s", + mailbox_get_vname(box), errstr); + return -1; + } + return imap_urlauth_backend_reset_mailbox_key(box); +} + +int imap_urlauth_backend_reset_all_keys(struct mail_user *user) +{ + const char *const patterns[] = { "*", NULL }; + struct mailbox_list_iterate_context *iter; + const struct mailbox_info *info; + struct mailbox *box; + int ret = 0; + + iter = mailbox_list_iter_init_namespaces(user->namespaces, patterns, + MAIL_NAMESPACE_TYPE_MASK_ALL, + MAILBOX_LIST_ITER_NO_AUTO_BOXES | + MAILBOX_LIST_ITER_SKIP_ALIASES | + MAILBOX_LIST_ITER_RETURN_NO_FLAGS); + while ((info = mailbox_list_iter_next(iter)) != NULL) { + box = mailbox_alloc(info->ns->list, info->vname, 0); + if (imap_urlauth_backend_mailbox_reset_key(box) < 0) + ret = -1; + mailbox_free(&box); + } + if (mailbox_list_iter_deinit(&iter) < 0) { + i_error("urlauth key reset: Couldn't iterate mailboxes: %s", + mailbox_list_get_last_internal_error(user->namespaces->list, NULL)); + ret = -1; + } + return ret; +} diff --git a/src/lib-imap-urlauth/imap-urlauth-backend.h b/src/lib-imap-urlauth/imap-urlauth-backend.h new file mode 100644 index 0000000..b76ecc5 --- /dev/null +++ b/src/lib-imap-urlauth/imap-urlauth-backend.h @@ -0,0 +1,16 @@ +#ifndef IMAP_URLAUTH_BACKEND_H +#define IMAP_URLAUTH_BACKEND_H + +#define IMAP_URLAUTH_KEY_LEN 64 + +struct imap_urlauth_backend; + +int imap_urlauth_backend_get_mailbox_key(struct mailbox *box, bool create, + unsigned char mailbox_key_r[IMAP_URLAUTH_KEY_LEN], + const char **client_error_r, + enum mail_error *error_code_r); +int imap_urlauth_backend_reset_mailbox_key(struct mailbox *box); +int imap_urlauth_backend_reset_all_keys(struct mail_user *user); + +#endif + diff --git a/src/lib-imap-urlauth/imap-urlauth-connection.c b/src/lib-imap-urlauth/imap-urlauth-connection.c new file mode 100644 index 0000000..5fce6f7 --- /dev/null +++ b/src/lib-imap-urlauth/imap-urlauth-connection.c @@ -0,0 +1,1027 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "llist.h" +#include "str.h" +#include "str-sanitize.h" +#include "strescape.h" +#include "ioloop.h" +#include "safe-mkstemp.h" +#include "hostpid.h" +#include "net.h" +#include "istream.h" +#include "ostream.h" +#include "write-full.h" +#include "array.h" +#include "aqueue.h" +#include "mail-user.h" +#include "imap-urlauth-fetch.h" + +#include "imap-urlauth-connection.h" + +enum imap_urlauth_state { + IMAP_URLAUTH_STATE_DISCONNECTED = 0, + IMAP_URLAUTH_STATE_AUTHENTICATING, + IMAP_URLAUTH_STATE_AUTHENTICATED, + IMAP_URLAUTH_STATE_SELECTING_TARGET, + IMAP_URLAUTH_STATE_UNSELECTING_TARGET, + IMAP_URLAUTH_STATE_READY, + IMAP_URLAUTH_STATE_REQUEST_PENDING, + IMAP_URLAUTH_STATE_REQUEST_WAIT, +}; + +struct imap_urlauth_request { + struct imap_urlauth_target *target; + struct imap_urlauth_request *prev, *next; + + char *url; + enum imap_urlauth_fetch_flags flags; + + char *bodypartstruct; + + imap_urlauth_request_callback_t *callback; + void *context; + + bool binary_has_nuls; +}; + +struct imap_urlauth_target { + struct imap_urlauth_target *prev, *next; + + char *userid; + + struct imap_urlauth_request *requests_head, *requests_tail; +}; + +struct imap_urlauth_connection { + int refcount; + + char *path, *service, *session_id; + struct mail_user *user; + + int fd; + struct istream *input; + struct ostream *output; + struct io *io; + + struct timeout *to_reconnect, *to_idle, *to_response; + time_t last_reconnect; + unsigned int reconnect_attempts; + unsigned int idle_timeout_msecs; + + char *literal_temp_path; + int literal_fd; + buffer_t *literal_buf; + uoff_t literal_size, literal_bytes_left; + + enum imap_urlauth_state state; + + /* userid => target struct */ + struct imap_urlauth_target *targets_head, *targets_tail; + + bool reading_literal:1; +}; + +#define IMAP_URLAUTH_RECONNECT_MIN_SECS 2 +#define IMAP_URLAUTH_RECONNECT_MAX_ATTEMPTS 3 + +#define IMAP_URLAUTH_RESPONSE_TIMEOUT_MSECS 2*60*1000 + +#define IMAP_URLAUTH_HANDSHAKE "VERSION\timap-urlauth\t2\t0\n" + +#define IMAP_URLAUTH_MAX_INLINE_LITERAL_SIZE (1024*32) + +static void imap_urlauth_connection_disconnect + (struct imap_urlauth_connection *conn, const char *reason); +static void imap_urlauth_connection_abort + (struct imap_urlauth_connection *conn, const char *reason); +static void imap_urlauth_connection_reconnect + (struct imap_urlauth_connection *conn); +static void imap_urlauth_connection_idle_disconnect + (struct imap_urlauth_connection *conn); +static void imap_urlauth_connection_timeout_abort + (struct imap_urlauth_connection *conn); +static void imap_urlauth_connection_fail + (struct imap_urlauth_connection *conn); + +struct imap_urlauth_connection * +imap_urlauth_connection_init(const char *path, const char *service, + struct mail_user *user, const char *session_id, + unsigned int idle_timeout_msecs) +{ + struct imap_urlauth_connection *conn; + + conn = i_new(struct imap_urlauth_connection, 1); + conn->refcount = 1; + conn->service = i_strdup(service); + conn->path = i_strdup(path); + if (session_id != NULL) + conn->session_id = i_strdup(session_id); + conn->user = user; + conn->fd = -1; + conn->literal_fd = -1; + conn->idle_timeout_msecs = idle_timeout_msecs; + return conn; +} + +void imap_urlauth_connection_deinit(struct imap_urlauth_connection **_conn) +{ + struct imap_urlauth_connection *conn = *_conn; + + *_conn = NULL; + + imap_urlauth_connection_abort(conn, NULL); + + i_free(conn->path); + i_free(conn->service); + if (conn->session_id != NULL) + i_free(conn->session_id); + + i_assert(conn->to_idle == NULL); + i_assert(conn->to_reconnect == NULL); + i_assert(conn->to_response == NULL); + + i_free(conn); +} + +static void +imap_urlauth_stop_response_timeout(struct imap_urlauth_connection *conn) +{ + timeout_remove(&conn->to_response); +} + +static void +imap_urlauth_start_response_timeout(struct imap_urlauth_connection *conn) +{ + imap_urlauth_stop_response_timeout(conn); + conn->to_response = timeout_add(IMAP_URLAUTH_RESPONSE_TIMEOUT_MSECS, + imap_urlauth_connection_timeout_abort, conn); +} + +static struct imap_urlauth_target * +imap_urlauth_connection_get_target(struct imap_urlauth_connection *conn, + const char *target_user) +{ + struct imap_urlauth_target *target = conn->targets_head; + + while (target != NULL) { + if (strcmp(target->userid, target_user) == 0) + return target; + target = target->next; + } + + target = i_new(struct imap_urlauth_target, 1); + target->userid = i_strdup(target_user); + DLLIST2_APPEND(&conn->targets_head, &conn->targets_tail, target); + return target; +} + +static void +imap_urlauth_target_free(struct imap_urlauth_connection *conn, + struct imap_urlauth_target *target) +{ + DLLIST2_REMOVE(&conn->targets_head, &conn->targets_tail, target); + i_free(target->userid); + i_free(target); +} + +static void +imap_urlauth_connection_select_target(struct imap_urlauth_connection *conn) +{ + struct imap_urlauth_target *target = conn->targets_head; + const char *cmd; + + if (target == NULL || conn->state != IMAP_URLAUTH_STATE_AUTHENTICATED) + return; + + e_debug(conn->user->event, + "imap-urlauth: Selecting target user `%s'", target->userid); + + conn->state = IMAP_URLAUTH_STATE_SELECTING_TARGET; + cmd = t_strdup_printf("USER\t%s\n", str_tabescape(target->userid)); + if (o_stream_send_str(conn->output, cmd) < 0) { + i_warning("Error sending USER request to imap-urlauth server: %m"); + imap_urlauth_connection_fail(conn); + } + + imap_urlauth_start_response_timeout(conn); +} + +static void +imap_urlauth_connection_send_request(struct imap_urlauth_connection *conn) +{ + struct imap_urlauth_request *urlreq; + string_t *cmd; + + if (conn->targets_head == NULL || + (conn->targets_head->requests_head == NULL && + conn->targets_head->next == NULL && + conn->state == IMAP_URLAUTH_STATE_READY)) { + e_debug(conn->user->event, + "imap-urlauth: No more requests pending; scheduling disconnect"); + timeout_remove(&conn->to_idle); + if (conn->idle_timeout_msecs > 0) { + conn->to_idle = timeout_add(conn->idle_timeout_msecs, + imap_urlauth_connection_idle_disconnect, conn); + } + return; + } + + if (conn->state == IMAP_URLAUTH_STATE_AUTHENTICATED) { + imap_urlauth_connection_select_target(conn); + return; + } + + if (conn->state != IMAP_URLAUTH_STATE_READY) + return; + + urlreq = conn->targets_head->requests_head; + if (urlreq == NULL) { + if (conn->targets_head->next == NULL) + return; + + conn->state = IMAP_URLAUTH_STATE_UNSELECTING_TARGET; + imap_urlauth_target_free(conn, conn->targets_head); + + if (o_stream_send_str(conn->output, "END\n") < 0) { + i_warning("Error sending END request to imap-urlauth server: %m"); + imap_urlauth_connection_fail(conn); + } + imap_urlauth_start_response_timeout(conn); + return; + } + + e_debug(conn->user->event, + "imap-urlauth: Fetching URL `%s'", urlreq->url); + + cmd = t_str_new(128); + str_append(cmd, "URL\t"); + str_append_tabescaped(cmd, urlreq->url); + if ((urlreq->flags & IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE) != 0) + str_append(cmd, "\tbpstruct"); + if ((urlreq->flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0) + str_append(cmd, "\tbinary"); + else if ((urlreq->flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0) + str_append(cmd, "\tbody"); + str_append_c(cmd, '\n'); + + conn->state = IMAP_URLAUTH_STATE_REQUEST_PENDING; + if (o_stream_send(conn->output, str_data(cmd), str_len(cmd)) < 0) { + i_warning("Error sending URL request to imap-urlauth server: %m"); + imap_urlauth_connection_fail(conn); + } + + imap_urlauth_start_response_timeout(conn); +} + +struct imap_urlauth_request * +imap_urlauth_request_new(struct imap_urlauth_connection *conn, + const char *target_user, const char *url, + enum imap_urlauth_fetch_flags flags, + imap_urlauth_request_callback_t *callback, + void *context) +{ + struct imap_urlauth_request *urlreq; + struct imap_urlauth_target *target; + + target = imap_urlauth_connection_get_target(conn, target_user); + + urlreq = i_new(struct imap_urlauth_request, 1); + urlreq->url = i_strdup(url); + urlreq->flags = flags; + urlreq->target = target; + urlreq->callback = callback; + urlreq->context = context; + + DLLIST2_APPEND(&target->requests_head, &target->requests_tail, urlreq); + + timeout_remove(&conn->to_idle); + + e_debug(conn->user->event, + "imap-urlauth: Added request for URL `%s' from user `%s'", + url, target_user); + + imap_urlauth_connection_send_request(conn); + return urlreq; +} + +static void imap_urlauth_request_free(struct imap_urlauth_request *urlreq) +{ + struct imap_urlauth_target *target = urlreq->target; + + DLLIST2_REMOVE(&target->requests_head, &target->requests_tail, urlreq); + i_free(urlreq->url); + i_free(urlreq->bodypartstruct); + i_free(urlreq); +} + +static void imap_urlauth_request_drop(struct imap_urlauth_connection *conn, + struct imap_urlauth_request *urlreq) +{ + if ((conn->state == IMAP_URLAUTH_STATE_REQUEST_PENDING || + conn->state == IMAP_URLAUTH_STATE_REQUEST_WAIT) && + conn->targets_head != NULL && + conn->targets_head->requests_head == urlreq) { + /* cannot just drop pending request without breaking + protocol state */ + return; + } + imap_urlauth_request_free(urlreq); + +} + +void imap_urlauth_request_abort(struct imap_urlauth_connection *conn, + struct imap_urlauth_request *urlreq) +{ + imap_urlauth_request_callback_t *callback; + + callback = urlreq->callback; + urlreq->callback = NULL; + if (callback != NULL) { + T_BEGIN { + callback(NULL, urlreq->context); + } T_END; + } + + imap_urlauth_request_drop(conn, urlreq); +} + +static void +imap_urlauth_request_fail(struct imap_urlauth_connection *conn, + struct imap_urlauth_request *urlreq, + const char *error) +{ + struct imap_urlauth_fetch_reply reply; + imap_urlauth_request_callback_t *callback; + int ret = 1; + + callback = urlreq->callback; + urlreq->callback = NULL; + if (callback != NULL) { + i_zero(&reply); + reply.url = urlreq->url; + reply.flags = urlreq->flags; + reply.succeeded = FALSE; + reply.error = error; + + T_BEGIN { + ret = callback(&reply, urlreq->context); + } T_END; + } + + void *urlreq_context = urlreq->context; + imap_urlauth_request_drop(conn, urlreq); + + if (ret < 0) { + /* Drop any related requests upon error */ + imap_urlauth_request_abort_by_context(conn, urlreq_context); + } + + if (ret != 0) + imap_urlauth_connection_continue(conn); +} + +static void +imap_urlauth_target_abort(struct imap_urlauth_connection *conn, + struct imap_urlauth_target *target) +{ + struct imap_urlauth_request *urlreq, *next; + + urlreq = target->requests_head; + while (urlreq != NULL) { + next = urlreq->next; + imap_urlauth_request_abort(conn, urlreq); + urlreq = next; + } + + imap_urlauth_target_free(conn, target); +} + +static void +imap_urlauth_target_fail(struct imap_urlauth_connection *conn, + struct imap_urlauth_target *target, const char *error) +{ + struct imap_urlauth_request *urlreq, *next; + + urlreq = target->requests_head; + while (urlreq != NULL) { + next = urlreq->next; + imap_urlauth_request_fail(conn, urlreq, error); + urlreq = next; + } + + imap_urlauth_target_free(conn, target); +} + +static void +imap_urlauth_target_abort_by_context(struct imap_urlauth_connection *conn, + struct imap_urlauth_target *target, + void *context) +{ + struct imap_urlauth_request *urlreq, *next; + + /* abort all matching requests */ + urlreq = target->requests_head; + while (urlreq != NULL) { + next = urlreq->next; + if (urlreq->context == context) + imap_urlauth_request_abort(conn, urlreq); + urlreq = next; + } + + if (target->requests_head == NULL) + imap_urlauth_target_free(conn, target); +} + +static void +imap_urlauth_connection_abort(struct imap_urlauth_connection *conn, + const char *reason) +{ + struct imap_urlauth_target *target, *next; + + if (reason == NULL) + reason = "Aborting due to error"; + imap_urlauth_connection_disconnect(conn, reason); + + /* abort all requests */ + target = conn->targets_head; + while (target != NULL) { + next = target->next; + imap_urlauth_target_abort(conn, target); + target = next; + } +} + +void imap_urlauth_request_abort_by_context(struct imap_urlauth_connection *conn, + void *context) +{ + struct imap_urlauth_target *target, *next; + + /* abort all matching requests */ + target = conn->targets_head; + while (target != NULL) { + next = target->next; + imap_urlauth_target_abort_by_context(conn, target, context); + target = next; + } +} + +static void imap_urlauth_connection_fail(struct imap_urlauth_connection *conn) +{ + if (conn->reconnect_attempts > IMAP_URLAUTH_RECONNECT_MAX_ATTEMPTS) { + imap_urlauth_connection_abort(conn, + "Connection failed and connection attempts exhausted"); + } else { + imap_urlauth_connection_reconnect(conn); + } +} + +static int +imap_urlauth_connection_create_temp_fd(struct imap_urlauth_connection *conn, + const char **path_r) +{ + string_t *path; + int fd; + + path = t_str_new(128); + mail_user_set_get_temp_prefix(path, conn->user->set); + fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1); + if (fd == -1) { + i_error("safe_mkstemp(%s) failed: %m", str_c(path)); + return -1; + } + + /* we just want the fd, unlink it */ + if (i_unlink(str_c(path)) < 0) { + /* shouldn't happen.. */ + i_close_fd(&fd); + return -1; + } + + *path_r = str_c(path); + return fd; +} + +static int +imap_urlauth_connection_read_literal_init(struct imap_urlauth_connection *conn, + uoff_t size) +{ + const char *path; + + i_assert(conn->literal_fd == -1 && conn->literal_buf == NULL); + + if (size <= IMAP_URLAUTH_MAX_INLINE_LITERAL_SIZE) { + /* read the literal directly */ + if (size > 0) { + conn->literal_buf = + buffer_create_dynamic(default_pool, size); + } + } else { + /* read it into a file */ + conn->literal_fd = + imap_urlauth_connection_create_temp_fd(conn, &path); + if (conn->literal_fd == -1) + return -1; + conn->literal_temp_path = i_strdup(path); + } + + conn->literal_size = size; + conn->literal_bytes_left = size; + conn->reading_literal = TRUE; + return 1; +} + +void imap_urlauth_connection_continue(struct imap_urlauth_connection *conn) +{ + i_assert(conn->targets_head != NULL); + i_assert(conn->targets_head->requests_head != NULL); + + if (conn->state != IMAP_URLAUTH_STATE_REQUEST_WAIT) + return; + + conn->state = IMAP_URLAUTH_STATE_READY; + imap_urlauth_request_free(conn->targets_head->requests_head); + + imap_urlauth_connection_send_request(conn); +} + +static int +imap_urlauth_connection_read_literal_data(struct imap_urlauth_connection *conn) +{ + const unsigned char *data; + size_t size; + + /* read data */ + data = i_stream_get_data(conn->input, &size); + if (size > conn->literal_bytes_left) + size = conn->literal_bytes_left; + + /* write to buffer or file */ + if (size > 0) { + if (conn->literal_fd >= 0) { + if (write_full(conn->literal_fd, data, size) < 0) { + i_error("imap-urlauth: write(%s) failed: %m", + conn->literal_temp_path); + return -1; + } + } else { + i_assert(conn->literal_buf != NULL); + buffer_append(conn->literal_buf, data, size); + } + i_stream_skip(conn->input, size); + conn->literal_bytes_left -= size; + } + + /* exit if not finished */ + if (conn->literal_bytes_left > 0) + return 0; + + /* read LF guard */ + data = i_stream_get_data(conn->input, &size); + if (size < 1) + return 0; + + /* check LF guard */ + if (data[0] != '\n') { + i_error("imap-urlauth: no LF at end of literal (found 0x%x)", + data[0]); + return -1; + } + i_stream_skip(conn->input, 1); + return 1; +} + +static void literal_stream_destroy(buffer_t *buffer) +{ + buffer_free(&buffer); +} + +static int +imap_urlauth_fetch_reply_set_literal_stream(struct imap_urlauth_connection *conn, + struct imap_urlauth_fetch_reply *reply) +{ + const unsigned char *data; + size_t size; + uoff_t fd_size; + + if (conn->literal_fd != -1) { + reply->input = i_stream_create_fd_autoclose(&conn->literal_fd, + SIZE_MAX); + if (i_stream_get_size(reply->input, TRUE, &fd_size) < 1 || + fd_size != conn->literal_size) { + i_stream_unref(&reply->input); + i_error("imap-urlauth: Failed to obtain proper size from literal stream"); + imap_urlauth_connection_abort(conn, + "Failed during literal transfer"); + return -1; + } + } else { + data = buffer_get_data(conn->literal_buf, &size); + i_assert(size == conn->literal_size); + reply->input = i_stream_create_from_data(data, size); + i_stream_add_destroy_callback(reply->input, + literal_stream_destroy, + conn->literal_buf); + } + reply->size = conn->literal_size; + return 0; +} + +static int +imap_urlauth_connection_read_literal(struct imap_urlauth_connection *conn) +{ + struct imap_urlauth_request *urlreq = conn->targets_head->requests_head; + struct imap_urlauth_fetch_reply reply; + imap_urlauth_request_callback_t *callback; + int ret; + + i_assert(conn->reading_literal); + i_assert(urlreq != NULL); + + if (conn->literal_size > 0) { + ret = imap_urlauth_connection_read_literal_data(conn); + if (ret <= 0) + return ret; + } + i_assert(conn->literal_bytes_left == 0); + + /* reply */ + i_zero(&reply); + reply.url = urlreq->url; + reply.flags = urlreq->flags; + reply.bodypartstruct = urlreq->bodypartstruct; + reply.binary_has_nuls = urlreq->binary_has_nuls ? 1 : 0; + + if (conn->literal_size > 0) { + if (imap_urlauth_fetch_reply_set_literal_stream(conn, &reply) < 0) + return -1; + } + reply.succeeded = TRUE; + + ret = 1; + callback = urlreq->callback; + urlreq->callback = NULL; + if (callback != NULL) T_BEGIN { + ret = callback(&reply, urlreq->context); + } T_END; + + if (reply.input != NULL) + i_stream_unref(&reply.input); + + if (ret < 0) { + /* Drop any related requests upon error */ + imap_urlauth_request_abort_by_context(conn, urlreq->context); + } + + conn->state = IMAP_URLAUTH_STATE_REQUEST_WAIT; + if (ret != 0) + imap_urlauth_connection_continue(conn); + + /* finished */ + i_free_and_null(conn->literal_temp_path); + conn->literal_fd = -1; + conn->literal_buf = NULL; + conn->reading_literal = FALSE; + return 1; +} + +static int imap_urlauth_input_pending(struct imap_urlauth_connection *conn) +{ + struct imap_urlauth_request *urlreq; + const char *response, *const *args, *bpstruct = NULL; + uoff_t literal_size; + + i_assert(conn->targets_head != NULL); + i_assert(conn->targets_head->requests_head != NULL); + urlreq = conn->targets_head->requests_head; + + if (conn->reading_literal) { + /* Read pending literal; may callback */ + return imap_urlauth_connection_read_literal(conn); + } + + /* "OK"[<metadata-items>]"\t"<literal-size>"\n" or + "NO"["\terror="<error>]"\n" */ + if ((response = i_stream_next_line(conn->input)) == NULL) + return 0; + imap_urlauth_stop_response_timeout(conn); + + args = t_strsplit_tabescaped(response); + if (args[0] == NULL) { + i_error("imap-urlauth: Empty URL response: %s", + str_sanitize(response, 80)); + return -1; + } + + if (strcmp(args[0], "OK") != 0 || args[1] == NULL) { + if (strcmp(args[0], "NO") == 0) { + const char *param = args[1], *error = NULL; + + if (param != NULL && + strncasecmp(param, "error=", 6) == 0 && + param[6] != '\0') { + error = param+6; + } + conn->state = IMAP_URLAUTH_STATE_REQUEST_WAIT; + imap_urlauth_request_fail(conn, + conn->targets_head->requests_head, error); + return 1; + } + i_error("imap-urlauth: Unexpected URL response: %s", + str_sanitize(response, 80)); + return -1; + } + + /* read metadata */ + args++; + for (; args[1] != NULL; args++) { + const char *param = args[0]; + + if (strcasecmp(param, "hasnuls") == 0) { + urlreq->binary_has_nuls = TRUE; + } else if (strncasecmp(param, "bpstruct=", 9) == 0 && + param[9] != '\0') { + bpstruct = param+9; + } + } + + /* read literal size */ + if (str_to_uoff(args[0], &literal_size) < 0) { + i_error("imap-urlauth: " + "Overflowing unsigned integer value for literal size: %s", + args[1]); + return -1; + } + + /* read literal */ + if (imap_urlauth_connection_read_literal_init(conn, literal_size) < 0) + return -1; + + urlreq->bodypartstruct = i_strdup(bpstruct); + return imap_urlauth_connection_read_literal(conn); +} + +static int imap_urlauth_input_next(struct imap_urlauth_connection *conn) +{ + const char *response; + int ret; + + switch (conn->state) { + case IMAP_URLAUTH_STATE_AUTHENTICATING: + case IMAP_URLAUTH_STATE_UNSELECTING_TARGET: + if ((response = i_stream_next_line(conn->input)) == NULL) + return 0; + imap_urlauth_stop_response_timeout(conn); + + if (strcasecmp(response, "OK") != 0) { + if (conn->state == IMAP_URLAUTH_STATE_AUTHENTICATING) + i_error("imap-urlauth: Failed to authenticate to service: " + "Got unexpected response: %s", str_sanitize(response, 80)); + else + i_error("imap-urlauth: Failed to unselect target user: " + "Got unexpected response: %s", str_sanitize(response, 80)); + imap_urlauth_connection_abort(conn, NULL); + return -1; + } + + if (conn->state == IMAP_URLAUTH_STATE_AUTHENTICATING) { + e_debug(conn->user->event, + "imap-urlauth: Successfully authenticated to service"); + } else { + e_debug(conn->user->event, + "imap-urlauth: Successfully unselected target user"); + } + + conn->state = IMAP_URLAUTH_STATE_AUTHENTICATED; + imap_urlauth_connection_select_target(conn); + return 0; + case IMAP_URLAUTH_STATE_SELECTING_TARGET: + if ((response = i_stream_next_line(conn->input)) == NULL) + return 0; + imap_urlauth_stop_response_timeout(conn); + + i_assert(conn->targets_head != NULL); + + if (strcasecmp(response, "NO") == 0) { + e_debug(conn->user->event, + "imap-urlauth: Failed to select target user %s", + conn->targets_head->userid); + imap_urlauth_target_fail(conn, conn->targets_head, NULL); + + conn->state = IMAP_URLAUTH_STATE_AUTHENTICATED; + imap_urlauth_connection_select_target(conn); + return 0; + } + if (strcasecmp(response, "OK") != 0) { + i_error("imap-urlauth: Failed to select target user %s: " + "Got unexpected response: %s", conn->targets_head->userid, + str_sanitize(response, 80)); + imap_urlauth_connection_abort(conn, NULL); + return -1; + } + + e_debug(conn->user->event, + "imap-urlauth: Successfully selected target user %s", + conn->targets_head->userid); + conn->state = IMAP_URLAUTH_STATE_READY; + imap_urlauth_connection_send_request(conn); + return 0; + case IMAP_URLAUTH_STATE_AUTHENTICATED: + case IMAP_URLAUTH_STATE_READY: + case IMAP_URLAUTH_STATE_REQUEST_WAIT: + if ((response = i_stream_next_line(conn->input)) == NULL) + return 0; + + i_error("imap-urlauth: Received input while no requests were pending: %s", + str_sanitize(response, 80)); + imap_urlauth_connection_abort(conn, NULL); + return -1; + case IMAP_URLAUTH_STATE_REQUEST_PENDING: + if ((ret = imap_urlauth_input_pending(conn)) < 0) + imap_urlauth_connection_fail(conn); + return ret; + case IMAP_URLAUTH_STATE_DISCONNECTED: + break; + } + i_unreached(); +} + +static void imap_urlauth_input(struct imap_urlauth_connection *conn) +{ + i_assert(conn->state != IMAP_URLAUTH_STATE_DISCONNECTED); + + if (conn->input->closed) { + /* disconnected */ + i_error("imap-urlauth: Service disconnected unexpectedly"); + imap_urlauth_connection_fail(conn); + return; + } + + switch (i_stream_read(conn->input)) { + case -1: + /* disconnected */ + i_error("imap-urlauth: Service disconnected unexpectedly"); + imap_urlauth_connection_fail(conn); + return; + case -2: + /* input buffer full */ + i_error("imap-urlauth: Service sent too large input"); + imap_urlauth_connection_abort(conn, NULL); + return; + } + + while (!conn->input->closed) { + if (imap_urlauth_input_next(conn) <= 0) + break; + } +} + +static int +imap_urlauth_connection_do_connect(struct imap_urlauth_connection *conn) +{ + string_t *str; + int fd; + + if (conn->state != IMAP_URLAUTH_STATE_DISCONNECTED) { + imap_urlauth_connection_send_request(conn); + return 1; + } + + if (conn->user->auth_token == NULL) { + i_error("imap-urlauth: cannot authenticate because no auth token " + "is available for this session (running standalone?)."); + imap_urlauth_connection_abort(conn, NULL); + return -1; + } + + e_debug(conn->user->event, "imap-urlauth: Connecting to service at %s", conn->path); + + i_assert(conn->fd == -1); + fd = net_connect_unix(conn->path); + if (fd == -1) { + i_error("imap-urlauth: net_connect_unix(%s) failed: %m", + conn->path); + imap_urlauth_connection_abort(conn, NULL); + return -1; + } + + timeout_remove(&conn->to_reconnect); + + conn->fd = fd; + conn->input = i_stream_create_fd(fd, SIZE_MAX); + conn->output = o_stream_create_fd(fd, SIZE_MAX); + conn->io = io_add(fd, IO_READ, imap_urlauth_input, conn); + conn->state = IMAP_URLAUTH_STATE_AUTHENTICATING; + + str = t_str_new(128); + str_printfa(str, IMAP_URLAUTH_HANDSHAKE"AUTH\t%s\t%s\t", + conn->service, my_pid); + str_append_tabescaped(str, conn->user->username); + str_append_c(str, '\t'); + if (conn->session_id != NULL) + str_append_tabescaped(str, conn->session_id); + str_append_c(str, '\t'); + str_append_tabescaped(str, conn->user->auth_token); + str_append_c(str, '\n'); + if (o_stream_send(conn->output, str_data(str), str_len(str)) < 0) { + i_warning("Error sending handshake to imap-urlauth server: %m"); + imap_urlauth_connection_abort(conn, NULL); + return -1; + } + + imap_urlauth_start_response_timeout(conn); + return 0; +} + +int imap_urlauth_connection_connect(struct imap_urlauth_connection *conn) +{ + conn->reconnect_attempts = 0; + + if (conn->to_reconnect == NULL) + return imap_urlauth_connection_do_connect(conn); + return 0; +} + +static void imap_urlauth_connection_disconnect +(struct imap_urlauth_connection *conn, const char *reason) +{ + conn->state = IMAP_URLAUTH_STATE_DISCONNECTED; + + if (conn->fd != -1) { + if (reason == NULL) + e_debug(conn->user->event, "imap-urlauth: Disconnecting from service"); + else + e_debug(conn->user->event, "imap-urlauth: Disconnected: %s", reason); + + io_remove(&conn->io); + i_stream_destroy(&conn->input); + o_stream_destroy(&conn->output); + net_disconnect(conn->fd); + conn->fd = -1; + } + conn->reading_literal = FALSE; + + if (conn->literal_fd != -1) { + if (close(conn->literal_fd) < 0) + i_error("imap-urlauth: close(%s) failed: %m", conn->literal_temp_path); + + i_free_and_null(conn->literal_temp_path); + conn->literal_fd = -1; + } + + buffer_free(&conn->literal_buf); + timeout_remove(&conn->to_reconnect); + timeout_remove(&conn->to_idle); + imap_urlauth_stop_response_timeout(conn); +} + +static void +imap_urlauth_connection_do_reconnect(struct imap_urlauth_connection *conn) +{ + if (conn->reconnect_attempts >= IMAP_URLAUTH_RECONNECT_MAX_ATTEMPTS) { + imap_urlauth_connection_abort(conn, + "Connection failed and connection attempts exhausted"); + return; + } + + if (ioloop_time - conn->last_reconnect < IMAP_URLAUTH_RECONNECT_MIN_SECS) { + e_debug(conn->user->event, "imap-urlauth: Scheduling reconnect"); + timeout_remove(&conn->to_reconnect); + conn->to_reconnect = + timeout_add(IMAP_URLAUTH_RECONNECT_MIN_SECS*1000, + imap_urlauth_connection_do_reconnect, conn); + } else { + conn->reconnect_attempts++; + conn->last_reconnect = ioloop_time; + (void)imap_urlauth_connection_do_connect(conn); + } +} + +static void +imap_urlauth_connection_reconnect(struct imap_urlauth_connection *conn) +{ + imap_urlauth_connection_disconnect(conn, NULL); + + /* don't reconnect if there are no requests */ + if (conn->targets_head == NULL) + return; + + imap_urlauth_connection_do_reconnect(conn); +} + +static void +imap_urlauth_connection_idle_disconnect(struct imap_urlauth_connection *conn) +{ + imap_urlauth_connection_disconnect(conn, "Idle timeout"); +} + +static void +imap_urlauth_connection_timeout_abort(struct imap_urlauth_connection *conn) +{ + imap_urlauth_connection_abort(conn, "Service is not responding"); +} + +bool imap_urlauth_connection_is_connected(struct imap_urlauth_connection *conn) +{ + return conn->fd != -1 && conn->state != IMAP_URLAUTH_STATE_DISCONNECTED; +} diff --git a/src/lib-imap-urlauth/imap-urlauth-connection.h b/src/lib-imap-urlauth/imap-urlauth-connection.h new file mode 100644 index 0000000..b60186a --- /dev/null +++ b/src/lib-imap-urlauth/imap-urlauth-connection.h @@ -0,0 +1,42 @@ +#ifndef IMAP_URLAUTH_CONNECTION_H +#define IMAP_URLAUTH_CONNECTION_H + +struct imap_urlauth_request; +struct imap_urlauth_fetch_reply; + +typedef int +imap_urlauth_request_callback_t(struct imap_urlauth_fetch_reply *reply, + void *context); + +/* If reconnect_callback is specified, it's called when connection is lost. + If the callback returns FALSE, reconnection isn't attempted. */ +struct imap_urlauth_connection * +imap_urlauth_connection_init(const char *path, const char *service, + struct mail_user *user, const char *session_id, + unsigned int idle_timeout_msecs); +void imap_urlauth_connection_deinit(struct imap_urlauth_connection **conn); + +/* Connect to imap-urlauth (even if failed for previous requests). */ +int imap_urlauth_connection_connect(struct imap_urlauth_connection *conn); + +/* Continue after request callback returned 0 */ +void imap_urlauth_connection_continue(struct imap_urlauth_connection *conn); + +/* Create a new URL fetch request */ +struct imap_urlauth_request * +imap_urlauth_request_new(struct imap_urlauth_connection *conn, + const char *target_user, const char *url, + enum imap_urlauth_fetch_flags flags, + imap_urlauth_request_callback_t *callback, + void *context); +/* Abort request */ +void imap_urlauth_request_abort(struct imap_urlauth_connection *conn, + struct imap_urlauth_request *urlreq); +/* Abort all requests with matching context value */ +void imap_urlauth_request_abort_by_context(struct imap_urlauth_connection *conn, + void *context); + +/* Returns TRUE if currently connected imap-urlauth service. */ +bool imap_urlauth_connection_is_connected(struct imap_urlauth_connection *conn); + +#endif diff --git a/src/lib-imap-urlauth/imap-urlauth-fetch.c b/src/lib-imap-urlauth/imap-urlauth-fetch.c new file mode 100644 index 0000000..d5746f1 --- /dev/null +++ b/src/lib-imap-urlauth/imap-urlauth-fetch.c @@ -0,0 +1,530 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "llist.h" +#include "array.h" +#include "net.h" +#include "istream.h" +#include "mail-user.h" +#include "mail-error.h" +#include "mail-storage.h" +#include "imap-url.h" +#include "imap-msgpart-url.h" +#include "imap-urlauth-private.h" +#include "imap-urlauth-fetch.h" +#include "imap-urlauth-connection.h" + +struct imap_urlauth_fetch_url { + struct imap_urlauth_fetch_url *prev, *next; + + char *url; + enum imap_urlauth_fetch_flags flags; +}; + +struct imap_urlauth_fetch { + unsigned int refcount; + struct imap_urlauth_context *uctx; + + imap_urlauth_fetch_callback_t *callback; + void *context; + + /* local urls */ + struct imap_urlauth_fetch_url *local_urls_head, *local_urls_tail; + struct imap_msgpart_url *local_url; + + unsigned int pending_requests; + + struct { + char *url; + enum imap_urlauth_fetch_flags flags; + + struct istream *input; + uoff_t size; + + char *bodypartstruct; + char *error; + + bool succeeded:1; + bool binary_has_nuls:1; + } pending_reply; + + bool failed:1; + bool waiting_local:1; + bool waiting_service:1; +}; + +static void imap_urlauth_fetch_abort_local(struct imap_urlauth_fetch *ufetch) +{ + struct imap_urlauth_fetch_url *url, *url_next; + + if (ufetch->local_url != NULL) { + ufetch->pending_requests--; + imap_msgpart_url_free(&ufetch->local_url); + } + + i_free_and_null(ufetch->pending_reply.url); + i_free_and_null(ufetch->pending_reply.bodypartstruct); + i_free_and_null(ufetch->pending_reply.error); + i_stream_unref(&ufetch->pending_reply.input); + + url = ufetch->local_urls_head; + for (; url != NULL; url = url_next) { + url_next = url->next; + i_free(url->url); + i_free(url); + ufetch->pending_requests--; + } + ufetch->local_urls_head = ufetch->local_urls_tail = NULL; +} + +static void imap_urlauth_fetch_abort(struct imap_urlauth_fetch *ufetch) +{ + if (ufetch->pending_requests > 0) + imap_urlauth_request_abort_by_context(ufetch->uctx->conn, ufetch); + + imap_urlauth_fetch_abort_local(ufetch); + + i_assert(ufetch->pending_requests == 0); +} + +static void imap_urlauth_fetch_fail(struct imap_urlauth_fetch *ufetch) +{ + imap_urlauth_fetch_abort(ufetch); + ufetch->failed = TRUE; +} + +struct imap_urlauth_fetch * +imap_urlauth_fetch_init(struct imap_urlauth_context *uctx, + imap_urlauth_fetch_callback_t *callback, void *context) +{ + struct imap_urlauth_fetch *ufetch; + + ufetch = i_new(struct imap_urlauth_fetch, 1); + ufetch->refcount = 1; + ufetch->uctx = uctx; + ufetch->callback = callback; + ufetch->context = context; + return ufetch; +} + +static void imap_urlauth_fetch_ref(struct imap_urlauth_fetch *ufetch) +{ + i_assert(ufetch->refcount > 0); + ufetch->refcount++; +} + +static void imap_urlauth_fetch_unref(struct imap_urlauth_fetch **_ufetch) +{ + struct imap_urlauth_fetch *ufetch = *_ufetch; + + i_assert(ufetch->refcount > 0); + + *_ufetch = NULL; + if (--ufetch->refcount > 0) + return; + + ufetch->refcount++; + imap_urlauth_fetch_abort(ufetch); + ufetch->refcount--; + i_assert(ufetch->refcount == 0); + + /* dont leave the connection in limbo; make sure continue is called */ + if (ufetch->waiting_service) + imap_urlauth_connection_continue(ufetch->uctx->conn); + i_free(ufetch); +} + +void imap_urlauth_fetch_deinit(struct imap_urlauth_fetch **_ufetch) +{ + imap_urlauth_fetch_unref(_ufetch); +} + +static void +imap_urlauth_fetch_error(struct imap_urlauth_fetch *ufetch, const char *url, + enum imap_urlauth_fetch_flags url_flags, + const char *error) +{ + struct imap_urlauth_fetch_reply reply; + int ret; + + ufetch->pending_requests--; + + i_zero(&reply); + reply.url = url; + reply.flags = url_flags; + reply.succeeded = FALSE; + reply.error = error; + + T_BEGIN { + ret = ufetch->callback(&reply, ufetch->pending_requests == 0, + ufetch->context); + } T_END; + + if (ret == 0) { + ufetch->waiting_local = TRUE; + ufetch->pending_requests++; + } else if (ret < 0) { + imap_urlauth_fetch_fail(ufetch); + } +} + +static void +imap_urlauth_fetch_local(struct imap_urlauth_fetch *ufetch, const char *url, + enum imap_urlauth_fetch_flags url_flags, + struct imap_url *imap_url) +{ + struct imap_urlauth_fetch_reply reply; + struct imap_msgpart_open_result mpresult; + const char *error, *errormsg = NULL, *bpstruct = NULL; + bool debug = ufetch->uctx->user->mail_debug, success; + enum mail_error error_code; + struct imap_msgpart_url *mpurl = NULL; + int ret; + + success = TRUE; + + if (debug) + i_debug("Fetching local URLAUTH %s", url); + + if (url_flags == 0) + url_flags = IMAP_URLAUTH_FETCH_FLAG_BODY; + + /* fetch URL */ + if (imap_url == NULL) { + ret = imap_urlauth_fetch(ufetch->uctx, url, + &mpurl, &error_code, &error); + } else { + ret = imap_urlauth_fetch_parsed(ufetch->uctx, imap_url, + &mpurl, &error_code, &error); + } + if (ret <= 0) { + if (ret == 0) { + errormsg = t_strdup_printf("Failed to fetch URLAUTH \"%s\": %s", + url, error); + if (debug) + i_debug("%s", errormsg); + } + success = FALSE; + } + + /* fetch metadata */ + if (success && (url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0) + imap_msgpart_url_set_decode_to_binary(mpurl); + if (success && + (url_flags & IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE) != 0) { + ret = imap_msgpart_url_get_bodypartstructure(mpurl, &bpstruct, &error); + if (ret <= 0) { + if (ret == 0) { + errormsg = t_strdup_printf + ("Failed to read URLAUTH \"%s\": %s", url, error); + if (debug) + i_debug("%s", errormsg); + } + success = FALSE; + } + } + + /* if requested, read the message part the URL points to */ + i_zero(&mpresult); + if (success && ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0 || + (url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0)) { + ret = imap_msgpart_url_read_part(mpurl, &mpresult, &error); + if (ret <= 0) { + if (ret == 0) { + errormsg = t_strdup_printf + ("Failed to read URLAUTH \"%s\": %s", url, error); + if (debug) + i_debug("%s", errormsg); + } + success = FALSE; + } + } + + if (debug && success) { + if (bpstruct != NULL) + i_debug("Fetched URLAUTH yielded BODYPARTSTRUCTURE (%s)", bpstruct); + if (mpresult.size == 0 || mpresult.input == NULL) + i_debug("Fetched URLAUTH yielded empty result"); + else { + i_debug("Fetched URLAUTH yielded %"PRIuUOFF_T" bytes " + "of %smessage data", mpresult.size, + (mpresult.binary_decoded_input_has_nuls ? "binary " : "")); + } + } + + ufetch->pending_requests--; + + if (!success && ret < 0) { + if (mpurl != NULL) + imap_msgpart_url_free(&mpurl); + (void)ufetch->callback(NULL, TRUE, ufetch->context); + imap_urlauth_fetch_fail(ufetch); + return; + } + + i_zero(&reply); + reply.url = url; + reply.flags = url_flags; + reply.error = errormsg; + reply.succeeded = success; + + reply.bodypartstruct = bpstruct; + reply.binary_has_nuls = mpresult.binary_decoded_input_has_nuls; + reply.size = mpresult.size; + reply.input = mpresult.input; + + ret = ufetch->callback(&reply, ufetch->pending_requests == 0, + ufetch->context); + if (ret == 0) { + ufetch->local_url = mpurl; + ufetch->waiting_local = TRUE; + ufetch->pending_requests++; + } else { + + if (mpurl != NULL) + imap_msgpart_url_free(&mpurl); + if (ret < 0) + imap_urlauth_fetch_fail(ufetch); + } +} + +static int +imap_urlauth_fetch_request_callback(struct imap_urlauth_fetch_reply *reply, + void *context) +{ + struct imap_urlauth_fetch *ufetch = + (struct imap_urlauth_fetch *)context; + int ret = 1; + + if (ufetch->waiting_local && reply != NULL) { + i_assert(ufetch->pending_reply.url == NULL); + ufetch->pending_reply.url = i_strdup(reply->url); + ufetch->pending_reply.flags = reply->flags; + ufetch->pending_reply.bodypartstruct = + i_strdup(reply->bodypartstruct); + ufetch->pending_reply.error = i_strdup(reply->error); + if (reply->input != NULL) { + ufetch->pending_reply.input = reply->input; + i_stream_ref(ufetch->pending_reply.input); + } + ufetch->pending_reply.size = reply->size; + ufetch->pending_reply.succeeded = reply->succeeded; + ufetch->pending_reply.binary_has_nuls = reply->binary_has_nuls; + ufetch->waiting_service = TRUE; + return 0; + } + + ufetch->waiting_local = FALSE; + ufetch->pending_requests--; + + imap_urlauth_fetch_ref(ufetch); + + if (!ufetch->failed) { + bool last = ufetch->pending_requests == 0 || reply == NULL; + ret = ufetch->callback(reply, last, ufetch->context); + } + + /* report failure only once */ + if (ret < 0 || reply == NULL) { + if (!ufetch->failed) + imap_urlauth_fetch_abort_local(ufetch); + ufetch->failed = TRUE; + } else if (ret == 0) { + ufetch->waiting_service = TRUE; + ufetch->pending_requests++; + } + + imap_urlauth_fetch_unref(&ufetch); + return ret; +} + +int imap_urlauth_fetch_url(struct imap_urlauth_fetch *ufetch, const char *url, + enum imap_urlauth_fetch_flags url_flags) +{ + struct imap_urlauth_context *uctx = ufetch->uctx; + enum imap_url_parse_flags url_parse_flags = + IMAP_URL_PARSE_ALLOW_URLAUTH; + struct mail_user *mail_user = uctx->user; + struct imap_url *imap_url; + const char *error, *errormsg; + + /* parse the url */ + if (imap_url_parse(url, NULL, url_parse_flags, &imap_url, &error) < 0) { + errormsg = t_strdup_printf( + "Failed to fetch URLAUTH \"%s\": %s", url, error); + e_debug(mail_user->event, "%s", errormsg); + ufetch->pending_requests++; + imap_urlauth_fetch_ref(ufetch); + imap_urlauth_fetch_error(ufetch, url, url_flags, errormsg); + imap_urlauth_fetch_unref(&ufetch); + return 1; + } + + return imap_urlauth_fetch_url_parsed(ufetch, url, imap_url, url_flags); +} + +int imap_urlauth_fetch_url_parsed(struct imap_urlauth_fetch *ufetch, + const char *url, struct imap_url *imap_url, + enum imap_urlauth_fetch_flags url_flags) +{ + struct imap_urlauth_context *uctx = ufetch->uctx; + struct mail_user *mail_user = uctx->user; + const char *error, *errormsg; + int ret = 0; + + ufetch->failed = FALSE; + ufetch->pending_requests++; + + imap_urlauth_fetch_ref(ufetch); + + /* if access user and target user match, handle fetch request locally */ + if (imap_url->userid != NULL && + strcmp(mail_user->username, imap_url->userid) == 0) { + + if (ufetch->waiting_local) { + struct imap_urlauth_fetch_url *url_local; + + url_local = i_new(struct imap_urlauth_fetch_url, 1); + url_local->url = i_strdup(url); + url_local->flags = url_flags; + + DLLIST2_APPEND(&ufetch->local_urls_head, + &ufetch->local_urls_tail, url_local); + } else T_BEGIN { + imap_urlauth_fetch_local(ufetch, url, + url_flags, imap_url); + } T_END; + imap_url = NULL; + /* don't try to fetch remote URLs that are already known to fail access */ + } else if (!imap_urlauth_check(uctx, imap_url, TRUE, &error)) { + errormsg = t_strdup_printf( + "Failed to fetch URLAUTH \"%s\": %s", url, error); + e_debug(mail_user->event, "%s", errormsg); + imap_urlauth_fetch_error(ufetch, url, url_flags, errormsg); + imap_url = NULL; + } + + /* create request for url */ + if (imap_url != NULL && imap_url->userid != NULL) { + i_assert(uctx->conn != NULL); + (void)imap_urlauth_request_new(uctx->conn, imap_url->userid, + url, url_flags, + imap_urlauth_fetch_request_callback, ufetch); + i_assert(uctx->conn != NULL); + if (imap_urlauth_connection_connect(uctx->conn) < 0) + ret = -1; + } + if (ret >= 0) + ret = (ufetch->pending_requests > 0 ? 0 : 1); + + imap_urlauth_fetch_unref(&ufetch); + return ret; +} + +static bool imap_urlauth_fetch_do_continue(struct imap_urlauth_fetch *ufetch) +{ + struct imap_urlauth_fetch_url *url, *url_next; + int ret; + + if (ufetch->failed) + return FALSE; + + if (!ufetch->waiting_local && !ufetch->waiting_service) { + /* not currently waiting for anything */ + return ufetch->pending_requests > 0; + } + + /* we finished a request */ + ufetch->pending_requests--; + + if (!ufetch->waiting_local) { + /* not waiting for local request handling */ + ufetch->waiting_service = FALSE; + imap_urlauth_connection_continue(ufetch->uctx->conn); + return ufetch->pending_requests > 0; + } + + /* finished local request */ + if (ufetch->local_url != NULL) { + imap_msgpart_url_free(&ufetch->local_url); + } + ufetch->waiting_local = FALSE; + + /* handle pending remote reply */ + if (ufetch->pending_reply.url != NULL) { + struct imap_urlauth_fetch_reply reply; + + ufetch->pending_requests--; + + i_zero(&reply); + reply.url = ufetch->pending_reply.url; + reply.flags = ufetch->pending_reply.flags; + reply.bodypartstruct = ufetch->pending_reply.bodypartstruct; + reply.error = ufetch->pending_reply.error; + reply.input = ufetch->pending_reply.input; + reply.size = ufetch->pending_reply.size; + reply.succeeded = ufetch->pending_reply.succeeded; + reply.binary_has_nuls = ufetch->pending_reply.binary_has_nuls; + + ret = ufetch->callback(&reply, ufetch->pending_requests == 0, + ufetch->context); + + if (ufetch->pending_reply.url != NULL) + i_free(ufetch->pending_reply.url); + if (ufetch->pending_reply.input != NULL) + i_stream_unref(&ufetch->pending_reply.input); + if (ufetch->pending_reply.bodypartstruct != NULL) + i_free(ufetch->pending_reply.bodypartstruct); + if (ufetch->pending_reply.error != NULL) + i_free(ufetch->pending_reply.error); + + if (ret < 0) { + imap_urlauth_fetch_fail(ufetch); + return FALSE; + } + + if (ret == 0) { + ufetch->waiting_service = TRUE; + ufetch->pending_requests++; + return TRUE; + } + + ufetch->waiting_service = FALSE; + imap_urlauth_connection_continue(ufetch->uctx->conn); + } + + /* handle pending local urls */ + url = ufetch->local_urls_head; + while (url != NULL) { + url_next = url->next; + T_BEGIN { + imap_urlauth_fetch_local(ufetch, url->url, + url->flags, NULL); + } T_END; + DLLIST2_REMOVE(&ufetch->local_urls_head, + &ufetch->local_urls_tail, url); + i_free(url->url); + i_free(url); + if (ufetch->waiting_local) + return TRUE; + url = url_next; + } + + return ufetch->pending_requests > 0; +} + +bool imap_urlauth_fetch_continue(struct imap_urlauth_fetch *ufetch) +{ + bool pending; + + imap_urlauth_fetch_ref(ufetch); + pending = imap_urlauth_fetch_do_continue(ufetch); + imap_urlauth_fetch_unref(&ufetch); + + return pending; +} + +bool imap_urlauth_fetch_is_pending(struct imap_urlauth_fetch *ufetch) +{ + return ufetch->pending_requests > 0; +} diff --git a/src/lib-imap-urlauth/imap-urlauth-fetch.h b/src/lib-imap-urlauth/imap-urlauth-fetch.h new file mode 100644 index 0000000..628a95e --- /dev/null +++ b/src/lib-imap-urlauth/imap-urlauth-fetch.h @@ -0,0 +1,56 @@ +#ifndef IMAP_URLAUTH_FETCH_H +#define IMAP_URLAUTH_FETCH_H + +struct imap_url; +struct imap_urlauth_context; +struct imap_urlauth_fetch; + +enum imap_urlauth_fetch_flags { + /* Indicates that this is an extended request */ + IMAP_URLAUTH_FETCH_FLAG_EXTENDED = 0x01, + /* Fetch body part unmodified */ + IMAP_URLAUTH_FETCH_FLAG_BODY = 0x02, + /* Fetch body part as binary, i.e. without content encoding */ + IMAP_URLAUTH_FETCH_FLAG_BINARY = 0x04, + /* Fetch IMAP bodypartstructure */ + IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE = 0x08, +}; + +struct imap_urlauth_fetch_reply { + const char *url; + enum imap_urlauth_fetch_flags flags; + + struct istream *input; + uoff_t size; + + const char *bodypartstruct; + const char *error; + + bool succeeded:1; + bool binary_has_nuls:1; +}; + +/* Callback to handle fetch reply. Returns 1 if handled completely and ready + for next reply, 0 if not all data was processed, and -1 for error. If a + callback returns 0, imap_urlauth_fetch_continue() must be called once + new replies may be processed. If this is the last request to yield a reply, + argument last is TRUE. */ +typedef int +imap_urlauth_fetch_callback_t(struct imap_urlauth_fetch_reply *reply, + bool last, void *context); + +struct imap_urlauth_fetch * +imap_urlauth_fetch_init(struct imap_urlauth_context *uctx, + imap_urlauth_fetch_callback_t *callback, void *context); +void imap_urlauth_fetch_deinit(struct imap_urlauth_fetch **ufetch); + +int imap_urlauth_fetch_url(struct imap_urlauth_fetch *ufetch, const char *url, + enum imap_urlauth_fetch_flags url_flags); +int imap_urlauth_fetch_url_parsed(struct imap_urlauth_fetch *ufetch, + const char *url, struct imap_url *imap_url, + enum imap_urlauth_fetch_flags url_flags); + +bool imap_urlauth_fetch_continue(struct imap_urlauth_fetch *ufetch); +bool imap_urlauth_fetch_is_pending(struct imap_urlauth_fetch *ufetch); + +#endif diff --git a/src/lib-imap-urlauth/imap-urlauth-private.h b/src/lib-imap-urlauth/imap-urlauth-private.h new file mode 100644 index 0000000..3b3622c --- /dev/null +++ b/src/lib-imap-urlauth/imap-urlauth-private.h @@ -0,0 +1,20 @@ +#ifndef IMAP_URLAUTH_PRIVATE_H +#define IMAP_URLAUTH_PRIVATE_H + +#include "imap-urlauth.h" + +struct imap_urlauth_context { + struct mail_user *user; + struct imap_urlauth_connection *conn; + + char *url_host; + in_port_t url_port; + + char *access_user; + char *access_service; + const char **access_applications; + + bool access_anonymous:1; +}; + +#endif diff --git a/src/lib-imap-urlauth/imap-urlauth.c b/src/lib-imap-urlauth/imap-urlauth.c new file mode 100644 index 0000000..a9c2673 --- /dev/null +++ b/src/lib-imap-urlauth/imap-urlauth.c @@ -0,0 +1,486 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "hostpid.h" +#include "var-expand.h" +#include "hmac.h" +#include "sha1.h" +#include "randgen.h" +#include "safe-memset.h" +#include "mail-storage.h" +#include "mail-storage-service.h" +#include "mail-namespace.h" +#include "mail-user.h" +#include "imap-url.h" +#include "imap-msgpart-url.h" +#include "imap-urlauth-backend.h" +#include "imap-urlauth-fetch.h" +#include "imap-urlauth-connection.h" + +#include "imap-urlauth-private.h" + +#include <time.h> + +#define IMAP_URLAUTH_MECH_INTERNAL_VERSION 0x01 + +#define IMAP_URLAUTH_NORMAL_TIMEOUT_MSECS 5*1000 +#define IMAP_URLAUTH_SPECIAL_TIMEOUT_MSECS 3*60*1000 + +#define URL_HOST_ALLOW_ANY "*" + +struct imap_urlauth_context * +imap_urlauth_init(struct mail_user *user, + const struct imap_urlauth_config *config) +{ + struct imap_urlauth_context *uctx; + unsigned int timeout; + + i_assert(*config->url_host != '\0'); + + uctx = i_new(struct imap_urlauth_context, 1); + uctx->user = user; + uctx->url_host = i_strdup(config->url_host); + uctx->url_port = config->url_port; + + if (config->access_anonymous) + uctx->access_user = i_strdup("anonymous"); + else + uctx->access_user = i_strdup(config->access_user); + uctx->access_service = i_strdup(config->access_service); + uctx->access_anonymous = config->access_anonymous; + if (config->access_applications != NULL && + *config->access_applications != NULL) { + uctx->access_applications = + p_strarray_dup(default_pool, + config->access_applications); + timeout = IMAP_URLAUTH_SPECIAL_TIMEOUT_MSECS; + } else { + timeout = IMAP_URLAUTH_NORMAL_TIMEOUT_MSECS; + } + + if (config->socket_path != NULL) { + uctx->conn = imap_urlauth_connection_init(config->socket_path, + config->access_service, user, config->session_id, timeout); + } + return uctx; +} + +void imap_urlauth_deinit(struct imap_urlauth_context **_uctx) +{ + struct imap_urlauth_context *uctx = *_uctx; + + *_uctx = NULL; + + if (uctx->conn != NULL) + imap_urlauth_connection_deinit(&uctx->conn); + i_free(uctx->url_host); + i_free(uctx->access_user); + i_free(uctx->access_service); + i_free(uctx->access_applications); + i_free(uctx); +} + +static const unsigned char * +imap_urlauth_internal_generate(const char *rumpurl, + const unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN], + size_t *token_len_r) +{ + struct hmac_context hmac; + unsigned char *token; + + token = t_new(unsigned char, SHA1_RESULTLEN + 1); + token[0] = IMAP_URLAUTH_MECH_INTERNAL_VERSION; + + hmac_init(&hmac, mailbox_key, IMAP_URLAUTH_KEY_LEN, &hash_method_sha1); + hmac_update(&hmac, rumpurl, strlen(rumpurl)); + hmac_final(&hmac, token+1); + + *token_len_r = SHA1_RESULTLEN + 1; + return token; +} + +static bool +imap_urlauth_internal_verify(const char *rumpurl, + const unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN], + const unsigned char *token, size_t token_len) +{ + const unsigned char *valtoken; + size_t valtoken_len; + + if (rumpurl == NULL || token == NULL) + return FALSE; + + valtoken = imap_urlauth_internal_generate(rumpurl, mailbox_key, + &valtoken_len); + /* Note: the token length has timing leak here in any case */ + if (token_len != valtoken_len) + return FALSE; + + return mem_equals_timing_safe(token, valtoken, valtoken_len); +} + +static bool +access_applications_have_access(struct imap_urlauth_context *uctx, + struct imap_url *url, const char *const *access_applications) +{ + const char *const *application; + + if (access_applications == NULL) + return FALSE; + + application = access_applications; + for (; *application != NULL; application++) { + const char *app = *application; + bool have_userid = FALSE; + size_t len = strlen(app); + + if (app[len-1] == '+') + have_userid = TRUE; + + if (strncasecmp(url->uauth_access_application, app, len-1) == 0) { + if (!have_userid) { + /* this access application must have no userid */ + return url->uauth_access_user == NULL; + } + + /* this access application must have a userid */ + return (!uctx->access_anonymous && url->uauth_access_user != NULL); + } + } + return FALSE; +} + +static bool +imap_urlauth_check_access(struct imap_urlauth_context *uctx, + struct imap_url *url, bool ignore_unknown, + const char **error_r) +{ + const char *userid; + + if (url->uauth_access_application == NULL) { + *error_r = "URL is missing URLAUTH"; + return FALSE; + } + + if (strcmp(uctx->access_service, "imap") == 0) { + /* these access types are only allowed if URL is accessed through imap */ + if (strcasecmp(url->uauth_access_application, "user") == 0) { + /* user+<access_user> */ + if (url->uauth_access_user == NULL) { + *error_r = "URLAUTH `user' access is missing userid"; + return FALSE; + } + if (!uctx->access_anonymous || + strcasecmp(url->uauth_access_user, uctx->access_user) == 0) + return TRUE; + } else if (strcasecmp(url->uauth_access_application, "authuser") == 0) { + /* authuser */ + if (!uctx->access_anonymous) + return TRUE; + } else if (strcasecmp(url->uauth_access_application, "anonymous") == 0) { + /* anonymous */ + return TRUE; + } else if (ignore_unknown || access_applications_have_access + (uctx, url, uctx->access_applications)) { + return TRUE; + } + } else if (strcmp(uctx->access_service, "submission") == 0) { + /* accessed directly through submission service */ + + if (strcasecmp(url->uauth_access_application, "submit") != 0) { + userid = url->uauth_access_user == NULL ? "" : + t_strdup_printf("+%s", url->uauth_access_user); + *error_r = t_strdup_printf( + "No '%s%s' access allowed for submission service", + url->uauth_access_application, userid); + return FALSE; + } else if (url->uauth_access_user == NULL) { + *error_r = "URLAUTH `submit' access is missing userid"; + return FALSE; + } else if (!uctx->access_anonymous && + strcasecmp(url->uauth_access_user, uctx->access_user) == 0) { + return TRUE; + } + } + + userid = url->uauth_access_user == NULL ? "" : + t_strdup_printf("+%s", url->uauth_access_user); + + if (uctx->access_anonymous) { + *error_r = t_strdup_printf( + "No '%s%s' access allowed for anonymous user", + url->uauth_access_application, userid); + } else { + *error_r = t_strdup_printf( + "No '%s%s' access allowed for user %s", + url->uauth_access_application, userid, uctx->access_user); + } + return FALSE; +} + +static bool +imap_urlauth_check_hostport(struct imap_urlauth_context *uctx, + struct imap_url *url, const char **client_error_r) +{ + /* validate host */ + /* FIXME: allow host ip/ip6 as well? */ + if (strcmp(uctx->url_host, URL_HOST_ALLOW_ANY) != 0 && + strcmp(url->host.name, uctx->url_host) != 0) { + *client_error_r = "Invalid URL: Inappropriate host name"; + return FALSE; + } + + /* validate port */ + if ((url->port == 0 && uctx->url_port != 143) || + (url->port != 0 && uctx->url_port != url->port)) { + *client_error_r = "Invalid URL: Inappropriate server port"; + return FALSE; + } + return TRUE; +} + +int imap_urlauth_generate(struct imap_urlauth_context *uctx, + const char *mechanism, const char *rumpurl, + const char **urlauth_r, const char **client_error_r) +{ + struct mail_user *user = uctx->user; + enum imap_url_parse_flags url_flags = + IMAP_URL_PARSE_ALLOW_URLAUTH; + struct imap_url *url; + struct imap_msgpart_url *mpurl = NULL; + struct mailbox *box; + const char *error; + enum mail_error error_code; + unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN]; + const unsigned char *token; + size_t token_len; + int ret; + + /* validate mechanism */ + if (strcasecmp(mechanism, "INTERNAL") != 0) { + *client_error_r = t_strdup_printf("Unsupported URLAUTH mechanism: %s", mechanism); + return 0; + } + + /* validate URL */ + if (imap_url_parse(rumpurl, NULL, url_flags, &url, &error) < 0) { + *client_error_r = t_strdup_printf("Invalid URL: %s", error); + return 0; + } + + if (url->mailbox == NULL || url->uid == 0 || url->search_program != NULL || + url->uauth_rumpurl == NULL || url->uauth_mechanism != NULL) { + *client_error_r = "Invalid URL: Must be an URLAUTH rump URL"; + return 0; + } + + /* validate expiry time */ + if (url->uauth_expire != (time_t)-1) { + time_t now = time(NULL); + + if (now > url->uauth_expire) { + *client_error_r = t_strdup_printf("URLAUTH has already expired"); + return 0; + } + } + + /* validate user */ + if (url->userid == NULL) { + *client_error_r = "Invalid URL: Missing user name"; + return 0; + } + if (user->anonymous || strcmp(url->userid, user->username) != 0) { + *client_error_r = t_strdup_printf( + "Not permitted to generate URLAUTH for user %s", + url->userid); + return 0; + } + + /* validate host:port */ + if (!imap_urlauth_check_hostport(uctx, url, client_error_r)) + return 0; + + /* validate mailbox */ + if ((ret = imap_msgpart_url_create(user, url, &mpurl, &error)) < 0 || + imap_msgpart_url_verify(mpurl, &error) <= 0) { + *client_error_r = t_strdup_printf("Invalid URL: %s", error); + if (mpurl != NULL) + imap_msgpart_url_free(&mpurl); + return ret; + } + box = imap_msgpart_url_get_mailbox(mpurl); + + /* obtain mailbox key */ + ret = imap_urlauth_backend_get_mailbox_key(box, TRUE, mailbox_key, + client_error_r, &error_code); + if (ret < 0) { + imap_msgpart_url_free(&mpurl); + return ret; + } + + token = imap_urlauth_internal_generate(rumpurl, mailbox_key, &token_len); + imap_msgpart_url_free(&mpurl); + + *urlauth_r = imap_url_add_urlauth(rumpurl, mechanism, token, token_len); + return 1; +} + +bool imap_urlauth_check(struct imap_urlauth_context *uctx, + struct imap_url *url, bool ignore_unknown_access, + const char **error_r) +{ + /* validate URL fields */ + if (url->mailbox == NULL || url->uid == 0 || + url->search_program != NULL || url->uauth_rumpurl == NULL || + url->uauth_mechanism == NULL) { + *error_r = "Invalid URL: Must be a full URLAUTH URL"; + return FALSE; + } + + /* check presence of userid */ + if (url->userid == NULL) { + *error_r = "Invalid URLAUTH: Missing user name"; + return FALSE; + } + + /* validate mechanism */ + if (strcasecmp(url->uauth_mechanism, "INTERNAL") != 0) { + *error_r = t_strdup_printf("Unsupported URLAUTH mechanism: %s", + url->uauth_mechanism); + return FALSE; + } + + /* validate expiry time */ + if (url->uauth_expire != (time_t)-1) { + time_t now = time(NULL); + + if (now > url->uauth_expire) { + *error_r = t_strdup_printf("URLAUTH has expired"); + return FALSE; + } + } + + /* validate access */ + if (!imap_urlauth_check_access(uctx, url, ignore_unknown_access, + error_r)) + return FALSE; + /* validate host:port */ + if (!imap_urlauth_check_hostport(uctx, url, error_r)) + return FALSE; + return TRUE; +} + +int imap_urlauth_fetch_parsed(struct imap_urlauth_context *uctx, + struct imap_url *url, + struct imap_msgpart_url **mpurl_r, + enum mail_error *error_code_r, + const char **error_r) +{ + struct mail_user *user = uctx->user; + struct imap_msgpart_url *mpurl; + struct mailbox *box; + const char *error; + unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN]; + int ret; + + *mpurl_r = NULL; + *error_r = NULL; + *error_code_r = MAIL_ERROR_NONE; + + /* check urlauth mechanism, access, userid and authority */ + if (!imap_urlauth_check(uctx, url, FALSE, error_r)) { + *error_code_r = MAIL_ERROR_PARAMS; + return 0; + } + + /* validate target user */ + if (user->anonymous || strcmp(url->userid, user->username) != 0) { + *error_r = t_strdup_printf("Not permitted to fetch URLAUTH for user %s", + url->userid); + *error_code_r = MAIL_ERROR_PARAMS; + return 0; + } + + /* validate mailbox */ + if ((ret = imap_msgpart_url_create(user, url, &mpurl, &error)) < 0) { + *error_r = t_strdup_printf("Invalid URLAUTH: %s", error); + *error_code_r = MAIL_ERROR_PARAMS; + return ret; + } + + if ((ret = imap_msgpart_url_open_mailbox(mpurl, &box, error_code_r, + &error)) < 0) { + *error_r = "Internal server error"; + imap_msgpart_url_free(&mpurl); + return -1; + } + + if (ret == 0) { + /* RFC says: `If the mailbox cannot be identified, an + authorization token is calculated on the rump URL, using + random "plausible" keys (selected by the server) as needed, + before returning a validation failure. This prevents timing + attacks aimed at identifying mailbox names.' */ + random_fill(mailbox_key, sizeof(mailbox_key)); + (void)imap_urlauth_internal_verify(url->uauth_rumpurl, + mailbox_key, url->uauth_token, url->uauth_token_size); + + *error_r = t_strdup_printf("Invalid URLAUTH: %s", error); + imap_msgpart_url_free(&mpurl); + return 0; + } + + /* obtain mailbox key */ + ret = imap_urlauth_backend_get_mailbox_key(box, FALSE, mailbox_key, + error_r, error_code_r); + if (ret < 0) { + imap_msgpart_url_free(&mpurl); + return -1; + } + + if (ret == 0 || + !imap_urlauth_internal_verify(url->uauth_rumpurl, mailbox_key, + url->uauth_token, + url->uauth_token_size)) { + *error_r = "URLAUTH verification failed"; + *error_code_r = MAIL_ERROR_PERM; + imap_msgpart_url_free(&mpurl); + ret = 0; + } else { + ret = 1; + } + + safe_memset(mailbox_key, 0, sizeof(mailbox_key)); + *mpurl_r = mpurl; + return ret; +} + +int imap_urlauth_fetch(struct imap_urlauth_context *uctx, + const char *urlauth, struct imap_msgpart_url **mpurl_r, + enum mail_error *error_code_r, const char **error_r) +{ + struct imap_url *url; + enum imap_url_parse_flags url_flags = IMAP_URL_PARSE_ALLOW_URLAUTH; + const char *error; + + /* validate URL */ + if (imap_url_parse(urlauth, NULL, url_flags, &url, &error) < 0) { + *error_r = t_strdup_printf("Invalid URLAUTH: %s", error); + *error_code_r = MAIL_ERROR_PARAMS; + return 0; + } + + return imap_urlauth_fetch_parsed(uctx, url, mpurl_r, + error_code_r, error_r); +} + +int imap_urlauth_reset_mailbox_key(struct imap_urlauth_context *uctx ATTR_UNUSED, + struct mailbox *box) +{ + return imap_urlauth_backend_reset_mailbox_key(box); +} + +int imap_urlauth_reset_all_keys(struct imap_urlauth_context *uctx) +{ + return imap_urlauth_backend_reset_all_keys(uctx->user); +} diff --git a/src/lib-imap-urlauth/imap-urlauth.h b/src/lib-imap-urlauth/imap-urlauth.h new file mode 100644 index 0000000..9c7c30f --- /dev/null +++ b/src/lib-imap-urlauth/imap-urlauth.h @@ -0,0 +1,55 @@ +#ifndef IMAP_URLAUTH_H +#define IMAP_URLAUTH_H + +#include "net.h" + +#define IMAP_URLAUTH_SOCKET_NAME "imap-urlauth" + +struct imap_url; +struct imap_msgpart_url; +struct imap_urlauth_context; + +struct imap_urlauth_config { + const char *url_host; + in_port_t url_port; + + const char *socket_path; + const char *session_id; + + /* the user who is requesting access to URLAUTHs */ + const char *access_user; + /* ... is using this service (i.e. imap or submission) */ + const char *access_service; + /* ... represents these applications */ + const char *const *access_applications; + /* ... is anonymous? */ + bool access_anonymous; +}; + +struct imap_urlauth_context * +imap_urlauth_init(struct mail_user *user, + const struct imap_urlauth_config *config); +void imap_urlauth_deinit(struct imap_urlauth_context **_uctx); + +int imap_urlauth_generate(struct imap_urlauth_context *uctx, + const char *mechanism, const char *rumpurl, + const char **urlauth_r, const char **client_error_r); + +bool imap_urlauth_check(struct imap_urlauth_context *uctx, + struct imap_url *url, bool ignore_unknown_access, + const char **error_r); + +int imap_urlauth_fetch_parsed(struct imap_urlauth_context *uctx, + struct imap_url *url, + struct imap_msgpart_url **mpurl_r, + enum mail_error *error_code_r, + const char **error_r); +int imap_urlauth_fetch(struct imap_urlauth_context *uctx, + const char *urlauth, struct imap_msgpart_url **mpurl_r, + enum mail_error *error_code_r, const char **error_r); + +int imap_urlauth_reset_mailbox_key(struct imap_urlauth_context *uctx, + struct mailbox *box); +int imap_urlauth_reset_all_keys(struct imap_urlauth_context *uctx); + +#endif diff --git a/src/lib-imap/Makefile.am b/src/lib-imap/Makefile.am new file mode 100644 index 0000000..2f59f93 --- /dev/null +++ b/src/lib-imap/Makefile.am @@ -0,0 +1,120 @@ +noinst_LTLIBRARIES = libimap.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-charset \ + -I$(top_srcdir)/src/lib-mail + +libimap_la_SOURCES = \ + imap-arg.c \ + imap-base-subject.c \ + imap-bodystructure.c \ + imap-date.c \ + imap-envelope.c \ + imap-id.c \ + imap-keepalive.c \ + imap-match.c \ + imap-parser.c \ + imap-quote.c \ + imap-url.c \ + imap-seqset.c \ + imap-utf7.c \ + imap-util.c + +headers = \ + imap-arg.h \ + imap-base-subject.h \ + imap-bodystructure.h \ + imap-date.h \ + imap-envelope.h \ + imap-id.h \ + imap-keepalive.h \ + imap-match.h \ + imap-parser.h \ + imap-resp-code.h \ + imap-quote.h \ + imap-url.h \ + imap-seqset.h \ + imap-utf7.h \ + imap-util.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers) + +test_programs = \ + test-imap-bodystructure \ + test-imap-envelope \ + test-imap-match \ + test-imap-parser \ + test-imap-quote \ + test-imap-url \ + test-imap-utf7 \ + test-imap-util + +noinst_PROGRAMS = $(test_programs) + +test_libs = \ + ../lib-test/libtest.la \ + ../lib/liblib.la + +test_deps = $(noinst_LTLIBRARIES) $(test_libs) + +test_imap_bodystructure_SOURCES = test-imap-bodystructure.c +test_imap_bodystructure_LDADD = imap-bodystructure.lo imap-envelope.lo imap-quote.lo imap-parser.lo imap-arg.lo ../lib-mail/libmail.la $(test_libs) +test_imap_bodystructure_DEPENDENCIES = $(test_deps) ../lib-mail/libmail.la + +test_imap_envelope_SOURCES = test-imap-envelope.c +test_imap_envelope_LDADD = imap-envelope.lo imap-quote.lo imap-parser.lo imap-arg.lo ../lib-mail/libmail.la $(test_libs) +test_imap_envelope_DEPENDENCIES = $(test_deps) ../lib-mail/libmail.la + +test_imap_match_SOURCES = test-imap-match.c +test_imap_match_LDADD = imap-match.lo $(test_libs) +test_imap_match_DEPENDENCIES = $(test_deps) + +test_imap_parser_SOURCES = test-imap-parser.c +test_imap_parser_LDADD = imap-parser.lo imap-arg.lo $(test_libs) +test_imap_parser_DEPENDENCIES = $(test_deps) + +test_imap_quote_SOURCES = test-imap-quote.c +test_imap_quote_LDADD = imap-quote.lo $(test_libs) +test_imap_quote_DEPENDENCIES = $(test_deps) + +test_imap_url_SOURCES = test-imap-url.c +test_imap_url_LDADD = imap-url.lo $(test_libs) +test_imap_url_DEPENDENCIES = $(test_deps) + +test_imap_utf7_SOURCES = test-imap-utf7.c +test_imap_utf7_LDADD = imap-utf7.lo $(test_libs) +test_imap_utf7_DEPENDENCIES = $(test_deps) + +test_imap_util_SOURCES = test-imap-util.c +test_imap_util_LDADD = imap-util.lo imap-arg.lo $(test_libs) +test_imap_util_DEPENDENCIES = $(test_deps) + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +if USE_FUZZER +noinst_PROGRAMS += \ + fuzz-imap-utf7 \ + fuzz-imap-bodystructure + +nodist_EXTRA_fuzz_imap_utf7_SOURCES = force-cxx-linking.cxx +fuzz_imap_utf7_SOURCES = fuzz-imap-utf7.c +fuzz_imap_utf7_CPPFLAGS = $(FUZZER_CPPFLAGS) +fuzz_imap_utf7_LDFLAGS = $(FUZZER_LDFLAGS) +fuzz_imap_utf7_LDADD = libimap.la $(test_libs) +fuzz_imap_utf7_DEPENDENCIES = libimap.la $(test_deps) + +nodist_EXTRA_fuzz_imap_bodystructure_SOURCES = force-cxx-linking.cxx +fuzz_imap_bodystructure_SOURCES = fuzz-imap-bodystructure.c +fuzz_imap_bodystructure_CPPFLAGS = $(FUZZER_CPPFLAGS) +fuzz_imap_bodystructure_LDFLAGS = $(FUZZER_LDFLAGS) +fuzz_imap_bodystructure_LDADD = libimap.la $(test_libs) +fuzz_imap_bodystructure_DEPENDENCIES = libimap.la $(test_deps) + + +endif diff --git a/src/lib-imap/Makefile.in b/src/lib-imap/Makefile.in new file mode 100644 index 0000000..f5e6f36 --- /dev/null +++ b/src/lib-imap/Makefile.in @@ -0,0 +1,1196 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_2) +@USE_FUZZER_TRUE@am__append_1 = \ +@USE_FUZZER_TRUE@ fuzz-imap-utf7 \ +@USE_FUZZER_TRUE@ fuzz-imap-bodystructure + +subdir = src/lib-imap +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = test-imap-bodystructure$(EXEEXT) \ + test-imap-envelope$(EXEEXT) test-imap-match$(EXEEXT) \ + test-imap-parser$(EXEEXT) test-imap-quote$(EXEEXT) \ + test-imap-url$(EXEEXT) test-imap-utf7$(EXEEXT) \ + test-imap-util$(EXEEXT) +@USE_FUZZER_TRUE@am__EXEEXT_2 = fuzz-imap-utf7$(EXEEXT) \ +@USE_FUZZER_TRUE@ fuzz-imap-bodystructure$(EXEEXT) +PROGRAMS = $(noinst_PROGRAMS) +LTLIBRARIES = $(noinst_LTLIBRARIES) +libimap_la_LIBADD = +am_libimap_la_OBJECTS = imap-arg.lo imap-base-subject.lo \ + imap-bodystructure.lo imap-date.lo imap-envelope.lo imap-id.lo \ + imap-keepalive.lo imap-match.lo imap-parser.lo imap-quote.lo \ + imap-url.lo imap-seqset.lo imap-utf7.lo imap-util.lo +libimap_la_OBJECTS = $(am_libimap_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +am__fuzz_imap_bodystructure_SOURCES_DIST = fuzz-imap-bodystructure.c +@USE_FUZZER_TRUE@am_fuzz_imap_bodystructure_OBJECTS = fuzz_imap_bodystructure-fuzz-imap-bodystructure.$(OBJEXT) +fuzz_imap_bodystructure_OBJECTS = \ + $(am_fuzz_imap_bodystructure_OBJECTS) +fuzz_imap_bodystructure_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) $(fuzz_imap_bodystructure_LDFLAGS) \ + $(LDFLAGS) -o $@ +am__fuzz_imap_utf7_SOURCES_DIST = fuzz-imap-utf7.c +@USE_FUZZER_TRUE@am_fuzz_imap_utf7_OBJECTS = \ +@USE_FUZZER_TRUE@ fuzz_imap_utf7-fuzz-imap-utf7.$(OBJEXT) +fuzz_imap_utf7_OBJECTS = $(am_fuzz_imap_utf7_OBJECTS) +fuzz_imap_utf7_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) $(fuzz_imap_utf7_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_test_imap_bodystructure_OBJECTS = \ + test-imap-bodystructure.$(OBJEXT) +test_imap_bodystructure_OBJECTS = \ + $(am_test_imap_bodystructure_OBJECTS) +am_test_imap_envelope_OBJECTS = test-imap-envelope.$(OBJEXT) +test_imap_envelope_OBJECTS = $(am_test_imap_envelope_OBJECTS) +am_test_imap_match_OBJECTS = test-imap-match.$(OBJEXT) +test_imap_match_OBJECTS = $(am_test_imap_match_OBJECTS) +am_test_imap_parser_OBJECTS = test-imap-parser.$(OBJEXT) +test_imap_parser_OBJECTS = $(am_test_imap_parser_OBJECTS) +am_test_imap_quote_OBJECTS = test-imap-quote.$(OBJEXT) +test_imap_quote_OBJECTS = $(am_test_imap_quote_OBJECTS) +am_test_imap_url_OBJECTS = test-imap-url.$(OBJEXT) +test_imap_url_OBJECTS = $(am_test_imap_url_OBJECTS) +am_test_imap_utf7_OBJECTS = test-imap-utf7.$(OBJEXT) +test_imap_utf7_OBJECTS = $(am_test_imap_utf7_OBJECTS) +am_test_imap_util_OBJECTS = test-imap-util.$(OBJEXT) +test_imap_util_OBJECTS = $(am_test_imap_util_OBJECTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = \ + ./$(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po \ + ./$(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po \ + ./$(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po \ + ./$(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po \ + ./$(DEPDIR)/imap-arg.Plo ./$(DEPDIR)/imap-base-subject.Plo \ + ./$(DEPDIR)/imap-bodystructure.Plo ./$(DEPDIR)/imap-date.Plo \ + ./$(DEPDIR)/imap-envelope.Plo ./$(DEPDIR)/imap-id.Plo \ + ./$(DEPDIR)/imap-keepalive.Plo ./$(DEPDIR)/imap-match.Plo \ + ./$(DEPDIR)/imap-parser.Plo ./$(DEPDIR)/imap-quote.Plo \ + ./$(DEPDIR)/imap-seqset.Plo ./$(DEPDIR)/imap-url.Plo \ + ./$(DEPDIR)/imap-utf7.Plo ./$(DEPDIR)/imap-util.Plo \ + ./$(DEPDIR)/test-imap-bodystructure.Po \ + ./$(DEPDIR)/test-imap-envelope.Po \ + ./$(DEPDIR)/test-imap-match.Po ./$(DEPDIR)/test-imap-parser.Po \ + ./$(DEPDIR)/test-imap-quote.Po ./$(DEPDIR)/test-imap-url.Po \ + ./$(DEPDIR)/test-imap-utf7.Po ./$(DEPDIR)/test-imap-util.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(libimap_la_SOURCES) $(fuzz_imap_bodystructure_SOURCES) \ + $(nodist_EXTRA_fuzz_imap_bodystructure_SOURCES) \ + $(fuzz_imap_utf7_SOURCES) \ + $(nodist_EXTRA_fuzz_imap_utf7_SOURCES) \ + $(test_imap_bodystructure_SOURCES) \ + $(test_imap_envelope_SOURCES) $(test_imap_match_SOURCES) \ + $(test_imap_parser_SOURCES) $(test_imap_quote_SOURCES) \ + $(test_imap_url_SOURCES) $(test_imap_utf7_SOURCES) \ + $(test_imap_util_SOURCES) +DIST_SOURCES = $(libimap_la_SOURCES) \ + $(am__fuzz_imap_bodystructure_SOURCES_DIST) \ + $(am__fuzz_imap_utf7_SOURCES_DIST) \ + $(test_imap_bodystructure_SOURCES) \ + $(test_imap_envelope_SOURCES) $(test_imap_match_SOURCES) \ + $(test_imap_parser_SOURCES) $(test_imap_quote_SOURCES) \ + $(test_imap_url_SOURCES) $(test_imap_utf7_SOURCES) \ + $(test_imap_util_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkginc_libdir)" +HEADERS = $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LTLIBRARIES = libimap.la +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-charset \ + -I$(top_srcdir)/src/lib-mail + +libimap_la_SOURCES = \ + imap-arg.c \ + imap-base-subject.c \ + imap-bodystructure.c \ + imap-date.c \ + imap-envelope.c \ + imap-id.c \ + imap-keepalive.c \ + imap-match.c \ + imap-parser.c \ + imap-quote.c \ + imap-url.c \ + imap-seqset.c \ + imap-utf7.c \ + imap-util.c + +headers = \ + imap-arg.h \ + imap-base-subject.h \ + imap-bodystructure.h \ + imap-date.h \ + imap-envelope.h \ + imap-id.h \ + imap-keepalive.h \ + imap-match.h \ + imap-parser.h \ + imap-resp-code.h \ + imap-quote.h \ + imap-url.h \ + imap-seqset.h \ + imap-utf7.h \ + imap-util.h + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = $(headers) +test_programs = \ + test-imap-bodystructure \ + test-imap-envelope \ + test-imap-match \ + test-imap-parser \ + test-imap-quote \ + test-imap-url \ + test-imap-utf7 \ + test-imap-util + +test_libs = \ + ../lib-test/libtest.la \ + ../lib/liblib.la + +test_deps = $(noinst_LTLIBRARIES) $(test_libs) +test_imap_bodystructure_SOURCES = test-imap-bodystructure.c +test_imap_bodystructure_LDADD = imap-bodystructure.lo imap-envelope.lo imap-quote.lo imap-parser.lo imap-arg.lo ../lib-mail/libmail.la $(test_libs) +test_imap_bodystructure_DEPENDENCIES = $(test_deps) ../lib-mail/libmail.la +test_imap_envelope_SOURCES = test-imap-envelope.c +test_imap_envelope_LDADD = imap-envelope.lo imap-quote.lo imap-parser.lo imap-arg.lo ../lib-mail/libmail.la $(test_libs) +test_imap_envelope_DEPENDENCIES = $(test_deps) ../lib-mail/libmail.la +test_imap_match_SOURCES = test-imap-match.c +test_imap_match_LDADD = imap-match.lo $(test_libs) +test_imap_match_DEPENDENCIES = $(test_deps) +test_imap_parser_SOURCES = test-imap-parser.c +test_imap_parser_LDADD = imap-parser.lo imap-arg.lo $(test_libs) +test_imap_parser_DEPENDENCIES = $(test_deps) +test_imap_quote_SOURCES = test-imap-quote.c +test_imap_quote_LDADD = imap-quote.lo $(test_libs) +test_imap_quote_DEPENDENCIES = $(test_deps) +test_imap_url_SOURCES = test-imap-url.c +test_imap_url_LDADD = imap-url.lo $(test_libs) +test_imap_url_DEPENDENCIES = $(test_deps) +test_imap_utf7_SOURCES = test-imap-utf7.c +test_imap_utf7_LDADD = imap-utf7.lo $(test_libs) +test_imap_utf7_DEPENDENCIES = $(test_deps) +test_imap_util_SOURCES = test-imap-util.c +test_imap_util_LDADD = imap-util.lo imap-arg.lo $(test_libs) +test_imap_util_DEPENDENCIES = $(test_deps) +@USE_FUZZER_TRUE@nodist_EXTRA_fuzz_imap_utf7_SOURCES = force-cxx-linking.cxx +@USE_FUZZER_TRUE@fuzz_imap_utf7_SOURCES = fuzz-imap-utf7.c +@USE_FUZZER_TRUE@fuzz_imap_utf7_CPPFLAGS = $(FUZZER_CPPFLAGS) +@USE_FUZZER_TRUE@fuzz_imap_utf7_LDFLAGS = $(FUZZER_LDFLAGS) +@USE_FUZZER_TRUE@fuzz_imap_utf7_LDADD = libimap.la $(test_libs) +@USE_FUZZER_TRUE@fuzz_imap_utf7_DEPENDENCIES = libimap.la $(test_deps) +@USE_FUZZER_TRUE@nodist_EXTRA_fuzz_imap_bodystructure_SOURCES = force-cxx-linking.cxx +@USE_FUZZER_TRUE@fuzz_imap_bodystructure_SOURCES = fuzz-imap-bodystructure.c +@USE_FUZZER_TRUE@fuzz_imap_bodystructure_CPPFLAGS = $(FUZZER_CPPFLAGS) +@USE_FUZZER_TRUE@fuzz_imap_bodystructure_LDFLAGS = $(FUZZER_LDFLAGS) +@USE_FUZZER_TRUE@fuzz_imap_bodystructure_LDADD = libimap.la $(test_libs) +@USE_FUZZER_TRUE@fuzz_imap_bodystructure_DEPENDENCIES = libimap.la $(test_deps) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .cxx .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-imap/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib-imap/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libimap.la: $(libimap_la_OBJECTS) $(libimap_la_DEPENDENCIES) $(EXTRA_libimap_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libimap_la_OBJECTS) $(libimap_la_LIBADD) $(LIBS) + +fuzz-imap-bodystructure$(EXEEXT): $(fuzz_imap_bodystructure_OBJECTS) $(fuzz_imap_bodystructure_DEPENDENCIES) $(EXTRA_fuzz_imap_bodystructure_DEPENDENCIES) + @rm -f fuzz-imap-bodystructure$(EXEEXT) + $(AM_V_CXXLD)$(fuzz_imap_bodystructure_LINK) $(fuzz_imap_bodystructure_OBJECTS) $(fuzz_imap_bodystructure_LDADD) $(LIBS) + +fuzz-imap-utf7$(EXEEXT): $(fuzz_imap_utf7_OBJECTS) $(fuzz_imap_utf7_DEPENDENCIES) $(EXTRA_fuzz_imap_utf7_DEPENDENCIES) + @rm -f fuzz-imap-utf7$(EXEEXT) + $(AM_V_CXXLD)$(fuzz_imap_utf7_LINK) $(fuzz_imap_utf7_OBJECTS) $(fuzz_imap_utf7_LDADD) $(LIBS) + +test-imap-bodystructure$(EXEEXT): $(test_imap_bodystructure_OBJECTS) $(test_imap_bodystructure_DEPENDENCIES) $(EXTRA_test_imap_bodystructure_DEPENDENCIES) + @rm -f test-imap-bodystructure$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_bodystructure_OBJECTS) $(test_imap_bodystructure_LDADD) $(LIBS) + +test-imap-envelope$(EXEEXT): $(test_imap_envelope_OBJECTS) $(test_imap_envelope_DEPENDENCIES) $(EXTRA_test_imap_envelope_DEPENDENCIES) + @rm -f test-imap-envelope$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_envelope_OBJECTS) $(test_imap_envelope_LDADD) $(LIBS) + +test-imap-match$(EXEEXT): $(test_imap_match_OBJECTS) $(test_imap_match_DEPENDENCIES) $(EXTRA_test_imap_match_DEPENDENCIES) + @rm -f test-imap-match$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_match_OBJECTS) $(test_imap_match_LDADD) $(LIBS) + +test-imap-parser$(EXEEXT): $(test_imap_parser_OBJECTS) $(test_imap_parser_DEPENDENCIES) $(EXTRA_test_imap_parser_DEPENDENCIES) + @rm -f test-imap-parser$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_parser_OBJECTS) $(test_imap_parser_LDADD) $(LIBS) + +test-imap-quote$(EXEEXT): $(test_imap_quote_OBJECTS) $(test_imap_quote_DEPENDENCIES) $(EXTRA_test_imap_quote_DEPENDENCIES) + @rm -f test-imap-quote$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_quote_OBJECTS) $(test_imap_quote_LDADD) $(LIBS) + +test-imap-url$(EXEEXT): $(test_imap_url_OBJECTS) $(test_imap_url_DEPENDENCIES) $(EXTRA_test_imap_url_DEPENDENCIES) + @rm -f test-imap-url$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_url_OBJECTS) $(test_imap_url_LDADD) $(LIBS) + +test-imap-utf7$(EXEEXT): $(test_imap_utf7_OBJECTS) $(test_imap_utf7_DEPENDENCIES) $(EXTRA_test_imap_utf7_DEPENDENCIES) + @rm -f test-imap-utf7$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_utf7_OBJECTS) $(test_imap_utf7_LDADD) $(LIBS) + +test-imap-util$(EXEEXT): $(test_imap_util_OBJECTS) $(test_imap_util_DEPENDENCIES) $(EXTRA_test_imap_util_DEPENDENCIES) + @rm -f test-imap-util$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_util_OBJECTS) $(test_imap_util_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-arg.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-base-subject.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-bodystructure.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-date.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-envelope.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-id.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-keepalive.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-match.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-quote.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-seqset.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-url.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-utf7.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-bodystructure.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-envelope.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-match.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-parser.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-quote.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-url.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-utf7.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-util.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +fuzz_imap_bodystructure-fuzz-imap-bodystructure.o: fuzz-imap-bodystructure.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_imap_bodystructure-fuzz-imap-bodystructure.o -MD -MP -MF $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Tpo -c -o fuzz_imap_bodystructure-fuzz-imap-bodystructure.o `test -f 'fuzz-imap-bodystructure.c' || echo '$(srcdir)/'`fuzz-imap-bodystructure.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Tpo $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-imap-bodystructure.c' object='fuzz_imap_bodystructure-fuzz-imap-bodystructure.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_imap_bodystructure-fuzz-imap-bodystructure.o `test -f 'fuzz-imap-bodystructure.c' || echo '$(srcdir)/'`fuzz-imap-bodystructure.c + +fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj: fuzz-imap-bodystructure.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj -MD -MP -MF $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Tpo -c -o fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj `if test -f 'fuzz-imap-bodystructure.c'; then $(CYGPATH_W) 'fuzz-imap-bodystructure.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-imap-bodystructure.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Tpo $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-imap-bodystructure.c' object='fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj `if test -f 'fuzz-imap-bodystructure.c'; then $(CYGPATH_W) 'fuzz-imap-bodystructure.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-imap-bodystructure.c'; fi` + +fuzz_imap_utf7-fuzz-imap-utf7.o: fuzz-imap-utf7.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_imap_utf7-fuzz-imap-utf7.o -MD -MP -MF $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Tpo -c -o fuzz_imap_utf7-fuzz-imap-utf7.o `test -f 'fuzz-imap-utf7.c' || echo '$(srcdir)/'`fuzz-imap-utf7.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Tpo $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-imap-utf7.c' object='fuzz_imap_utf7-fuzz-imap-utf7.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_imap_utf7-fuzz-imap-utf7.o `test -f 'fuzz-imap-utf7.c' || echo '$(srcdir)/'`fuzz-imap-utf7.c + +fuzz_imap_utf7-fuzz-imap-utf7.obj: fuzz-imap-utf7.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_imap_utf7-fuzz-imap-utf7.obj -MD -MP -MF $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Tpo -c -o fuzz_imap_utf7-fuzz-imap-utf7.obj `if test -f 'fuzz-imap-utf7.c'; then $(CYGPATH_W) 'fuzz-imap-utf7.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-imap-utf7.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Tpo $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-imap-utf7.c' object='fuzz_imap_utf7-fuzz-imap-utf7.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_imap_utf7-fuzz-imap-utf7.obj `if test -f 'fuzz-imap-utf7.c'; then $(CYGPATH_W) 'fuzz-imap-utf7.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-imap-utf7.c'; fi` + +.cxx.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cxx.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cxx.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +fuzz_imap_bodystructure-force-cxx-linking.o: force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_imap_bodystructure-force-cxx-linking.o -MD -MP -MF $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Tpo -c -o fuzz_imap_bodystructure-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Tpo $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_imap_bodystructure-force-cxx-linking.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_imap_bodystructure-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx + +fuzz_imap_bodystructure-force-cxx-linking.obj: force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_imap_bodystructure-force-cxx-linking.obj -MD -MP -MF $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Tpo -c -o fuzz_imap_bodystructure-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Tpo $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_imap_bodystructure-force-cxx-linking.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_imap_bodystructure-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi` + +fuzz_imap_utf7-force-cxx-linking.o: force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_imap_utf7-force-cxx-linking.o -MD -MP -MF $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Tpo -c -o fuzz_imap_utf7-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Tpo $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_imap_utf7-force-cxx-linking.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_imap_utf7-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx + +fuzz_imap_utf7-force-cxx-linking.obj: force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_imap_utf7-force-cxx-linking.obj -MD -MP -MF $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Tpo -c -o fuzz_imap_utf7-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Tpo $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_imap_utf7-force-cxx-linking.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_imap_utf7-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po + -rm -f ./$(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po + -rm -f ./$(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po + -rm -f ./$(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po + -rm -f ./$(DEPDIR)/imap-arg.Plo + -rm -f ./$(DEPDIR)/imap-base-subject.Plo + -rm -f ./$(DEPDIR)/imap-bodystructure.Plo + -rm -f ./$(DEPDIR)/imap-date.Plo + -rm -f ./$(DEPDIR)/imap-envelope.Plo + -rm -f ./$(DEPDIR)/imap-id.Plo + -rm -f ./$(DEPDIR)/imap-keepalive.Plo + -rm -f ./$(DEPDIR)/imap-match.Plo + -rm -f ./$(DEPDIR)/imap-parser.Plo + -rm -f ./$(DEPDIR)/imap-quote.Plo + -rm -f ./$(DEPDIR)/imap-seqset.Plo + -rm -f ./$(DEPDIR)/imap-url.Plo + -rm -f ./$(DEPDIR)/imap-utf7.Plo + -rm -f ./$(DEPDIR)/imap-util.Plo + -rm -f ./$(DEPDIR)/test-imap-bodystructure.Po + -rm -f ./$(DEPDIR)/test-imap-envelope.Po + -rm -f ./$(DEPDIR)/test-imap-match.Po + -rm -f ./$(DEPDIR)/test-imap-parser.Po + -rm -f ./$(DEPDIR)/test-imap-quote.Po + -rm -f ./$(DEPDIR)/test-imap-url.Po + -rm -f ./$(DEPDIR)/test-imap-utf7.Po + -rm -f ./$(DEPDIR)/test-imap-util.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po + -rm -f ./$(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po + -rm -f ./$(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po + -rm -f ./$(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po + -rm -f ./$(DEPDIR)/imap-arg.Plo + -rm -f ./$(DEPDIR)/imap-base-subject.Plo + -rm -f ./$(DEPDIR)/imap-bodystructure.Plo + -rm -f ./$(DEPDIR)/imap-date.Plo + -rm -f ./$(DEPDIR)/imap-envelope.Plo + -rm -f ./$(DEPDIR)/imap-id.Plo + -rm -f ./$(DEPDIR)/imap-keepalive.Plo + -rm -f ./$(DEPDIR)/imap-match.Plo + -rm -f ./$(DEPDIR)/imap-parser.Plo + -rm -f ./$(DEPDIR)/imap-quote.Plo + -rm -f ./$(DEPDIR)/imap-seqset.Plo + -rm -f ./$(DEPDIR)/imap-url.Plo + -rm -f ./$(DEPDIR)/imap-utf7.Plo + -rm -f ./$(DEPDIR)/imap-util.Plo + -rm -f ./$(DEPDIR)/test-imap-bodystructure.Po + -rm -f ./$(DEPDIR)/test-imap-envelope.Po + -rm -f ./$(DEPDIR)/test-imap-match.Po + -rm -f ./$(DEPDIR)/test-imap-parser.Po + -rm -f ./$(DEPDIR)/test-imap-quote.Po + -rm -f ./$(DEPDIR)/test-imap-url.Po + -rm -f ./$(DEPDIR)/test-imap-utf7.Po + -rm -f ./$(DEPDIR)/test-imap-util.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkginc_libHEADERS + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ + check-local clean clean-generic clean-libtool \ + clean-noinstLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-pkginc_libHEADERS install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-pkginc_libHEADERS + +.PRECIOUS: Makefile + + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib-imap/fuzz-imap-bodystructure.c b/src/lib-imap/fuzz-imap-bodystructure.c new file mode 100644 index 0000000..a9d4499 --- /dev/null +++ b/src/lib-imap/fuzz-imap-bodystructure.c @@ -0,0 +1,52 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "fuzzer.h" +#include "imap-bodystructure.c" +#include <ctype.h> + +static const char *str_sanitize_binary(const char *input) +{ + string_t *dest = t_str_new(strlen(input)); + for (;*input != '\0';input++) { + if (i_isprint(*input) == 0) + str_printfa(dest, "<%02x>", (unsigned char)*input); + else + str_append_c(dest, *input); + } + return str_c(dest); +} + +FUZZ_BEGIN_STR(const char *str) +{ + pool_t pool = + pool_alloconly_create(MEMPOOL_GROWING"fuzz bodystructure", 1024); + struct message_part parts; + string_t *dest = str_new(pool, 32); + const char *error ATTR_UNUSED; + i_zero(&parts); + + if (imap_bodystructure_parse(str, pool, &parts, &error) == 0) { + if (imap_bodystructure_write(&parts, dest, TRUE, &error) < 0) + i_panic("Failed to write bodystructure: %s", error); + /* The written bodystructure must be parseable *and* + it must come out exactly the same again */ + if (imap_bodystructure_parse(str_c(dest), pool, &parts, &error) != 0) { + i_panic("Failed to reparse bodystructure '%s'", + str_sanitize_binary(str_c(dest))); + } else { + const char *new_str = t_strdup(str_c(dest)); + str_truncate(dest, 0); + if (imap_bodystructure_write(&parts, dest, TRUE, &error) < 0) + i_panic("Failed to write reparsed bodystructure: %s", error); + if (strcmp(str_c(dest), new_str) != 0) { + i_panic("Parsed bodystructure '%s' does not match '%s'", + str_sanitize_binary(new_str), + str_sanitize_binary(str_c(dest))); + } + } + } + pool_unref(&pool); +} +FUZZ_END diff --git a/src/lib-imap/fuzz-imap-utf7.c b/src/lib-imap/fuzz-imap-utf7.c new file mode 100644 index 0000000..2a561f1 --- /dev/null +++ b/src/lib-imap/fuzz-imap-utf7.c @@ -0,0 +1,15 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "fuzzer.h" +#include "imap-utf7.h" + +FUZZ_BEGIN_STR(const char *str) +{ + string_t *dest = t_str_new(32); + + imap_utf8_to_utf7(str, dest); + imap_utf7_to_utf8(str, dest); +} +FUZZ_END diff --git a/src/lib-imap/imap-arg.c b/src/lib-imap/imap-arg.c new file mode 100644 index 0000000..0b947ce --- /dev/null +++ b/src/lib-imap/imap-arg.c @@ -0,0 +1,137 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "imap-arg.h" + +bool imap_arg_get_atom(const struct imap_arg *arg, const char **str_r) +{ + if (arg->type != IMAP_ARG_ATOM && arg->type != IMAP_ARG_NIL) + return FALSE; + + *str_r = arg->_data.str; + return TRUE; +} + +bool imap_arg_get_quoted(const struct imap_arg *arg, const char **str_r) +{ + if (arg->type != IMAP_ARG_STRING) + return FALSE; + + *str_r = arg->_data.str; + return TRUE; +} + +bool imap_arg_get_string(const struct imap_arg *arg, const char **str_r) +{ + if (arg->type != IMAP_ARG_STRING && arg->type != IMAP_ARG_LITERAL) + return FALSE; + + *str_r = arg->_data.str; + return TRUE; +} + +bool imap_arg_get_astring(const struct imap_arg *arg, const char **str_r) +{ + /* RFC 3501 4.5. specifies that NIL is the same as "NIL" when + reading astring. */ + if (!IMAP_ARG_IS_ASTRING(arg) && arg->type != IMAP_ARG_NIL) + return FALSE; + + *str_r = arg->_data.str; + return TRUE; +} + +bool imap_arg_get_nstring(const struct imap_arg *arg, const char **str_r) +{ + if (arg->type == IMAP_ARG_NIL) { + *str_r = NULL; + return TRUE; + } + return imap_arg_get_string(arg, str_r); +} + +bool imap_arg_get_literal_size(const struct imap_arg *arg, uoff_t *size_r) +{ + if (arg->type != IMAP_ARG_LITERAL_SIZE && + arg->type != IMAP_ARG_LITERAL_SIZE_NONSYNC) + return FALSE; + + *size_r = arg->_data.literal_size; + return TRUE; +} + +bool imap_arg_get_list(const struct imap_arg *arg, + const struct imap_arg **list_r) +{ + unsigned int count; + + return imap_arg_get_list_full(arg, list_r, &count); +} + +bool imap_arg_get_list_full(const struct imap_arg *arg, + const struct imap_arg **list_r, + unsigned int *list_count_r) +{ + unsigned int count; + + if (arg->type != IMAP_ARG_LIST) + return FALSE; + + *list_r = array_get(&arg->_data.list, &count); + + if (count > 0 && (*list_r)[count-1].type == IMAP_ARG_EOL) + count--; + else { + /* imap-parser stopped early (e.g. due to reading literal size). + The IMAP_ARG_EOL was added to the list only temporarily. */ + i_assert((*list_r)[count].type == IMAP_ARG_EOL); + } + *list_count_r = count; + return TRUE; +} + +const char *imap_arg_as_astring(const struct imap_arg *arg) +{ + const char *str; + + if (!imap_arg_get_astring(arg, &str)) + i_unreached(); + return str; +} + +const char *imap_arg_as_nstring(const struct imap_arg *arg) +{ + const char *str; + + if (!imap_arg_get_nstring(arg, &str)) + i_unreached(); + return str; +} + +uoff_t imap_arg_as_literal_size(const struct imap_arg *arg) +{ + uoff_t size; + + if (!imap_arg_get_literal_size(arg, &size)) + i_unreached(); + return size; +} + +const struct imap_arg * +imap_arg_as_list(const struct imap_arg *arg) +{ + const struct imap_arg *ret; + + if (!imap_arg_get_list(arg, &ret)) + i_unreached(); + return ret; +} + +bool imap_arg_atom_equals(const struct imap_arg *arg, const char *str) +{ + const char *value; + + if (!imap_arg_get_atom(arg, &value)) + return FALSE; + return strcasecmp(value, str) == 0; +} diff --git a/src/lib-imap/imap-arg.h b/src/lib-imap/imap-arg.h new file mode 100644 index 0000000..d55ee15 --- /dev/null +++ b/src/lib-imap/imap-arg.h @@ -0,0 +1,108 @@ +#ifndef IMAP_ARG_H +#define IMAP_ARG_H + +#include "array.h" + +/* ABNF: + + CHAR = %x01-7F + CTL = %x00-1F / %x7F + SP = %x20 + DQUOTE = %x22 */ + +/* ASTRING-CHAR = ATOM-CHAR / resp-specials */ +#define IS_ASTRING_CHAR(c) (IS_ATOM_CHAR(c) || IS_RESP_SPECIAL(c)) +/* ATOM-CHAR = <any CHAR except atom-specials> */ +#define IS_ATOM_CHAR(c) (!IS_ATOM_SPECIAL(c)) +/* atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / + quoted-specials / resp-specials + Since atoms are only 7bit, we'll also optimize a bit by assuming 8bit chars + are also atom-specials. */ +#define IS_ATOM_SPECIAL(c) \ + ((unsigned char)(c) <= 0x20 || (unsigned char)(c) >= 0x7f || \ + (c) == '(' || (c) == ')' || (c) == '{' || IS_LIST_WILDCARD(c) || \ + IS_QUOTED_SPECIAL(c) || IS_RESP_SPECIAL(c)) + +/* list-wildcards = "%" / "*" */ +#define IS_LIST_WILDCARD(c) ((c) == '%' || (c) == '*') +/* quoted-specials = DQUOTE / "\" */ +#define IS_QUOTED_SPECIAL(c) ((c) == '\"' || (c) == '\\') +/* resp-specials = "]" */ +#define IS_RESP_SPECIAL(c) ((c) == ']') + +enum imap_arg_type { + IMAP_ARG_NIL = 0, + IMAP_ARG_ATOM, + IMAP_ARG_STRING, + IMAP_ARG_LIST, + + /* literals are returned as IMAP_ARG_STRING by default */ + IMAP_ARG_LITERAL, + IMAP_ARG_LITERAL_SIZE, + IMAP_ARG_LITERAL_SIZE_NONSYNC, + + IMAP_ARG_EOL /* end of argument list */ +}; + +ARRAY_DEFINE_TYPE(imap_arg_list, struct imap_arg); +struct imap_arg { + enum imap_arg_type type; + struct imap_arg *parent; /* always of type IMAP_ARG_LIST */ + + /* Set when _data.str is set */ + size_t str_len; + + union { + const char *str; + uoff_t literal_size; + ARRAY_TYPE(imap_arg_list) list; + } _data; + bool literal8:1; /* BINARY literal8 used */ +}; + +/* RFC 3501's astring type. Note that this doesn't return TRUE for + IMAP_ARG_NIL, although it should be treated the same as "NIL" string when + reading an astring. */ +#define IMAP_ARG_TYPE_IS_ASTRING(type) \ + ((type) == IMAP_ARG_ATOM || \ + (type) == IMAP_ARG_STRING || \ + (type) == IMAP_ARG_LITERAL) +#define IMAP_ARG_IS_ASTRING(arg) \ + IMAP_ARG_TYPE_IS_ASTRING((arg)->type) +#define IMAP_ARG_IS_NSTRING(arg) \ + (IMAP_ARG_IS_ASTRING(arg) || (arg)->type == IMAP_ARG_NIL) +#define IMAP_ARG_IS_EOL(arg) \ + ((arg)->type == IMAP_ARG_EOL) + +bool imap_arg_get_atom(const struct imap_arg *arg, const char **str_r) + ATTR_WARN_UNUSED_RESULT; +bool imap_arg_get_quoted(const struct imap_arg *arg, const char **str_r) + ATTR_WARN_UNUSED_RESULT; +bool imap_arg_get_string(const struct imap_arg *arg, const char **str_r) + ATTR_WARN_UNUSED_RESULT; +bool imap_arg_get_astring(const struct imap_arg *arg, const char **str_r) + ATTR_WARN_UNUSED_RESULT; +/* str is set to NULL for NIL. */ +bool imap_arg_get_nstring(const struct imap_arg *arg, const char **str_r) + ATTR_WARN_UNUSED_RESULT; + +bool imap_arg_get_literal_size(const struct imap_arg *arg, uoff_t *size_r) + ATTR_WARN_UNUSED_RESULT; + +bool imap_arg_get_list(const struct imap_arg *arg, + const struct imap_arg **list_r) + ATTR_WARN_UNUSED_RESULT; +bool imap_arg_get_list_full(const struct imap_arg *arg, + const struct imap_arg **list_r, + unsigned int *list_count_r) ATTR_WARN_UNUSED_RESULT; + +/* Similar to above, but assumes that arg is already of correct type. */ +const char *imap_arg_as_astring(const struct imap_arg *arg); +const char *imap_arg_as_nstring(const struct imap_arg *arg); +uoff_t imap_arg_as_literal_size(const struct imap_arg *arg); +const struct imap_arg *imap_arg_as_list(const struct imap_arg *arg); + +/* Returns TRUE if arg is atom and case-insensitively matches str */ +bool imap_arg_atom_equals(const struct imap_arg *arg, const char *str); + +#endif diff --git a/src/lib-imap/imap-base-subject.c b/src/lib-imap/imap-base-subject.c new file mode 100644 index 0000000..02469d3 --- /dev/null +++ b/src/lib-imap/imap-base-subject.c @@ -0,0 +1,248 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +/* Implemented against draft-ietf-imapext-sort-10 and + draft-ietf-imapext-thread-12 */ + +#include "lib.h" +#include "buffer.h" +#include "charset-utf8.h" +#include "message-header-decode.h" +#include "imap-base-subject.h" + +static void pack_whitespace(buffer_t *buf) +{ + char *data, *dest; + bool last_lwsp; + + data = buffer_get_modifiable_data(buf, NULL); + + /* check if we need to do anything */ + while (*data != '\0') { + if (*data == '\t' || *data == '\n' || *data == '\r' || + (*data == ' ' && (data[1] == ' ' || data[1] == '\t'))) + break; + data++; + } + + if (*data == '\0') + return; + + /* @UNSAFE: convert/pack the whitespace */ + dest = data; last_lwsp = FALSE; + while (*data != '\0') { + if (*data == '\t' || *data == ' ' || + *data == '\r' || *data == '\n') { + if (!last_lwsp) { + *dest++ = ' '; + last_lwsp = TRUE; + } + } else { + *dest++ = *data; + last_lwsp = FALSE; + } + data++; + } + *dest = '\0'; + + data = buffer_get_modifiable_data(buf, NULL); + buffer_set_used_size(buf, (size_t) (dest - data)+1); +} + +static void remove_subj_trailers(buffer_t *buf, size_t start_pos, + bool *is_reply_or_forward_r) +{ + const char *data; + size_t orig_size, size; + + /* subj-trailer = "(fwd)" / WSP */ + data = buffer_get_data(buf, &orig_size); + + if (orig_size < 1) /* size includes trailing \0 */ + return; + + for (size = orig_size-1; size > start_pos; ) { + if (data[size-1] == ' ') + size--; + else if (size >= 5 && + memcmp(data + size - 5, "(FWD)", 5) == 0) { + *is_reply_or_forward_r = TRUE; + size -= 5; + } else { + break; + } + } + + if (size != orig_size-1) { + buffer_set_used_size(buf, size); + buffer_append_c(buf, '\0'); + } +} + +static bool remove_blob(const char **datap) +{ + const char *data = *datap; + + if (*data != '[') + return FALSE; + + data++; + while (*data != '\0' && *data != '[' && *data != ']') + data++; + + if (*data != ']') + return FALSE; + + data++; + if (*data == ' ') + data++; + + *datap = data; + return TRUE; +} + +static bool remove_subj_leader(buffer_t *buf, size_t *start_pos, + bool *is_reply_or_forward_r) +{ + const char *data, *orig_data; + bool ret = FALSE; + + /* subj-leader = (*subj-blob subj-refwd) / WSP + + subj-blob = "[" *BLOBCHAR "]" *WSP + subj-refwd = ("re" / ("fw" ["d"])) *WSP [subj-blob] ":" + + BLOBCHAR = %x01-5a / %x5c / %x5e-7f + ; any CHAR except '[' and ']' */ + orig_data = buf->data; + orig_data += *start_pos; + data = orig_data; + + if (*data == ' ') { + /* independent from checks below - always removed */ + data++; orig_data++; + *start_pos += 1; + ret = TRUE; + } + + while (*data == '[') { + if (!remove_blob(&data)) + return ret; + } + + if (str_begins(data, "RE")) + data += 2; + else if (str_begins(data, "FWD")) + data += 3; + else if (str_begins(data, "FW")) + data += 2; + else + return ret; + + if (*data == ' ') + data++; + + if (*data == '[' && !remove_blob(&data)) + return ret; + + if (*data != ':') + return ret; + + data++; + *start_pos += (size_t)(data - orig_data); + *is_reply_or_forward_r = TRUE; + return TRUE; +} + +static bool remove_blob_when_nonempty(buffer_t *buf, size_t *start_pos) +{ + const char *data, *orig_data; + + orig_data = buf->data; + orig_data += *start_pos; + data = orig_data; + if (*data == '[' && remove_blob(&data) && *data != '\0') { + *start_pos += (size_t)(data - orig_data); + return TRUE; + } + + return FALSE; +} + +static bool remove_subj_fwd_hdr(buffer_t *buf, size_t *start_pos, + bool *is_reply_or_forward_r) +{ + const char *data = buf->data; + size_t size = buf->used; + + /* subj-fwd = subj-fwd-hdr subject subj-fwd-trl + subj-fwd-hdr = "[fwd:" + subj-fwd-trl = "]" */ + + if (!str_begins(data + *start_pos, "[FWD:")) + return FALSE; + + if (data[size-2] != ']') + return FALSE; + + *is_reply_or_forward_r = TRUE; + + buffer_set_used_size(buf, size-2); + buffer_append_c(buf, '\0'); + + *start_pos += 5; + return TRUE; +} + +const char *imap_get_base_subject_cased(pool_t pool, const char *subject, + bool *is_reply_or_forward_r) +{ + buffer_t *buf; + size_t start_pos, subject_len; + bool found; + + *is_reply_or_forward_r = FALSE; + + subject_len = strlen(subject); + buf = buffer_create_dynamic(pool, subject_len); + + /* (1) Convert any RFC 2047 encoded-words in the subject to + UTF-8. Convert all tabs and continuations to space. + Convert all multiple spaces to a single space. */ + message_header_decode_utf8((const unsigned char *)subject, subject_len, + buf, uni_utf8_to_decomposed_titlecase); + buffer_append_c(buf, '\0'); + + pack_whitespace(buf); + + start_pos = 0; + do { + /* (2) Remove all trailing text of the subject that matches + the subj-trailer ABNF, repeat until no more matches are + possible. */ + remove_subj_trailers(buf, start_pos, is_reply_or_forward_r); + + do { + /* (3) Remove all prefix text of the subject that + matches the subj-leader ABNF. */ + found = remove_subj_leader(buf, &start_pos, + is_reply_or_forward_r); + + /* (4) If there is prefix text of the subject that + matches the subj-blob ABNF, and removing that prefix + leaves a non-empty subj-base, then remove the prefix + text. */ + found = remove_blob_when_nonempty(buf, &start_pos) || + found; + + /* (5) Repeat (3) and (4) until no matches remain. */ + } while (found); + + /* (6) If the resulting text begins with the subj-fwd-hdr ABNF + and ends with the subj-fwd-trl ABNF, remove the + subj-fwd-hdr and subj-fwd-trl and repeat from step (2). */ + } while (remove_subj_fwd_hdr(buf, &start_pos, is_reply_or_forward_r)); + + /* (7) The resulting text is the "base subject" used in the + SORT. */ + return (const char *)buf->data + start_pos; +} diff --git a/src/lib-imap/imap-base-subject.h b/src/lib-imap/imap-base-subject.h new file mode 100644 index 0000000..f086284 --- /dev/null +++ b/src/lib-imap/imap-base-subject.h @@ -0,0 +1,13 @@ +#ifndef IMAP_BASE_SUBJECT_H +#define IMAP_BASE_SUBJECT_H + +/* Returns the base subject of the given string, according to + draft-ietf-imapext-sort-10. String is returned so that it's suitable for + strcmp() comparing with another base subject. + + is_reply_or_forward is set to TRUE if message looks like reply or forward, + according to draft-ietf-imapext-thread-12 rules. */ +const char *imap_get_base_subject_cased(pool_t pool, const char *subject, + bool *is_reply_or_forward_r); + +#endif diff --git a/src/lib-imap/imap-bodystructure.c b/src/lib-imap/imap-bodystructure.c new file mode 100644 index 0000000..d4f4220 --- /dev/null +++ b/src/lib-imap/imap-bodystructure.c @@ -0,0 +1,972 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "istream.h" +#include "str.h" +#include "message-part-data.h" +#include "message-parser.h" +#include "rfc822-parser.h" +#include "rfc2231-parser.h" +#include "imap-parser.h" +#include "imap-quote.h" +#include "imap-envelope.h" +#include "imap-bodystructure.h" + +#define EMPTY_BODY "(\"text\" \"plain\" " \ + "(\"charset\" \""MESSAGE_PART_DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0)" +#define EMPTY_BODYSTRUCTURE "(\"text\" \"plain\" " \ + "(\"charset\" \""MESSAGE_PART_DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0 " \ + "NIL NIL NIL NIL)" + +/* + * IMAP BODY/BODYSTRUCTURE write + */ + +static void +params_write(const struct message_part_param *params, + unsigned int params_count, string_t *str, + bool default_charset) +{ + unsigned int i; + bool seen_charset; + + if (!default_charset && params_count == 0) { + str_append(str, "NIL"); + return; + } + str_append_c(str, '('); + + seen_charset = FALSE; + for (i = 0; i < params_count; i++) { + i_assert(params[i].name != NULL); + i_assert(params[i].value != NULL); + + if (i > 0) + str_append_c(str, ' '); + if (default_charset && + strcasecmp(params[i].name, "charset") == 0) + seen_charset = TRUE; + imap_append_string(str, params[i].name); + str_append_c(str, ' '); + imap_append_string(str, params[i].value); + } + if (default_charset && !seen_charset) { + if (i > 0) + str_append_c(str, ' '); + str_append(str, "\"charset\" " + "\""MESSAGE_PART_DEFAULT_CHARSET"\""); + } + str_append_c(str, ')'); +} + +static int +part_write_bodystructure_siblings(const struct message_part *part, + string_t *dest, bool extended, + const char **error_r) +{ + for (; part != NULL; part = part->next) { + str_append_c(dest, '('); + if (imap_bodystructure_write(part, dest, extended, error_r) < 0) + return -1; + str_append_c(dest, ')'); + } + return 0; +} + +static void +part_write_bodystructure_common(const struct message_part_data *data, + string_t *str) +{ + str_append_c(str, ' '); + if (data->content_disposition == NULL) + str_append(str, "NIL"); + else { + str_append_c(str, '('); + imap_append_string(str, data->content_disposition); + + str_append_c(str, ' '); + params_write(data->content_disposition_params, + data->content_disposition_params_count, str, FALSE); + + str_append_c(str, ')'); + } + + str_append_c(str, ' '); + if (data->content_language == NULL) + str_append(str, "NIL"); + else { + const char *const *lang = data->content_language; + + i_assert(*lang != NULL); + str_append_c(str, '('); + imap_append_string(str, *lang); + lang++; + while (*lang != NULL) { + str_append_c(str, ' '); + imap_append_string(str, *lang); + lang++; + } + str_append_c(str, ')'); + } + + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->content_location); +} + +static int part_write_body_multipart(const struct message_part *part, + string_t *str, bool extended, + const char **error_r) +{ + const struct message_part_data *data = part->data; + + i_assert(part->data != NULL); + + if (part->children != NULL) { + if (part_write_bodystructure_siblings(part->children, str, + extended, error_r) < 0) + return -1; + } else { + /* no parts in multipart message, + that's not allowed. write a single + 0-length text/plain structure */ + if (!extended) + str_append(str, EMPTY_BODY); + else + str_append(str, EMPTY_BODYSTRUCTURE); + } + + str_append_c(str, ' '); + imap_append_string(str, data->content_subtype); + + if (!extended) + return 0; + + /* BODYSTRUCTURE data */ + + str_append_c(str, ' '); + params_write(data->content_type_params, + data->content_type_params_count, str, FALSE); + + part_write_bodystructure_common(data, str); + return 0; +} + +static bool part_is_truncated(const struct message_part *part) +{ + const struct message_part_data *data = part->data; + + i_assert((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0); + i_assert((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0); + + if (data->content_type != NULL) { + if (strcasecmp(data->content_type, "message") == 0 && + strcasecmp(data->content_subtype, "rfc822") == 0) { + /* It's message/rfc822, but without + MESSAGE_PART_FLAG_MESSAGE_RFC822. */ + return TRUE; + } + if (strcasecmp(data->content_type, "multipart") == 0) { + /* It's multipart/, but without + MESSAGE_PART_FLAG_MULTIPART. */ + return TRUE; + } + } else { + /* No Content-Type */ + if (part->parent != NULL && + (part->parent->flags & MESSAGE_PART_FLAG_MULTIPART_DIGEST) != 0) { + /* Parent is MESSAGE_PART_FLAG_MULTIPART_DIGEST + (so this should have been message/rfc822). */ + return TRUE; + } + } + return FALSE; +} + +static int part_write_body(const struct message_part *part, + string_t *str, bool extended, const char **error_r) +{ + const struct message_part_data *data = part->data; + bool text; + + i_assert(part->data != NULL); + + if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) { + str_append(str, "\"message\" \"rfc822\""); + text = FALSE; + } else if (part_is_truncated(part)) { + /* Maximum MIME part count was reached while parsing the mail. + Write this part out as application/octet-stream instead. + We're not using text/plain, because it would require + message-parser to use MESSAGE_PART_FLAG_TEXT for this part + to avoid losing line count in message_part serialization. */ + str_append(str, "\"application\" \"octet-stream\""); + text = FALSE; + } else { + /* "content type" "subtype" */ + if (data->content_type == NULL) { + text = TRUE; + str_append(str, "\"text\" \"plain\""); + } else { + text = (strcasecmp(data->content_type, "text") == 0); + imap_append_string(str, data->content_type); + str_append_c(str, ' '); + imap_append_string(str, data->content_subtype); + } + bool part_is_text = (part->flags & MESSAGE_PART_FLAG_TEXT) != 0; + if (text != part_is_text) { + *error_r = "text flag mismatch"; + return -1; + } + } + + /* ("content type param key" "value" ...) */ + str_append_c(str, ' '); + params_write(data->content_type_params, + data->content_type_params_count, str, text); + + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->content_id); + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->content_description); + str_append_c(str, ' '); + if (data->content_transfer_encoding != NULL) + imap_append_string(str, data->content_transfer_encoding); + else + str_append(str, "\"7bit\""); + str_printfa(str, " %"PRIuUOFF_T, part->body_size.virtual_size); + + if (text) { + /* text/.. contains line count */ + str_printfa(str, " %u", part->body_size.lines); + } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) { + /* message/rfc822 contains envelope + body + line count */ + const struct message_part_data *child_data; + + i_assert(part->children != NULL); + i_assert(part->children->next == NULL); + + child_data = part->children->data; + + str_append(str, " ("); + imap_envelope_write(child_data->envelope, str); + str_append(str, ") "); + + if (part_write_bodystructure_siblings(part->children, str, + extended, error_r) < 0) + return -1; + str_printfa(str, " %u", part->body_size.lines); + } + + if (!extended) + return 0; + + /* BODYSTRUCTURE data */ + + /* "md5" ("content disposition" ("disposition" "params")) + ("body" "language" "params") "location" */ + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->content_md5); + part_write_bodystructure_common(data, str); + return 0; +} + +int imap_bodystructure_write(const struct message_part *part, + string_t *dest, bool extended, + const char **error_r) +{ + if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) + return part_write_body_multipart(part, dest, extended, error_r); + else + return part_write_body(part, dest, extended, error_r); +} + +/* + * IMAP BODYSTRUCTURE parsing + */ + +static int +imap_bodystructure_strlist_parse(const struct imap_arg *arg, + pool_t pool, const char *const **list_r) +{ + const char *item, **list; + const struct imap_arg *list_args; + unsigned int list_count, i; + + if (arg->type == IMAP_ARG_NIL) { + *list_r = NULL; + return 0; + } + if (imap_arg_get_nstring(arg, &item)) { + list = p_new(pool, const char *, 2); + list[0] = p_strdup(pool, item); + } else { + if (!imap_arg_get_list_full(arg, &list_args, &list_count)) + return -1; + if (list_count == 0) + return -1; + + list = p_new(pool, const char *, list_count+1); + for (i = 0; i < list_count; i++) { + if (!imap_arg_get_string(&list_args[i], &item)) + return -1; + list[i] = p_strdup(pool, item); + } + } + *list_r = list; + return 0; +} + +static int +imap_bodystructure_params_parse(const struct imap_arg *arg, + pool_t pool, const struct message_part_param **params_r, + unsigned int *count_r) +{ + struct message_part_param *params; + const struct imap_arg *list_args; + unsigned int list_count, params_count, i; + + if (arg->type == IMAP_ARG_NIL) { + *params_r = NULL; + return 0; + } + if (!imap_arg_get_list_full(arg, &list_args, &list_count)) + return -1; + if ((list_count % 2) != 0) + return -1; + if (list_count == 0) + return -1; + + params_count = list_count/2; + params = p_new(pool, struct message_part_param, params_count+1); + for (i = 0; i < params_count; i++) { + const char *name, *value; + + if (!imap_arg_get_string(&list_args[i*2+0], &name)) + return -1; + if (!imap_arg_get_string(&list_args[i*2+1], &value)) + return -1; + params[i].name = p_strdup(pool, name); + params[i].value = p_strdup(pool, value); + } + *params_r = params; + *count_r = params_count; + return 0; +} + +static int +imap_bodystructure_parse_args_common(struct message_part *part, + pool_t pool, const struct imap_arg *args, + const char **error_r) +{ + struct message_part_data *data = part->data; + const struct imap_arg *list_args; + + if (args->type == IMAP_ARG_EOL) + return 0; + if (args->type == IMAP_ARG_NIL) + args++; + else if (!imap_arg_get_list(args, &list_args)) { + *error_r = "Invalid content-disposition list"; + return -1; + } else { + if (!imap_arg_get_string + (list_args++, &data->content_disposition)) { + *error_r = "Invalid content-disposition"; + return -1; + } + data->content_disposition = p_strdup(pool, data->content_disposition); + if (imap_bodystructure_params_parse(list_args, pool, + &data->content_disposition_params, + &data->content_disposition_params_count) < 0) { + *error_r = "Invalid content-disposition params"; + return -1; + } + args++; + } + if (args->type == IMAP_ARG_EOL) + return 0; + if (imap_bodystructure_strlist_parse + (args++, pool, &data->content_language) < 0) { + *error_r = "Invalid content-language"; + return -1; + } + if (args->type == IMAP_ARG_EOL) + return 0; + if (!imap_arg_get_nstring + (args++, &data->content_location)) { + *error_r = "Invalid content-location"; + return -1; + } + data->content_location = p_strdup(pool, data->content_location); + return 0; +} + +int +imap_bodystructure_parse_args(const struct imap_arg *args, pool_t pool, + struct message_part **_part, + const char **error_r) +{ + struct message_part *part = *_part, *child_part;; + struct message_part **child_part_p; + struct message_part_data *data; + const struct imap_arg *list_args; + const char *value, *content_type, *subtype, *error; + bool multipart, text, message_rfc822, parsing_tree, has_lines; + unsigned int lines; + uoff_t vsize; + + if (part != NULL) { + /* parsing with pre-existing message_part tree */ + parsing_tree = FALSE; + } else { + /* parsing message_part tree from BODYSTRUCTURE as well */ + part = *_part = p_new(pool, struct message_part, 1); + parsing_tree = TRUE; + } + part->data = data = p_new(pool, struct message_part_data, 1); + + multipart = FALSE; + if (!parsing_tree) { + if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 && + part->children == NULL) { + struct message_part_data dummy_part_data = { + .content_type = "text", + .content_subtype = "plain", + .content_transfer_encoding = "7bit" + }; + struct message_part dummy_part = { + .parent = part, + .data = &dummy_part_data, + .flags = MESSAGE_PART_FLAG_TEXT + }; + struct message_part *dummy_partp = &dummy_part; + + /* no parts in multipart message, + that's not allowed. expect a single + 0-length text/plain structure */ + if (args->type != IMAP_ARG_LIST || + (args+1)->type == IMAP_ARG_LIST) { + *error_r = "message_part hierarchy " + "doesn't match BODYSTRUCTURE"; + return -1; + } + + list_args = imap_arg_as_list(args); + if (imap_bodystructure_parse_args(list_args, pool, + &dummy_partp, error_r) < 0) + return -1; + child_part = NULL; + + multipart = TRUE; + args++; + + } else { + child_part = part->children; + while (args->type == IMAP_ARG_LIST) { + if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 || + child_part == NULL) { + *error_r = "message_part hierarchy " + "doesn't match BODYSTRUCTURE"; + return -1; + } + + list_args = imap_arg_as_list(args); + if (imap_bodystructure_parse_args(list_args, pool, + &child_part, error_r) < 0) + return -1; + child_part = child_part->next; + + multipart = TRUE; + args++; + } + } + if (multipart) { + if (child_part != NULL) { + *error_r = "message_part hierarchy " + "doesn't match BODYSTRUCTURE"; + return -1; + } + } else if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) { + *error_r = "message_part multipart flag " + "doesn't match BODYSTRUCTURE"; + return -1; + } + } else { + child_part_p = &part->children; + while (args->type == IMAP_ARG_LIST) { + list_args = imap_arg_as_list(args); + if (imap_bodystructure_parse_args(list_args, pool, + child_part_p, error_r) < 0) + return -1; + (*child_part_p)->parent = part; + child_part_p = &(*child_part_p)->next; + + multipart = TRUE; + args++; + } + if (multipart) { + part->flags |= MESSAGE_PART_FLAG_MULTIPART; + } + } + + if (multipart) { + data->content_type = "multipart"; + if (!imap_arg_get_string(args++, &data->content_subtype)) { + *error_r = "Invalid multipart content-type"; + return -1; + } + data->content_subtype = p_strdup(pool, data->content_subtype); + if (args->type == IMAP_ARG_EOL) + return 0; + if (imap_bodystructure_params_parse(args++, pool, + &data->content_type_params, + &data->content_type_params_count) < 0) { + *error_r = "Invalid content params"; + return -1; + } + return imap_bodystructure_parse_args_common + (part, pool, args, error_r); + } + + /* "content type" "subtype" */ + if (!imap_arg_get_string(&args[0], &content_type) || + !imap_arg_get_string(&args[1], &subtype)) { + *error_r = "Invalid content-type"; + return -1; + } + data->content_type = p_strdup(pool, content_type); + data->content_subtype = p_strdup(pool, subtype); + args += 2; + + text = strcasecmp(content_type, "text") == 0; + message_rfc822 = strcasecmp(content_type, "message") == 0 && + strcasecmp(subtype, "rfc822") == 0; + + if (!parsing_tree) { +#if 0 + /* Disabled for now. Earlier Dovecot versions handled broken + Content-Type headers by writing them as "text" "plain" to + BODYSTRUCTURE reply, but the message_part didn't have + MESSAGE_PART_FLAG_TEXT. */ + if (text != ((part->flags & MESSAGE_PART_FLAG_TEXT) != 0)) { + *error_r = "message_part text flag " + "doesn't match BODYSTRUCTURE"; + return -1; + } +#endif + if (message_rfc822 != + ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0)) { + *error_r = "message_part message/rfc822 flag " + "doesn't match BODYSTRUCTURE"; + return -1; + } + } else { + if (text) + part->flags |= MESSAGE_PART_FLAG_TEXT; + if (message_rfc822) + part->flags |= MESSAGE_PART_FLAG_MESSAGE_RFC822; + } + + /* ("content type param key" "value" ...) | NIL */ + if (imap_bodystructure_params_parse(args++, pool, + &data->content_type_params, + &data->content_type_params_count) < 0) { + *error_r = "Invalid content params"; + return -1; + } + + /* "content id" "content description" "transfer encoding" size */ + if (!imap_arg_get_nstring(args++, &data->content_id)) { + *error_r = "Invalid content-id"; + return -1; + } + if (!imap_arg_get_nstring(args++, &data->content_description)) { + *error_r = "Invalid content-description"; + return -1; + } + if (!imap_arg_get_string(args++, &data->content_transfer_encoding)) { + *error_r = "Invalid content-transfer-encoding"; + return -1; + } + data->content_id = p_strdup(pool, data->content_id); + data->content_description = p_strdup(pool, data->content_description); + data->content_transfer_encoding = + p_strdup(pool, data->content_transfer_encoding); + if (!imap_arg_get_atom(args++, &value) || + str_to_uoff(value, &vsize) < 0) { + *error_r = "Invalid size field"; + return -1; + } + if (!parsing_tree) { + if (vsize != part->body_size.virtual_size) { + *error_r = "message_part virtual_size doesn't match " + "size in BODYSTRUCTURE"; + return -1; + } + } else { + part->body_size.virtual_size = vsize; + } + + if (text) { + /* text/xxx - text lines */ + if (!imap_arg_get_atom(args++, &value) || + str_to_uint(value, &lines) < 0) { + *error_r = "Invalid lines field"; + return -1; + } + i_assert(part->children == NULL); + has_lines = TRUE; + } else if (message_rfc822) { + /* message/rfc822 - envelope + bodystructure + text lines */ + + if (!parsing_tree) { + i_assert(part->children != NULL && + part->children->next == NULL); + } + + if (!imap_arg_get_list(&args[1], &list_args)) { + *error_r = "Child bodystructure list expected"; + return -1; + } + if (imap_bodystructure_parse_args + (list_args, pool, &part->children, error_r) < 0) + return -1; + if (parsing_tree) { + i_assert(part->children != NULL && + part->children->next == NULL); + part->children->parent = part; + } + + if (!imap_arg_get_list(&args[0], &list_args)) { + *error_r = "Envelope list expected"; + return -1; + } + if (!imap_envelope_parse_args(list_args, pool, + &part->children->data->envelope, &error)) { + *error_r = t_strdup_printf + ("Invalid envelope list: %s", error); + return -1; + } + args += 2; + if (!imap_arg_get_atom(args++, &value) || + str_to_uint(value, &lines) < 0) { + *error_r = "Invalid lines field"; + return -1; + } + has_lines = TRUE; + } else { + i_assert(part->children == NULL); + lines = 0; + has_lines = FALSE; + } + if (!parsing_tree) { + if (has_lines && lines != part->body_size.lines) { + *error_r = "message_part lines " + "doesn't match lines in BODYSTRUCTURE"; + return -1; + } + } else { + part->body_size.lines = lines; + } + if (args->type == IMAP_ARG_EOL) + return 0; + if (!imap_arg_get_nstring(args++, &data->content_md5)) { + *error_r = "Invalid content-md5"; + return -1; + } + data->content_md5 = p_strdup(pool, data->content_md5); + return imap_bodystructure_parse_args_common + (part, pool, args, error_r); +} + + +static void imap_bodystructure_reset_data(struct message_part *parts) +{ + for (; parts != NULL; parts = parts->next) { + parts->data = NULL; + imap_bodystructure_reset_data(parts->children); + } +} + +int imap_bodystructure_parse_full(const char *bodystructure, + pool_t pool, struct message_part **parts, + const char **error_r) +{ + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args; + int ret; + + i_assert(*parts == NULL || (*parts)->next == NULL); + + input = i_stream_create_from_data(bodystructure, strlen(bodystructure)); + (void)i_stream_read(input); + + parser = imap_parser_create(input, NULL, SIZE_MAX); + ret = imap_parser_finish_line(parser, 0, + IMAP_PARSE_FLAG_LITERAL_TYPE, &args); + if (ret < 0) { + *error_r = t_strdup_printf("IMAP parser failed: %s", + imap_parser_get_error(parser, NULL)); + } else if (ret == 0) { + *error_r = "Empty bodystructure"; + ret = -1; + } else { + T_BEGIN { + ret = imap_bodystructure_parse_args + (args, pool, parts, error_r); + } T_END_PASS_STR_IF(ret < 0, error_r); + } + + if (ret < 0) { + /* Don't leave partially filled data to message_parts. Some of + the code expects that if the first message_part->data is + filled, they all are. */ + imap_bodystructure_reset_data(*parts); + } + + imap_parser_unref(&parser); + i_stream_destroy(&input); + return ret; +} + +int imap_bodystructure_parse(const char *bodystructure, + pool_t pool, struct message_part *parts, + const char **error_r) +{ + i_assert(parts != NULL); + + return imap_bodystructure_parse_full(bodystructure, + pool, &parts, error_r); +} + +/* + * IMAP BODYSTRUCTURE to BODY conversion + */ + +static bool str_append_nstring(string_t *str, const struct imap_arg *arg) +{ + const char *cstr; + + if (!imap_arg_get_nstring(arg, &cstr)) + return FALSE; + + switch (arg->type) { + case IMAP_ARG_NIL: + str_append(str, "NIL"); + break; + case IMAP_ARG_STRING: + str_append_c(str, '"'); + /* NOTE: we're parsing with no-unescape flag, + so don't double-escape it here */ + str_append(str, cstr); + str_append_c(str, '"'); + break; + case IMAP_ARG_LITERAL: { + str_printfa(str, "{%zu}\r\n", strlen(cstr)); + str_append(str, cstr); + break; + } + default: + i_unreached(); + return FALSE; + } + return TRUE; +} + +static void +imap_write_envelope_list(const struct imap_arg *args, string_t *str, + bool toplevel) +{ + const struct imap_arg *children; + + /* don't do any typechecking, just write it out */ + while (!IMAP_ARG_IS_EOL(args)) { + bool list = FALSE; + + if (!str_append_nstring(str, args)) { + if (!imap_arg_get_list(args, &children)) { + /* everything is either nstring or list */ + i_unreached(); + } + + str_append_c(str, '('); + imap_write_envelope_list(children, str, FALSE); + str_append_c(str, ')'); + + list = TRUE; + } + args++; + + if ((toplevel || !list) && !IMAP_ARG_IS_EOL(args)) + str_append_c(str, ' '); + } +} + +static void +imap_write_envelope(const struct imap_arg *args, string_t *str) +{ + imap_write_envelope_list(args, str, TRUE); +} + +static int imap_parse_bodystructure_args(const struct imap_arg *args, + string_t *str, const char **error_r) +{ + const struct imap_arg *subargs; + const struct imap_arg *list_args; + const char *value, *content_type, *subtype; + bool multipart, text, message_rfc822; + int i; + + multipart = FALSE; + while (args->type == IMAP_ARG_LIST) { + str_append_c(str, '('); + list_args = imap_arg_as_list(args); + if (imap_parse_bodystructure_args(list_args, str, error_r) < 0) + return -1; + str_append_c(str, ')'); + + multipart = TRUE; + args++; + } + + if (multipart) { + /* next is subtype of Content-Type. rest is skipped. */ + str_append_c(str, ' '); + if (!str_append_nstring(str, args)) { + *error_r = "Invalid multipart content-type"; + return -1; + } + return 0; + } + + /* "content type" "subtype" */ + if (!imap_arg_get_string(&args[0], &content_type) || + !imap_arg_get_string(&args[1], &subtype)) { + *error_r = "Invalid content-type"; + return -1; + } + + if (!str_append_nstring(str, &args[0])) + i_unreached(); + str_append_c(str, ' '); + if (!str_append_nstring(str, &args[1])) + i_unreached(); + + text = strcasecmp(content_type, "text") == 0; + message_rfc822 = strcasecmp(content_type, "message") == 0 && + strcasecmp(subtype, "rfc822") == 0; + + args += 2; + + /* ("content type param key" "value" ...) | NIL */ + if (imap_arg_get_list(args, &subargs)) { + str_append(str, " ("); + while (!IMAP_ARG_IS_EOL(subargs)) { + if (!str_append_nstring(str, &subargs[0])) { + *error_r = "Invalid content param key"; + return -1; + } + str_append_c(str, ' '); + if (!str_append_nstring(str, &subargs[1])) { + *error_r = "Invalid content param value"; + return -1; + } + + subargs += 2; + if (IMAP_ARG_IS_EOL(subargs)) + break; + str_append_c(str, ' '); + } + str_append(str, ")"); + } else if (args->type == IMAP_ARG_NIL) { + str_append(str, " NIL"); + } else { + *error_r = "list/NIL expected"; + return -1; + } + args++; + + /* "content id" "content description" "transfer encoding" size */ + for (i = 0; i < 3; i++, args++) { + str_append_c(str, ' '); + + if (!str_append_nstring(str, args)) { + *error_r = "nstring expected"; + return -1; + } + } + if (!imap_arg_get_atom(args, &value)) { + *error_r = "atom expected for size"; + return -1; + } + str_printfa(str, " %s", value); + args++; + + if (text) { + /* text/xxx - text lines */ + if (!imap_arg_get_atom(args, &value)) { + *error_r = "Text lines expected"; + return -1; + } + + str_append_c(str, ' '); + str_append(str, value); + } else if (message_rfc822) { + /* message/rfc822 - envelope + bodystructure + text lines */ + str_append_c(str, ' '); + + if (!imap_arg_get_list(&args[0], &list_args)) { + *error_r = "Envelope list expected"; + return -1; + } + str_append_c(str, '('); + imap_write_envelope(list_args, str); + str_append(str, ") ("); + + if (!imap_arg_get_list(&args[1], &list_args)) { + *error_r = "Child bodystructure list expected"; + return -1; + } + if (imap_parse_bodystructure_args(list_args, str, error_r) < 0) + return -1; + + str_append(str, ") "); + if (!imap_arg_get_atom(&args[2], &value)) { + *error_r = "Text lines expected"; + return -1; + } + str_append(str, value); + } + return 0; +} + +int imap_body_parse_from_bodystructure(const char *bodystructure, + string_t *dest, const char **error_r) +{ + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args; + int ret; + + input = i_stream_create_from_data(bodystructure, strlen(bodystructure)); + (void)i_stream_read(input); + + parser = imap_parser_create(input, NULL, SIZE_MAX); + ret = imap_parser_finish_line(parser, 0, IMAP_PARSE_FLAG_NO_UNESCAPE | + IMAP_PARSE_FLAG_LITERAL_TYPE, &args); + if (ret < 0) { + *error_r = t_strdup_printf("IMAP parser failed: %s", + imap_parser_get_error(parser, NULL)); + } else if (ret == 0) { + *error_r = "Empty bodystructure"; + ret = -1; + } else { + ret = imap_parse_bodystructure_args(args, dest, error_r); + } + + imap_parser_unref(&parser); + i_stream_destroy(&input); + return ret; +} diff --git a/src/lib-imap/imap-bodystructure.h b/src/lib-imap/imap-bodystructure.h new file mode 100644 index 0000000..a7cc6cd --- /dev/null +++ b/src/lib-imap/imap-bodystructure.h @@ -0,0 +1,39 @@ +#ifndef IMAP_BODYSTRUCTURE_H +#define IMAP_BODYSTRUCTURE_H + +struct message_part; +struct message_header_line; +struct imap_arg; + +/* Write a BODY/BODYSTRUCTURE from given message_part. The message_part->data + field must be set. part->body_size.virtual_size and .lines are also used + for writing it. Returns 0 on success, -1 if parts don't internally match + (e.g. broken cached mime.parts mixed with parsed message). */ +int imap_bodystructure_write(const struct message_part *part, + string_t *dest, bool extended, + const char **error_r); + +/* Parse BODYSTRUCTURE and save the contents to message_part->data for each + message tree node. If the parts argument points to NULL, the message_part + tree is created from the BODYSTRUCTURE. Otherwise, existing tree is used. + Returns 0 if ok, -1 if bodystructure wasn't valid. */ +int imap_bodystructure_parse_full(const char *bodystructure, pool_t pool, + struct message_part **parts, const char **error_r); + +/* Same as imap_bodystructure_parse_full(), but read the input from imap_args + instead of a string. */ +int imap_bodystructure_parse_args(const struct imap_arg *args, pool_t pool, + struct message_part **parts, const char **error_r); + +/* Parse BODYSTRUCTURE and save the contents to message_part->data for each + message tree node. The parts argument must point to an existing message_part + tree. Returns 0 if ok, -1 if bodystructure wasn't valid. */ +int imap_bodystructure_parse(const char *bodystructure, pool_t pool, + struct message_part *parts, const char **error_r); + +/* Get BODY part from BODYSTRUCTURE and write it to dest. + Returns 0 if ok, -1 if bodystructure wasn't valid. */ +int imap_body_parse_from_bodystructure(const char *bodystructure, + string_t *dest, const char **error_r); + +#endif diff --git a/src/lib-imap/imap-date.c b/src/lib-imap/imap-date.c new file mode 100644 index 0000000..2e5569a --- /dev/null +++ b/src/lib-imap/imap-date.c @@ -0,0 +1,247 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "utc-offset.h" +#include "utc-mktime.h" +#include "imap-date.h" + +#include <ctype.h> + +static const char *month_names[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static int parse_timezone(const char *str) +{ + int offset; + + /* +|-zone */ + if ((*str != '+' && *str != '-') || + !i_isdigit(str[1]) || !i_isdigit(str[2]) || + !i_isdigit(str[3]) || !i_isdigit(str[4])) + return 0; + + offset = (str[1]-'0') * 10*60 + (str[2]-'0') * 60 + + (str[3]-'0') * 10 + (str[4]-'0'); + return *str == '+' ? offset : -offset; +} + +static const char *imap_parse_date_internal(const char *str, struct tm *tm) +{ + int i; + + if (str == NULL || *str == '\0') + return NULL; + + i_zero(tm); + + /* "dd-mon-yyyy [hh:mi:ss +|-zone]" + dd is 1-2 digits and may be prefixed with space or zero. */ + + if (str[0] == ' ') { + /* " d-..." */ + str++; + } + + if (!(i_isdigit(str[0]) && (str[1] == '-' || + (i_isdigit(str[1]) && str[2] == '-')))) + return NULL; + + tm->tm_mday = (str[0]-'0'); + if (str[1] == '-') + str += 2; + else { + tm->tm_mday = (tm->tm_mday * 10) + (str[1]-'0'); + str += 3; + } + + /* month name */ + for (i = 0; i < 12; i++) { + if (strncasecmp(month_names[i], str, 3) == 0) { + tm->tm_mon = i; + break; + } + } + if (i == 12 || str[3] != '-') + return NULL; + str += 4; + + /* yyyy */ + if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || + !i_isdigit(str[2]) || !i_isdigit(str[3])) + return NULL; + + tm->tm_year = (str[0]-'0') * 1000 + (str[1]-'0') * 100 + + (str[2]-'0') * 10 + (str[3]-'0') - 1900; + + str += 4; + return str; +} + +static bool imap_mktime(struct tm *tm, time_t *time_r) +{ + *time_r = utc_mktime(tm); + if (*time_r != (time_t)-1) + return TRUE; + + /* the date is outside valid range for time_t. it might still be + technically valid though, so try to handle this case. + with 64bit time_t the full 0..9999 year range is valid. */ + if (tm->tm_year <= 100) { + /* too old. time_t can be signed or unsigned, handle + both cases. */ + *time_r = (time_t)-1 < (int)0 ? INT_MIN : 0; + } else { + /* too high. return the highest allowed value. + we shouldn't get here with 64bit time_t, + but handle that anyway. */ +#if (TIME_T_MAX_BITS == 32 || TIME_T_MAX_BITS == 64) + *time_r = (1UL << (TIME_T_MAX_BITS-1)) - 1; +#else + *time_r = (1UL << TIME_T_MAX_BITS) - 1; +#endif + } + return FALSE; +} + +bool imap_parse_date(const char *str, time_t *timestamp_r) +{ + struct tm tm; + + str = imap_parse_date_internal(str, &tm); + if (str == NULL || str[0] != '\0') + return FALSE; + + tm.tm_isdst = -1; + (void)imap_mktime(&tm, timestamp_r); + return TRUE; +} + +bool imap_parse_datetime(const char *str, time_t *timestamp_r, + int *timezone_offset_r) +{ + struct tm tm; + + str = imap_parse_date_internal(str, &tm); + if (str == NULL) + return FALSE; + + if (str[0] != ' ') + return FALSE; + str++; + + /* hh: */ + if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || str[2] != ':') + return FALSE; + tm.tm_hour = (str[0]-'0') * 10 + (str[1]-'0'); + str += 3; + + /* mm: */ + if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || str[2] != ':') + return FALSE; + tm.tm_min = (str[0]-'0') * 10 + (str[1]-'0'); + str += 3; + + /* ss */ + if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || str[2] != ' ') + return FALSE; + tm.tm_sec = (str[0]-'0') * 10 + (str[1]-'0'); + str += 3; + + /* timezone */ + *timezone_offset_r = parse_timezone(str); + + tm.tm_isdst = -1; + if (imap_mktime(&tm, timestamp_r)) + *timestamp_r -= *timezone_offset_r * 60; + return TRUE; +} + +static void imap_to_date_tm(char buf[11], const struct tm *tm) +{ + int year; + + /* dd-mon- */ + buf[0] = (tm->tm_mday / 10) + '0'; + buf[1] = (tm->tm_mday % 10) + '0'; + buf[2] = '-'; + memcpy(buf+3, month_names[tm->tm_mon], 3); + buf[6] = '-'; + + /* yyyy */ + year = tm->tm_year + 1900; + buf[7] = (year / 1000) + '0'; + buf[8] = ((year / 100) % 10) + '0'; + buf[9] = ((year / 10) % 10) + '0'; + buf[10] = (year % 10) + '0'; +} + +static const char * +imap_to_datetime_tm(const struct tm *tm, int timezone_offset) +{ + char *buf; + + /* @UNSAFE: but faster than t_strdup_printf() call.. */ + buf = t_malloc0(27); + imap_to_date_tm(buf, tm); + buf[11] = ' '; + + /* hh:mi:ss */ + buf[12] = (tm->tm_hour / 10) + '0'; + buf[13] = (tm->tm_hour % 10) + '0'; + buf[14] = ':'; + buf[15] = (tm->tm_min / 10) + '0'; + buf[16] = (tm->tm_min % 10) + '0'; + buf[17] = ':'; + buf[18] = (tm->tm_sec / 10) + '0'; + buf[19] = (tm->tm_sec % 10) + '0'; + buf[20] = ' '; + + /* timezone */ + if (timezone_offset >= 0) + buf[21] = '+'; + else { + buf[21] = '-'; + timezone_offset = -timezone_offset; + } + buf[22] = (timezone_offset / 600) + '0'; + buf[23] = ((timezone_offset / 60) % 10) + '0'; + buf[24] = ((timezone_offset % 60) / 10) + '0'; + buf[25] = (timezone_offset % 10) + '0'; + buf[26] = '\0'; + + return buf; +} + +const char *imap_to_datetime(time_t timestamp) +{ + struct tm *tm; + int timezone_offset; + + tm = localtime(×tamp); + timezone_offset = utc_offset(tm, timestamp); + return imap_to_datetime_tm(tm, timezone_offset); +} + +const char *imap_to_datetime_tz(time_t timestamp, int timezone_offset) +{ + const struct tm *tm; + time_t adjusted = timestamp + timezone_offset*60; + + tm = gmtime(&adjusted); + return imap_to_datetime_tm(tm, timezone_offset); +} + +bool imap_to_date(time_t timestamp, const char **str_r) +{ + const struct tm *tm; + char *buf; + + tm = localtime(×tamp); + + buf = t_malloc0(12); + imap_to_date_tm(buf, tm); + *str_r = buf; + return tm->tm_hour == 0 && tm->tm_min == 0 && tm->tm_sec == 0; +} diff --git a/src/lib-imap/imap-date.h b/src/lib-imap/imap-date.h new file mode 100644 index 0000000..b8ea982 --- /dev/null +++ b/src/lib-imap/imap-date.h @@ -0,0 +1,25 @@ +#ifndef IMAP_DATE_H +#define IMAP_DATE_H + +/* Parses IMAP date/time string and returns TRUE if the string is valid. + time_t is filled with UTC date. timezone_offset is filled with parsed + timezone. If no timezone is given, local timezone is assumed. + + If date is too low or too high to fit to time_t, it's set to lowest/highest + allowed value. This allows you to use the value directly for comparing + timestamps. */ +bool imap_parse_date(const char *str, time_t *timestamp_r); +bool imap_parse_datetime(const char *str, time_t *timestamp_r, + int *timezone_offset_r); + +/* Returns given UTC timestamp as IMAP date-time string in local timezone. */ +const char *imap_to_datetime(time_t timestamp); +/* Returns given UTC timestamp as IMAP date-time string in given timezone. */ +const char *imap_to_datetime_tz(time_t timestamp, int timezone_offset); + +/* Returns TRUE if timestamp was successfully converted to a date, + FALSE if time would have been required as well (but the string is still + returned). */ +bool imap_to_date(time_t timestamp, const char **str_r); + +#endif diff --git a/src/lib-imap/imap-envelope.c b/src/lib-imap/imap-envelope.c new file mode 100644 index 0000000..87297f4 --- /dev/null +++ b/src/lib-imap/imap-envelope.c @@ -0,0 +1,248 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "str.h" +#include "message-address.h" +#include "message-part-data.h" +#include "message-parser.h" +#include "imap-parser.h" +#include "imap-envelope.h" +#include "imap-quote.h" + +/* + * Envelope write + */ + +static void imap_write_address(string_t *str, struct message_address *addr) +{ + if (addr == NULL) { + str_append(str, "NIL"); + return; + } + + str_append_c(str, '('); + while (addr != NULL) { + str_append_c(str, '('); + if (addr->name == NULL) + str_append(str, "NIL"); + else { + imap_append_string_for_humans(str, + (const void *)addr->name, strlen(addr->name)); + } + str_append_c(str, ' '); + imap_append_nstring(str, addr->route); + str_append_c(str, ' '); + imap_append_nstring(str, addr->mailbox); + str_append_c(str, ' '); + imap_append_nstring(str, addr->domain); + str_append_c(str, ')'); + + addr = addr->next; + } + str_append_c(str, ')'); +} + +void imap_envelope_write(struct message_part_envelope *data, + string_t *str) +{ +#define NVL(str, nullstr) ((str) != NULL ? (str) : (nullstr)) + static const char *empty_envelope = + "NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL"; + + if (data == NULL) { + str_append(str, empty_envelope); + return; + } + + imap_append_nstring_nolf(str, data->date); + str_append_c(str, ' '); + if (data->subject == NULL) + str_append(str, "NIL"); + else { + imap_append_string_for_humans(str, + (const unsigned char *)data->subject, + strlen(data->subject)); + } + + str_append_c(str, ' '); + imap_write_address(str, data->from); + str_append_c(str, ' '); + imap_write_address(str, NVL(data->sender, data->from)); + str_append_c(str, ' '); + imap_write_address(str, NVL(data->reply_to, data->from)); + str_append_c(str, ' '); + imap_write_address(str, data->to); + str_append_c(str, ' '); + imap_write_address(str, data->cc); + str_append_c(str, ' '); + imap_write_address(str, data->bcc); + + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->in_reply_to); + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->message_id); +} + +/* + * ENVELOPE parsing + */ + +static bool +imap_envelope_parse_address(const struct imap_arg *arg, + pool_t pool, struct message_address **addr_r) +{ + struct message_address *addr; + const struct imap_arg *list_args; + const char *name, *route, *mailbox, *domain; + unsigned int list_count; + + if (!imap_arg_get_list_full(arg, &list_args, &list_count)) + return FALSE; + + /* we require 4 arguments, strings or NILs */ + if (list_count < 4) + return FALSE; + + if (!imap_arg_get_nstring(&list_args[0], &name)) + return FALSE; + if (!imap_arg_get_nstring(&list_args[1], &route)) + return FALSE; + if (!imap_arg_get_nstring(&list_args[2], &mailbox)) + return FALSE; + if (!imap_arg_get_nstring(&list_args[3], &domain)) + return FALSE; + + addr = p_new(pool, struct message_address, 1); + addr->name = p_strdup(pool, name); + addr->route = p_strdup(pool, route); + addr->mailbox = p_strdup(pool, mailbox); + addr->domain = p_strdup(pool, domain); + + *addr_r = addr; + return TRUE; +} + +static bool +imap_envelope_parse_addresses(const struct imap_arg *arg, + pool_t pool, struct message_address **addrs_r) +{ + struct message_address *first, *addr, *prev; + const struct imap_arg *list_args; + + if (arg->type == IMAP_ARG_NIL) { + *addrs_r = NULL; + return TRUE; + } + + if (!imap_arg_get_list(arg, &list_args)) + return FALSE; + + first = addr = prev = NULL; + for (; !IMAP_ARG_IS_EOL(list_args); list_args++) { + if (!imap_envelope_parse_address + (list_args, pool, &addr)) + return FALSE; + if (first == NULL) + first = addr; + if (prev != NULL) + prev->next = addr; + prev = addr; + } + + *addrs_r = first; + return TRUE; +} + +bool imap_envelope_parse_args(const struct imap_arg *args, + pool_t pool, struct message_part_envelope **envlp_r, + const char **error_r) +{ + struct message_part_envelope *envlp; + + envlp = p_new(pool, struct message_part_envelope, 1); + + if (!imap_arg_get_nstring(args++, &envlp->date)) { + *error_r = "Invalid date field"; + return FALSE; + } + envlp->date = p_strdup(pool, envlp->date); + + if (!imap_arg_get_nstring(args++, &envlp->subject)) { + *error_r = "Invalid subject field"; + return FALSE; + } + envlp->subject = p_strdup(pool, envlp->subject); + + if (!imap_envelope_parse_addresses(args++, pool, &envlp->from)) { + *error_r = "Invalid from field"; + return FALSE; + } + if (!imap_envelope_parse_addresses(args++, pool, &envlp->sender)) { + *error_r = "Invalid sender field"; + return FALSE; + } + if (!imap_envelope_parse_addresses(args++, pool, &envlp->reply_to)) { + *error_r = "Invalid reply_to field"; + return FALSE; + } + if (!imap_envelope_parse_addresses(args++, pool, &envlp->to)) { + *error_r = "Invalid to field"; + return FALSE; + } + if (!imap_envelope_parse_addresses(args++, pool, &envlp->cc)) { + *error_r = "Invalid cc field"; + return FALSE; + } + if (!imap_envelope_parse_addresses(args++, pool, &envlp->bcc)) { + *error_r = "Invalid bcc field"; + return FALSE; + } + + if (!imap_arg_get_nstring(args++, &envlp->in_reply_to)) { + *error_r = "Invalid in_reply_to field"; + return FALSE; + } + envlp->in_reply_to = p_strdup(pool, envlp->in_reply_to); + + if (!imap_arg_get_nstring(args++, &envlp->message_id)) { + *error_r = "Invalid message_id field"; + return FALSE; + } + envlp->message_id = p_strdup(pool, envlp->message_id); + + *envlp_r = envlp; + return TRUE; +} + +bool imap_envelope_parse(const char *envelope, + pool_t pool, struct message_part_envelope **envlp_r, + const char **error_r) +{ + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args; + int ret; + bool success; + + input = i_stream_create_from_data(envelope, strlen(envelope)); + (void)i_stream_read(input); + + parser = imap_parser_create(input, NULL, SIZE_MAX); + ret = imap_parser_finish_line(parser, 0, + IMAP_PARSE_FLAG_LITERAL_TYPE, &args); + if (ret < 0) { + *error_r = t_strdup_printf("IMAP parser failed: %s", + imap_parser_get_error(parser, NULL)); + success = FALSE; + } else if (ret == 0) { + *error_r = "Empty envelope"; + success = FALSE; + } else { + success = imap_envelope_parse_args(args, pool, envlp_r, error_r); + } + + imap_parser_unref(&parser); + i_stream_destroy(&input); + return success; +} diff --git a/src/lib-imap/imap-envelope.h b/src/lib-imap/imap-envelope.h new file mode 100644 index 0000000..f9e4c0b --- /dev/null +++ b/src/lib-imap/imap-envelope.h @@ -0,0 +1,20 @@ +#ifndef IMAP_ENVELOPE_H +#define IMAP_ENVELOPE_H + +struct imap_arg; +struct message_part_envelope; + +/* Write envelope to given string */ +void imap_envelope_write(struct message_part_envelope *data, + string_t *str); + +/* Parse envelope from arguments */ +bool imap_envelope_parse_args(const struct imap_arg *args, + pool_t pool, struct message_part_envelope **envlp_r, + const char **error_r); +/* Parse envelope from string */ +bool imap_envelope_parse(const char *envelope, + pool_t pool, struct message_part_envelope **envlp_r, + const char **error_r); + +#endif diff --git a/src/lib-imap/imap-id.c b/src/lib-imap/imap-id.c new file mode 100644 index 0000000..f873c57 --- /dev/null +++ b/src/lib-imap/imap-id.c @@ -0,0 +1,173 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "str-sanitize.h" +#include "istream.h" +#include "imap-parser.h" +#include "imap-quote.h" +#include "imap-id.h" +#include "dovecot-version.h" + +#ifdef HAVE_SYS_UTSNAME_H +# include <sys/utsname.h> +#endif + +#ifdef HAVE_UNAME +static struct utsname utsname_result; +static bool utsname_set = FALSE; + +static const char *imap_id_get_uname(const char *key) +{ + if (!utsname_set) { + utsname_set = TRUE; + if (uname(&utsname_result) < 0) { + i_error("uname() failed: %m"); + i_zero(&utsname_result); + } + } + + if (strcasecmp(key, "os") == 0) + return utsname_result.sysname; + if (strcasecmp(key, "os-version") == 0) + return utsname_result.release; + return NULL; +} +#endif + +static const char *imap_id_get_default(const char *key) +{ + if (strcasecmp(key, "name") == 0) + return PACKAGE_NAME; + if (strcasecmp(key, "version") == 0) + return PACKAGE_VERSION; + if (strcasecmp(key, "revision") == 0) + return DOVECOT_REVISION; + if (strcasecmp(key, "support-url") == 0) + return PACKAGE_WEBPAGE; + if (strcasecmp(key, "support-email") == 0) + return PACKAGE_BUGREPORT; +#ifdef HAVE_UNAME + return imap_id_get_uname(key); +#endif +} + +static const char * +imap_id_reply_generate_from_imap_args(const struct imap_arg *args) +{ + string_t *str; + const char *key, *value; + + if (IMAP_ARG_IS_EOL(args)) + return "NIL"; + + str = t_str_new(256); + str_append_c(str, '('); + for (; !IMAP_ARG_IS_EOL(args); args++) { + if (!imap_arg_get_astring(args, &key)) { + /* broken input */ + if (IMAP_ARG_IS_EOL(&args[1])) + break; + args++; + } else { + /* key */ + if (str_len(str) > 1) + str_append_c(str, ' '); + imap_append_quoted(str, key); + str_append_c(str, ' '); + /* value */ + if (IMAP_ARG_IS_EOL(&args[1])) { + str_append(str, "NIL"); + break; + } + args++; + if (!imap_arg_get_astring(args, &value)) + value = NULL; + else { + if (strcmp(value, "*") == 0) + value = imap_id_get_default(key); + } + imap_append_nstring(str, value); + } + } + if (str_len(str) == 1) { + /* broken */ + return "NIL"; + } + str_append_c(str, ')'); + return str_c(str); +} + +const char *imap_id_reply_generate(const char *settings) +{ + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args; + const char *ret; + + if (settings == NULL) + return "NIL"; + + input = i_stream_create_from_data(settings, strlen(settings)); + (void)i_stream_read(input); + + parser = imap_parser_create(input, NULL, SIZE_MAX); + if (imap_parser_finish_line(parser, 0, 0, &args) <= 0) + ret = "NIL"; + else + ret = imap_id_reply_generate_from_imap_args(args); + + imap_parser_unref(&parser); + i_stream_destroy(&input); + return ret; +} + +void imap_id_log_reply_append(string_t *reply, const char *key, + const char *value) +{ + if (str_len(reply) > 0) + str_append(reply, ", "); + str_append(reply, str_sanitize(key, IMAP_ID_KEY_MAX_LEN)); + str_append_c(reply, '='); + str_append(reply, value == NULL ? "NIL" : str_sanitize(value, 80)); +} + +const char *imap_id_args_get_log_reply(const struct imap_arg *args, + const char *settings) +{ + const char *const *keys, *key, *value; + string_t *reply; + bool log_all; + + if (settings == NULL || *settings == '\0') + return NULL; + if (!imap_arg_get_list(args, &args)) + return NULL; + + log_all = strcmp(settings, "*") == 0; + reply = t_str_new(256); + keys = t_strsplit_spaces(settings, " "); + while (!IMAP_ARG_IS_EOL(&args[0]) && + !IMAP_ARG_IS_EOL(&args[1])) { + if (!imap_arg_get_string(args, &key)) { + /* broken input */ + args += 2; + continue; + } + args++; + if (strlen(key) > 30) { + /* broken: ID spec requires fields to be max. 30 + octets */ + args++; + continue; + } + + if (log_all || str_array_icase_find(keys, key)) { + if (!imap_arg_get_nstring(args, &value)) + value = ""; + imap_id_log_reply_append(reply, key, value); + } + args++; + } + return str_len(reply) == 0 ? NULL : str_c(reply); +} diff --git a/src/lib-imap/imap-id.h b/src/lib-imap/imap-id.h new file mode 100644 index 0000000..853153a --- /dev/null +++ b/src/lib-imap/imap-id.h @@ -0,0 +1,19 @@ +#ifndef IMAP_ID_H +#define IMAP_ID_H + +struct imap_arg; + +/* RFC 2971 says keys are max. 30 octets */ +#define IMAP_ID_KEY_MAX_LEN 30 + +/* Return ID reply based on given settings. */ +const char *imap_id_reply_generate(const char *settings); +/* Return a line that should be logged based on given args and settings. + Returns NULL if nothing should be logged. */ +const char *imap_id_args_get_log_reply(const struct imap_arg *args, + const char *settings); +/* Append [, ]key=value to the reply sanitized. */ +void imap_id_log_reply_append(string_t *reply, const char *key, + const char *value); + +#endif diff --git a/src/lib-imap/imap-keepalive.c b/src/lib-imap/imap-keepalive.c new file mode 100644 index 0000000..0756ce8 --- /dev/null +++ b/src/lib-imap/imap-keepalive.c @@ -0,0 +1,47 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "crc32.h" +#include "net.h" +#include "imap-keepalive.h" + +#include <time.h> + +static bool imap_remote_ip_is_usable(const struct ip_addr *ip) +{ + unsigned int addr; + + if (ip->family == 0) + return FALSE; + if (ip->family == AF_INET) { +#define IP4(a,b,c,d) ((unsigned)(a)<<24|(unsigned)(b)<<16|(unsigned)(c)<<8|(unsigned)(d)) + addr = ip->u.ip4.s_addr; + if (addr >= IP4(10,0,0,0) && addr <= IP4(10,255,255,255)) + return FALSE; /* 10/8 */ + if (addr >= IP4(192,168,0,0) && addr <= IP4(192,168,255,255)) + return FALSE; /* 192.168/16 */ + if (addr >= IP4(172,16,0,0) && addr <= IP4(172,31,255,255)) + return FALSE; /* 172.16/12 */ + if (addr >= IP4(127,0,0,0) && addr <= IP4(127,255,255,255)) + return FALSE; /* 127/8 */ +#undef IP4 + } + else if (ip->family == AF_INET6) { + addr = ip->u.ip6.s6_addr[0]; + if (addr == 0xfc || addr == 0xfd) + return FALSE; /* fc00::/7 */ + } + return TRUE; +} + +unsigned int +imap_keepalive_interval_msecs(const char *username, const struct ip_addr *ip, + unsigned int interval_secs) +{ + unsigned int client_hash; + + client_hash = ip != NULL && imap_remote_ip_is_usable(ip) ? + net_ip_hash(ip) : crc32_str(username); + interval_secs -= (time(NULL) + client_hash) % interval_secs; + return interval_secs * 1000; +} diff --git a/src/lib-imap/imap-keepalive.h b/src/lib-imap/imap-keepalive.h new file mode 100644 index 0000000..238cdbd --- /dev/null +++ b/src/lib-imap/imap-keepalive.h @@ -0,0 +1,24 @@ +#ifndef IMAP_KEEPALIVE_H +#define IMAP_KEEPALIVE_H + +/* This function can be used to set IMAP IDLE keepalive notification timeout + interval so that the client gets the keepalive notifications at exactly the + same time for all the IMAP connections. This helps to reduce battery usage + in mobile devices. + + One problem with this is that we don't really want to send the notifications + to everyone at the same time, because it would cause huge peaks of activity. + Basing the notifications on the username works well for one account, but + basing it on the IP address allows the client to get all of the + notifications at the same time for multiple accounts as well (of course + assuming Dovecot is running on all the servers :) + + One potential downside to using IP is that if a proxy hides the client's IP + address, the notifications are sent to everyone at the same time. This can + be avoided by using a properly configured Dovecot proxy, but we'll also try + to avoid this by not doing it for the commonly used intranet IP ranges. */ +unsigned int +imap_keepalive_interval_msecs(const char *username, const struct ip_addr *ip, + unsigned int interval_secs); + +#endif diff --git a/src/lib-imap/imap-match.c b/src/lib-imap/imap-match.c new file mode 100644 index 0000000..8603e00 --- /dev/null +++ b/src/lib-imap/imap-match.c @@ -0,0 +1,382 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +/* imap_match_init() logic originates from Cyrus, but the code is fully + rewritten. */ + +#include "lib.h" +#include "array.h" +#include "imap-match.h" + +#include <ctype.h> + +struct imap_match_pattern { + const char *pattern; + bool inboxcase; +}; + +struct imap_match_glob { + pool_t pool; + + struct imap_match_pattern *patterns; + + char sep; + char patterns_data[FLEXIBLE_ARRAY_MEMBER]; +}; + +struct imap_match_context { + const char *inboxcase_end; + + char sep; + bool inboxcase; +}; + +/* name of "INBOX" - must not have repeated substrings */ +static const char inbox[] = "INBOX"; +#define INBOXLEN (sizeof(inbox) - 1) + +struct imap_match_glob * +imap_match_init(pool_t pool, const char *pattern, + bool inboxcase, char separator) +{ + const char *patterns[2]; + + patterns[0] = pattern; + patterns[1] = NULL; + return imap_match_init_multiple(pool, patterns, inboxcase, separator); +} + +static const char *pattern_compress(const char *pattern) +{ + char *dest, *ret; + + dest = ret = t_strdup_noconst(pattern); + + /* @UNSAFE: compress the pattern */ + while (*pattern != '\0') { + if (*pattern == '*' || *pattern == '%') { + /* remove duplicate hierarchy wildcards */ + while (*pattern == '%') pattern++; + + /* "%*" -> "*" */ + if (*pattern == '*') { + /* remove duplicate wildcards */ + while (*pattern == '*' || *pattern == '%') + pattern++; + *dest++ = '*'; + } else { + *dest++ = '%'; + } + } else { + *dest++ = *pattern++; + } + } + *dest = '\0'; + return ret; +} + +static bool pattern_is_inboxcase(const char *pattern, char separator) +{ + const char *p = pattern, *inboxp = inbox; + + /* skip over exact matches */ + while (*inboxp == i_toupper(*p) && *p != '\0') { + inboxp++; p++; + } + if (*p != '%') { + return *p == '*' || *p == separator || + (*inboxp == '\0' && *p == '\0'); + } + + /* handle 'I%B%X' style checks */ + for (; *p != '\0' && *p != '*' && *p != separator; p++) { + if (*p != '%') { + inboxp = strchr(inboxp, i_toupper(*p)); + if (inboxp == NULL) + return FALSE; + + if (*++inboxp == '\0') { + /* now check that it doesn't end with + any invalid chars */ + if (*++p == '%') p++; + if (*p != '\0' && *p != '*' && + *p != separator) + return FALSE; + break; + } + } + } + return TRUE; +} + +static struct imap_match_glob * +imap_match_init_multiple_real(pool_t pool, const char *const *patterns, + bool inboxcase, char separator) +{ + struct imap_match_glob *glob; + struct imap_match_pattern *match_patterns; + unsigned int i, patterns_count; + size_t len, pos, patterns_data_len = 0; + + patterns_count = str_array_length(patterns); + match_patterns = p_new(pool, struct imap_match_pattern, + patterns_count + 1); + + /* compress the patterns */ + for (i = 0; i < patterns_count; i++) { + match_patterns[i].pattern = pattern_compress(patterns[i]); + match_patterns[i].inboxcase = inboxcase && + pattern_is_inboxcase(match_patterns[i].pattern, + separator); + + patterns_data_len += strlen(match_patterns[i].pattern) + 1; + } + patterns_count = i; + + /* now we know how much memory we need */ + glob = p_malloc(pool, sizeof(struct imap_match_glob) + + patterns_data_len); + glob->pool = pool; + glob->sep = separator; + + /* copy pattern strings to our allocated memory */ + for (i = 0, pos = 0; i < patterns_count; i++) { + len = strlen(match_patterns[i].pattern) + 1; + i_assert(pos + len <= patterns_data_len); + + /* @UNSAFE */ + memcpy(glob->patterns_data + pos, + match_patterns[i].pattern, len); + match_patterns[i].pattern = glob->patterns_data + pos; + pos += len; + } + glob->patterns = match_patterns; + return glob; +} + +struct imap_match_glob * +imap_match_init_multiple(pool_t pool, const char *const *patterns, + bool inboxcase, char separator) +{ + struct imap_match_glob *glob; + + if (pool->datastack_pool) { + return imap_match_init_multiple_real(pool, patterns, + inboxcase, separator); + } + T_BEGIN { + glob = imap_match_init_multiple_real(pool, patterns, + inboxcase, separator); + } T_END; + return glob; +} + +void imap_match_deinit(struct imap_match_glob **glob) +{ + if (glob == NULL || *glob == NULL) + return; + p_free((*glob)->pool, (*glob)->patterns); + p_free((*glob)->pool, *glob); + *glob = NULL; +} + +static struct imap_match_glob * +imap_match_dup_real(pool_t pool, const struct imap_match_glob *glob) +{ + ARRAY_TYPE(const_string) patterns; + const struct imap_match_pattern *p; + bool inboxcase = FALSE; + + t_array_init(&patterns, 8); + for (p = glob->patterns; p->pattern != NULL; p++) { + if (p->inboxcase) + inboxcase = TRUE; + array_push_back(&patterns, &p->pattern); + } + array_append_zero(&patterns); + return imap_match_init_multiple_real(pool, array_front(&patterns), + inboxcase, glob->sep); +} + +struct imap_match_glob * +imap_match_dup(pool_t pool, const struct imap_match_glob *glob) +{ + struct imap_match_glob *new_glob; + + if (pool->datastack_pool) { + return imap_match_dup_real(pool, glob); + } else { + T_BEGIN { + new_glob = imap_match_dup_real(pool, glob); + } T_END; + return new_glob; + } +} + +bool imap_match_globs_equal(const struct imap_match_glob *glob1, + const struct imap_match_glob *glob2) +{ + const struct imap_match_pattern *p1 = glob1->patterns; + const struct imap_match_pattern *p2 = glob2->patterns; + + if (glob1->sep != glob2->sep) + return FALSE; + + for (; p1->pattern != NULL && p2->pattern != NULL; p1++, p2++) { + if (strcmp(p1->pattern, p2->pattern) != 0) + return FALSE; + if (p1->inboxcase != p2->inboxcase) + return FALSE; + } + return p1->pattern == p2->pattern; +} + +#define CMP_CUR_CHR(ctx, data, pattern) \ + (*(data) == *(pattern) || \ + (i_toupper(*(data)) == i_toupper(*(pattern)) && \ + (data) < (ctx)->inboxcase_end)) + +static enum imap_match_result +match_sub(struct imap_match_context *ctx, const char **data_p, + const char **pattern_p) +{ + enum imap_match_result ret, match; + unsigned int i; + const char *data = *data_p, *pattern = *pattern_p; + + /* match all non-wildcards */ + i = 0; + while (pattern[i] != '\0' && pattern[i] != '*' && pattern[i] != '%') { + if (!CMP_CUR_CHR(ctx, data+i, pattern+i)) { + if (data[i] != '\0') + return IMAP_MATCH_NO; + if (pattern[i] == ctx->sep) + return IMAP_MATCH_CHILDREN; + if (i > 0 && pattern[i-1] == ctx->sep) { + /* data="foo/" pattern = "foo/bar/%" */ + return IMAP_MATCH_CHILDREN; + } + return IMAP_MATCH_NO; + } + i++; + } + data += i; + pattern += i; + + if (*data == '\0' && *data_p != data && data[-1] == ctx->sep && + *pattern != '\0') { + /* data="/" pattern="/%..." */ + match = IMAP_MATCH_CHILDREN; + } else { + match = IMAP_MATCH_NO; + } + while (*pattern == '%') { + pattern++; + + if (*pattern == '\0') { + /* match, if this is the last hierarchy */ + while (*data != '\0' && *data != ctx->sep) + data++; + break; + } + + /* skip over this hierarchy */ + while (*data != '\0') { + if (CMP_CUR_CHR(ctx, data, pattern)) { + ret = match_sub(ctx, &data, &pattern); + if (ret == IMAP_MATCH_YES) + break; + + match |= ret; + } + + if (*data == ctx->sep) + break; + + data++; + } + } + + if (*pattern != '*') { + if (*data == '\0' && *pattern != '\0') { + if (*pattern == ctx->sep) + match |= IMAP_MATCH_CHILDREN; + return match; + } + + if (*data != '\0') { + if (*pattern == '\0' && *data == ctx->sep) + match |= IMAP_MATCH_PARENT; + return match; + } + } + + *data_p = data; + *pattern_p = pattern; + return IMAP_MATCH_YES; +} + +static enum imap_match_result +imap_match_pattern(struct imap_match_context *ctx, + const char *data, const char *pattern) +{ + enum imap_match_result ret, match; + + ctx->inboxcase_end = data; + if (ctx->inboxcase && strncasecmp(data, inbox, INBOXLEN) == 0 && + (data[INBOXLEN] == '\0' || data[INBOXLEN] == ctx->sep)) { + /* data begins with INBOX/, use case-insensitive comparison + for it */ + ctx->inboxcase_end += INBOXLEN; + } + + if (*pattern != '*') { + /* handle the pattern up to the first '*' */ + ret = match_sub(ctx, &data, &pattern); + if (ret != IMAP_MATCH_YES || *pattern == '\0') + return ret; + } + + match = IMAP_MATCH_CHILDREN; + while (*pattern == '*') { + pattern++; + + if (*pattern == '\0') + return IMAP_MATCH_YES; + + while (*data != '\0') { + if (CMP_CUR_CHR(ctx, data, pattern)) { + ret = match_sub(ctx, &data, &pattern); + if (ret == IMAP_MATCH_YES) + break; + match |= ret; + } + + data++; + } + } + + return *data == '\0' && *pattern == '\0' ? + IMAP_MATCH_YES : match; +} + +enum imap_match_result +imap_match(struct imap_match_glob *glob, const char *data) +{ + struct imap_match_context ctx; + unsigned int i; + enum imap_match_result ret, match; + + match = IMAP_MATCH_NO; + ctx.sep = glob->sep; + for (i = 0; glob->patterns[i].pattern != NULL; i++) { + ctx.inboxcase = glob->patterns[i].inboxcase; + + ret = imap_match_pattern(&ctx, data, glob->patterns[i].pattern); + if (ret == IMAP_MATCH_YES) + return IMAP_MATCH_YES; + + match |= ret; + } + + return match; +} diff --git a/src/lib-imap/imap-match.h b/src/lib-imap/imap-match.h new file mode 100644 index 0000000..f2e5b51 --- /dev/null +++ b/src/lib-imap/imap-match.h @@ -0,0 +1,42 @@ +#ifndef IMAP_MATCH_H +#define IMAP_MATCH_H + +enum imap_match_result { + IMAP_MATCH_NO = 0x00, /* definite non-match */ + IMAP_MATCH_YES = 0x01, /* match */ + + /* YES and NO are returned alone, but CHILDREN and PARENT may be + returned both (eg. "foo*bar" vs. "foobar/baz") */ + + /* non-match, but its children could match (eg. "box" vs "box/%") */ + IMAP_MATCH_CHILDREN = 0x02, + /* non-match, but one of its parents does match. This should often be + handled with YES matches, because when listing for "%" and "box/foo" + exists but "box" doesn't, you should still list "box" as + (Nonexistent HasChildren) mailbox. */ + IMAP_MATCH_PARENT = 0x04 +}; + +struct imap_match_glob; + +/* If inboxcase is TRUE, the "INBOX" string at the beginning of line is + compared case-insensitively */ +struct imap_match_glob * +imap_match_init(pool_t pool, const char *pattern, + bool inboxcase, char separator); +struct imap_match_glob * +imap_match_init_multiple(pool_t pool, const char *const *patterns, + bool inboxcase, char separator); +void imap_match_deinit(struct imap_match_glob **glob); + +struct imap_match_glob * +imap_match_dup(pool_t pool, const struct imap_match_glob *glob); +/* Returns TRUE if two globs were created with same init() parameters + (but inboxcase is ignored if no patterns can match INBOX) */ +bool imap_match_globs_equal(const struct imap_match_glob *glob1, + const struct imap_match_glob *glob2); + +enum imap_match_result +imap_match(struct imap_match_glob *glob, const char *data); + +#endif diff --git a/src/lib-imap/imap-parser.c b/src/lib-imap/imap-parser.c new file mode 100644 index 0000000..2deb75f --- /dev/null +++ b/src/lib-imap/imap-parser.c @@ -0,0 +1,1023 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "ostream.h" +#include "strescape.h" +#include "imap-parser.h" + +/* We use this macro to read atoms from input. It should probably contain + everything some day, but for now we can't handle some input otherwise: + + ']' is required for parsing section (FETCH BODY[]) + '%', '*' and ']' are valid list-chars for LIST patterns + '\' is used in flags */ +#define IS_ATOM_PARSER_INPUT(c) \ + ((c) == '(' || (c) == ')' || (c) == '{' || \ + (c) == '"' || (c) <= 32 || (c) == 0x7f) + +#define is_linebreak(c) \ + ((c) == '\r' || (c) == '\n') + +#define LIST_INIT_COUNT 7 + +enum arg_parse_type { + ARG_PARSE_NONE = 0, + ARG_PARSE_ATOM, + ARG_PARSE_STRING, + ARG_PARSE_LITERAL, + ARG_PARSE_LITERAL8, + ARG_PARSE_LITERAL_DATA, + ARG_PARSE_LITERAL_DATA_FORCED, + ARG_PARSE_TEXT +}; + +struct imap_parser { + /* permanent */ + int refcount; + pool_t pool; + struct istream *input; + struct ostream *output; + size_t max_line_size; + enum imap_parser_flags flags; + + /* reset by imap_parser_reset(): */ + size_t line_size; + ARRAY_TYPE(imap_arg_list) root_list; + ARRAY_TYPE(imap_arg_list) *cur_list; + struct imap_arg *list_arg; + + enum arg_parse_type cur_type; + size_t cur_pos; /* parser position in input buffer */ + bool cur_resp_text; /* we're parsing [resp-text-code] */ + + int str_first_escape; /* ARG_PARSE_STRING: index to first '\' */ + uoff_t literal_size; /* ARG_PARSE_LITERAL: string size */ + + enum imap_parser_error error; + const char *error_msg; + + bool literal_minus:1; + bool literal_skip_crlf:1; + bool literal_nonsync:1; + bool literal8:1; + bool literal_size_return:1; + bool eol:1; + bool args_added_extra_eol:1; + bool fatal_error:1; +}; + +struct imap_parser * +imap_parser_create(struct istream *input, struct ostream *output, + size_t max_line_size) +{ + struct imap_parser *parser; + + parser = i_new(struct imap_parser, 1); + parser->refcount = 1; + parser->pool = pool_alloconly_create(MEMPOOL_GROWING"IMAP parser", + 1024); + parser->input = input; + parser->output = output; + parser->max_line_size = max_line_size; + + p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT); + parser->cur_list = &parser->root_list; + return parser; +} + +void imap_parser_ref(struct imap_parser *parser) +{ + i_assert(parser->refcount > 0); + + parser->refcount++; +} + +void imap_parser_unref(struct imap_parser **_parser) +{ + struct imap_parser *parser = *_parser; + + *_parser = NULL; + + i_assert(parser->refcount > 0); + if (--parser->refcount > 0) + return; + + pool_unref(&parser->pool); + i_free(parser); +} + +void imap_parser_enable_literal_minus(struct imap_parser *parser) +{ + parser->literal_minus = TRUE; +} + +void imap_parser_reset(struct imap_parser *parser) +{ + p_clear(parser->pool); + + parser->line_size = 0; + + p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT); + parser->cur_list = &parser->root_list; + parser->list_arg = NULL; + + parser->cur_type = ARG_PARSE_NONE; + parser->cur_pos = 0; + parser->cur_resp_text = FALSE; + + parser->str_first_escape = 0; + parser->literal_size = 0; + + parser->error = IMAP_PARSE_ERROR_NONE; + parser->error_msg = NULL; + + parser->literal_skip_crlf = FALSE; + parser->eol = FALSE; + parser->args_added_extra_eol = FALSE; + parser->literal_size_return = FALSE; +} + +void imap_parser_set_streams(struct imap_parser *parser, struct istream *input, + struct ostream *output) +{ + parser->input = input; + parser->output = output; +} + +const char *imap_parser_get_error(struct imap_parser *parser, + enum imap_parser_error *error_r) +{ + if (error_r != NULL) + *error_r = parser->error; + return parser->error_msg; +} + +/* skip over everything parsed so far, plus the following whitespace */ +static bool imap_parser_skip_to_next(struct imap_parser *parser, + const unsigned char **data, + size_t *data_size) +{ + size_t i; + + for (i = parser->cur_pos; i < *data_size; i++) { + if ((*data)[i] != ' ') + break; + } + + parser->line_size += i; + i_stream_skip(parser->input, i); + parser->cur_pos = 0; + + *data += i; + *data_size -= i; + return *data_size > 0; +} + +static struct imap_arg *imap_arg_create(struct imap_parser *parser) +{ + struct imap_arg *arg; + + arg = array_append_space(parser->cur_list); + arg->parent = parser->list_arg; + return arg; +} + +static void imap_parser_open_list(struct imap_parser *parser) +{ + parser->list_arg = imap_arg_create(parser); + parser->list_arg->type = IMAP_ARG_LIST; + p_array_init(&parser->list_arg->_data.list, parser->pool, + LIST_INIT_COUNT); + parser->cur_list = &parser->list_arg->_data.list; + + parser->cur_type = ARG_PARSE_NONE; +} + +static bool imap_parser_close_list(struct imap_parser *parser) +{ + struct imap_arg *arg; + + if (parser->list_arg == NULL) { + /* we're not inside list */ + if ((parser->flags & IMAP_PARSE_FLAG_INSIDE_LIST) != 0) { + parser->eol = TRUE; + parser->cur_type = ARG_PARSE_NONE; + return TRUE; + } + parser->error_msg = "Unexpected ')'"; + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + return FALSE; + } + + arg = imap_arg_create(parser); + arg->type = IMAP_ARG_EOL; + + parser->list_arg = parser->list_arg->parent; + if (parser->list_arg == NULL) { + parser->cur_list = &parser->root_list; + } else { + parser->cur_list = &parser->list_arg->_data.list; + } + + parser->cur_type = ARG_PARSE_NONE; + return TRUE; +} + +static char * +imap_parser_strdup(struct imap_parser *parser, + const void *data, size_t len) +{ + char *ret; + + ret = p_malloc(parser->pool, len + 1); + memcpy(ret, data, len); + return ret; +} + +static void imap_parser_save_arg(struct imap_parser *parser, + const unsigned char *data, size_t size) +{ + struct imap_arg *arg; + char *str; + + arg = imap_arg_create(parser); + + switch (parser->cur_type) { + case ARG_PARSE_ATOM: + case ARG_PARSE_TEXT: + if (size == 3 && i_memcasecmp(data, "NIL", 3) == 0) { + /* NIL argument. it might be an actual NIL, but if + we're reading astring, it's an atom and we can't + lose its case. */ + arg->type = IMAP_ARG_NIL; + } else { + /* simply save the string */ + arg->type = IMAP_ARG_ATOM; + } + arg->_data.str = imap_parser_strdup(parser, data, size); + arg->str_len = size; + break; + case ARG_PARSE_STRING: + /* data is quoted and may contain escapes. */ + i_assert(size > 0); + + arg->type = IMAP_ARG_STRING; + str = p_strndup(parser->pool, data+1, size-1); + + /* remove the escapes */ + if (parser->str_first_escape >= 0 && + (parser->flags & IMAP_PARSE_FLAG_NO_UNESCAPE) == 0) + (void)str_unescape(str); + arg->_data.str = str; + arg->str_len = strlen(str); + break; + case ARG_PARSE_LITERAL_DATA: + if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_SIZE) != 0) { + /* save literal size */ + arg->type = parser->literal_nonsync ? + IMAP_ARG_LITERAL_SIZE_NONSYNC : + IMAP_ARG_LITERAL_SIZE; + arg->_data.literal_size = parser->literal_size; + arg->literal8 = parser->literal8; + break; + } + /* fall through */ + case ARG_PARSE_LITERAL_DATA_FORCED: + if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_TYPE) != 0) + arg->type = IMAP_ARG_LITERAL; + else + arg->type = IMAP_ARG_STRING; + arg->_data.str = imap_parser_strdup(parser, data, size); + arg->literal8 = parser->literal8; + arg->str_len = size; + break; + default: + i_unreached(); + } + + parser->cur_type = ARG_PARSE_NONE; +} + +static bool is_valid_atom_char(struct imap_parser *parser, char chr) +{ + const char *error_msg; + + if (IS_ATOM_PARSER_INPUT((unsigned char)chr)) + error_msg = "Invalid characters in atom"; + else if ((((unsigned char)chr) & 0x80) != 0) + error_msg = "8bit data in atom"; + else + return TRUE; + + if ((parser->flags & IMAP_PARSE_FLAG_ATOM_ALLCHARS) != 0) + return TRUE; + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = error_msg; + return FALSE; +} + +static bool imap_parser_read_atom(struct imap_parser *parser, + const unsigned char *data, size_t data_size) +{ + size_t i; + + /* read until we've found space, CR or LF. */ + for (i = parser->cur_pos; i < data_size; i++) { + if (data[i] == ' ' || is_linebreak(data[i])) { + imap_parser_save_arg(parser, data, i); + break; + } else if (data[i] == ')') { + if (parser->list_arg != NULL || + (parser->flags & IMAP_PARSE_FLAG_INSIDE_LIST) != 0) { + imap_parser_save_arg(parser, data, i); + break; + } else if ((parser->flags & + IMAP_PARSE_FLAG_ATOM_ALLCHARS) == 0) { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Unexpected ')'"; + return FALSE; + } + /* assume it's part of the atom */ + } else if (!is_valid_atom_char(parser, data[i])) + return FALSE; + } + + parser->cur_pos = i; + return parser->cur_type == ARG_PARSE_NONE; +} + +static bool imap_parser_read_string(struct imap_parser *parser, + const unsigned char *data, size_t data_size) +{ + size_t i; + + /* read until we've found non-escaped ", CR or LF */ + for (i = parser->cur_pos; i < data_size; i++) { + if (data[i] == '"') { + imap_parser_save_arg(parser, data, i); + + i++; /* skip the trailing '"' too */ + break; + } + + if (data[i] == '\0') { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "NULs not allowed in strings"; + return FALSE; + } + + if (data[i] == '\\') { + if (i+1 == data_size) { + /* known data ends with '\' - leave it to + next time as well if it happens to be \" */ + break; + } + + /* save the first escaped char */ + if (parser->str_first_escape < 0) + parser->str_first_escape = i; + + /* skip the escaped char */ + i++; + } + + /* check linebreaks here, so escaping CR/LF isn't possible. + string always ends with '"', so it's an error if we found + a linebreak.. */ + if (is_linebreak(data[i]) && + (parser->flags & IMAP_PARSE_FLAG_MULTILINE_STR) == 0) { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Missing '\"'"; + return FALSE; + } + } + + parser->cur_pos = i; + return parser->cur_type == ARG_PARSE_NONE; +} + +static bool imap_parser_literal_end(struct imap_parser *parser) +{ + if (parser->literal_minus && parser->literal_nonsync && + parser->literal_size > 4096) { + parser->error_msg = "Non-synchronizing literal size too large"; + parser->error = IMAP_PARSE_ERROR_LITERAL_TOO_BIG; + return FALSE; + } + + if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_SIZE) == 0) { + if (parser->line_size >= parser->max_line_size || + parser->literal_size > + parser->max_line_size - parser->line_size) { + /* too long string, abort. */ + parser->error = IMAP_PARSE_ERROR_LITERAL_TOO_BIG; + parser->error_msg = "Literal size too large"; + return FALSE; + } + + if (parser->output != NULL && !parser->literal_nonsync) { + o_stream_nsend(parser->output, "+ OK\r\n", 6); + if (o_stream_is_corked(parser->output)) { + /* make sure this continuation is sent to the + client as soon as possible */ + o_stream_uncork(parser->output); + o_stream_cork(parser->output); + } + } + } + + parser->cur_type = ARG_PARSE_LITERAL_DATA; + parser->literal_skip_crlf = TRUE; + + parser->cur_pos = 0; + return TRUE; +} + +static bool imap_parser_read_literal(struct imap_parser *parser, + const unsigned char *data, + size_t data_size) +{ + size_t i; + + /* expecting digits + "}" */ + for (i = parser->cur_pos; i < data_size; i++) { + if (data[i] == '}') { + parser->line_size += i+1; + i_stream_skip(parser->input, i+1); + return imap_parser_literal_end(parser); + } + + if (parser->literal_nonsync) { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Expecting '}' after '+'"; + return FALSE; + } + + if (data[i] == '+') { + parser->literal_nonsync = TRUE; + continue; + } + + if (data[i] < '0' || data[i] > '9') { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Invalid literal size"; + return FALSE; + } + + if (parser->literal_size >= ((uoff_t)-1 / 10)) { + if (parser->literal_size > ((uoff_t)-1 / 10) || + (uoff_t)(data[i] - '0') > ((uoff_t)-1 % 10)) { + parser->error = IMAP_PARSE_ERROR_LITERAL_TOO_BIG; + parser->error_msg = "Literal size too large"; + return FALSE; + } + } + parser->literal_size = parser->literal_size * 10 + + (data[i] - '0'); + } + + parser->cur_pos = i; + return FALSE; +} + +static bool imap_parser_read_literal_data(struct imap_parser *parser, + const unsigned char *data, + size_t data_size) +{ + if (parser->literal_skip_crlf) { + /* skip \r\n or \n, anything else gives an error */ + if (data_size == 0) + return FALSE; + + if (*data == '\r') { + parser->line_size++; + data++; data_size--; + i_stream_skip(parser->input, 1); + + if (data_size == 0) + return FALSE; + } + + if (*data != '\n') { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Missing LF after literal size"; + return FALSE; + } + + parser->line_size++; + data++; data_size--; + i_stream_skip(parser->input, 1); + + parser->literal_skip_crlf = FALSE; + + i_assert(parser->cur_pos == 0); + } + + if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_SIZE) == 0 || + parser->cur_type == ARG_PARSE_LITERAL_DATA_FORCED) { + /* now we just wait until we've read enough data */ + if (data_size < parser->literal_size) + return FALSE; + else { + imap_parser_save_arg(parser, data, + (size_t)parser->literal_size); + parser->cur_pos = (size_t)parser->literal_size; + return TRUE; + } + } else { + /* we want to save only literal size, not the literal itself. */ + parser->literal_size_return = TRUE; + imap_parser_save_arg(parser, uchar_empty_ptr, 0); + return FALSE; + } +} + +static bool imap_parser_is_next_resp_text(struct imap_parser *parser) +{ + const struct imap_arg *arg; + + if (parser->cur_list != &parser->root_list || + array_count(parser->cur_list) != 1) + return FALSE; + + arg = array_front(&parser->root_list); + if (arg->type != IMAP_ARG_ATOM) + return FALSE; + + return strcasecmp(arg->_data.str, "OK") == 0 || + strcasecmp(arg->_data.str, "NO") == 0 || + strcasecmp(arg->_data.str, "BAD") == 0 || + strcasecmp(arg->_data.str, "BYE") == 0; +} + +static bool imap_parser_is_next_text(struct imap_parser *parser) +{ + const struct imap_arg *arg; + size_t len; + + if (parser->cur_list != &parser->root_list) + return FALSE; + + arg = array_back(&parser->root_list); + if (arg->type != IMAP_ARG_ATOM) + return FALSE; + + len = strlen(arg->_data.str); + return len > 0 && arg->_data.str[len-1] == ']'; +} + +static bool imap_parser_read_text(struct imap_parser *parser, + const unsigned char *data, size_t data_size) +{ + size_t i; + + /* read until end of line */ + for (i = parser->cur_pos; i < data_size; i++) { + if (is_linebreak(data[i])) { + imap_parser_save_arg(parser, data, i); + break; + } + } + parser->cur_pos = i; + return parser->cur_type == ARG_PARSE_NONE; +} + +/* Returns TRUE if argument was fully processed. Also returns TRUE if + an argument inside a list was processed. */ +static bool imap_parser_read_arg(struct imap_parser *parser) +{ + const unsigned char *data; + size_t data_size; + + data = i_stream_get_data(parser->input, &data_size); + if (data_size == 0) + return FALSE; + + while (parser->cur_type == ARG_PARSE_NONE) { + /* we haven't started parsing yet */ + if (!imap_parser_skip_to_next(parser, &data, &data_size)) + return FALSE; + i_assert(parser->cur_pos == 0); + + if (parser->cur_resp_text && + imap_parser_is_next_text(parser)) { + /* we just parsed [resp-text-code] */ + parser->cur_type = ARG_PARSE_TEXT; + break; + } + + switch (data[0]) { + case '\r': + if (data_size == 1) { + /* wait for LF */ + return FALSE; + } + if (data[1] != '\n') { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "CR sent without LF"; + return FALSE; + } + /* fall through */ + case '\n': + /* unexpected end of line */ + if ((parser->flags & IMAP_PARSE_FLAG_INSIDE_LIST) != 0) { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Missing ')'"; + return FALSE; + } + parser->eol = TRUE; + return FALSE; + case '"': + parser->cur_type = ARG_PARSE_STRING; + parser->str_first_escape = -1; + break; + case '~': + /* This could be either literal8 or atom */ + if (data_size == 1) { + /* wait for the next char */ + return FALSE; + } + if (data[1] != '{') { + parser->cur_type = ARG_PARSE_ATOM; + break; + } + if ((parser->flags & IMAP_PARSE_FLAG_LITERAL8) == 0) { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "literal8 not allowed here"; + return FALSE; + } + parser->cur_type = ARG_PARSE_LITERAL8; + parser->literal_size = 0; + parser->literal_nonsync = FALSE; + parser->literal8 = TRUE; + break; + case '{': + parser->cur_type = ARG_PARSE_LITERAL; + parser->literal_size = 0; + parser->literal_nonsync = FALSE; + parser->literal8 = FALSE; + break; + case '(': + imap_parser_open_list(parser); + if ((parser->flags & IMAP_PARSE_FLAG_STOP_AT_LIST) != 0) { + i_stream_skip(parser->input, 1); + return FALSE; + } + break; + case ')': + if (!imap_parser_close_list(parser)) + return FALSE; + + if (parser->list_arg == NULL) { + /* end of argument */ + parser->cur_pos++; + return TRUE; + } + break; + default: + if (!is_valid_atom_char(parser, data[0])) + return FALSE; + parser->cur_type = ARG_PARSE_ATOM; + break; + } + + parser->cur_pos++; + } + + i_assert(data_size > 0); + + switch (parser->cur_type) { + case ARG_PARSE_ATOM: + if (!imap_parser_read_atom(parser, data, data_size)) + return FALSE; + if ((parser->flags & IMAP_PARSE_FLAG_SERVER_TEXT) == 0) + break; + + if (imap_parser_is_next_resp_text(parser)) { + /* we just parsed OK/NO/BAD/BYE. after parsing the + [resp-text-code] the rest of the message can contain + pretty much any random text, which we can't parse + as if it was valid IMAP input */ + parser->cur_resp_text = TRUE; + } + break; + case ARG_PARSE_STRING: + if (!imap_parser_read_string(parser, data, data_size)) + return FALSE; + break; + case ARG_PARSE_LITERAL8: + if (parser->cur_pos == data_size) + return FALSE; + if (data[parser->cur_pos] != '{') { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Expected '{'"; + return FALSE; + } + parser->cur_type = ARG_PARSE_LITERAL; + parser->cur_pos++; + /* fall through */ + case ARG_PARSE_LITERAL: + if (!imap_parser_read_literal(parser, data, data_size)) + return FALSE; + + /* pass through to parsing data. since input->skip was + modified, we need to get the data start position again. */ + data = i_stream_get_data(parser->input, &data_size); + + /* fall through */ + case ARG_PARSE_LITERAL_DATA: + case ARG_PARSE_LITERAL_DATA_FORCED: + if (!imap_parser_read_literal_data(parser, data, data_size)) + return FALSE; + break; + case ARG_PARSE_TEXT: + if (!imap_parser_read_text(parser, data, data_size)) + return FALSE; + break; + default: + i_unreached(); + } + + i_assert(parser->cur_type == ARG_PARSE_NONE); + return TRUE; +} + +static void list_add_ghost_eol(struct imap_arg *list_arg) +{ + struct imap_arg *arg; + + i_assert(list_arg->type == IMAP_ARG_LIST); + + arg = array_append_space(&list_arg->_data.list); + arg->type = IMAP_ARG_EOL; + array_pop_back(&list_arg->_data.list); + + if (list_arg->parent != NULL) + list_add_ghost_eol(list_arg->parent); +} + +/* ARG_PARSE_NONE checks that last argument isn't only partially parsed. */ +#define IS_UNFINISHED(parser) \ + ((parser)->cur_type != ARG_PARSE_NONE || \ + ((parser)->cur_list != &parser->root_list && \ + ((parser)->flags & IMAP_PARSE_FLAG_STOP_AT_LIST) == 0)) + +static int finish_line(struct imap_parser *parser, unsigned int count, + const struct imap_arg **args_r) +{ + struct imap_arg *arg; + int ret = array_count(&parser->root_list); + + parser->line_size += parser->cur_pos; + i_stream_skip(parser->input, parser->cur_pos); + parser->cur_pos = 0; + parser->cur_resp_text = FALSE; + + if (parser->list_arg == NULL) { + /* no open list */ + } else if (!parser->literal_size_return && + (parser->flags & IMAP_PARSE_FLAG_STOP_AT_LIST) == 0) { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Missing ')'"; + *args_r = NULL; + return -1; + } else { + list_add_ghost_eol(parser->list_arg); + } + + arg = array_append_space(&parser->root_list); + arg->type = IMAP_ARG_EOL; + parser->args_added_extra_eol = TRUE; + + *args_r = array_get(&parser->root_list, &count); + return ret; +} + +static void imap_parser_delete_extra_eol(struct imap_parser *parser) +{ + array_pop_back(&parser->root_list); + parser->args_added_extra_eol = FALSE; +} + +int imap_parser_read_args(struct imap_parser *parser, unsigned int count, + enum imap_parser_flags flags, + const struct imap_arg **args_r) +{ + parser->flags = flags; + + if (parser->args_added_extra_eol) { + /* delete EOL */ + imap_parser_delete_extra_eol(parser); + parser->literal_size_return = FALSE; + } + + while (!parser->eol && (count == 0 || IS_UNFINISHED(parser) || + array_count(&parser->root_list) < count)) { + if (!imap_parser_read_arg(parser)) + break; + + if (parser->line_size > parser->max_line_size) { + parser->error = IMAP_PARSE_ERROR_LINE_TOO_LONG; + parser->error_msg = "IMAP command line too large"; + break; + } + } + + if (parser->error != IMAP_PARSE_ERROR_NONE) { + /* error, abort */ + parser->line_size += parser->cur_pos; + i_stream_skip(parser->input, parser->cur_pos); + parser->cur_pos = 0; + *args_r = NULL; + return -1; + } else if ((!IS_UNFINISHED(parser) && count > 0 && + array_count(&parser->root_list) >= count) || + parser->eol || parser->literal_size_return) { + /* all arguments read / end of line. */ + return finish_line(parser, count, args_r); + } else { + /* need more data */ + *args_r = NULL; + return -2; + } +} + +static struct imap_arg * +imap_parser_get_last_literal_size(struct imap_parser *parser, + ARRAY_TYPE(imap_arg_list) **list_r) +{ + ARRAY_TYPE(imap_arg_list) *list; + struct imap_arg *args; + unsigned int count; + + list = &parser->root_list; + args = array_get_modifiable(&parser->root_list, &count); + i_assert(count > 1 && args[count-1].type == IMAP_ARG_EOL); + count--; + + while (args[count-1].type != IMAP_ARG_LITERAL_SIZE && + args[count-1].type != IMAP_ARG_LITERAL_SIZE_NONSYNC) { + if (args[count-1].type != IMAP_ARG_LIST) + return NULL; + + /* maybe the list ends with literal size */ + list = &args[count-1]._data.list; + args = array_get_modifiable(list, &count); + if (count == 0) + return NULL; + } + + *list_r = list; + return &args[count-1]; +} + +bool imap_parser_get_literal_size(struct imap_parser *parser, uoff_t *size_r) +{ + ARRAY_TYPE(imap_arg_list) *list; + struct imap_arg *last_arg; + + last_arg = imap_parser_get_last_literal_size(parser, &list); + if (last_arg == NULL) + return FALSE; + + return imap_arg_get_literal_size(last_arg, size_r); +} + +void imap_parser_read_last_literal(struct imap_parser *parser) +{ + ARRAY_TYPE(imap_arg_list) *list; + struct imap_arg *last_arg; + + i_assert(parser->literal_size_return); + i_assert(parser->args_added_extra_eol); + + last_arg = imap_parser_get_last_literal_size(parser, &list); + i_assert(last_arg != NULL); + + parser->cur_type = ARG_PARSE_LITERAL_DATA_FORCED; + i_assert(parser->literal_size == last_arg->_data.literal_size); + + /* delete EOL */ + imap_parser_delete_extra_eol(parser); + + /* delete literal size */ + array_pop_back(list); + parser->literal_size_return = FALSE; +} + +int imap_parser_finish_line(struct imap_parser *parser, unsigned int count, + enum imap_parser_flags flags, + const struct imap_arg **args_r) +{ + const unsigned char *data; + size_t data_size; + int ret; + + ret = imap_parser_read_args(parser, count, flags, args_r); + if (ret == -1) + return -1; + if (ret == -2) { + /* we should have noticed end of everything except atom */ + if (parser->cur_type == ARG_PARSE_ATOM) { + data = i_stream_get_data(parser->input, &data_size); + imap_parser_save_arg(parser, data, data_size); + } + } + return finish_line(parser, count, args_r); +} + +const char *imap_parser_read_word(struct imap_parser *parser) +{ + const unsigned char *data; + size_t i, data_size; + + data = i_stream_get_data(parser->input, &data_size); + + for (i = 0; i < data_size; i++) { + if (data[i] == ' ' || data[i] == '\r' || data[i] == '\n') + break; + } + + if (i < data_size) { + data_size = i + (data[i] == ' ' ? 1 : 0); + parser->line_size += data_size; + i_stream_skip(parser->input, data_size); + return p_strndup(parser->pool, data, i); + } else { + return NULL; + } +} + +static int +imap_parser_read_next_atom(struct imap_parser *parser, bool parsing_tag, + const char **atom_r) +{ + const unsigned char *data; + size_t i, data_size; + + data = i_stream_get_data(parser->input, &data_size); + + /* + tag = 1*<any ASTRING-CHAR except "+"> + ASTRING-CHAR = ATOM-CHAR / resp-specials + ATOM-CHAR = <any CHAR except atom-specials> + + x-command = "X" atom <experimental command arguments> + atom = 1*ATOM-CHAR + */ + for (i = 0; i < data_size; i++) { + /* explicitly check for atom-specials, because + IS_ATOM_PARSER_INPUT() allows some atom-specials */ + switch (data[i]) { + case ' ': + case '\r': + case '\n': + data_size = i + (data[i] == ' ' ? 1 : 0); + parser->line_size += data_size; + i_stream_skip(parser->input, data_size); + *atom_r = p_strndup(parser->pool, data, i); + /* don't allow empty string */ + return i == 0 ? -1 : 1; + /* atom-specials: */ + case '(': + case ')': + case '{': + /* list-wildcards: */ + case '%': + case '*': + /* quoted-specials: */ + case '"': + case '\\': + /* resp-specials: */ + case ']': + return -1; + case '+': + if (parsing_tag) + return -1; + break; + default: + if ((unsigned char)data[i] < ' ' || + (unsigned char)data[i] >= 0x80) + return -1; + } + } + return 0; +} + +int imap_parser_read_tag(struct imap_parser *parser, const char **tag_r) +{ + return imap_parser_read_next_atom(parser, TRUE, tag_r); +} + +int imap_parser_read_command_name(struct imap_parser *parser, + const char **name_r) +{ + return imap_parser_read_next_atom(parser, FALSE, name_r); +} + +int imap_parser_client_read_tag(struct imap_parser *parser, + const char **tag_r) +{ + return imap_parser_read_next_atom(parser, FALSE, tag_r); +} diff --git a/src/lib-imap/imap-parser.h b/src/lib-imap/imap-parser.h new file mode 100644 index 0000000..cd3748c --- /dev/null +++ b/src/lib-imap/imap-parser.h @@ -0,0 +1,117 @@ +#ifndef IMAP_PARSER_H +#define IMAP_PARSER_H + +#include "imap-arg.h" + +enum imap_parser_flags { + /* Set this flag if you wish to read only size of literal argument + and not convert literal into string. Useful when you need to deal + with large literal sizes. The literal must be the last read + parameter. */ + IMAP_PARSE_FLAG_LITERAL_SIZE = 0x01, + /* Don't remove '\' chars from string arguments */ + IMAP_PARSE_FLAG_NO_UNESCAPE = 0x02, + /* Return literals as IMAP_ARG_LITERAL instead of IMAP_ARG_STRING */ + IMAP_PARSE_FLAG_LITERAL_TYPE = 0x04, + /* Don't check if atom contains invalid characters */ + IMAP_PARSE_FLAG_ATOM_ALLCHARS = 0x08, + /* Allow strings to contain CRLFs */ + IMAP_PARSE_FLAG_MULTILINE_STR = 0x10, + /* Parse in list context; ')' parses as EOL */ + IMAP_PARSE_FLAG_INSIDE_LIST = 0x20, + /* Parse literal8 and set it as flag to imap_arg. */ + IMAP_PARSE_FLAG_LITERAL8 = 0x40, + /* We're parsing IMAP server replies. Parse the "text" after + OK/NO/BAD/BYE replies as a single atom. We assume that the initial + "*" or tag was already skipped over. */ + IMAP_PARSE_FLAG_SERVER_TEXT = 0x80, + /* Parse until '(' and return it as an empty list */ + IMAP_PARSE_FLAG_STOP_AT_LIST = 0x100 +}; + +enum imap_parser_error { + /* not fatal */ + IMAP_PARSE_ERROR_NONE = 0, + IMAP_PARSE_ERROR_BAD_SYNTAX, + IMAP_PARSE_ERROR_LINE_TOO_LONG, + /* fatal */ + IMAP_PARSE_ERROR_LITERAL_TOO_BIG +}; + +struct imap_parser; + +/* Create new IMAP argument parser. output is used for sending command + continuation requests for literals. + + max_line_size can be used to approximately limit the maximum amount of + memory that gets allocated when parsing a line. Input buffer size limits + the maximum size of each parsed token. + + Usually the largest lines are large only because they have a one huge + message set token, so you'll probably want to keep input buffer size the + same as max_line_size. That means the maximum memory usage is around + 2 * max_line_size. */ +struct imap_parser * +imap_parser_create(struct istream *input, struct ostream *output, + size_t max_line_size) ATTR_NULL(2); +void imap_parser_ref(struct imap_parser *parser); +void imap_parser_unref(struct imap_parser **parser); + +/* Enable LITERAL- parser semantics: non-synchronizing literals must not + exceed 4096 bytes */ +void imap_parser_enable_literal_minus(struct imap_parser *parser); + +/* Reset the parser to initial state. */ +void imap_parser_reset(struct imap_parser *parser); + +/* Change parser's input and output streams */ +void imap_parser_set_streams(struct imap_parser *parser, struct istream *input, + struct ostream *output) ATTR_NULL(3); + +/* Return the last error in parser. fatal is set to TRUE if there's no way to + continue parsing, currently only if too large non-sync literal size was + given. */ +const char *imap_parser_get_error(struct imap_parser *parser, + enum imap_parser_error *error_r) ATTR_NULL(2); + +/* Read a number of arguments. This function doesn't call i_stream_read(), you + need to do that. Returns number of arguments read (may be less than count + in case of EOL), -2 if more data is needed or -1 if error occurred. + + count-sized array of arguments are stored into args when return value is + 0 or larger. If all arguments weren't read, they're set to NIL. count + can be set to 0 to read all arguments in the line. Last element in + args is always of type IMAP_ARG_EOL. */ +int imap_parser_read_args(struct imap_parser *parser, unsigned int count, + enum imap_parser_flags flags, + const struct imap_arg **args_r); +/* If parsing ended with literal size, return it. */ +bool imap_parser_get_literal_size(struct imap_parser *parser, uoff_t *size_r); +/* IMAP_PARSE_FLAG_LITERAL_SIZE is set and last read argument was a literal. + Calling this function causes the literal size to be replaced with the actual + literal data when continuing argument parsing. */ +void imap_parser_read_last_literal(struct imap_parser *parser); + +/* just like imap_parser_read_args(), but assume \n at end of data in + input stream. */ +int imap_parser_finish_line(struct imap_parser *parser, unsigned int count, + enum imap_parser_flags flags, + const struct imap_arg **args_r); + +/* Read one word - used for reading tag and command name. + Returns NULL if more data is needed. */ +const char *imap_parser_read_word(struct imap_parser *parser); +/* Read command tag. Returns 1 if tag was returned, 0 if more data is needed, + -1 if input isn't a valid tag. */ +int imap_parser_read_tag(struct imap_parser *parser, const char **tag_r); +/* Read command name. Returns 1 if command name was returned, 0 if more data is + needed, -1 if input isn't a valid command name string. */ +int imap_parser_read_command_name(struct imap_parser *parser, + const char **name_r); +/* For IMAP clients: Read the command tag, which could also be "+" or "*". + Returns 1 if tag was returned, 0 if more data is needed, -1 if input isn't + valid. */ +int imap_parser_client_read_tag(struct imap_parser *parser, + const char **tag_r); + +#endif diff --git a/src/lib-imap/imap-quote.c b/src/lib-imap/imap-quote.c new file mode 100644 index 0000000..622e21c --- /dev/null +++ b/src/lib-imap/imap-quote.c @@ -0,0 +1,239 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "imap-arg.h" +#include "imap-quote.h" + +/* If we have quoted-specials (<">, <\>) in a string, the minimum quoted-string + overhead is 3 bytes ("\") while the minimum literal overhead is 5 bytes + ("{n}\r\n"). But the literal overhead also depends on the string size. If + the string length is less than 10, literal catches up to quoted-string after + 3 quoted-specials. If the string length is 10..99, it catches up after 4 + quoted-specials, and so on. We'll assume that the string lengths are usually + in double digits, so we'll switch to literals after seeing 4 + quoted-specials. */ +#define QUOTED_MAX_ESCAPE_CHARS 4 + +void imap_append_string(string_t *dest, const char *src) +{ + i_assert(src != NULL); + + imap_append_nstring(dest, src); +} + +void imap_append_astring(string_t *dest, const char *src) +{ + unsigned int i; + + i_assert(src != NULL); + + for (i = 0; src[i] != '\0'; i++) { + if (!IS_ASTRING_CHAR(src[i])) { + imap_append_string(dest, src); + return; + } + } + /* don't mix up NIL and "NIL"! */ + if (i == 0 || strcasecmp(src, "NIL") == 0) + imap_append_string(dest, src); + else + str_append(dest, src); +} + +static void +imap_append_literal(string_t *dest, const char *src, unsigned int pos) +{ + size_t full_len = pos + strlen(src+pos); + + str_printfa(dest, "{%zu}\r\n", full_len); + buffer_append(dest, src, full_len); +} + +void imap_append_nstring(string_t *dest, const char *src) +{ + unsigned int escape_count = 0; + size_t i; + + if (src == NULL) { + str_append(dest, "NIL"); + return; + } + + /* first check if we can (or want to) write this as quoted or + as literal. + + quoted-specials = DQUOTE / "\" + QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> / + "\" quoted-specials + TEXT-CHAR = <any CHAR except CR and LF> + */ + for (i = 0; src[i] != '\0'; i++) { + switch (src[i]) { + case '"': + case '\\': + if (escape_count++ < QUOTED_MAX_ESCAPE_CHARS) + break; + /* fall through */ + case 13: + case 10: + imap_append_literal(dest, src, i); + return; + default: + if ((unsigned char)src[i] >= 0x80) { + imap_append_literal(dest, src, i); + return; + } + break; + } + } + imap_append_quoted(dest, src); +} + +static void remove_newlines_and_append(string_t *dest, const char *src) +{ + size_t src_len; + string_t *src_nolf; + src_len = strlen(src); + src_nolf = t_str_new(src_len + 1); + for (size_t i = 0; i < src_len; ++i) { + if (src[i] != '\r' && src[i] != '\n') { + str_append_c(src_nolf, src[i]); + } else if (src[i+1] != ' ' && + src[i+1] != '\t' && + src[i+1] != '\r' && + src[i+1] != '\n' && + src[i+1] != '\0') { + /* ensure whitespace between lines if new line doesn't start with whitespace */ + str_append_c(src_nolf, ' '); + } + } + imap_append_nstring(dest, str_c(src_nolf)); +} + +void imap_append_nstring_nolf(string_t *dest, const char *src) +{ + if (src == NULL || strpbrk(src, "\r\n") == NULL) + imap_append_nstring(dest, src); + else if (buffer_get_pool(dest)->datastack_pool) + remove_newlines_and_append(dest, src); + else T_BEGIN { + remove_newlines_and_append(dest, src); + } T_END; +} + +void imap_append_quoted(string_t *dest, const char *src) +{ + str_append_c(dest, '"'); + for (; *src != '\0'; src++) { + switch (*src) { + case 13: + case 10: + /* not allowed */ + break; + case '"': + case '\\': + str_append_c(dest, '\\'); + str_append_c(dest, *src); + break; + default: + if ((unsigned char)*src >= 0x80) { + /* 8bit input not allowed in dquotes */ + break; + } + + str_append_c(dest, *src); + break; + } + } + str_append_c(dest, '"'); +} + +void imap_append_string_for_humans(string_t *dest, + const unsigned char *src, size_t size) +{ + size_t i, pos, remove_count = 0; + bool whitespace_prefix = TRUE, last_lwsp = TRUE, modify = FALSE; + + /* first check if there is anything to change */ + for (i = 0; i < size; i++) { + switch (src[i]) { + case 0: + /* convert NUL to #0x80 */ + last_lwsp = FALSE; + modify = TRUE; + break; + case 13: + case 10: + case '\t': + modify = TRUE; + /* fall through */ + case ' ': + if (last_lwsp) { + modify = TRUE; + remove_count++; + } + last_lwsp = TRUE; + break; + case '"': + case '\\': + modify = TRUE; + last_lwsp = FALSE; + break; + default: + if ((src[i] & 0x80) != 0) + modify = TRUE; + last_lwsp = FALSE; + break; + } + if (!last_lwsp) + whitespace_prefix = FALSE; + } + if (last_lwsp && i > 0 && !whitespace_prefix) { + modify = TRUE; + remove_count++; + } + if (!modify) { + /* fast path: we can simply write it as quoted string + without any escaping */ + str_append_c(dest, '"'); + str_append_data(dest, src, size); + str_append_c(dest, '"'); + return; + } + if (size == remove_count) { + /* contained only whitespace */ + str_append(dest, "\"\""); + return; + } + + str_printfa(dest, "{%zu}\r\n", size - remove_count); + pos = str_len(dest); + + last_lwsp = TRUE; whitespace_prefix = TRUE; + for (i = 0; i < size; i++) { + switch (src[i]) { + case 0: + str_append_c(dest, 128); + last_lwsp = FALSE; + break; + case 13: + case 10: + case '\t': + case ' ': + if (!last_lwsp) + str_append_c(dest, ' '); + last_lwsp = TRUE; + break; + default: + last_lwsp = FALSE; + str_append_c(dest, src[i]); + break; + } + if (!last_lwsp) + whitespace_prefix = FALSE; + } + if (last_lwsp && i > 0 && !whitespace_prefix) + str_truncate(dest, str_len(dest)-1); + i_assert(str_len(dest) - pos == size - remove_count); +} diff --git a/src/lib-imap/imap-quote.h b/src/lib-imap/imap-quote.h new file mode 100644 index 0000000..a397ec3 --- /dev/null +++ b/src/lib-imap/imap-quote.h @@ -0,0 +1,21 @@ +#ifndef IMAP_QUOTE_H +#define IMAP_QUOTE_H + +/* Append "quoted" or literal. */ +void imap_append_string(string_t *dest, const char *src); +/* Append atom, "quoted" or literal. */ +void imap_append_astring(string_t *dest, const char *src); +/* Append NIL, "quoted" or literal. */ +void imap_append_nstring(string_t *dest, const char *src); +/* Append NIL, "quoted" or literal, CRs and LFs skipped. */ +void imap_append_nstring_nolf(string_t *dest, const char *src); +/* Append "quoted". If src has 8bit chars, skip over them. */ +void imap_append_quoted(string_t *dest, const char *src); + +/* Otherwise the same as imap_append_string(), but cleanup the input data + so that it's more readable by humans. This includes converting TABs to + spaces, multiple spaces into a single space and NULs to #0x80. */ +void imap_append_string_for_humans(string_t *dest, + const unsigned char *src, size_t size); + +#endif diff --git a/src/lib-imap/imap-resp-code.h b/src/lib-imap/imap-resp-code.h new file mode 100644 index 0000000..a7a4de8 --- /dev/null +++ b/src/lib-imap/imap-resp-code.h @@ -0,0 +1,28 @@ +#ifndef IMAP_RESP_CODE_H +#define IMAP_RESP_CODE_H + +/* IMAP response codes (RFC 5530) */ +#define IMAP_RESP_CODE_UNAVAILABLE "UNAVAILABLE" +#define IMAP_RESP_CODE_AUTHFAILED "AUTHENTICATIONFAILED" +#define IMAP_RESP_CODE_AUTHZFAILED "AUTHORIZATIONFAILED" +#define IMAP_RESP_CODE_EXPIRED "EXPIRED" +#define IMAP_RESP_CODE_PRIVACYREQUIRED "PRIVACYREQUIRED" +#define IMAP_RESP_CODE_CONTACTADMIN "CONTACTADMIN" +#define IMAP_RESP_CODE_NOPERM "NOPERM" +#define IMAP_RESP_CODE_INUSE "INUSE" +#define IMAP_RESP_CODE_EXPUNGEISSUED "EXPUNGEISSUED" +#define IMAP_RESP_CODE_CORRUPTION "CORRUPTION" +#define IMAP_RESP_CODE_SERVERBUG "SERVERBUG" +#define IMAP_RESP_CODE_CLIENTBUG "CLIENTBUG" +#define IMAP_RESP_CODE_CANNOT "CANNOT" +#define IMAP_RESP_CODE_LIMIT "LIMIT" +#define IMAP_RESP_CODE_OVERQUOTA "OVERQUOTA" +#define IMAP_RESP_CODE_ALREADYEXISTS "ALREADYEXISTS" +#define IMAP_RESP_CODE_NONEXISTENT "NONEXISTENT" + +#define IMAP_RESP_CODE_UNKNOWN_CTE "UNKNOWN-CTE" /* BINARY */ + +/* IMAP standard (RFC 3501) */ +#define IMAP_RESP_CODE_PARSE "PARSE" + +#endif diff --git a/src/lib-imap/imap-seqset.c b/src/lib-imap/imap-seqset.c new file mode 100644 index 0000000..5e7ea21 --- /dev/null +++ b/src/lib-imap/imap-seqset.c @@ -0,0 +1,105 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "imap-seqset.h" + +static uint32_t get_next_number(const char **str) +{ + uint32_t num; + + num = 0; + while (**str != '\0') { + if (**str < '0' || **str > '9') + break; + + num = num*10 + (**str - '0'); + (*str)++; + } + + if (num == (uint32_t)-1) { + /* FIXME: ugly hack, we're using this number to mean the + last existing message. In reality UIDs should never get + this high, so we can quite safely just drop this one down. */ + num--; + } + + return num; +} + +static int +get_next_seq_range(const char **str, uint32_t *seq1_r, uint32_t *seq2_r) +{ + uint32_t seq1, seq2; + + if (**str == '*') { + /* last message */ + seq1 = (uint32_t)-1; + *str += 1; + } else { + seq1 = get_next_number(str); + if (seq1 == 0) + return -1; + } + + if (**str != ':') + seq2 = seq1; + else { + /* first:last range */ + *str += 1; + + if (**str == '*') { + seq2 = (uint32_t)-1; + *str += 1; + } else { + seq2 = get_next_number(str); + if (seq2 == 0) + return -1; + } + } + + if (seq1 > seq2) { + /* swap, as specified by RFC-3501 */ + *seq1_r = seq2; + *seq2_r = seq1; + } else { + *seq1_r = seq1; + *seq2_r = seq2; + } + return 0; +} + +int imap_seq_set_parse(const char *str, ARRAY_TYPE(seq_range) *dest) +{ + uint32_t seq1, seq2; + + while (*str != '\0') { + if (get_next_seq_range(&str, &seq1, &seq2) < 0) + return -1; + seq_range_array_add_range(dest, seq1, seq2); + + if (*str == ',') + str++; + else if (*str != '\0') + return -1; + } + return 0; +} + +int imap_seq_set_nostar_parse(const char *str, ARRAY_TYPE(seq_range) *dest) +{ + if (imap_seq_set_parse(str, dest) < 0) + return -1; + + if (seq_range_exists(dest, (uint32_t)-1)) { + /* '*' used */ + return -1; + } + return 0; +} + +int imap_seq_range_parse(const char *str, uint32_t *seq1_r, uint32_t *seq2_r) +{ + if (get_next_seq_range(&str, seq1_r, seq2_r) < 0) + return -1; + return *str == '\0' ? 0 : -1; +} diff --git a/src/lib-imap/imap-seqset.h b/src/lib-imap/imap-seqset.h new file mode 100644 index 0000000..a7e1ffd --- /dev/null +++ b/src/lib-imap/imap-seqset.h @@ -0,0 +1,15 @@ +#ifndef IMAP_SEQSET_H +#define IMAP_SEQSET_H + +#include "seq-range-array.h" + +/* Parse IMAP sequence-set and store the result in dest. '*' is stored as + (uint32_t)-1. Returns 0 if successful, -1 if input is invalid. */ +int imap_seq_set_parse(const char *str, ARRAY_TYPE(seq_range) *dest); +/* Like imap_seq_set_parse(), but fail if '*' is used. */ +int imap_seq_set_nostar_parse(const char *str, ARRAY_TYPE(seq_range) *dest); + +/* Parse IMAP seq-number / seq-range. */ +int imap_seq_range_parse(const char *str, uint32_t *seq1_r, uint32_t *seq2_r); + +#endif diff --git a/src/lib-imap/imap-url.c b/src/lib-imap/imap-url.c new file mode 100644 index 0000000..6da6e21 --- /dev/null +++ b/src/lib-imap/imap-url.c @@ -0,0 +1,1009 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "strfuncs.h" +#include "str-sanitize.h" +#include "hex-binary.h" +#include "net.h" +#include "iso8601-date.h" +#include "uri-util.h" + +#include "imap-url.h" + +#include <ctype.h> + +/* + * IMAP URL parsing + */ + +/* +IMAP URL Grammar overview + +RFC5092 Section 11: + +imapurl = "imap://" iserver ipath-query + ; Defines an absolute IMAP URL +iserver = [iuserinfo "@"] host [ ":" port ] + ; This is the same as "authority" defined in [URI-GEN]. +iuserinfo = enc-user [iauth] / [enc-user] iauth + ; conforms to the generic syntax of "userinfo" as + ; defined in [URI-GEN]. +enc-user = 1*achar + ; %-encoded version of [IMAP4] authorization identity or + ; "userid". +iauth = ";AUTH=" ( "*" / enc-auth-type ) +enc-auth-type = 1*achar + ; %-encoded version of [IMAP4] "auth-type" +ipath-query = ["/" [ icommand ]] + ; Corresponds to "path-abempty [ "?" query ]" in + ; [URI-GEN] +icommand = imessagelist / + imessagepart [iurlauth] +imessagelist = imailbox-ref [ "?" enc-search ] + ; "enc-search" is [URI-GEN] "query". +imessagepart = imailbox-ref iuid [isection] [ipartial] +imailbox-ref = enc-mailbox [uidvalidity] +uidvalidity = ";UIDVALIDITY=" nz-number + ; See [IMAP4] for "nz-number" definition +iuid = "/" iuid-only +iuid-only = ";UID=" nz-number + ; See [IMAP4] for "nz-number" definition +isection = "/" isection-only +isection-only = ";SECTION=" enc-section +ipartial = "/" ipartial-only +ipartial-only = ";PARTIAL=" partial-range +enc-search = 1*bchar + ; %-encoded version of [IMAPABNF] + ; "search-program". Note that IMAP4 + ; literals may not be used in + ; a "search-program", i.e., only + ; quoted or non-synchronizing + ; literals (if the server supports + ; LITERAL+ [LITERAL+]) are allowed. +enc-mailbox = 1*bchar + ; %-encoded version of [IMAP4] "mailbox" +enc-section = 1*bchar + ; %-encoded version of [IMAP4] "section-spec" +partial-range = number ["." nz-number] + ; partial FETCH. The first number is + ; the offset of the first byte, + ; the second number is the length of + ; the fragment. +bchar = achar / ":" / "@" / "/" +achar = uchar / "&" / "=" + ;; Same as [URI-GEN] 'unreserved / sub-delims / + ;; pct-encoded', but ";" is disallowed. +uchar = unreserved / sub-delims-sh / pct-encoded +sub-delims-sh = "!" / "$" / "'" / "(" / ")" / + "*" / "+" / "," + ;; Same as [URI-GEN] sub-delims, + ;; but without ";", "&" and "=". + +The following rules are only used in the presence of the IMAP +[URLAUTH] extension: + +authimapurl = "imap://" iserver "/" imessagepart + ; Same as "imapurl" when "[icommand]" is + ; "imessagepart" +authimapurlfull = authimapurl iurlauth + ; Same as "imapurl" when "[icommand]" is + ; "imessagepart iurlauth" +authimapurlrump = authimapurl iurlauth-rump + +iurlauth = iurlauth-rump iua-verifier +enc-urlauth = 32*HEXDIG +iua-verifier = ":" uauth-mechanism ":" enc-urlauth +iurlauth-rump = [expire] ";URLAUTH=" access +access = ("submit+" enc-user) / ("user+" enc-user) / + "authuser" / "anonymous" +expire = ";EXPIRE=" date-time + ; date-time is defined in [DATETIME] +uauth-mechanism = "INTERNAL" / 1*(ALPHA / DIGIT / "-" / ".") + ; Case-insensitive. + +[URI-GEN] RFC3986 Appendix A: + +Implemented in src/lib/uri-util.c + +*/ + +/* + * Imap URL parser + */ + +struct imap_url_parser { + struct uri_parser parser; + + enum imap_url_parse_flags flags; + + struct imap_url *url; + const struct imap_url *base; + + bool relative:1; +}; + +static int +imap_url_parse_number(struct uri_parser *parser, const char *data, + uint32_t *number_r) +{ + /* [IMAP4] RFC3501, Section 9 + * + * number = 1*DIGIT + * ; Unsigned 32-bit integer + * ; (0 <= n < 4,294,967,296) + */ + + if (i_isdigit(*data)) { + if (str_to_uint32(data, number_r) == 0) + return 1; + parser->error = "IMAP number is too high"; + return -1; + } + + parser->error = t_strdup_printf( + "Value '%s' is not a valid IMAP number", data); + return -1; +} + +static int +imap_url_parse_offset(struct uri_parser *parser, const char *data, + uoff_t *number_r) +{ + /* Syntax for big (uoff_t) numbers. Not strictly IMAP syntax, but this + is handled similarly for Dovecot IMAP FETCH BODY partial <.> + implementation. */ + if (i_isdigit(*data)) { + if (str_to_uoff(data, number_r) == 0) + return 1; + parser->error = "IMAP number is too high"; + return -1; + } + + parser->error = t_strdup_printf( + "Value '%s' is not a valid IMAP number", data); + return -1; +} + +static int imap_url_parse_iserver(struct imap_url_parser *url_parser) +{ + struct uri_parser *parser = &url_parser->parser; + struct uri_authority auth; + struct imap_url *url = url_parser->url; + const char *data; + int ret = 0; + + /* imapurl = "imap://" iserver {...} + * inetwork-path = "//" iserver {...} + * iserver = [iuserinfo "@"] host [":" port] + * ; This is the same as "authority" defined + * ; in [URI-GEN]. + * iuserinfo = enc-user [iauth] / [enc-user] iauth + * ; conforms to the generic syntax of "userinfo" as + * ; defined in [URI-GEN]. + * enc-user = 1*achar + * ; %-encoded version of [IMAP4] authorization identity or + * ; "userid". + * iauth = ";AUTH=" ( "*" / enc-auth-type ) + * enc-auth-type = 1*achar + * ; %-encoded version of [IMAP4] "auth-type" + */ + + /* "//" iserver */ + if ((ret = uri_parse_slashslash_host_authority + (parser, &auth)) <= 0) + return ret; + if (auth.host.name == NULL || *auth.host.name == '\0') { + /* This situation is not documented anywhere, but it is not + currently useful either and potentially problematic if not + handled explicitly everywhere. So, it is denied hier for now. + */ + parser->error = "IMAP URL does not allow empty host identifier"; + return -1; + } + /* iuserinfo = enc-user [iauth] / [enc-user] iauth */ + if (auth.enc_userinfo != NULL) { + const char *p, *uend; + + /* Scan for ";AUTH=" */ + for (p = auth.enc_userinfo; *p != '\0'; p++) { + if (*p == ';') + break; + /* check for unallowed userinfo characters */ + if (*p == ':') { + parser->error = t_strdup_printf( + "Stray ':' in userinfo `%s'", auth.enc_userinfo); + return -1; + } + } + + uend = p; + + if (*p == ';') { + if (strncasecmp(p, ";AUTH=", 6) != 0) { + parser->error = t_strdup_printf( + "Stray ';' in userinfo `%s'", + auth.enc_userinfo); + return -1; + } + + for (p += 6; *p != '\0'; p++) { + if (*p == ';' || *p == ':') { + parser->error = t_strdup_printf( + "Stray '%c' in userinfo `%s'", *p, auth.enc_userinfo); + return -1; + } + } + } + + /* enc-user */ + if (url != NULL && uend > auth.enc_userinfo) { + if (!uri_data_decode(parser, auth.enc_userinfo, uend, &data)) + return -1; + url->userid = p_strdup(parser->pool, data); + } + + /* ( "*" / enc-auth-type ) */ + if (*uend == ';') { + p = uend + 6; + if (*p == '\0') { + parser->error = "Empty auth-type value after ';AUTH='"; + return -1; + } + if (url != NULL) { + if (!uri_data_decode(parser, p, NULL, &data)) + return -1; + url->auth_type = p_strdup(parser->pool, data); + } + } + } + + if (url != NULL) { + url->host = auth.host; + url->port = auth.port; + } + return 1; +} + +static int +imap_url_parse_urlauth(struct imap_url_parser *url_parser, const char *urlext) +{ + struct uri_parser *parser = &url_parser->parser; + struct imap_url *url = url_parser->url; + const char *p, *q, *data; + buffer_t *uauth_token; + time_t expire = (time_t)-1; + int tz; + + /* iurlauth = iurlauth-rump iua-verifier + * enc-urlauth = 32*HEXDIG + * iua-verifier = ":" uauth-mechanism ":" enc-urlauth + * iurlauth-rump = [expire] ";URLAUTH=" access + * access = ("submit+" enc-user) / ("user+" enc-user) / + * "authuser" / "anonymous" + * expire = ";EXPIRE=" date-time + * ; date-time is defined in [DATETIME] + * uauth-mechanism = "INTERNAL" / 1*(ALPHA / DIGIT / "-" / ".") + * ; Case-insensitive. + */ + + /* ";EXPIRE=" date-time */ + if (strncasecmp(urlext, ";EXPIRE=", 8) == 0) { + if ((url_parser->flags & IMAP_URL_PARSE_ALLOW_URLAUTH) == 0) { + parser->error = "`;EXPIRE=' is not allowed in this context"; + return -1; + } + + if ((p = strchr(urlext+8, ';')) != NULL) { + if (!iso8601_date_parse((const unsigned char *)urlext+8, + p-urlext-8, &expire, &tz)) { + parser->error = "invalid date-time for `;EXPIRE='"; + return -1; + } + urlext = p; + } + } + + /* ";URLAUTH=" access */ + if (strncasecmp(urlext, ";URLAUTH=", 9) != 0) { + if (expire != (time_t)-1) { + parser->error = "`;EXPIRE=' without `;URLAUTH='"; + return -1; + } + return 0; + } + urlext += 9; + + if (url != NULL) + url->uauth_expire = expire; + + if ((url_parser->flags & IMAP_URL_PARSE_ALLOW_URLAUTH) == 0) { + parser->error = "`;URLAUTH=' is not allowed in this context"; + return -1; + } + + if (url_parser->relative) { + parser->error = "IMAP URLAUTH requires absolute URL"; + return -1; + } + + if ((p = strchr(urlext, ':')) == NULL) { + size_t len = strlen(urlext); + if (len == 0) { + parser->error = "Missing URLAUTH access specifier"; + return -1; + } + p = urlext+len; + } else if (p == urlext) { + parser->error = "Empty URLAUTH access specifier"; + return -1; + } + + /* parse access */ + if ((q = strchr(urlext, '+')) == NULL) { + /* application */ + if (url != NULL) { + url->uauth_access_application = + p_strdup_until(parser->pool, urlext, p); + } + } else { + /* application "+" enc-user */ + if (urlext == q) { + parser->error = "Empty URLAUTH access application"; + return -1; + } + if (q+1 == p) { + parser->error = t_strdup_printf( + "Empty URLAUTH access user for `%s' application", + t_strdup_until(urlext, q)); + return -1; + } + if (!uri_data_decode(parser, q+1, p, &data)) + return -1; + if (url != NULL) { + url->uauth_access_application = + p_strdup_until(parser->pool, urlext, q); + url->uauth_access_user = p_strdup(parser->pool, data); + } + } + + if (url != NULL) { + /* get rump url */ + if ((url_parser->flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0) { + url->uauth_rumpurl = p_strdup_until(parser->pool, + parser->begin, parser->end-strlen(p)); + } else { + url->uauth_rumpurl = p_strconcat(parser->pool, "imap:", + t_strdup_until(parser->begin, parser->end-strlen(p)), + NULL); + } + } + + if (*p == '\0') { + /* rump url; caller should check whether this is appropriate */ + return 1; + } + + /* iua-verifier = ":" uauth-mechanism ":" enc-urlauth */ + + q = p + 1; + if (*q == '\0') { + parser->error = "Missing URLAUTH verifier"; + return -1; + } + if ((p = strchr(q, ':')) == NULL || p[1] == '\0') { + parser->error = "Missing URLAUTH token"; + return -1; + } + if (p == q) { + parser->error = "Missing URLAUTH mechanism"; + return -1; + } + if (url != NULL) { + /* get mechanism */ + url->uauth_mechanism = p_strdup_until(parser->pool, q, p); + } + + /* enc-urlauth = 32*HEXDIG */ + + q = p+1; + if (strlen(q) < 32) { + parser->error = "Too short URLAUTH token"; + return -1; + } + + uauth_token = t_buffer_create(64); + if (hex_to_binary(q, uauth_token) < 0) { + parser->error = "Invalid URLAUTH token"; + return -1; + } + + if (url != NULL) { + url->uauth_token = uauth_token->data; + url->uauth_token_size = uauth_token->used; + } + return 1; +} + +static int +imap_url_parse_path(struct imap_url_parser *url_parser, + const char *const *path, int relative, + bool *is_messagelist_r) +{ + struct uri_parser *parser = &url_parser->parser; + struct imap_url *url = url_parser->url; + const char *const *segment; + string_t *mailbox, *section = NULL; + uint32_t uid = 0, uidvalidity = 0; + uoff_t partial_offset = 0, partial_size = 0; + bool have_partial = FALSE; + const char *p, *value, *urlext = NULL; + bool mailbox_endslash = FALSE, section_endslash = FALSE; + int ret; + + /* icommand = imessagelist / + * imessagepart [iurlauth] + * imessagelist = imailbox-ref [ "?" enc-search ] + * ; "enc-search" is [URI-GEN] "query". + * imessagepart = imailbox-ref iuid [isection] [ipartial] + * imailbox-ref = enc-mailbox [uidvalidity] + * uidvalidity = ";UIDVALIDITY=" nz-number + * iuid = "/" iuid-only + * iuid-only = ";UID=" nz-number + * ; See [IMAP4] for "nz-number" definition + * isection = "/" isection-only + * isection-only = ";SECTION=" enc-section + * ipartial = "/" ipartial-only + * ipartial-only = ";PARTIAL=" partial-range + * enc-mailbox = 1*bchar + * ; %-encoded version of [IMAP4] "mailbox" + * enc-section = 1*bchar + * ; %-encoded version of [IMAP4] "section-spec" + * partial-range = number ["." nz-number] + * ; partial FETCH. The first number is + * ; the offset of the first byte, + * ; the second number is the length of + * ; the fragment. + */ + + /* IMAP URL syntax is quite horrible to parse. It relies upon the + generic URI path resolution, but the icommand syntax also relies on + ';' separators. We use the generic URI path parse functions to + adhere to the URI path resolution rules and glue back together path + segments when these are part of the same (mailbox or section) value. + */ + + mailbox = t_str_new(256); + segment = path; + + /* Resolve relative URI path; determine what to copy from the base URI */ + if (url != NULL && url_parser->base != NULL && relative > 0) { + const struct imap_url *base = url_parser->base; + int rel = relative; + + /* /;PARTIAL= */ + if (base->have_partial && --rel <= 0) { + have_partial = base->have_partial; + partial_offset = base->partial_offset; + partial_size = base->partial_size; + } + /* /;SECTION= */ + if (base->section != NULL) { + p = base->section + strlen(base->section); + /* determine what to retain from base section path */ + for (; p > base->section && rel > 0; p--) { + if (*p =='/' && --rel <= 0) break; + } + if (--rel <= 0 && p > base->section) { + if (p[-1] == '/') section_endslash = TRUE; + if (section == NULL) + section = t_str_new(256); + str_append_data(section, base->section, p-base->section); + } + } + /* /;UID= */ + if (base->uid > 0 && --rel <= 0) { + uid = base->uid; + } + /* /mail/box;UIDVALIDITY= */ + if (base->mailbox != NULL) { + uidvalidity = base->uidvalidity; + p = base->mailbox + strlen(base->mailbox); + /* mailbox has implicit trailing '/' */ + if (p[-1] != '/' && base->uid == 0 && rel > 0) + rel--; + /* determine what to retain from base mailbox path */ + for (; p > base->mailbox && rel > 0; p--) { + if (*p =='/') { + uidvalidity = 0; + if (--rel <= 0) + break; + } + } + if (--rel <= 0 && p > base->mailbox) { + if (p[-1] == '/') + mailbox_endslash = TRUE; + str_append_data(mailbox, base->mailbox, + p - base->mailbox); + } + } + } + + /* Scan for last mailbox-ref segment */ + if (segment != NULL) { + if (relative == 0 || (!have_partial && section == NULL)) { + p = NULL; + while (*segment != NULL) { + /* ';' must be pct-encoded; if it is not, this is + either the last mailbox-ref path segment containing + ';UIDVALIDITY=' or the subsequent iuid ';UID=' path + segment */ + if ((p = strchr(*segment, ';')) != NULL) + break; + + if (**segment != '\0') { + if (segment > path || + (!mailbox_endslash && str_len(mailbox) > 0)) + str_append_c(mailbox, '/'); + if (!uri_data_decode(parser, *segment, NULL, &value)) + return -1; + str_append(mailbox, value); + mailbox_endslash = FALSE; + } + segment++; + } + + /* Handle ';' */ + if (p != NULL) { + /* [uidvalidity] */ + if (strncasecmp(p, ";UIDVALIDITY=", 13) == 0) { + /* append last bit of mailbox */ + if (*segment != p) { + if (segment > path || + (!mailbox_endslash && str_len(mailbox) > 0)) + str_append_c(mailbox, '/'); + if (!uri_data_decode(parser, *segment, p, &value)) + return -1; + str_append(mailbox, value); + } + + /* ";UIDVALIDITY=" nz-number */ + if (strchr(p+13, ';') != NULL) { + parser->error = "Encountered stray ';' after UIDVALIDITY"; + return -1; + } + + /* nz-number */ + if (p[13] == '\0') { + parser->error = "Empty UIDVALIDITY value"; + return -1; + } + if (imap_url_parse_number(parser, p+13, &uidvalidity) <= 0) + return -1; + if (uidvalidity == 0) { + parser->error = "UIDVALIDITY cannot be zero"; + return -1; + } + segment++; + } else if (p != *segment) { + parser->error = "Encountered stray ';' in mailbox reference"; + return -1; + } + } + + /* iuid */ + if (*segment != NULL && strncasecmp(*segment, ";UID=", 5) == 0) { + /* ";UID=" nz-number */ + value = (*segment)+5; + if ((p = strchr(value,';')) != NULL) { + if (segment[1] != NULL ) { + /* not the last segment, so it cannot be extension like iurlauth */ + parser->error = "Encountered stray ';' in UID path segment"; + return -1; + } + urlext = p; + value = t_strdup_until(value, p); + } + /* nz-number */ + if (*value == '\0') { + parser->error = "Empty UID value"; + return -1; + } + if (imap_url_parse_number(parser, value, &uid) <= 0) + return -1; + if (uid == 0) { + parser->error = "UID cannot be zero"; + return -1; + } + segment++; + } + } + + /* [isection] [ipartial] */ + if (*segment != NULL && uid > 0) { + /* [isection] */ + if (section != NULL || + strncasecmp(*segment, ";SECTION=", 9) == 0) { + /* ";SECTION=" enc-section */ + if (section == NULL) { + section = t_str_new(256); + value = (*segment) + 9; + } else { + value = *segment; + } + + /* enc-section can contain slashes, so we merge path segments until one + contains ';' */ + while ((p = strchr(value,';')) == NULL) { + if (!section_endslash && str_len(section) > 0) + str_append_c(section, '/'); + if (*value != '\0') { + if (!uri_data_decode(parser, value, NULL, &value)) + return -1; + str_append(section, value); + section_endslash = FALSE; + } + + segment++; + if (*segment == NULL) + break; + value = *segment; + } + + if (p != NULL) { + /* found ';' */ + if (p != value) { + /* it is not at the beginning of the path segment */ + if (segment[1] != NULL) { + /* not the last segment, so it cannot be extension like iurlauth */ + parser->error = "Encountered stray ';' in SECTION path segment"; + return -1; + } + urlext = p; + value = t_strdup_until(value, p); + if (!section_endslash && str_len(section) > 0) + str_append_c(section, '/'); + if (!uri_data_decode(parser, value, NULL, &value)) + return -1; + str_append(section, value); + segment++; + } + } + + if (str_len(section) == 0) { + parser->error = "Empty SECTION value"; + return -1; + } + } + + /* [ipartial] */ + if (*segment != NULL && + strncasecmp(*segment, ";PARTIAL=", 9) == 0) { + have_partial = TRUE; + + /* ";PARTIAL=" partial-range */ + value = (*segment) + 9; + if ((p = strchr(value,';')) != NULL) { + urlext = p; + value = t_strdup_until(value, p); + } + if (*value == '\0') { + parser->error = "Empty PARTIAL value"; + return -1; + } + /* partial-range = number ["." nz-number] */ + if ((p = strchr(value,'.')) != NULL) { + if (p[1] == '\0') { + parser->error = "Empty PARTIAL size"; + return -1; + } + if (imap_url_parse_offset(parser, p+1, &partial_size) <= 0) + return -1; + if (partial_size == 0) { + parser->error = "PARTIAL size cannot be zero"; + return -1; + } + value = t_strdup_until(value, p); + if (*value == '\0') { + parser->error = "Empty PARTIAL offset"; + return -1; + } + } + if (imap_url_parse_offset(parser,value, &partial_offset) <= 0) + return -1; + segment++; + } + } + + if (*segment != NULL) { + if (urlext != NULL || **segment != '\0' || *(segment+1) != NULL ) { + parser->error = t_strdup_printf( + "Unexpected IMAP URL path segment: `%s'", + str_sanitize(*segment, 80)); + return -1; + } + } + } + + /* ";" {...} at end of URL */ + if (urlext != NULL) { + /* [iurlauth] */ + if ((ret = imap_url_parse_urlauth(url_parser, urlext)) < 0) + return ret; + else if (ret == 0) { + /* something else */ + parser->error = t_strdup_printf( + "Unrecognized IMAP URL extension: %s", + str_sanitize(urlext, 80)); + return -1; + } + } + + if (is_messagelist_r != NULL) + *is_messagelist_r = (uid == 0); + + if (url != NULL) { + if (str_len(mailbox) > 0) + url->mailbox = p_strdup(parser->pool, str_c(mailbox)); + url->uidvalidity = uidvalidity; + url->uid = uid; + if (section != NULL) + url->section = p_strdup(parser->pool, str_c(section)); + url->have_partial = have_partial; + url->partial_offset = partial_offset; + url->partial_size = partial_size; + } + return 1; +} + +static bool imap_url_do_parse(struct imap_url_parser *url_parser) +{ + struct uri_parser *parser = &url_parser->parser; + const char *const *path; + bool is_messagelist = FALSE; + bool have_scheme = FALSE; + int relative; + const char *query; + int ret, sret; + + /* + * imapurl = "imap://" iserver ipath-query + * ; Defines an absolute IMAP URL + * iserver = [iuserinfo "@"] host [":" port] + * ; This is the same as "authority" defined + * ; in [URI-GEN]. + * ipath-query = ["/" [ icommand ]] + * ; Corresponds to "path-abempty [ "?" query ]" in + * ; [URI-GEN] + * icommand = imessagelist / + * imessagepart [iurlauth] + * imessagelist = imailbox-ref [ "?" enc-search ] + * ; "enc-search" is [URI-GEN] "query". + * imessagepart = imailbox-ref iuid [isection] [ipartial] + * enc-search = 1*bchar + * ; %-encoded version of [IMAPABNF] + * ; "search-program". Note that IMAP4 + * ; literals may not be used in + * ; a "search-program", i.e., only + * ; quoted or non-synchronizing + * ; literals (if the server supports + * ; LITERAL+ [LITERAL+]) are allowed. + */ + + /* "imap:" */ + if ((url_parser->flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0) { + const char *scheme; + + if (uri_parse_scheme(parser, &scheme) <= 0) { + parser->cur = parser->begin; + } else { + if (strcasecmp(scheme, "imap") != 0) { + parser->error = "Not an IMAP URL"; + return FALSE; + } + have_scheme = TRUE; + } + } else { + have_scheme = TRUE; + } + + /* "//" iserver */ + if ((sret = imap_url_parse_iserver(url_parser)) < 0) + return FALSE; + + if (have_scheme && sret == 0) { + parser->error = "Absolute IMAP URL requires `//' after `imap:'"; + return FALSE; + } + + if (sret > 0 && + (url_parser->flags & IMAP_URL_PARSE_REQUIRE_RELATIVE) != 0) { + parser->error = "Relative URL required"; + return FALSE; + } + + /* ipath-query = ["/" [ icommand ]] ; excludes `[ "?" enc-search ]` */ + if ((ret = uri_parse_path(parser, &relative, &path)) < 0) + return FALSE; + + /* Relative urls are only valid when we have a base url */ + if (sret == 0) { + if (url_parser->base == NULL) { + parser->error = "Relative URL not allowed"; + return FALSE; + } else if (url_parser->url != NULL) { + struct imap_url *url = url_parser->url; + const struct imap_url *base = url_parser->base; + + uri_host_copy(parser->pool, &url->host, &base->host); + url->port = base->port; + url->userid = p_strdup_empty(parser->pool, base->userid); + url->auth_type = p_strdup_empty(parser->pool, base->auth_type); + } + + url_parser->relative = TRUE; + } + + /* Parse path, i.e. `[ icommand ]` from `*( "/" segment )` */ + if (ret > 0 || url_parser->relative) { + if (imap_url_parse_path(url_parser, path, relative, + &is_messagelist) < 0) + return FALSE; + } + + /* [ "?" enc-search ] */ + if ((ret = uri_parse_query(parser, &query)) != 0) { + if (ret < 0) + return FALSE; + + if (!is_messagelist) { + parser->error = + "Search query part only valid for messagelist-type IMAP URL"; + return FALSE; + } else if (*query == '\0') { + parser->error = "Empty IMAP URL search query not allowed"; + return FALSE; + } + + if (url_parser->url != NULL) { + if (!uri_data_decode(parser, query, NULL, &query)) + return FALSE; + url_parser->url->search_program = + p_strdup(parser->pool, query); + } + } + + /* IMAP URL has no fragment */ + if ((ret = uri_parse_fragment(parser, &query)) != 0) { + if (ret == 1) + parser->error = "Fragment component not allowed in IMAP URL"; + return FALSE; + } + + /* must be at end of URL now */ + i_assert(parser->cur == parser->end); + + return TRUE; +} + +/* Public API */ + +int imap_url_parse(const char *url, const struct imap_url *base, + enum imap_url_parse_flags flags, + struct imap_url **url_r, const char **error_r) +{ + struct imap_url_parser url_parser; + + /* base != NULL indicates whether relative URLs are allowed. However, certain + flags may also dictate whether relative URLs are allowed/required. */ + i_assert((flags & IMAP_URL_PARSE_REQUIRE_RELATIVE) == 0 || base != NULL); + i_assert((flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0 || base == NULL); + + i_zero(&url_parser); + uri_parser_init(&url_parser.parser, pool_datastack_create(), url); + + url_parser.url = t_new(struct imap_url, 1); + url_parser.url->uauth_expire = (time_t)-1; + url_parser.base = base; + url_parser.flags = flags; + + if (!imap_url_do_parse(&url_parser)) { + *error_r = url_parser.parser.error; + return -1; + } + *url_r = url_parser.url; + return 0; +} + +/* + * IMAP URL construction + */ + +static void +imap_url_append_mailbox(const struct imap_url *url, string_t *urlstr) +{ + uri_append_path_data(urlstr, ";", url->mailbox); + if (url->uidvalidity != 0) + str_printfa(urlstr, ";UIDVALIDITY=%u", url->uidvalidity); + if (url->uid == 0) { + /* message list */ + if (url->search_program != NULL) { + str_append_c(urlstr, '?'); + uri_append_query_data(urlstr, ";", url->search_program); + } + } else { + /* message part */ + str_printfa(urlstr, "/;UID=%u", url->uid); + if (url->section != NULL) { + str_append(urlstr, "/;SECTION="); + uri_append_path_data(urlstr, ";", url->section); + } + if (url->have_partial) { + str_append(urlstr, "/;PARTIAL="); + if (url->partial_size == 0) { + str_printfa(urlstr, "%"PRIuUOFF_T, + url->partial_offset); + } else { + str_printfa(urlstr, "%"PRIuUOFF_T".%"PRIuUOFF_T, + url->partial_offset, + url->partial_size); + } + } + + /* urlauth */ + if (url->uauth_access_application != NULL) { + if (url->uauth_expire != (time_t)-1) { + str_append(urlstr, ";EXPIRE="); + str_append(urlstr, iso8601_date_create(url->uauth_expire)); + } + str_append(urlstr, ";URLAUTH="); + str_append(urlstr, url->uauth_access_application); + if (url->uauth_access_user != NULL) { + str_append_c(urlstr, '+'); + uri_append_user_data(urlstr, ";", + url->uauth_access_user); + } + } + } +} + +const char *imap_url_create(const struct imap_url *url) +{ + string_t *urlstr = t_str_new(512); + + /* scheme */ + uri_append_scheme(urlstr, "imap"); + str_append(urlstr, "//"); + + /* user */ + if (url->userid != NULL || url->auth_type != NULL) { + if (url->userid != NULL) + uri_append_user_data(urlstr, ";:", url->userid); + if (url->auth_type != NULL) { + str_append(urlstr, ";AUTH="); + uri_append_user_data(urlstr, ";:", url->auth_type); + } + str_append_c(urlstr, '@'); + } + + /* server */ + uri_append_host(urlstr, &url->host); + uri_append_port(urlstr, url->port); + + /* Older syntax (RFC 2192) requires this slash at all times */ + str_append_c(urlstr, '/'); + + /* mailbox */ + if (url->mailbox != NULL) + imap_url_append_mailbox(url, urlstr); + return str_c(urlstr); +} + +const char * +imap_url_add_urlauth(const char *rumpurl, const char *mechanism, + const unsigned char *token, size_t token_len) +{ + return t_strconcat(rumpurl, ":", t_str_lcase(mechanism), ":", + binary_to_hex(token, token_len), NULL); +} diff --git a/src/lib-imap/imap-url.h b/src/lib-imap/imap-url.h new file mode 100644 index 0000000..9f0b2aa --- /dev/null +++ b/src/lib-imap/imap-url.h @@ -0,0 +1,71 @@ +#ifndef IMAP_URL_H +#define IMAP_URL_H + +#include "uri-util.h" + +struct imap_url { + /* server */ + struct uri_host host; + in_port_t port; + + /* user */ + const char *userid; + const char *auth_type; + + /* mailbox */ + const char *mailbox; + uint32_t uidvalidity; /* 0 if not set */ + + /* message part */ + uint32_t uid; + const char *section; + uoff_t partial_offset; + uoff_t partial_size; /* 0 if not set */ + + /* message list (uid == 0) */ + const char *search_program; + + /* urlauth */ + const char *uauth_rumpurl; + const char *uauth_access_application; + const char *uauth_access_user; + const char *uauth_mechanism; + const unsigned char *uauth_token; + size_t uauth_token_size; + time_t uauth_expire; /* (time_t)-1 if not set */ + + bool have_partial:1; +}; + +/* + * IMAP URL parsing + */ + +enum imap_url_parse_flags { + /* Scheme part 'imap:' is already parsed externally. This implies that + this is an absolute IMAP URL. */ + IMAP_URL_PARSE_SCHEME_EXTERNAL = 0x01, + /* Require relative URL (omitting _both_ scheme and authority), e.g. + /MAILBOX/;UID=uid or even ;UID=uid. This flag means that an absolute + URL makes no sense in this context. Relative URLs are allowed once a + base URL is provided to the parser. */ + IMAP_URL_PARSE_REQUIRE_RELATIVE = 0x02, + /* Allow URLAUTH URL */ + IMAP_URL_PARSE_ALLOW_URLAUTH = 0x04 +}; + +/* Parses full IMAP URL. The returned URL is allocated from data stack. */ +int imap_url_parse(const char *url, const struct imap_url *base, + enum imap_url_parse_flags flags, + struct imap_url **url_r, const char **error_r); + +/* + * IMAP URL construction + */ + +const char *imap_url_create(const struct imap_url *url); + +const char *imap_url_add_urlauth(const char *rumpurl, const char *mechanism, + const unsigned char *token, size_t token_len); + +#endif diff --git a/src/lib-imap/imap-utf7.c b/src/lib-imap/imap-utf7.c new file mode 100644 index 0000000..7ea53f5 --- /dev/null +++ b/src/lib-imap/imap-utf7.c @@ -0,0 +1,380 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "unichar.h" +#include "imap-utf7.h" + +static const char imap_b64enc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; + +#define XX 0xff +static const unsigned char imap_b64dec[256] = { + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, 63,XX,XX,XX, + 52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX, + XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX, + XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX +}; + +static void +mbase64_encode(string_t *dest, const unsigned char *in, size_t len) +{ + str_append_c(dest, '&'); + while (len >= 3) { + str_append_c(dest, imap_b64enc[in[0] >> 2]); + str_append_c(dest, imap_b64enc[((in[0] & 3) << 4) | + (in[1] >> 4)]); + str_append_c(dest, imap_b64enc[((in[1] & 0x0f) << 2) | + ((in[2] & 0xc0) >> 6)]); + str_append_c(dest, imap_b64enc[in[2] & 0x3f]); + in += 3; + len -= 3; + } + if (len > 0) { + str_append_c(dest, imap_b64enc[in[0] >> 2]); + if (len == 1) + str_append_c(dest, imap_b64enc[(in[0] & 0x03) << 4]); + else { + str_append_c(dest, imap_b64enc[((in[0] & 0x03) << 4) | + (in[1] >> 4)]); + str_append_c(dest, imap_b64enc[(in[1] & 0x0f) << 2]); + } + } + str_append_c(dest, '-'); +} + +static const char * +imap_utf8_first_encode_char(const char *str, char escape_char) +{ + const char *p; + + for (p = str; *p != '\0'; p++) { + if (*p == '&' || *p < 0x20 || *p >= 0x7f || *p == escape_char) + return p; + } + return NULL; +} + +int imap_escaped_utf8_hex_to_char(const char *str, unsigned char *chr_r) +{ + unsigned int i = 0; + unsigned char c = 0; + + /* NOTE: Only lowercase hex characters are allowed so the output is + reversible. */ + for (;;) { + if (str[i] >= '0' && str[i] <= '9') + c += str[i] - '0'; + else if (str[i] >= 'a' && str[i] <= 'f') + c += str[i] - 'a' + 10; + else + return -1; + if (++i == 2) + break; + c *= 0x10; + } + *chr_r = c; + return 0; +} + +static int +imap_utf8_to_utf7_int(const char *src, char escape_char, string_t *dest) +{ + const char *p; + unichar_t chr; + uint8_t *utf16, *u; + uint16_t u16; + unsigned char c; + + p = imap_utf8_first_encode_char(src, escape_char); + if (p == NULL) { + /* no characters that need to be encoded */ + str_append(dest, src); + return 0; + } + + /* at least one encoded character */ + str_append_data(dest, src, p-src); + utf16 = t_malloc0(MALLOC_MULTIPLY(strlen(p), 2)); + while (*p != '\0') { + if (*p == escape_char && + imap_escaped_utf8_hex_to_char(p+1, &c) == 0) { + str_append_c(dest, c); + p += 3; + continue; + } + if (*p == '&') { + str_append(dest, "&-"); + p++; + continue; + } + if (*p >= 0x20 && *p < 0x7f) { + str_append_c(dest, *p); + p++; + continue; + } + + u = utf16; + while (*p != '\0' && (*p < 0x20 || *p >= 0x7f)) { + if (uni_utf8_get_char(p, &chr) <= 0) + return -1; + /* @UNSAFE */ + if (chr < UTF16_SURROGATE_BASE) { + *u++ = chr >> 8; + *u++ = chr & 0xff; + } else { + u16 = UTF16_SURROGATE_HIGH(chr); + *u++ = u16 >> 8; + *u++ = u16 & 0xff; + u16 = UTF16_SURROGATE_LOW(chr); + *u++ = u16 >> 8; + *u++ = u16 & 0xff; + } + p += uni_utf8_char_bytes((unsigned char)*p); + } + mbase64_encode(dest, utf16, u-utf16); + } + return 0; +} + +int imap_utf8_to_utf7(const char *src, string_t *dest) +{ + return imap_utf8_to_utf7_int(src, '\0', dest); +} + +int imap_escaped_utf8_to_utf7(const char *src, char escape_char, string_t *dest) +{ + i_assert(escape_char != '&'); + + return imap_utf8_to_utf7_int(src, escape_char, dest); +} + +int t_imap_utf8_to_utf7(const char *src, const char **dest_r) +{ + string_t *str; + int ret; + + if (imap_utf8_first_encode_char(src, '\0') == NULL) { + *dest_r = src; + return 0; + } + + str = t_str_new(64); + ret = imap_utf8_to_utf7(src, str); + *dest_r = str_c(str); + return ret; +} + +static int utf16buf_to_utf8(string_t *dest, const unsigned char output[4], + unsigned int *_pos, unsigned int len) +{ + unsigned int pos = *_pos; + uint16_t high, low; + unichar_t chr; + + if (len % 2 != 0) + return -1; + + high = (output[pos % 4] << 8) | output[(pos+1) % 4]; + if (high < UTF16_SURROGATE_HIGH_FIRST || + high > UTF16_SURROGATE_HIGH_MAX) { + /* single byte */ + size_t oldlen = str_len(dest); + + if (high == 0) { + /* Encoded NUL isn't going to work in Dovecot code, + even though it's technically valid. Return failure + so the callers don't even get a chance to handle the + NUL in the string inconsistently. */ + return -1; + } + uni_ucs4_to_utf8_c(high, dest); + if (str_len(dest) - oldlen == 1) { + unsigned char last = str_data(dest)[oldlen]; + if (last >= 0x20 && last < 0x7f) + return -1; + } + *_pos = (pos + 2) % 4; + return 0; + } + + if (high > UTF16_SURROGATE_HIGH_LAST) + return -1; + if (len != 4) { + /* missing the second character */ + return -1; + } + + low = (output[(pos+2)%4] << 8) | output[(pos+3) % 4]; + if (low < UTF16_SURROGATE_LOW_FIRST || low > UTF16_SURROGATE_LOW_LAST) + return -1; + + chr = UTF16_SURROGATE_BASE + + (((high & UTF16_SURROGATE_MASK) << UTF16_SURROGATE_SHIFT) | + (low & UTF16_SURROGATE_MASK)); + uni_ucs4_to_utf8_c(chr, dest); + return 0; +} + +static int mbase64_decode_to_utf8(string_t *dest, const char **_src) +{ + const char *src = *_src; + unsigned char input[4], output[4]; + unsigned int outstart = 0, outpos = 0; + + while (*src != '-') { + input[0] = imap_b64dec[(uint8_t)src[0]]; + if (input[0] == 0xff) + return -1; + input[1] = imap_b64dec[(uint8_t)src[1]]; + if (input[1] == 0xff) + return -1; + + output[outpos % 4] = (input[0] << 2) | (input[1] >> 4); + if (++outpos % 4 == outstart) { + if (utf16buf_to_utf8(dest, output, &outstart, 4) < 0) + return -1; + } + + input[2] = imap_b64dec[(uint8_t)src[2]]; + if (input[2] == 0xff) { + if (src[2] != '-') + return -1; + + src += 2; + break; + } + + output[outpos % 4] = ((input[1] << 4) & 0xff) | (input[2] >> 2); + if (++outpos % 4 == outstart) { + if (utf16buf_to_utf8(dest, output, &outstart, 4) < 0) + return -1; + } + + input[3] = imap_b64dec[(uint8_t)src[3]]; + if (input[3] == 0xff) { + if (src[3] != '-') + return -1; + + src += 3; + break; + } + + output[outpos % 4] = ((input[2] << 6) & 0xc0) | input[3]; + if (++outpos % 4 == outstart) { + if (utf16buf_to_utf8(dest, output, &outstart, 4) < 0) + return -1; + } + + src += 4; + } + if (outstart != outpos % 4) { + if (utf16buf_to_utf8(dest, output, &outstart, + (4 + outpos - outstart) % 4) < 0) + return -1; + } + + /* Found the ending '-'. Make sure it's not followed by unnecessary + shift. Note that '&' is always escaped as "&-" so it's not an + unnecessary shift. */ + if (src[1] == '&' && src[2] != '-') + return -1; + + *_src = src + 1; + return 0; +} + +static int +imap_utf7_to_utf8_int(const char *src, const char *escape_chars, string_t *dest) +{ + const char *p; + + for (p = src; *p != '\0'; p++) { + if (*p < 0x20 || *p >= 0x7f) { + if (escape_chars[0] == '\0') + return -1; + break; + } + if (*p == '&' || strchr(escape_chars, *p) != NULL) + break; + } + if (*p == '\0') { + /* no IMAP-UTF-7 encoded characters */ + str_append(dest, src); + return 0; + } + + /* at least one encoded character */ + str_append_data(dest, src, p-src); + while (*p != '\0') { + if (strchr(escape_chars, *p) != NULL || + *p < 0x20 || *p >= 0x7f) { + str_printfa(dest, "%c%02x", escape_chars[0], + (unsigned char)*p); + p++; + } else if (*p == '&') { + if (*++p == '-') { + str_append_c(dest, '&'); + p++; + } else { + size_t orig_size = str_len(dest); + if (mbase64_decode_to_utf8(dest, &p) < 0) { + if (escape_chars[0] == '\0') + return -1; + str_truncate(dest, orig_size); + str_printfa(dest, "%c26", escape_chars[0]); + } + } + } else { + str_append_c(dest, *p++); + } + } + return 0; +} + +int imap_utf7_to_utf8(const char *src, string_t *dest) +{ + return imap_utf7_to_utf8_int(src, "", dest); +} + +void imap_utf7_to_utf8_escaped(const char *src, const char *escape_chars, + string_t *dest) +{ + i_assert(escape_chars[0] != '&'); + + if (imap_utf7_to_utf8_int(src, escape_chars, dest) < 0) + i_unreached(); +} + +bool imap_utf7_is_valid(const char *src) +{ + const char *p; + int ret; + + for (p = src; *p != '\0'; p++) { + if (*p < 0x20 || *p >= 0x7f) + return FALSE; + if (*p == '&') { + /* slow scan */ + T_BEGIN { + string_t *tmp = t_str_new(128); + ret = imap_utf7_to_utf8(p, tmp); + } T_END; + if (ret < 0) + return FALSE; + } + } + return TRUE; +} diff --git a/src/lib-imap/imap-utf7.h b/src/lib-imap/imap-utf7.h new file mode 100644 index 0000000..d7ae306 --- /dev/null +++ b/src/lib-imap/imap-utf7.h @@ -0,0 +1,28 @@ +#ifndef IMAP_UTF7_H +#define IMAP_UTF7_H + +/* Convert an UTF-8 string to IMAP-UTF-7. Returns 0 if ok, -1 if src isn't + valid UTF-8. */ +int imap_utf8_to_utf7(const char *src, string_t *dest); +int t_imap_utf8_to_utf7(const char *src, const char **dest_r); +/* Like imap_utf8_to_utf7(), but decode all <escape_char><hex> instances. + Returns -1 if src isn't valid UTF-8. Note that invalid <escape_char> content + isn't treated as an error - it's simply passed through. */ +int imap_escaped_utf8_to_utf7(const char *src, char escape_char, string_t *dest); +/* For manually parsing the <hex> after <escape_char>. Returns 0 on success, + -1 if str doesn't point to valid <hex>. */ +int imap_escaped_utf8_hex_to_char(const char *str, unsigned char *chr_r); + +/* Convert IMAP-UTF-7 string to UTF-8. Returns 0 if ok, -1 if src isn't + valid IMAP-UTF-7. */ +int imap_utf7_to_utf8(const char *src, string_t *dest); +/* Like imap_utf7_to_utf8(), but write invalid input as <escape_chars[0]><hex>. + All the characters in escape_chars[] are escaped in the same way. This + allows converting the escaped output back to the original (broken) + IMAP-UTF-7 input. */ +void imap_utf7_to_utf8_escaped(const char *src, const char *escape_chars, + string_t *dest); +/* Returns TRUE if the string is valid IMAP-UTF-7 string. */ +bool imap_utf7_is_valid(const char *src); + +#endif diff --git a/src/lib-imap/imap-util.c b/src/lib-imap/imap-util.c new file mode 100644 index 0000000..dc1ae24 --- /dev/null +++ b/src/lib-imap/imap-util.c @@ -0,0 +1,202 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "strescape.h" +#include "unichar.h" +#include "mail-types.h" +#include "imap-parser.h" +#include "imap-util.h" + +void imap_write_flags(string_t *dest, enum mail_flags flags, + const char *const *keywords) +{ + size_t size; + + size = str_len(dest); + if ((flags & MAIL_ANSWERED) != 0) + str_append(dest, "\\Answered "); + if ((flags & MAIL_FLAGGED) != 0) + str_append(dest, "\\Flagged "); + if ((flags & MAIL_DELETED) != 0) + str_append(dest, "\\Deleted "); + if ((flags & MAIL_SEEN) != 0) + str_append(dest, "\\Seen "); + if ((flags & MAIL_DRAFT) != 0) + str_append(dest, "\\Draft "); + if ((flags & MAIL_RECENT) != 0) + str_append(dest, "\\Recent "); + + if (keywords != NULL) { + /* we have keywords too */ + while (*keywords != NULL) { + str_append(dest, *keywords); + str_append_c(dest, ' '); + keywords++; + } + } + + if (str_len(dest) != size) + str_truncate(dest, str_len(dest)-1); +} + +enum mail_flags imap_parse_system_flag(const char *str) +{ + if (strcasecmp(str, "\\Answered") == 0) + return MAIL_ANSWERED; + else if (strcasecmp(str, "\\Flagged") == 0) + return MAIL_FLAGGED; + else if (strcasecmp(str, "\\Deleted") == 0) + return MAIL_DELETED; + else if (strcasecmp(str, "\\Seen") == 0) + return MAIL_SEEN; + else if (strcasecmp(str, "\\Draft") == 0) + return MAIL_DRAFT; + else if (strcasecmp(str, "\\Recent") == 0) + return MAIL_RECENT; + else + return 0; +} + +void imap_write_seq_range(string_t *dest, const ARRAY_TYPE(seq_range) *array) +{ + const struct seq_range *range; + unsigned int i, count; + + range = array_get(array, &count); + for (i = 0; i < count; i++) { + if (i > 0) + str_append_c(dest, ','); + str_printfa(dest, "%u", range[i].seq1); + if (range[i].seq1 != range[i].seq2) + str_printfa(dest, ":%u", range[i].seq2); + } +} + +void imap_write_arg(string_t *dest, const struct imap_arg *arg) +{ + switch (arg->type) { + case IMAP_ARG_NIL: + str_append(dest, "NIL"); + break; + case IMAP_ARG_ATOM: + str_append(dest, imap_arg_as_astring(arg)); + break; + case IMAP_ARG_STRING: { + const char *strarg = imap_arg_as_astring(arg); + str_append_c(dest, '"'); + str_append_escaped(dest, strarg, strlen(strarg)); + str_append_c(dest, '"'); + break; + } + case IMAP_ARG_LITERAL: { + const char *strarg = imap_arg_as_astring(arg); + str_printfa(dest, "{%zu}\r\n", + strlen(strarg)); + str_append(dest, strarg); + break; + } + case IMAP_ARG_LIST: + str_append_c(dest, '('); + imap_write_args(dest, imap_arg_as_list(arg)); + str_append_c(dest, ')'); + break; + case IMAP_ARG_LITERAL_SIZE: + case IMAP_ARG_LITERAL_SIZE_NONSYNC: + str_printfa(dest, "<%"PRIuUOFF_T" byte literal>", + imap_arg_as_literal_size(arg)); + break; + case IMAP_ARG_EOL: + i_unreached(); + } +} + +void imap_write_args(string_t *dest, const struct imap_arg *args) +{ + bool first = TRUE; + + for (; !IMAP_ARG_IS_EOL(args); args++) { + if (first) + first = FALSE; + else + str_append_c(dest, ' '); + imap_write_arg(dest, args); + } +} + +static void imap_human_args_fix_control_chars(char *str) +{ + size_t i; + + for (i = 0; str[i] != '\0'; i++) { + if (str[i] < 0x20 || str[i] == 0x7f) + str[i] = '?'; + } +} + +void imap_write_args_for_human(string_t *dest, const struct imap_arg *args) +{ + bool first = TRUE; + + for (; !IMAP_ARG_IS_EOL(args); args++) { + if (first) + first = FALSE; + else + str_append_c(dest, ' '); + + switch (args->type) { + case IMAP_ARG_NIL: + str_append(dest, "NIL"); + break; + case IMAP_ARG_ATOM: + /* atom has only printable us-ascii chars */ + str_append(dest, imap_arg_as_astring(args)); + break; + case IMAP_ARG_STRING: + case IMAP_ARG_LITERAL: { + const char *strarg = imap_arg_as_astring(args); + + if (strpbrk(strarg, "\r\n") != NULL) { + str_printfa(dest, "<%zu byte multi-line literal>", + strlen(strarg)); + break; + } + strarg = str_escape(strarg); + + str_append_c(dest, '"'); + size_t start_pos = str_len(dest); + /* append only valid UTF-8 chars */ + if (uni_utf8_get_valid_data((const unsigned char *)strarg, + strlen(strarg), dest)) + str_append(dest, strarg); + /* replace all control chars */ + imap_human_args_fix_control_chars( + str_c_modifiable(dest) + start_pos); + str_append_c(dest, '"'); + break; + } + case IMAP_ARG_LIST: + str_append_c(dest, '('); + imap_write_args_for_human(dest, imap_arg_as_list(args)); + str_append_c(dest, ')'); + break; + case IMAP_ARG_LITERAL_SIZE: + case IMAP_ARG_LITERAL_SIZE_NONSYNC: + str_printfa(dest, "<%"PRIuUOFF_T" byte literal>", + imap_arg_as_literal_size(args)); + break; + case IMAP_ARG_EOL: + i_unreached(); + } + } +} + +const char *imap_args_to_str(const struct imap_arg *args) +{ + string_t *str; + + str = t_str_new(128); + imap_write_args(str, args); + return str_c(str); +} diff --git a/src/lib-imap/imap-util.h b/src/lib-imap/imap-util.h new file mode 100644 index 0000000..47b7d2c --- /dev/null +++ b/src/lib-imap/imap-util.h @@ -0,0 +1,29 @@ +#ifndef IMAP_UTIL_H +#define IMAP_UTIL_H + +#include "seq-range-array.h" +#include "mail-types.h" + +struct imap_arg; + +/* Write flags as a space separated string. */ +void imap_write_flags(string_t *dest, enum mail_flags flags, + const char *const *keywords) ATTR_NULL(3); +/* Parse system flag from a string, or return 0 if it's invalid. */ +enum mail_flags imap_parse_system_flag(const char *str); + +/* Write sequence range as IMAP sequence-set */ +void imap_write_seq_range(string_t *dest, const ARRAY_TYPE(seq_range) *array); +/* Write IMAP arg to the given string. Because IMAP_ARG_LITERAL_SIZE* have no + content, they're written as "{size}\r\n<too large>". */ +void imap_write_arg(string_t *dest, const struct imap_arg *arg); +/* Same as imap_write_arg(), but write all the args until EOL. */ +void imap_write_args(string_t *dest, const struct imap_arg *args); +/* Write IMAP args in a human-readable format to given string (e.g. for + logging). The output is a single valid UTF-8 line without control + characters. Multi-line literals are replaced with a generic placeholder. */ +void imap_write_args_for_human(string_t *dest, const struct imap_arg *args); +/* Like imap_write_args(), but return the string allocated from data stack. */ +const char *imap_args_to_str(const struct imap_arg *args); + +#endif diff --git a/src/lib-imap/test-imap-bodystructure.c b/src/lib-imap/test-imap-bodystructure.c new file mode 100644 index 0000000..736eb99 --- /dev/null +++ b/src/lib-imap/test-imap-bodystructure.c @@ -0,0 +1,734 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "str.h" +#include "message-part-data.h" +#include "message-part-serialize.h" +#include "message-parser.h" +#include "imap-bodystructure.h" +#include "test-common.h" + +struct parse_test { + const char *message; + const char *body; + const char *bodystructure; +}; + +struct parse_test parse_tests[] = { + { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "body\n", + .bodystructure = + "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL", + .body = + "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1" + },{ + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=utf-8\n" + "Content-Transfer-Encoding: 8bit\n" + "\n" + "body\n" + "\n", + .bodystructure = + "\"text\" \"plain\" (\"charset\" \"utf-8\") NIL NIL \"8bit\" 8 2 NIL NIL NIL NIL", + .body = + "\"text\" \"plain\" (\"charset\" \"utf-8\") NIL NIL \"8bit\" 8 2" + },{ + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2007 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo\n" + " bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/x-myown; charset=us-ascii\n" + "\n" + "hello\n" + "\n" + "--foo bar--\n" + "\n", + .bodystructure = + "(\"text\" \"x-myown\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 7 1 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL", + .body = + "(\"text\" \"x-myown\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 7 1) \"mixed\"" + },{ + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "See attached...\n" + "\n" + "--foo bar\n" + "Content-Type: message/rfc822\n" + "\n" + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "body\n" + "\n" + "--foo bar--\n" + "\n", + .bodystructure = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL NIL)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL) 6 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL", + .body = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1) 6) \"mixed\"" + },{ + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "Content-ID: <A.frop.example.com>\n" + "Content-Description: Container message\n" + "\n" + "See attached...\n" + "\n" + "--foo bar\n" + "Content-Type: message/rfc822\n" + "Content-ID: <B.frop.example.com>\n" + "Content-Description: Forwarded\n" + "\n" + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "body\n" + "\n" + "--foo bar--\n", + .bodystructure = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") \"<A.frop.example.com>\" \"Container message\" \"7bit\" 17 1 NIL NIL NIL NIL)(\"message\" \"rfc822\" NIL \"<B.frop.example.com>\" \"Forwarded\" \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL) 6 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL", + .body = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") \"<A.frop.example.com>\" \"Container message\" \"7bit\" 17 1)(\"message\" \"rfc822\" NIL \"<B.frop.example.com>\" \"Forwarded\" \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1) 6) \"mixed\"" + },{ + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii; format=\"flowed\";\n" + " delsp=\"no\"\n" + "Content-Language: la\n" + "\n" + "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo\n" + "ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis\n" + "parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec,\n" + "pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec\n" + "pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo,\n" + "rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede\n" + "mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper\n" + "nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu,\n" + "consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra\n" + "quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet.\n" + "Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur\n" + "ullamcorper ultricies nisi. Nam eget dui.\n" + "\n" + "--foo bar\n" + "Content-Type: image/png\n" + "Content-Transfer-Encoding: base64\n" + "Content-Disposition: attachment; filename=\"pigeon.png\"\n" + "\n" + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n" + "AAAGJwAABicBTVTYxwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAN2SURB\n" + "VEiJ7ZfdK7tvHMffZuWpKTlR1my3bTEPU0hJcsABh0QrKYUTJ5KyHCzlyPwBkpSyg1mUpB3ILYna\n" + "lEYKcbADm4cDD7NpM2bv75HF1/az8l3fX/1+77pO7vu6rtf1/jx0X3caSeIvSfK3wP/DPykcDsNs\n" + "NqOqqgpdXV3Y2NhIGVz6+4PJyUlcXFxAFEV4vV709fVBrVZDpVL9eTo/aGVlhXl5efT5fDw/P2c4\n" + "HKbdbmd3dzdTIZDk6+srh4eHqdPpqNfrSZJGo5H7+/sMBoNUKpUpgUsAQCqVQq1WIxgMfgp/dXU1\n" + "srKykJ+fD6/X+8ejHiu4wcFB2Gy2uJPq6uqwt7eXOjgAaLXahHCn05laeCKlyvmXVosnrVaLQCCA\n" + "4uJiyOVyCIIQGyqVCoIgoKCgIDXwtLQ0HBwc4O3tDR6PB263G263G8fHx1hbW4Pb7cbNzQ1yc3Nj\n" + "hxEEAdXV1WhoaEi88cfSf3h4iLXaR01MTFChUFChULCjo4M7OztxW8fn89HlcnF5eZlTU1Nsb29n\n" + "W1sb/X5/3PlJwd/19vbG7e1tlpWVcXV19ftGJjk0NESbzRb33aeCy8zMhM/nSxgliUSCxsZGiKKI\n" + "ra2tb9N1e3uLSCSC8fFxGAwGDAwMfC7c309TWFjI09PTpFwlis76+jo7OzspCAJNJhN3d3fpcDho\n" + "tVqpUCgYCoVIkl8Krr6+Hna7HSUlJd86+yiPx4P5+XlYLBZUVFSgv78fVqsV6enpAIC7uzt4vV5E\n" + "IhFIJJL4zp1OJ2traxmJRL51+fLywuXlZba2trK0tJRms5mXl5c8PT3l4uIix8bG2NbWRqVSyfLy\n" + "cvb09PDw8DC2Po38eocbGRlBIBDA9PQ0pNKv3Xh2doa5uTksLCwgJycHNTU1yM3NxdHREa6vr6HR\n" + "aKDX62NDp9MhIyPjyz5x4SQxOjoKURTR29sLmUyG7Oxs+P1+WCwW7O7uQiKRQKvVQq/Xo7KyMgaS\n" + "y+VJpyou/F0nJydYWlqCy+XC1dUV7u/vYTQa8fT0BIfDgaWlpaRBcZVsFc/OznJsbIwkaTKZODMz\n" + "k+zShEr6Arm5uYnm5mYAgCiKaGlp+ZnrZJ1Ho1EWFRXx+fmZj4+P1Gg0P3ZNJqj2dxkMhth3PBAI\n" + "QCaTIRqNIhQKoamp6cc5/0d4qvXv+mn4z8B/AV1UVu6zi+zUAAAAAElFTkSuQmCC\n" + "--foo bar--\n", + .bodystructure = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\" \"format\" \"flowed\" \"delsp\" \"no\") NIL NIL \"7bit\" 881 12 NIL NIL (\"la\") NIL)(\"image\" \"png\" NIL NIL NIL \"base64\" 1390 NIL (\"attachment\" (\"filename\" \"pigeon.png\")) NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL", + .body = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\" \"format\" \"flowed\" \"delsp\" \"no\") NIL NIL \"7bit\" 881 12)(\"image\" \"png\" NIL NIL NIL \"base64\" 1390) \"mixed\"" + },{ + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2007 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo\n" + " bar\"\n" + "\n" + "Root MIME prologue\n" + "\n" + "--foo bar\n" + "Content-Type: text/x-myown; charset=us-ascii; foo=\"quoted\\\"string\"\n" + "Content-ID: <foo@example.com>\n" + "Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==\n" + "Content-Disposition: inline; foo=bar\n" + "Content-Description: hellodescription\n" + "Content-Language: en, fi, se\n" + "Content-Location: http://example.com/test.txt\n" + "\n" + "hello\n" + "\n" + "--foo bar\n" + "Content-Type: message/rfc822\n" + "\n" + "From: sub@domain.org\n" + "To: sub-to1@domain.org, sub-to2@domain.org\n" + "Date: Sun, 12 Aug 2012 12:34:56 +0300\n" + "Subject: submsg\n" + "Content-Type: multipart/alternative; boundary=\"sub1\"\n" + "\n" + "Sub MIME prologue\n" + "--sub1\n" + "Content-Type: text/html\n" + "Content-Transfer-Encoding: 8bit\n" + "\n" + "<p>Hello world</p>\n" + "\n" + "--sub1\n" + "Content-Type: text/plain\n" + "Content-Transfer-Encoding: ?invalid\n" + "\n" + "Hello another world\n" + "\n" + "--sub1--\n" + "Sub MIME epilogue\n" + "\n" + "--foo bar--\n" + "Root MIME epilogue\n", + .bodystructure = + "(\"text\" \"x-myown\" (\"charset\" \"us-ascii\" \"foo\" \"quoted\\\"string\") \"<foo@example.com>\" \"hellodescription\" \"7bit\" 7 1 \"Q2hlY2sgSW50ZWdyaXR5IQ==\" (\"inline\" (\"foo\" \"bar\")) (\"en\" \"fi\" \"se\") \"http://example.com/test.txt\")(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 412 (\"Sun, 12 Aug 2012 12:34:56 +0300\" \"submsg\" ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub-to1\" \"domain.org\")(NIL NIL \"sub-to2\" \"domain.org\")) NIL NIL NIL NIL) ((\"text\" \"html\" (\"charset\" \"us-ascii\") NIL NIL \"8bit\" 20 1 NIL NIL NIL NIL)(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 21 1 NIL NIL NIL NIL) \"alternative\" (\"boundary\" \"sub1\") NIL NIL NIL) 21 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL", + .body = + "(\"text\" \"x-myown\" (\"charset\" \"us-ascii\" \"foo\" \"quoted\\\"string\") \"<foo@example.com>\" \"hellodescription\" \"7bit\" 7 1)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 412 (\"Sun, 12 Aug 2012 12:34:56 +0300\" \"submsg\" ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub-to1\" \"domain.org\")(NIL NIL \"sub-to2\" \"domain.org\")) NIL NIL NIL NIL) ((\"text\" \"html\" (\"charset\" \"us-ascii\") NIL NIL \"8bit\" 20 1)(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 21 1) \"alternative\") 21) \"mixed\"" + },{ + .message = + "Content-Type: multipart/mixed; boundary=\"foo\"\n" + "\n", + .bodystructure = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo\") NIL NIL NIL", + .body = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0) \"mixed\"" + + } +}; + +static const unsigned int parse_tests_count = N_ELEMENTS(parse_tests); + +struct normalize_test { + const char *message; + const char *input; + const char *output; +}; + +struct normalize_test normalize_tests[] = { + { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "body\n", + .input = + "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1", + .output = + "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL", + }, { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=us-ascii\n" + "Content-MD5: ae6ba5b4c6eb1efd4a9fac3708046cbe\n" + "\n" + "body\n", + .input = + "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 \"ae6ba5b4c6eb1efd4a9fac3708046cbe\"", + .output = + "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 \"ae6ba5b4c6eb1efd4a9fac3708046cbe\" NIL NIL NIL", + }, { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "See attached...\n" + "\n" + "--foo bar\n" + "Content-Type: message/rfc822\n" + "\n" + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "body\n" + "\n" + "--foo bar--\n" + "\n", + .input = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1) 6) \"mixed\"", + .output = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL NIL)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL) 6 NIL NIL NIL NIL) \"mixed\" NIL NIL NIL NIL" + }, { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "See attached...\n" + "\n" + "--foo bar--\n" + "\n", + .input = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1) \"mixed\" (\"boundary\" \"foo bar\")", + .output = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL" + }, { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "Content-MD5: 6537bae18ed07779c9dc25f24635b0f3\n" + "\n" + "See attached...\n" + "\n" + "--foo bar--\n" + "\n", + .input = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 \"6537bae18ed07779c9dc25f24635b0f3\") \"mixed\" (\"boundary\" \"foo bar\")", + .output = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 \"6537bae18ed07779c9dc25f24635b0f3\" NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL" + }, { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "Content-Language: en\n" + "\n" + "See attached...\n" + "\n" + "--foo bar--\n" + "\n", + .input = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL \"en\") \"mixed\" (\"boundary\" \"foo bar\")", + .output = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL (\"en\") NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL" + }, { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "Content-Location: http://www.example.com/frop.txt\n" + "\n" + "See attached...\n" + "\n" + "--foo bar--\n" + "\n", + .input = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL \"http://www.example.com/frop.txt\") \"mixed\" (\"boundary\" \"foo bar\")", + .output = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL \"http://www.example.com/frop.txt\") \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL" + } +}; + +static const unsigned int normalize_tests_count = N_ELEMENTS(normalize_tests); + +static struct message_part * +msg_parse(pool_t pool, const char *message, unsigned int max_nested_mime_parts, + unsigned int max_total_mime_parts, bool parse_bodystructure) +{ + const struct message_parser_settings parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | + MESSAGE_HEADER_PARSER_FLAG_DROP_CR, + .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, + .max_nested_mime_parts = max_nested_mime_parts, + .max_total_mime_parts = max_total_mime_parts, + }; + struct message_parser_ctx *parser; + struct istream *input; + struct message_block block; + struct message_part *parts; + int ret; + + input = i_stream_create_from_data(message, strlen(message)); + parser = message_parser_init(pool, input, &parser_set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) { + if (parse_bodystructure) { + message_part_data_parse_from_header(pool, block.part, + block.hdr); + } + } + test_assert(ret < 0); + + message_parser_deinit(&parser, &parts); + i_stream_unref(&input); + return parts; +} + +static void test_imap_bodystructure_write(void) +{ + struct message_part *parts; + const char *error; + unsigned int i; + + for (i = 0; i < parse_tests_count; i++) T_BEGIN { + struct parse_test *test = &parse_tests[i]; + string_t *str = t_str_new(128); + pool_t pool = pool_alloconly_create("imap bodystructure write", 1024); + + test_begin(t_strdup_printf("imap bodystructure write [%u]", i)); + parts = msg_parse(pool, test->message, 0, 0, TRUE); + + test_assert(imap_bodystructure_write(parts, str, TRUE, &error) == 0); + test_assert(strcmp(str_c(str), test->bodystructure) == 0); + + str_truncate(str, 0); + test_assert(imap_bodystructure_write(parts, str, FALSE, &error) == 0); + test_assert(strcmp(str_c(str), test->body) == 0); + + pool_unref(&pool); + test_end(); + } T_END; + + T_BEGIN { + test_begin("imap bodystructure write - corrupted"); + pool_t pool = pool_alloconly_create("imap bodystructure write", 1024); + + parts = msg_parse(pool, "Subject: hello world", 0, 0, TRUE); + i_assert((parts->flags & MESSAGE_PART_FLAG_TEXT) != 0); + parts->flags &= ENUM_NEGATE(MESSAGE_PART_FLAG_TEXT); + + string_t *str = t_str_new(128); + test_assert(imap_bodystructure_write(parts, str, FALSE, &error) < 0); + test_assert_strcmp(error, "text flag mismatch"); + pool_unref(&pool); + test_end(); + } T_END; +} + +static void test_imap_bodystructure_parse(void) +{ + struct message_part *parts; + const char *error; + unsigned int i; + int ret; + + for (i = 0; i < parse_tests_count; i++) T_BEGIN { + struct parse_test *test = &parse_tests[i]; + string_t *str = t_str_new(128); + pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); + + test_begin(t_strdup_printf("imap bodystructure parser [%u]", i)); + parts = msg_parse(pool, test->message, 0, 0, FALSE); + + test_assert(imap_body_parse_from_bodystructure(test->bodystructure, + str, &error) == 0); + test_assert(strcmp(str_c(str), test->body) == 0); + + ret = imap_bodystructure_parse(test->bodystructure, + pool, parts, &error); + test_assert(ret == 0); + + if (ret == 0) { + str_truncate(str, 0); + test_assert(imap_bodystructure_write(parts, str, TRUE, &error) == 0); + test_assert(strcmp(str_c(str), test->bodystructure) == 0); + } else { + i_error("Invalid BODYSTRUCTURE: %s", error); + } + + pool_unref(&pool); + test_end(); + } T_END; +} + +static void test_imap_bodystructure_parse_invalid(void) +{ + static const struct parse_test_invalid { + const char *message; + const char *bodystructure; + const char *error; + } invalid_bodystructure_tests[] = { + /* Make sure NILs aren't allowed where strings are expected */ + { "foo", "NIL \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-type" }, + { "foo", "\"text\" NIL (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-type" }, + { "foo", "\"text\" \"plain\" (NIL \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" }, + { "foo", "\"text\" \"plain\" (\"charset\" NIL) NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL NIL 0 0 NIL NIL NIL NIL", "Invalid content-transfer-encoding" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" NIL 0 NIL NIL NIL NIL", "Invalid size field" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 NIL NIL NIL NIL NIL", "Invalid lines field" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (NIL (\"foo\" \"bar\")) NIL NIL", "Invalid content-disposition" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (\"inline\" (NIL \"bar\")) NIL NIL", "Invalid content-disposition params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (\"inline\" (\"foo\" NIL)) NIL NIL", "Invalid content-disposition params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL (NIL \"bar\") NIL", "Invalid content-language" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL (\"foo\" NIL) NIL", "Invalid content-language" }, + + /* Make sure atoms aren't allowed anywhere */ + { "foo", "ATOM \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-type" }, + { "foo", "\"text\" ATOM (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-type" }, + { "foo", "\"text\" \"plain\" (ATOM \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" }, + { "foo", "\"text\" \"plain\" (\"charset\" ATOM) NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") ATOM NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-id" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL ATOM \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-description" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL ATOM 0 0 NIL NIL NIL NIL", "Invalid content-transfer-encoding" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" ATOM 0 NIL NIL NIL NIL", "Invalid size field" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 ATOM NIL NIL NIL NIL", "Invalid lines field" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 ATOM NIL NIL NIL", "Invalid content-md5" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL ATOM NIL NIL", "Invalid content-disposition list" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (ATOM (\"foo\" \"bar\")) NIL NIL", "Invalid content-disposition" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (\"inline\" (ATOM \"bar\")) NIL NIL", "Invalid content-disposition params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (\"inline\" (\"foo\" ATOM)) NIL NIL", "Invalid content-disposition params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL ATOM NIL", "Invalid content-language" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL (ATOM \"bar\") NIL", "Invalid content-language" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL (\"foo\" ATOM) NIL", "Invalid content-language" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL ATOM", "Invalid content-location" }, + + /* Make sure empty lists aren't allowed anywhere */ + { "foo", "\"text\" \"plain\" () NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") () NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-id" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL () \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-description" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL () 0 0 NIL NIL NIL NIL", "Invalid content-transfer-encoding" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" () 0 NIL NIL NIL NIL", "Invalid size field" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 () NIL NIL NIL NIL", "Invalid lines field" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 () NIL NIL NIL", "Invalid content-md5" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL () NIL NIL", "Invalid content-disposition" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL () NIL", "Invalid content-language" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL ()", "Invalid content-location" }, + }; + struct message_part *parts; + const char *error; + unsigned int i; + + test_begin("imap bodystructure parser invalid"); + for (i = 0; i < N_ELEMENTS(invalid_bodystructure_tests); i++) T_BEGIN { + const struct parse_test_invalid *test = + &invalid_bodystructure_tests[i]; + pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); + + parts = msg_parse(pool, test->message, 0, 0, FALSE); + test_assert_idx(imap_bodystructure_parse(test->bodystructure, + pool, parts, &error) == -1, i); + test_assert(parts->data == NULL); + test_assert_strcmp_idx(error, test->error, i); + pool_unref(&pool); + } T_END; + test_end(); +} + +static void test_imap_bodystructure_parse_full(void) +{ + const char *error; + unsigned int i; + int ret; + + for (i = 0; i < parse_tests_count; i++) T_BEGIN { + struct parse_test *test = &parse_tests[i]; + struct message_part *parts = NULL; + string_t *str = t_str_new(128); + pool_t pool = pool_alloconly_create("imap bodystructure parse full", 1024); + + test_begin(t_strdup_printf("imap bodystructure parser full [%u]", i)); + + ret = imap_bodystructure_parse_full(test->bodystructure, + pool, &parts, &error); + test_assert(ret == 0); + + if (ret == 0) { + str_truncate(str, 0); + test_assert(imap_bodystructure_write(parts, str, TRUE, &error) == 0); + test_assert(strcmp(str_c(str), test->bodystructure) == 0); + } else { + i_error("Invalid BODYSTRUCTURE: %s", error); + } + + pool_unref(&pool); + test_end(); + } T_END; +} + +static void test_imap_bodystructure_normalize(void) +{ + struct message_part *parts; + const char *error; + unsigned int i; + int ret; + + for (i = 0; i < normalize_tests_count; i++) T_BEGIN { + struct normalize_test *test = &normalize_tests[i]; + string_t *str = t_str_new(128); + pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); + + test_begin(t_strdup_printf("imap bodystructure normalize [%u]", i)); + parts = msg_parse(pool, test->message, 0, 0, FALSE); + + ret = imap_bodystructure_parse(test->input, + pool, parts, &error); + test_assert(ret == 0); + + if (ret == 0) { + str_truncate(str, 0); + test_assert(imap_bodystructure_write(parts, str, TRUE, &error) == 0); + test_assert(strcmp(str_c(str), test->output) == 0); + } else { + i_error("Invalid BODYSTRUCTURE: %s", error); + } + + pool_unref(&pool); + test_end(); + } T_END; +} + +static const struct { + const char *input; + const char *bodystructure; + unsigned int max_depth; + unsigned int max_total; +} truncation_tests[] = { + { + .input = "Content-Type: message/rfc822\n" + "\n" + "Content-Type: message/rfc822\n" + "Header2: value2\n" + "\n" + "Subject: hello world\n" + "Header2: value2\n" + "Header3: value3\n" + "\n" + "body line 1\n" + "body line 2\n" + "body line 4\n" + "body line 3\n", + .bodystructure = "\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 159 (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) (\"application\" \"octet-stream\" NIL NIL NIL \"7bit\" 110 NIL NIL NIL NIL) 11 NIL NIL NIL NIL", + .max_depth = 2, + }, + { + .input = "Content-Type: multipart/mixed; boundary=1\n" + "\n" + "--1\n" + "Content-Type: multipart/mixed; boundary=2\n" + "\n" + "--2\n" + "Content-Type: multipart/mixed; boundary=3\n" + "\n" + "--3\n" + "\n" + "body\n", + .bodystructure = "(\"application\" \"octet-stream\" (\"boundary\" \"2\") NIL NIL \"7bit\" 63 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"1\") NIL NIL NIL", + .max_depth = 2, + }, + { + .input = "Content-Type: multipart/digest; boundary=1\n" + "\n" + "--1\n" + "\n" + "Subject: hdr1\n" + "\n" + "body1\n" + "--1\n" + "\n" + "Subject: hdr2\n" + "\n" + "body2\n", + .bodystructure = "(\"application\" \"octet-stream\" NIL NIL NIL \"7bit\" 55 NIL NIL NIL NIL) \"digest\" (\"boundary\" \"1\") NIL NIL NIL", + .max_total = 2, + }, + +}; + +static void test_imap_bodystructure_truncation(void) +{ + struct message_part *parts; + const char *error; + string_t *str_body = t_str_new(128); + string_t *str_parts = t_str_new(128); + pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); + + test_begin("imap bodystructure truncation"); + + for (unsigned int i = 0; i < N_ELEMENTS(truncation_tests); i++) { + p_clear(pool); + str_truncate(str_body, 0); + str_truncate(str_parts, 0); + + parts = msg_parse(pool, truncation_tests[i].input, + truncation_tests[i].max_depth, + truncation_tests[i].max_total, + TRUE); + + /* write out BODYSTRUCTURE and serialize message_parts */ + test_assert(imap_bodystructure_write(parts, str_body, TRUE, &error) == 0); + message_part_serialize(parts, str_parts); + + /* now deserialize message_parts and make sure they can be used + to parse BODYSTRUCTURE */ + parts = message_part_deserialize(pool, str_data(str_parts), + str_len(str_parts), &error); + test_assert(parts != NULL); + test_assert(imap_bodystructure_parse(str_c(str_body), pool, + parts, &error) == 0); + test_assert_strcmp(str_c(str_body), + truncation_tests[i].bodystructure); + } + pool_unref(&pool); + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_bodystructure_write, + test_imap_bodystructure_parse, + test_imap_bodystructure_parse_invalid, + test_imap_bodystructure_normalize, + test_imap_bodystructure_parse_full, + test_imap_bodystructure_truncation, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-envelope.c b/src/lib-imap/test-imap-envelope.c new file mode 100644 index 0000000..1f295e5 --- /dev/null +++ b/src/lib-imap/test-imap-envelope.c @@ -0,0 +1,205 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "str.h" +#include "message-part-data.h" +#include "message-parser.h" +#include "imap-envelope.h" +#include "test-common.h" + +struct parse_test { + const char *message; + const char *envelope; +}; + +struct parse_test parse_tests[] = { + /* Tests copied from imaptest */ + { + .message = + "Message-ID: <msg@id>\n" + "In-Reply-To: <reply@to.id>\n" + "Date: Thu, 15 Feb 2007 01:02:03 +0200\n" + "Subject: subject header\n" + "From: From Real <fromuser@fromdomain.org>\n" + "To: To Real <touser@todomain.org>\n" + "Cc: Cc Real <ccuser@ccdomain.org>\n" + "Bcc: Bcc Real <bccuser@bccdomain.org>\n" + "Sender: Sender Real <senderuser@senderdomain.org>\n" + "Reply-To: ReplyTo Real <replytouser@replytodomain.org>\n" + "\n" + "body\n", + .envelope = + "\"Thu, 15 Feb 2007 01:02:03 +0200\" " + "\"subject header\" " + "((\"From Real\" NIL \"fromuser\" \"fromdomain.org\")) " + "((\"Sender Real\" NIL \"senderuser\" \"senderdomain.org\")) " + "((\"ReplyTo Real\" NIL \"replytouser\" \"replytodomain.org\")) " + "((\"To Real\" NIL \"touser\" \"todomain.org\")) " + "((\"Cc Real\" NIL \"ccuser\" \"ccdomain.org\")) " + "((\"Bcc Real\" NIL \"bccuser\" \"bccdomain.org\")) " + "\"<reply@to.id>\" \"<msg@id>\"" + }, { + .message = + "Date: Thu, 15 Feb 2007 01:02:03 +0200\n" + "From: user@domain\n" + "\n" + "body\n", + .envelope = + "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL " + "((NIL NIL \"user\" \"domain\")) " + "((NIL NIL \"user\" \"domain\")) " + "((NIL NIL \"user\" \"domain\")) NIL NIL NIL NIL NIL" + }, { + .message = + "Date: Thu, 15 Feb 2007 01:02:03 +0200\n" + "From: user@domain\n" + "\n" + "body\n", + .envelope = + "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL " + "((NIL NIL \"user\" \"domain\")) " + "((NIL NIL \"user\" \"domain\")) " + "((NIL NIL \"user\" \"domain\")) NIL NIL NIL NIL NIL" + }, { + .message = + "Date: Thu, 15 Feb 2007 01:02:03 +0200\n" + "From: user@domain (Real Name)\n" + "To: group: g1@d1.org, g2@d2.org;, group2: g3@d3.org;\n" + "Cc: group:;, group2: (foo) ;\n" + "\n" + "body\n", + .envelope = + "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL " + "((\"Real Name\" NIL \"user\" \"domain\")) " + "((\"Real Name\" NIL \"user\" \"domain\")) " + "((\"Real Name\" NIL \"user\" \"domain\")) " + "((NIL NIL \"group\" NIL)" + "(NIL NIL \"g1\" \"d1.org\")" + "(NIL NIL \"g2\" \"d2.org\")" + "(NIL NIL NIL NIL)" + "(NIL NIL \"group2\" NIL)" + "(NIL NIL \"g3\" \"d3.org\")" + "(NIL NIL NIL NIL)) " + "((NIL NIL \"group\" NIL)(NIL NIL NIL NIL)" + "(NIL NIL \"group2\" NIL)(NIL NIL NIL NIL)) " + "NIL NIL NIL" + }, { + .message = + "Date: Thu, 15 Feb 2007 01:02:03 +0200\n" + "From: user@domain (Real Name)\n" + "Sender: \n" + "Reply-To: \n" + "\n" + "body\n", + .envelope = + "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL " + "((\"Real Name\" NIL \"user\" \"domain\")) " + "((\"Real Name\" NIL \"user\" \"domain\")) " + "((\"Real Name\" NIL \"user\" \"domain\")) " + "NIL NIL NIL NIL NIL" + }, { + .message = + "Date: Thu, 15 Feb 2007 01:02:03 +0200\n" + "From: <@route:user@domain>\n" + "\n" + "body\n", + .envelope = + "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL " + "((NIL \"@route\" \"user\" \"domain\")) " + "((NIL \"@route\" \"user\" \"domain\")) " + "((NIL \"@route\" \"user\" \"domain\")) " + "NIL NIL NIL NIL NIL" + } +}; + +static const unsigned int parse_tests_count = N_ELEMENTS(parse_tests); + +static struct message_part_envelope * +msg_parse(pool_t pool, const char *message) +{ + const struct message_parser_settings parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | + MESSAGE_HEADER_PARSER_FLAG_DROP_CR, + .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, + }; + struct message_parser_ctx *parser; + struct message_part_envelope *envlp = NULL; + struct istream *input; + struct message_block block; + struct message_part *parts; + int ret; + + input = i_stream_create_from_data(message, strlen(message)); + parser = message_parser_init(pool, input, &parser_set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) { + i_assert(block.part->parent == NULL); + message_part_envelope_parse_from_header(pool, &envlp, block.hdr); + } + test_assert(ret < 0); + + message_parser_deinit(&parser, &parts); + i_stream_unref(&input); + return envlp; +} + +static void test_imap_envelope_write(void) +{ + struct message_part_envelope *envlp; + unsigned int i; + + for (i = 0; i < parse_tests_count; i++) T_BEGIN { + struct parse_test *test = &parse_tests[i]; + string_t *str = t_str_new(128); + pool_t pool = pool_alloconly_create("imap envelope write", 1024); + + test_begin(t_strdup_printf("imap envelope write [%u]", i)); + envlp = msg_parse(pool, test->message); + + imap_envelope_write(envlp, str); + test_assert(strcmp(str_c(str), test->envelope) == 0); + + pool_unref(&pool); + test_end(); + } T_END; +} + +static void test_imap_envelope_parse(void) +{ + struct message_part_envelope *envlp; + const char *error; + unsigned int i; + bool ret; + + for (i = 0; i < parse_tests_count; i++) T_BEGIN { + struct parse_test *test = &parse_tests[i]; + string_t *str = t_str_new(128); + pool_t pool = pool_alloconly_create("imap envelope parse", 1024); + + test_begin(t_strdup_printf("imap envelope parser [%u]", i)); + + ret = imap_envelope_parse(test->envelope, pool, &envlp, &error); + test_assert(ret); + + if (ret) { + str_truncate(str, 0); + imap_envelope_write(envlp, str); + test_assert(strcmp(str_c(str), test->envelope) == 0); + } else { + i_error("Invalid envelope: %s", error); + } + + pool_unref(&pool); + test_end(); + } T_END; +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_envelope_write, + test_imap_envelope_parse, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-match.c b/src/lib-imap/test-imap-match.c new file mode 100644 index 0000000..df911da --- /dev/null +++ b/src/lib-imap/test-imap-match.c @@ -0,0 +1,127 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "imap-match.h" +#include "test-common.h" + +struct test_imap_match { + const char *pattern; + const char *input; + enum imap_match_result result; +}; + +static void test_imap_match(void) +{ + struct test_imap_match test[] = { + { "", "", IMAP_MATCH_YES }, + { "a", "b", IMAP_MATCH_NO }, + { "foo", "foo", IMAP_MATCH_YES }, + { "foo", "foo/", IMAP_MATCH_PARENT }, + { "%", "", IMAP_MATCH_YES }, + { "%", "foo", IMAP_MATCH_YES }, + { "%", "foo/", IMAP_MATCH_PARENT }, + { "%/", "foo/", IMAP_MATCH_YES }, + { "%", "foo/bar", IMAP_MATCH_PARENT }, + { "%/%", "foo", IMAP_MATCH_CHILDREN }, + { "%/%", "foo/", IMAP_MATCH_YES }, + { "foo/bar/%", "foo", IMAP_MATCH_CHILDREN }, + { "foo/bar/%", "foo/", IMAP_MATCH_CHILDREN }, + { "foo*", "foo", IMAP_MATCH_YES }, + { "foo*", "foo/", IMAP_MATCH_YES }, + { "foo*", "fobo", IMAP_MATCH_NO }, + { "*foo*", "bar/foo/", IMAP_MATCH_YES }, + { "*foo*", "fobo", IMAP_MATCH_CHILDREN }, + { "foo*bar", "foobar/baz", IMAP_MATCH_CHILDREN | IMAP_MATCH_PARENT }, + { "*foo*", "fobo", IMAP_MATCH_CHILDREN }, + { "%/%/%", "foo/", IMAP_MATCH_CHILDREN }, + { "%/%o/%", "foo/", IMAP_MATCH_CHILDREN }, + { "%/%o/%", "foo", IMAP_MATCH_CHILDREN }, + { "inbox", "inbox", IMAP_MATCH_YES }, + { "inbox", "INBOX", IMAP_MATCH_NO } + }; + struct test_imap_match inbox_test[] = { + { "inbox", "inbox", IMAP_MATCH_YES }, + { "inbox", "iNbOx", IMAP_MATCH_YES }, + { "i%X", "iNbOx", IMAP_MATCH_YES }, + { "%I%N%B%O%X%", "inbox", IMAP_MATCH_YES }, + { "i%X/foo", "iNbOx/foo", IMAP_MATCH_YES }, + { "%I%N%B%O%X%/foo", "inbox/foo", IMAP_MATCH_YES }, + { "i%X/foo", "inbx/foo", IMAP_MATCH_NO } + }; + struct imap_match_glob *glob, *glob2; + unsigned int i; + pool_t pool; + + pool = pool_alloconly_create("imap match", 1024); + + /* first try tests without inboxcasing */ + test_begin("imap match"); + for (i = 0; i < N_ELEMENTS(test); i++) { + glob = imap_match_init(pool, test[i].pattern, + FALSE, '/'); + test_assert(imap_match(glob, test[i].input) == test[i].result); + + glob2 = imap_match_dup(default_pool, glob); + test_assert(imap_match_globs_equal(glob, glob2)); + p_clear(pool); + + /* test the dup after clearing first one's memory */ + test_assert(imap_match(glob2, test[i].input) == test[i].result); + imap_match_deinit(&glob2); + } + + /* inboxcasing tests */ + for (i = 0; i < N_ELEMENTS(inbox_test); i++) { + glob = imap_match_init(pool, inbox_test[i].pattern, + TRUE, '/'); + test_assert(imap_match(glob, inbox_test[i].input) == inbox_test[i].result); + + glob2 = imap_match_dup(default_pool, glob); + test_assert(imap_match_globs_equal(glob, glob2)); + p_clear(pool); + + /* test the dup after clearing first one's memory */ + test_assert(imap_match(glob2, inbox_test[i].input) == inbox_test[i].result); + imap_match_deinit(&glob2); + } + pool_unref(&pool); + test_end(); +} + +static void test_imap_match_globs_equal(void) +{ + struct imap_match_glob *glob; + pool_t pool; + + pool = pool_alloconly_create("imap match globs equal", 1024); + test_begin("imap match globs equal"); + + glob = imap_match_init(pool, "1", FALSE, '/'); + test_assert(imap_match_globs_equal(glob, + imap_match_init(pool, "1", FALSE, '/'))); + test_assert(imap_match_globs_equal(glob, + imap_match_init(pool, "1", TRUE, '/'))); + test_assert(!imap_match_globs_equal(glob, + imap_match_init(pool, "1", FALSE, '.'))); + test_assert(!imap_match_globs_equal(glob, + imap_match_init(pool, "11", FALSE, '/'))); + + glob = imap_match_init(pool, "in%", TRUE, '/'); + test_assert(!imap_match_globs_equal(glob, + imap_match_init(pool, "in%", FALSE, '/'))); + test_assert(!imap_match_globs_equal(glob, + imap_match_init(pool, "In%", TRUE, '/'))); + + pool_unref(&pool); + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_match, + test_imap_match_globs_equal, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-parser.c b/src/lib-imap/test-imap-parser.c new file mode 100644 index 0000000..3ca4e34 --- /dev/null +++ b/src/lib-imap/test-imap-parser.c @@ -0,0 +1,157 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "imap-parser.h" +#include "test-common.h" + +static void test_imap_parser_crlf(void) +{ + static const char *test_input = "foo\r\nx\ry\n"; + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args; + unsigned int i; + enum imap_parser_error parse_error; + + test_begin("imap parser crlf handling"); + input = test_istream_create(test_input); + parser = imap_parser_create(input, NULL, 1024); + + /* must return -2 until LF is read */ + for (i = 0; test_input[i] != '\n'; i++) { + test_istream_set_size(input, i+1); + (void)i_stream_read(input); + test_assert(imap_parser_read_args(parser, 0, 0, &args) == -2); + } + test_istream_set_size(input, i+1); + (void)i_stream_read(input); + test_assert(imap_parser_read_args(parser, 0, 0, &args) == 1); + test_assert(args[0].type == IMAP_ARG_ATOM); + test_assert(args[1].type == IMAP_ARG_EOL); + + /* CR without LF should fail with error */ + imap_parser_reset(parser); + i_stream_seek(input, ++i); + test_istream_set_size(input, ++i); + (void)i_stream_read(input); + test_assert(imap_parser_read_args(parser, 0, 0, &args) == -2); + test_istream_set_size(input, ++i); + (void)i_stream_read(input); + test_assert(imap_parser_read_args(parser, 0, 0, &args) == -2); + test_istream_set_size(input, ++i); + (void)i_stream_read(input); + test_assert(imap_parser_read_args(parser, 0, 0, &args) == -1); + test_assert(strcmp(imap_parser_get_error + (parser, &parse_error), "CR sent without LF") == 0 && + parse_error == IMAP_PARSE_ERROR_BAD_SYNTAX); + + imap_parser_unref(&parser); + i_stream_destroy(&input); + test_end(); +} + +static void test_imap_parser_partial_list(void) +{ + static const char *test_input = "((((foo {1000000}\r\n"; + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args, *sub_list; + + test_begin("imap parser partial list"); + input = test_istream_create(test_input); + parser = imap_parser_create(input, NULL, 1024); + + (void)i_stream_read(input); + test_assert(imap_parser_read_args(parser, 0, + IMAP_PARSE_FLAG_LITERAL_SIZE, &args) == 1); + for (unsigned int i = 0; i < 4; i++) { + sub_list = imap_arg_as_list(&args[0]); + test_assert(IMAP_ARG_IS_EOL(&args[1])); + args = sub_list; + } + test_assert(imap_arg_atom_equals(&args[0], "foo")); + test_assert(args[1].type == IMAP_ARG_LITERAL_SIZE); + test_assert(IMAP_ARG_IS_EOL(&args[2])); + + imap_parser_unref(&parser); + i_stream_destroy(&input); + test_end(); +} + +static void test_imap_parser_read_tag_cmd(void) +{ + enum read_type { + BOTH, + TAG, + COMMAND + }; + struct { + const char *input; + const char *tag; + int ret; + enum read_type type; + } tests[] = { + { "tag foo", "tag", 1, BOTH }, + { "tag\r", "tag", 1, BOTH }, + { "tag\rfoo", "tag", 1, BOTH }, + { "tag\nfoo", "tag", 1, BOTH }, + { "tag\r\nfoo", "tag", 1, BOTH }, + { "\n", NULL, -1, BOTH }, + { "tag", NULL, 0, BOTH }, + { "tag\t", NULL, -1, BOTH }, + { "tag\001", NULL, -1, BOTH }, + { "tag\x80", NULL, -1, BOTH }, + { "tag(", NULL, -1, BOTH }, + { "tag)", NULL, -1, BOTH }, + { "tag{", NULL, -1, BOTH }, + { "tag/ ", "tag/", 1, BOTH }, + { "tag%", NULL, -1, BOTH }, + { "tag*", NULL, -1, BOTH }, + { "tag\"", NULL, -1, BOTH }, + { "tag\\", NULL, -1, BOTH }, + { "tag+", NULL, -1, TAG }, + { "tag+ ", "tag+", 1, COMMAND }, + }; + struct istream *input; + struct imap_parser *parser; + const char *atom; + int ret; + + test_begin("imap_parser_read_tag and imap_parser_read_command_name"); + for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { + if (tests[i].type != COMMAND) { + input = test_istream_create(tests[i].input); + test_assert(i_stream_read(input) > 0); + parser = imap_parser_create(input, NULL, 1024); + ret = imap_parser_read_tag(parser, &atom); + test_assert_idx(ret == tests[i].ret, i); + test_assert_idx(ret <= 0 || strcmp(tests[i].tag, atom) == 0, i); + imap_parser_unref(&parser); + i_stream_destroy(&input); + } + + if (tests[i].type != TAG) { + input = test_istream_create(tests[i].input); + test_assert(i_stream_read(input) > 0); + parser = imap_parser_create(input, NULL, 1024); + ret = imap_parser_read_command_name(parser, &atom); + test_assert_idx(ret == tests[i].ret, i); + test_assert_idx(ret <= 0 || strcmp(tests[i].tag, atom) == 0, i); + imap_parser_unref(&parser); + i_stream_destroy(&input); + } + } + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_parser_crlf, + test_imap_parser_partial_list, + test_imap_parser_read_tag_cmd, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-quote.c b/src/lib-imap/test-imap-quote.c new file mode 100644 index 0000000..77b46b3 --- /dev/null +++ b/src/lib-imap/test-imap-quote.c @@ -0,0 +1,171 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "imap-quote.h" +#include "test-common.h" + +static void test_imap_append_string_for_humans(void) +{ + static const struct { + const char *input, *output; + } tests[] = { + { "", "\"\"" }, + { " ", "\"\"" }, + { " ", "\"\"" }, + { "\t", "\"\"" }, + { " \t", "\"\"" }, + { " \t ", "\"\"" }, + { " foo", "{3}\r\nfoo" }, + { "\tfoo", "{3}\r\nfoo" }, + { "\t \tfoo", "{3}\r\nfoo" }, + { " foo ", "{3}\r\nfoo" }, + { " foo ", "{3}\r\nfoo" }, + { " foo \t \t", "{3}\r\nfoo" }, + { "hello\"world", "{11}\r\nhello\"world" }, + { "hello\\world", "{11}\r\nhello\\world" }, + { "hello\rworld", "{11}\r\nhello world" }, + { "hello\nworld", "{11}\r\nhello world" }, + { "hello\r\nworld", "{11}\r\nhello world" }, + { "hello\r\n world", "{11}\r\nhello world" }, + { "hello \r\n world", "{11}\r\nhello world" }, + }; + string_t *str = t_str_new(128); + unsigned int i; + + test_begin("imap_append_string_for_humans()"); + + for (i = 0; i < N_ELEMENTS(tests); i++) { + str_truncate(str, 0); + imap_append_string_for_humans(str, (const void *)tests[i].input, + strlen(tests[i].input)); + test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i); + } + test_end(); +} + +static void test_imap_append_astring(void) +{ + static const struct { + const char *input, *output; + } tests[] = { + { "", "\"\"" }, + { "NIL", "\"NIL\"" }, + { "niL", "\"niL\"" }, + { "ni", "ni" }, + { "\\", "\"\\\\\"" }, + { "\\\\", "\"\\\\\\\\\"" }, + { "\\\\\\", "\"\\\\\\\\\\\\\"" }, + { "\\\\\\\\", "\"\\\\\\\\\\\\\\\\\"" }, + { "\\\\\\\\\\", "{5}\r\n\\\\\\\\\\" }, + { "\\\\\\\\\\\\", "{6}\r\n\\\\\\\\\\\\" }, + { "\"", "\"\\\"\"" }, + { "\"\"", "\"\\\"\\\"\"" }, + { "\"\"\"", "\"\\\"\\\"\\\"\"" }, + { "\"\"\"\"", "\"\\\"\\\"\\\"\\\"\"" }, + { "\"\"\"\"\"", "{5}\r\n\"\"\"\"\"" }, + { "\"\"\"\"\"\"", "{6}\r\n\"\"\"\"\"\"" }, + { "\r", "{1}\r\n\r" }, + { "\n", "{1}\r\n\n" }, + { "\r\n", "{2}\r\n\r\n" }, + { "\x7f", "\"\x7f\"" }, + { "\x80", "{1}\r\n\x80" }, + { "\xff", "{1}\r\n\xff" }, + }; + string_t *str = t_str_new(128); + unsigned int i; + + test_begin("test_imap_append_astring()"); + + for (i = 0; i < N_ELEMENTS(tests); i++) { + str_truncate(str, 0); + imap_append_astring(str, tests[i].input); + test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i); + } + test_end(); +} + +static void test_imap_append_nstring(void) +{ + static const struct { + const char *input, *output; + } tests[] = { + { "", "\"\"" }, + { NULL, "NIL" }, + { "NIL", "\"NIL\"" }, + { "\"America N.\"", "\"\\\"America N.\\\"\"" }, + { "\"America N.\", \"America S.\"", "\"\\\"America N.\\\", \\\"America S.\\\"\"" }, + { "\"America N.\", \"America S.\", \"Africa\"", "{36}\r\n\"America N.\", \"America S.\", \"Africa\"" }, + { "Antarctica\n Australia", "{21}\r\nAntarctica\n Australia" }, + { "ni", "\"ni\"" } + }; + string_t *str = t_str_new(128); + unsigned int i; + + test_begin("test_imap_append_nstring()"); + + for (i = 0; i < N_ELEMENTS(tests); i++) { + str_truncate(str, 0); + imap_append_nstring(str, tests[i].input); + test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i); + } + test_end(); +} + +static void test_imap_append_nstring_nolf(void) +{ + static const struct { + const char *input, *output; + } tests[] = { + { "", "\"\"" }, + { NULL, "NIL" }, + { "NIL", "\"NIL\"" }, + { "ni", "\"ni\"" }, + { "\"NIL\n foo", "\"\\\"NIL foo\"" }, + { "\"America N.\", \"America S.\", \"Africa\"", "{36}\r\n\"America N.\", \"America S.\", \"Africa\"" }, + { "foo\nbar", "\"foo bar\"" }, + { "foo\r\nbar", "\"foo bar\"" }, + { "foo\rbar", "\"foo bar\"" }, + { "foo\n bar", "\"foo bar\"" }, + { "foo\r\n bar", "\"foo bar\"" }, + { "foo\r bar", "\"foo bar\"" }, + { "foo\n\tbar", "\"foo\tbar\"" }, + { "foo\r\n\tbar", "\"foo\tbar\"" }, + { "foo\r\tbar", "\"foo\tbar\"" }, + { "foo\n bar", "\"foo bar\"" }, + { "foo\r\n bar", "\"foo bar\"" }, + { "foo\r bar", "\"foo bar\"" }, + { "\nfoo\r bar\r\n", "\" foo bar\"" } + }; + unsigned int i; + + test_begin("test_imap_append_nstring_nolf()"); + + for (i = 0; i < N_ELEMENTS(tests); i++) T_BEGIN { + string_t *str = t_str_new(1); + string_t *str2 = str_new(default_pool, 1); + + str_truncate(str, 0); + imap_append_nstring_nolf(str, tests[i].input); + test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i); + + str_truncate(str2, 0); + imap_append_nstring_nolf(str2, tests[i].input); + test_assert_idx(strcmp(tests[i].output, str_c(str2)) == 0, i); + + str_free(&str2); + } T_END; + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_append_string_for_humans, + test_imap_append_astring, + test_imap_append_nstring, + test_imap_append_nstring_nolf, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-url.c b/src/lib-imap/test-imap-url.c new file mode 100644 index 0000000..a63ca33 --- /dev/null +++ b/src/lib-imap/test-imap-url.c @@ -0,0 +1,1029 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "imap-url.h" +#include "test-common.h" + +struct valid_imap_url_test { + const char *url; + enum imap_url_parse_flags flags; + struct imap_url url_base; + + struct imap_url url_parsed; +}; + +/* Valid IMAP URL tests */ +static const struct valid_imap_url_test valid_url_tests[] = { + { + .url = "imap://localhost", + .url_parsed = { + .host = { .name = "localhost" } } + },{ + .url = "imap://user@localhost", + .url_parsed = { + .host = { .name = "localhost" }, + .userid = "user" } + },{ + .url = "imap://user;AUTH=PLAIN@localhost", + .url_parsed = { + .host = { .name = "localhost" }, + .userid = "user", + .auth_type = "PLAIN" } + },{ + .url = "imap://;AUTH=PLAIN@localhost", + .url_parsed = { + .host = { .name = "localhost" }, + .auth_type = "PLAIN" } + },{ + .url = "imap://%68endri%6B;AUTH=GSS%41PI@%65%78%61%6d%70%6c%65.com", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "hendrik", + .auth_type = "GSSAPI" } + },{ + .url = "imap://user@localhost:993", + .url_parsed = { + .host = { .name = "localhost" }, + .userid = "user", + .port = 993 } + },{ + .url = "imap://user@127.0.0.1", + .url_parsed = { + .host = { + .name = "127.0.0.1", + .ip = { .family = AF_INET } }, + .userid = "user" } + },{ + .url = "imap://user@[::1]", + .url_parsed = { + .host = { + .name = "[::1]", + .ip = { .family = AF_INET6 } }, + .userid = "user" } + },{ + .url = "imap://user@4example.com:423", + .url_parsed = { + .host = { .name = "4example.com" }, + .userid = "user", + .port = 423 } + },{ + .url = "imap://beelzebub@666.4example.com:999", + .url_parsed = { + .host = { .name = "666.4example.com" }, + .userid = "beelzebub", + .port = 999 } + },{ + .url = "imap://user@example.com/", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = NULL } + },{ + .url = "imap://user@example.com/./", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = NULL } + },{ + .url = "imap://user@example.com/INBOX", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX" } + },{ + .url = "imap://user@example.com/INBOX/", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX" } + },{ + .url = "imap://user@example.com//", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user"} + },{ + .url = "imap://user@example.com/INBOX/Trash", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash" } + },{ + .url = "imap://user@example.com/INBOX/Trash/..", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX" } + },{ + .url = "imap://user@example.com/INBOX/Trash/../", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX" } + },{ + .url = "imap://user@example.com/INBOX/Trash/../..", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = NULL } + },{ + .url = "imap://user@example.com/INBOX.Trash", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX.Trash" } + },{ + .url = "imap://user@example.com/INBOX%3BTrash", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX;Trash" } + },{ + .url = "imap://user@example.com/INBOX;UIDVALIDITY=1341", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", .uidvalidity = 1341 } + },{ + .url = "imap://user@example.com/INBOX/;UIDVALIDITY=23423", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", .uidvalidity = 23423 } + },{ + .url = "imap://user@example.com/INBOX/Drafts;UIDVALIDITY=6567", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", .uidvalidity = 6567 } + },{ + .url = "imap://user@example.com/INBOX/Drafts;UIDVALIDITY=788/;UID=16", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", .uidvalidity = 788, + .uid = 16 } + },{ + .url = "imap://user@example.com/INBOX/Drafts;UIDVALIDITY=788/;UID=16/..", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", .uidvalidity = 788, + .uid = 0 } + },{ + .url = "imap://user@example.com/INBOX/Drafts;UIDVALIDITY=788/;UID=16/../..", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", .uidvalidity = 0, + .uid = 0 } + },{ + .url = "imap://user@example.com/INBOX/Junk;UIDVALIDITY=27667/" + ";UID=434/;SECTION=HEADER", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Junk", .uidvalidity = 27667, + .uid = 434, .section = "HEADER" } + },{ + .url = "imap://user@example.com/INBOX/Important/" + ";UID=437/;SECTION=1.2.MIME", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Important", + .uid = 437, .section = "1.2.MIME" } + },{ + .url = "imap://user@example.com/INBOX/Important/;UID=56/;SECTION=AA/BB", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Important", + .uid = 56, .section = "AA/BB" } + },{ + .url = "imap://user@example.com/INBOX/Important/;UID=56/;SECTION=AA/BB/..", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Important", + .uid = 56, .section = "AA/" } + },{ + .url = "imap://user@example.com/INBOX/Important/;UID=56/" + ";SECTION=AA/BB/../..", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Important", + .uid = 56, .section = NULL } + },{ + .url = "imap://user@example.com/INBOX/Important/;UID=234/" + ";SECTION=HEADER.FIELDS%20(%22To%22%20%22From%22)", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Important", + .uid = 234, .section = "HEADER.FIELDS (\"To\" \"From\")" } + },{ + .url = "imap://user@example.com/INBOX/Important/;UID=234/" + ";PARTIAL=10.250", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Important", + .uid = 234, .section = NULL, .partial_offset = 10, .partial_size = 250 } + },{ + .url = "imap://hendrik@example.com/INBOX/Important/;UID=34534/" + ";SECTION=1.3.TEXT/;PARTIAL=0.34254", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "hendrik", + .mailbox = "INBOX/Important", + .uid = 34534, .section = "1.3.TEXT", + .partial_offset = 0, .partial_size = 34254 } + },{ + .url = "imap://hendrik@example.com/INBOX/Sent" + ";UIDVALIDITY=534?SUBJECT%20%22Frop?%22", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "hendrik", + .mailbox = "INBOX/Sent", .uidvalidity = 534, + .search_program = "SUBJECT \"Frop?\"" } + },{ + .url = "//hendrik@example.org/INBOX/Trash", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user" }, + .url_parsed = { + .host = { .name = "example.org" }, + .userid = "hendrik", + .mailbox = "INBOX/Trash" } + },{ + .url = "/INBOX/Trash", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash" } + },{ + .url = "user@example.com", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Accounts" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Accounts/user@example.com" } + },{ + .url = "Drafts", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts" } + },{ + .url = "../Drafts", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts" } + },{ + .url = "../Junk", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Junk", + .uidvalidity = 0 } + },{ + .url = "../Junk;UIDVALIDITY=23", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Junk", + .uidvalidity = 23 } + },{ + .url = "../../%23shared;UIDVALIDITY=23452", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 764 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "#shared", + .uidvalidity = 23452 } + },{ + .url = "../../%23news;UIDVALIDITY=546/;UID=456", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "#news", + .uidvalidity = 546, + .uid = 456 } + },{ + .url = "", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452 } + },{ + .url = "", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65 } + },{ + .url = "", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65, + .section = "AA/BB", + .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65, + .section = "AA/BB", + .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 } + },{ + .url = "", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65, + .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65, + .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 } + },{ + .url = ";UID=4767", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 4767 } + },{ + .url = ";UID=4767", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452}, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 4767 } + },{ + .url = "../;UID=4767", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", + .uidvalidity = 0, + .uid = 4767 } + },{ + .url = "../;UID=4767/;SECTION=TEXT", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65, + .section = "1.2.3.MIME" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 4767, + .section = "TEXT" } + },{ + .url = ";SECTION=TEXT", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "1.2.3.MIME" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "TEXT" } + },{ + .url = "..", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "AA/BB" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43 } + },{ + .url = "../;SECTION=CC", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "AA/BB" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "CC" } + },{ + .url = "CC", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "AA/BB" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "AA/CC" } + },{ + .url = ";PARTIAL=1024.1024", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .have_partial = TRUE, .partial_offset = 0, .partial_size = 1024 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 } + },{ + .url = "../CC/;PARTIAL=0.512", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "AA/BB", + .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "AA/CC", + .have_partial = TRUE, .partial_offset = 0, .partial_size = 512 } + },{ + .url = "imap://user@example.com/INBOX/;UID=377;URLAUTH=anonymous", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", + .uid = 377, + .uauth_rumpurl = "imap://user@example.com/INBOX/;UID=377" + ";URLAUTH=anonymous", + .uauth_access_application = "anonymous"} + },{ + .url = "imap://user@example.com/INBOX/;UID=377" + ";URLAUTH=anonymous:internal:4142434445464748494A4B4C4D4E4F5051525354", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", + .uid = 377, + .uauth_rumpurl = "imap://user@example.com/INBOX/;UID=377" + ";URLAUTH=anonymous", + .uauth_access_application = "anonymous", + .uauth_mechanism = "internal", + .uauth_token = (const unsigned char *)"ABCDEFGHIJKLMNOPQRST", + .uauth_token_size = 20} + },{ + .url = "imap://user@example.com/INBOX/;UID=377" + ";EXPIRE=2011-02-12T12:45:14+01:00" + ";URLAUTH=user+frop:internal:4142434445464748494A4B4C4D4E4F5051525354", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", + .uid = 377, + .uauth_rumpurl = "imap://user@example.com/INBOX/;UID=377" + ";EXPIRE=2011-02-12T12:45:14+01:00;URLAUTH=user+frop", + .uauth_access_application = "user", + .uauth_access_user = "frop", + .uauth_mechanism = "internal", + .uauth_token = (const unsigned char *)"ABCDEFGHIJKLMNOPQRST", + .uauth_token_size = 20} + } +}; + +static const unsigned int valid_url_test_count = N_ELEMENTS(valid_url_tests); + +static void test_imap_url_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_url_test_count; i++) T_BEGIN { + const char *url = valid_url_tests[i].url; + enum imap_url_parse_flags flags = valid_url_tests[i].flags; + const struct imap_url *urlt = &valid_url_tests[i].url_parsed; + const struct imap_url *urlb = &valid_url_tests[i].url_base; + struct imap_url *urlp; + const char *error = NULL; + + test_begin(t_strdup_printf("imap url valid [%d]", i)); + + if (urlb->host.name == NULL) urlb = NULL; + if (imap_url_parse(url, urlb, flags, &urlp, &error) < 0) + urlp = NULL; + + test_out_reason(t_strdup_printf("imap_url_parse(%s)", + valid_url_tests[i].url), urlp != NULL, error); + if (urlp != NULL) { + if (urlp->host.name == NULL || urlt->host.name == NULL) { + test_out_quiet(t_strdup_printf("url->host.name = %s", urlp->host.name), + urlp->host.name == urlt->host.name); + } else { + test_out_quiet(t_strdup_printf("url->host.name = %s", urlp->host.name), + strcmp(urlp->host.name, urlt->host.name) == 0); + } + if (urlp->userid == NULL || urlt->userid == NULL) { + test_out_quiet(t_strdup_printf("url->userid = %s", urlp->userid), + urlp->userid == urlt->userid); + } else { + test_out_quiet(t_strdup_printf("url->userid = %s", urlp->userid), + strcmp(urlp->userid, urlt->userid) == 0); + } + if (urlp->auth_type == NULL || urlt->auth_type == NULL) { + test_out_quiet(t_strdup_printf("url->auth_type = %s", urlp->auth_type), + urlp->auth_type == urlt->auth_type); + } else { + test_out_quiet(t_strdup_printf("url->auth_type = %s", urlp->auth_type), + strcmp(urlp->auth_type, urlt->auth_type) == 0); + } + if (urlp->port == 0) { + test_out_quiet("url->port = (unspecified)", + urlp->port == urlt->port); + } else { + test_out_quiet(t_strdup_printf("url->port = %u", urlp->port), + urlp->port == urlt->port); + } + if (urlp->host.ip.family == 0) { + test_out_quiet("url->host.ip = (unspecified)", + urlp->host.ip.family == urlt->host.ip.family); + } else { + test_out_quiet("url->host.ip = (valid)", + urlp->host.ip.family == urlt->host.ip.family); + } + if (urlp->mailbox == NULL || urlt->mailbox == NULL) { + test_out_quiet(t_strdup_printf("url->mailbox = %s", urlp->mailbox), + urlp->mailbox == urlt->mailbox); + } else { + test_out_quiet(t_strdup_printf("url->mailbox = %s", urlp->mailbox), + strcmp(urlp->mailbox, urlt->mailbox) == 0); + } + test_out_quiet(t_strdup_printf("url->uidvalidity = %u", urlp->uidvalidity), + urlp->uidvalidity == urlt->uidvalidity); + test_out_quiet(t_strdup_printf("url->uid = %u", urlp->uid), + urlp->uid == urlt->uid); + if (urlp->section == NULL || urlt->section == NULL) { + test_out_quiet(t_strdup_printf("url->section = %s", urlp->section), + urlp->section == urlt->section); + } else { + test_out_quiet(t_strdup_printf("url->section = %s", urlp->section), + strcmp(urlp->section, urlt->section) == 0); + } + test_out_quiet(t_strdup_printf("url->partial = %"PRIuUOFF_T".%"PRIuUOFF_T, + urlp->partial_offset, urlp->partial_size), + urlp->partial_offset == urlt->partial_offset && + urlp->partial_size == urlt->partial_size); + if (urlp->search_program == NULL || urlt->search_program == NULL) { + test_out_quiet(t_strdup_printf( + "url->search_program = %s", urlp->search_program), + urlp->search_program == urlt->search_program); + } else { + test_out_quiet(t_strdup_printf( + "url->search_program = %s", urlp->search_program), + strcmp(urlp->search_program, urlt->search_program) == 0); + } + if (urlt->uauth_rumpurl != NULL) { + if (urlp->uauth_rumpurl == NULL) { + test_out_quiet("url->uauth_rumpurl = NULL", FALSE); + } else { + test_out_quiet(t_strdup_printf( + "url->uauth_rumpurl = %s", urlp->uauth_rumpurl), + strcmp(urlp->uauth_rumpurl, urlt->uauth_rumpurl) == 0); + } + if (urlp->uauth_access_application == NULL || + urlt->uauth_access_application == NULL) { + test_out_quiet(t_strdup_printf("url->uauth_access_application = %s", + urlp->uauth_access_application), + urlp->uauth_access_application == urlt->uauth_access_application); + } else { + test_out_quiet(t_strdup_printf("url->uauth_access_application = %s", + urlp->uauth_access_application), + strcmp(urlp->uauth_access_application, + urlt->uauth_access_application) == 0); + } + if (urlp->uauth_access_user == NULL || + urlt->uauth_access_user == NULL) { + test_out_quiet(t_strdup_printf("url->uauth_access_user = %s", + urlp->uauth_access_user), + urlp->uauth_access_user == urlt->uauth_access_user); + } else { + test_out_quiet(t_strdup_printf("url->uauth_access_user = %s", + urlp->uauth_access_user), + strcmp(urlp->uauth_access_user, + urlt->uauth_access_user) == 0); + } + if (urlp->uauth_mechanism == NULL || urlt->uauth_mechanism == NULL) { + test_out_quiet(t_strdup_printf( + "url->uauth_mechanism = %s", urlp->uauth_mechanism), + urlp->uauth_mechanism == urlt->uauth_mechanism); + } else { + test_out_quiet(t_strdup_printf( + "url->uauth_mechanism = %s", urlp->uauth_mechanism), + strcmp(urlp->uauth_mechanism, urlt->uauth_mechanism) == 0); + } + if (urlp->uauth_token == NULL || urlt->uauth_token == NULL) { + test_out_quiet(t_strdup_printf( + "url->uauth_token = %s", urlp->uauth_token), + urlp->uauth_token == urlt->uauth_token); + } else { + bool equal = urlp->uauth_token_size == urlt->uauth_token_size; + size_t i; + test_out_quiet(t_strdup_printf( + "url->uauth_token_size = %zu", urlp->uauth_token_size), + equal); + + if (equal) { + for (i = 0; i < urlp->uauth_token_size; i++) { + if (urlp->uauth_token[i] != urlt->uauth_token[i]) { + equal = FALSE; + break; + } + } + test_out_quiet(t_strdup_printf("url->uauth_token [index=%d]", (int)i), + equal); + } + } + } + } + + test_end(); + } T_END; +} + +struct invalid_imap_url_test { + const char *url; + enum imap_url_parse_flags flags; + struct imap_url url_base; +}; + +static const struct invalid_imap_url_test invalid_url_tests[] = { + { + .url = "http://www.dovecot.org" + },{ + .url = "imap:/INBOX" + },{ + .url = "imap://user@example.com/INBOX", + .flags = IMAP_URL_PARSE_REQUIRE_RELATIVE, + .url_base = { + .host = { .name = "example.com" }, + .userid = "user" } + },{ + .url = "" + },{ + .url = "/INBOX/;UID=377" + },{ + .url = "imap://user@example.com/INBOX/;UID=377/;SECTION=TEXT?ALL" + },{ + .url = "imap://user@example.com/INBOX/?" + },{ + .url = "imap://user@example.com/INBOX/#Fragment" + },{ + .url = "imap://user@example.com/INBOX/\"" + },{ + .url = "imap:///INBOX" + },{ + .url = "imap://[]/INBOX" + },{ + .url = "imap://[v08.234:232:234:234:2221]/INBOX" + },{ + .url = "imap://[1::34a:34:234::6]/INBOX" + },{ + .url = "imap://example%a.com/INBOX" + },{ + .url = "imap://example.com%/INBOX" + },{ + .url = "imap://example%00.com/INBOX" + },{ + .url = "imap://example.com:65539/INBOX" + },{ + .url = "imap://user;ATH=frop@example.com" + },{ + .url = "imap://user;AUTH=frop;friep@example.com" + },{ + .url = "imap://user;AUTH=@example.com" + },{ + .url = "imap://user:password@example.com" + },{ + .url = "imap://user;AUTH=A:B@example.com" + },{ + .url = "imap://user%@example.com" + },{ + .url = "imap://user%00@example.com" + },{ + .url = "imap://user%ar;AUTH=*@example.com" + },{ + .url = "imap://;AUTH=FR%etD@example.com" + },{ + .url = "imap://user;AUTH=%@example.com" + },{ + .url = "imap://user;AUTH=%00@example.com" + },{ + .url = "imap://example.com/INBOX/%00/" + },{ + .url = "imap://example.com/INBOX/%0r/" + },{ + .url = "imap://example.com/INBOX/Trash/%/" + },{ + .url = "imap://example.com/INBOX;UIDVALIDITY=23423;FROP=friep/" + },{ + .url = "imap://example.com/INBOX;UIDVALIDITY=0/;UID=377" + },{ + .url = "imap://example.com/INBOX;UIDVALIDITY=/" + },{ + .url = "imap://example.com/INBOX;UIDVALIDITY=33a/" + },{ + .url = "imap://example.com/INBOX;FROP=friep/" + },{ + .url = "imap://example.com/INBOX/;UID=377;FROP=friep/" + },{ + .url = "imap://example.com/INBOX/;UID=0/" + },{ + .url = "imap://example.com/INBOX/;UID=/" + },{ + .url = "imap://example.com/INBOX/;UID=5e6/" + },{ + .url = "imap://example.com/INBOX/;UID=35/;SECTION=ALL;FROP=43/" + },{ + .url = "imap://example.com/INBOX/;UID=35/;SECTION=/" + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=" + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=0." + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=0.e10" + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=.3" + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=5t4.3" + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=0.0" + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=0.23409823409820938409823" + },{ + .url = "imap://example.com/INBOX/;UID=377/;FROP=34" + },{ + .url = "imap://example.com/INBOX/;UID=377;FROP=34" + },{ + .url = "imap://example.com/INBOX/;UID=377;EXPIRE=2010-02-02T12:00:12Z" + },{ + .url = "imap://example.com/INBOX/;UID=377" + ";URLAUTH=anonymous:internal:0ad89fafd79f54afe4523f45aadf2afe" + },{ + .url = "imap://example.com/INBOX/;UID=377;EXPIRE=2011-15-02T00:00:00Z" + ";URLAUTH=anonymous:internal:0ad89fafd79f54afe4523f45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377;EXPIRE=2011-10-02T00:00:00Z", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "/INBOX/;UID=377;EXPIRE=2011-10-02T00:00:00Z" + ";URLAUTH=anonymous:internal:0ad89fafd79f54afe4523f45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377;URLAUTH=", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377" + ";URLAUTH=:internal:0ad89fafd79f54afe4523f45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377" + ";URLAUTH=user+:internal:0ad89fafd79f54afe4523f45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377" + ";URLAUTH=+frop:internal:0ad89fafd79f54afe4523f45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377;URLAUTH=anonymous:", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377" + ";URLAUTH=anonymous::0ad89fafd79f54afe4523f45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377;URLAUTH=anonymous:internal:", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377" + ";URLAUTH=anonymous:internal:fd79f54afe4523", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377;EXPIRE=2011-10-02T00:00:00Z" + ";URLAUTH=anonymous:internal:0ad89fafd79f54afe4523q45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + }, +}; + +static const unsigned int invalid_url_test_count = N_ELEMENTS(invalid_url_tests); + +static void test_imap_url_invalid(void) +{ + unsigned int i; + + for (i = 0; i < invalid_url_test_count; i++) T_BEGIN { + const char *url = invalid_url_tests[i].url; + enum imap_url_parse_flags flags = invalid_url_tests[i].flags; + const struct imap_url *urlb = &invalid_url_tests[i].url_base; + struct imap_url *urlp; + const char *error = NULL; + + if (urlb->host.name == NULL) + urlb = NULL; + + test_begin(t_strdup_printf("imap url invalid [%d]", i)); + + if (imap_url_parse(url, urlb, flags, &urlp, &error) < 0) + urlp = NULL; + test_out_reason(t_strdup_printf("parse %s", url), urlp == NULL, error); + + test_end(); + } T_END; + +} + +static const char *parse_create_url_tests[] = { + "imap://host.example.com/", + "imap://10.0.0.1/", + "imap://[::1]/", + "imap://user@host.example.com/", + "imap://user@host.example.com:993/", + "imap://su%3auser@host.example.com/", + "imap://user;AUTH=PLAIN@host.example.com/", + "imap://user;AUTH=PLAIN@host.example.com/INBOX", + "imap://user;AUTH=PLAIN@host.example.com/INBOX/;UID=5", + "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5", + "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=TEXT", + "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=TEXT/;PARTIAL=1", + "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=TEXT/;PARTIAL=1.14", + "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=TEXT/;PARTIAL=1.14;URLAUTH=anonymous", + "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=TEXT/;PARTIAL=1.14;URLAUTH=user+username", + "imap://user;AUTH=PLAIN@host.example.com/INBOX?SUBJECT%20%22Frop?%22", + "imap://user%3ba@host.example.com/", + "imap://user%40example.com@host.example.com/", + "imap://user%40example.com;AUTH=STR%23ANGE@host.example.com/", + "imap://user;AUTH=PLAIN@host.example.com/INBOX/Important%3bWork", + "imap://user@host.example.com/%23shared/news", + "imap://user@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=HEADER.FIELDS%20(DATE%20FROM)", + "imap://user@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=TEXT/;PARTIAL=1.14;URLAUTH=user+user%3bname", +}; + +static const unsigned int parse_create_url_test_count = N_ELEMENTS(parse_create_url_tests); + +static void test_imap_url_parse_create(void) +{ + unsigned int i; + + for (i = 0; i < parse_create_url_test_count; i++) T_BEGIN { + const char *url = parse_create_url_tests[i]; + struct imap_url *urlp; + const char *error = NULL; + + test_begin(t_strdup_printf("imap url parse/create [%d]", i)); + + if (imap_url_parse + (url, NULL, IMAP_URL_PARSE_ALLOW_URLAUTH, &urlp, &error) < 0) + urlp = NULL; + test_out_reason(t_strdup_printf("parse %s", url), urlp != NULL, error); + if (urlp != NULL) { + const char *urlnew = imap_url_create(urlp); + test_out(t_strdup_printf + ("create %s", urlnew), strcmp(url, urlnew) == 0); + } + + test_end(); + } T_END; + +} + + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_url_valid, + test_imap_url_invalid, + test_imap_url_parse_create, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-utf7.c b/src/lib-imap/test-imap-utf7.c new file mode 100644 index 0000000..216eebf --- /dev/null +++ b/src/lib-imap/test-imap-utf7.c @@ -0,0 +1,216 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "unichar.h" +#include "imap-utf7.h" +#include "test-common.h" + +static void test_imap_utf7_by_example(void) +{ + static const struct test { + const char *utf8; + const char *mutf7; + } tests[] = { + { "&&x&&", "&-&-x&-&-" }, + { "~peter/mail/\xe5\x8f\xb0\xe5\x8c\x97/\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e", + "~peter/mail/&U,BTFw-/&ZeVnLIqe-" }, + { "tiet\xc3\xa4&j\xc3\xa4&", "tiet&AOQ-&-j&AOQ-&-" }, /* & is always encoded as &- */ + { "p\xe4\xe4", NULL }, + { NULL, "&" }, + { NULL, "&Jjo" }, + { NULL, "&Jjo!" }, + { NULL, "&U,BTFw-&ZeVnLIqe-" } /* unnecessary shift */ + }; + string_t *dest, *dest2; + unsigned int i; + + dest = t_str_new(256); + dest2 = t_str_new(256); + + test_begin("imap mutf7 examples"); + for (i = 0; i < N_ELEMENTS(tests); i++) { + str_truncate(dest, 0); + if (tests[i].utf8 != NULL) { + if (imap_utf8_to_utf7(tests[i].utf8, dest) < 0) + test_assert_idx(tests[i].mutf7 == NULL, i); + else + test_assert_idx(null_strcmp(tests[i].mutf7, str_c(dest)) == 0, i); + } else { + /* invalid mUTF-7 - test that escaping works */ + str_truncate(dest2, 0); + imap_utf7_to_utf8_escaped(tests[i].mutf7, "%", dest); + imap_escaped_utf8_to_utf7(str_c(dest), '%', dest2); + test_assert_idx(strcmp(tests[i].mutf7, str_c(dest2)) == 0, i); + } + if (tests[i].mutf7 != NULL) { + str_truncate(dest, 0); + if (imap_utf7_to_utf8(tests[i].mutf7, dest) < 0) + test_assert_idx(tests[i].utf8 == NULL, i); + else + test_assert_idx(null_strcmp(tests[i].utf8, str_c(dest)) == 0, i); + test_assert_idx(imap_utf7_is_valid(tests[i].mutf7) != (tests[i].utf8 == NULL), i); + } + } + + str_truncate(dest, 0); + imap_utf7_to_utf8_escaped(".foo%", "%.", dest); + test_assert_strcmp(str_c(dest), "%2efoo%25"); + + str_truncate(dest, 0); + test_assert(imap_escaped_utf8_to_utf7("%foo%2ebar", '%', dest) == 0); + test_assert_strcmp(str_c(dest), "%foo.bar"); + + test_end(); +} + +static void test_imap_utf7_ucs4_cases(void) +{ + string_t *src, *dest; + const char *orig_src; + unsigned int i, j; + unichar_t chr; + + src = t_str_new(256); + dest = t_str_new(256); + + test_begin("imap mutf7 ucs4 cases"); + for (chr = 0xffff; chr <= 0x10010; chr++) T_BEGIN { + for (i = 1; i <= 10; i++) { + str_truncate(src, 0); + str_truncate(dest, 0); + for (j = 0; j < i; j++) { + if (j % 3 == 0) + str_append_c(src, 'x'); + if (j % 5 == 0) + str_append_c(src, '&'); + uni_ucs4_to_utf8_c(chr, src); + } + + orig_src = t_strdup(str_c(src)); + str_truncate(src, 0); + + test_assert_idx(imap_utf8_to_utf7(orig_src, dest) == 0, chr*100+i); + test_assert_idx(imap_utf7_to_utf8(str_c(dest), src) == 0, chr*100+i); + test_assert_idx(strcmp(str_c(src), orig_src) == 0, chr+100+i); + } + } T_END; + test_end(); +} + +static const char mb64[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; +static void test_imap_utf7_non_utf16(void) +{ + string_t *dest, *dest2; + unsigned int i; + + test_begin("imap mutf7 non-utf16"); + dest = t_str_new(32); + dest2 = t_str_new(32); + for (i = 0; i <= 255; ++i) { + /* Invalid, code a single 8-bit octet */ + const char csrc[] = { + '&', + mb64[i >> 2], + mb64[(i & 3) << 4], + '-', + '\0' + }; + test_assert_idx(!imap_utf7_is_valid(csrc), i); + + /* escaping can reverse the original string */ + str_truncate(dest, 0); + str_truncate(dest2, 0); + imap_utf7_to_utf8_escaped(csrc, "%", dest); + imap_escaped_utf8_to_utf7(str_c(dest), '%', dest2); + test_assert_idx(strcmp(csrc, str_c(dest2)) == 0, i); + } + for (i = 0; i <= 255; ++i) { + /* Invalid, U+00E4 followed by a single octet */ + const char csrc[] = { + '&', + mb64[ (0x00 >> 2)], + mb64[((0x00 & 0x03) << 4) | (0xe4 >> 4)], + mb64[((0xe4 & 0x0f) << 2) | ( i >> 6)], + mb64[ i & 0x3f ], + '-', + '\0' + }; + test_assert_idx(!imap_utf7_is_valid(csrc), i); + + /* escaping can reverse the original string */ + str_truncate(dest, 0); + str_truncate(dest2, 0); + imap_utf7_to_utf8_escaped(csrc, "%", dest); + imap_escaped_utf8_to_utf7(str_c(dest), '%', dest2); + test_assert_idx(strcmp(csrc, str_c(dest2)) == 0, i); + } + test_end(); +} + +static void test_imap_utf7_bad_ascii(void) +{ + string_t *dest; + char csrc[1+1]; + unsigned int i; + + dest = t_str_new(256); + + test_begin("imap mutf7 bad ascii"); + for (i = 1; i <= 0x7f; ++i) { + if (i == ' ') + i = 0x7f; + csrc[0] = i; + csrc[1] = '\0'; + test_assert_idx(!imap_utf7_is_valid(csrc), i); + str_truncate(dest, 0); + test_assert_idx(imap_utf7_to_utf8(csrc, dest) < 0, i); + } + test_end(); +} + +static void test_imap_utf7_unnecessary(void) +{ + string_t *dest; + char csrc[1+3+1+1]; + unsigned int i; + + dest = t_str_new(256); + + test_begin("imap mutf7 unnecessary"); + for (i = 0x20; i < 0x7f; ++i) { + /* Create an invalid escaped encoding of a simple char or '&' */ + csrc[0] = '&'; + csrc[1] = mb64[ (0x00 >> 2)]; + csrc[2] = mb64[((0x00 & 0x03) << 4) | ( i >> 4)]; + csrc[3] = mb64[(( i & 0x0f) << 2) | 0 ]; + csrc[4] = '-'; + csrc[5] = '\0'; + test_assert_idx(!imap_utf7_is_valid(csrc), i); + str_truncate(dest, 0); + test_assert_idx(imap_utf7_to_utf8(csrc, dest) < 0, i); + + /* All self-coding characters must self-code */ + if (i == '&') + continue; + csrc[0] = i; + csrc[1] = '\0'; + str_truncate(dest, 0); + test_assert_idx(imap_utf8_to_utf7(csrc, dest) >= 0, i); + test_assert_idx(strcmp(csrc, str_c(dest)) == 0, i); + } + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_utf7_by_example, + test_imap_utf7_ucs4_cases, + test_imap_utf7_non_utf16, + test_imap_utf7_bad_ascii, + test_imap_utf7_unnecessary, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-util.c b/src/lib-imap/test-imap-util.c new file mode 100644 index 0000000..90f59a1 --- /dev/null +++ b/src/lib-imap/test-imap-util.c @@ -0,0 +1,79 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "mail-types.h" +#include "imap-arg.h" +#include "imap-util.h" +#include "test-common.h" + +static void test_imap_parse_system_flag(void) +{ + test_begin("imap_parse_system_flag"); + test_assert(imap_parse_system_flag("\\aNswered") == MAIL_ANSWERED); + test_assert(imap_parse_system_flag("\\fLagged") == MAIL_FLAGGED); + test_assert(imap_parse_system_flag("\\dEleted") == MAIL_DELETED); + test_assert(imap_parse_system_flag("\\sEen") == MAIL_SEEN); + test_assert(imap_parse_system_flag("\\dRaft") == MAIL_DRAFT); + test_assert(imap_parse_system_flag("\\rEcent") == MAIL_RECENT); + test_assert(imap_parse_system_flag("answered") == 0); + test_assert(imap_parse_system_flag("\\broken") == 0); + test_assert(imap_parse_system_flag("\\") == 0); + test_assert(imap_parse_system_flag("") == 0); + test_end(); +} + +static void test_imap_write_arg(void) +{ + ARRAY_TYPE(imap_arg_list) list_root, list_sub; + struct imap_arg *arg; + + t_array_init(&list_sub, 2); + arg = array_append_space(&list_sub); + arg->type = IMAP_ARG_ATOM; + arg->_data.str = "foo"; + arg = array_append_space(&list_sub); + arg->type = IMAP_ARG_EOL; + + t_array_init(&list_root, 2); + arg = array_append_space(&list_root); + arg->type = IMAP_ARG_LIST; + arg->_data.list = list_sub; + arg = array_append_space(&list_root); + arg->type = IMAP_ARG_STRING; + arg->_data.str = "bar"; + arg = array_append_space(&list_root); + arg->type = IMAP_ARG_EOL; + + const struct { + struct imap_arg input; + const char *output; + } tests[] = { + { { .type = IMAP_ARG_NIL }, "NIL" }, + { { .type = IMAP_ARG_ATOM, ._data.str = "atom" }, "atom" }, + { { .type = IMAP_ARG_STRING, ._data.str = "s\\t\"ring" }, "\"s\\\\t\\\"ring\"" }, + { { .type = IMAP_ARG_LITERAL, ._data.str = "l\\i\"t\r\neral" }, "{11}\r\nl\\i\"t\r\neral" }, + { { .type = IMAP_ARG_LITERAL_SIZE, ._data.literal_size = 12345678 }, "<12345678 byte literal>" }, + { { .type = IMAP_ARG_LITERAL_SIZE_NONSYNC, ._data.literal_size = 12345678 }, "<12345678 byte literal>" }, + { { .type = IMAP_ARG_LIST, ._data.list = list_root }, "((foo) \"bar\")" }, + }; + string_t *str = t_str_new(100); + + test_begin("imap_write_arg"); + for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { + str_truncate(str, 0); + imap_write_arg(str, &tests[i].input); + test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i); + } + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_parse_system_flag, + test_imap_write_arg, + NULL + }; + return test_run(test_functions); +} |