Compare commits

..

3 Commits

49 changed files with 5129 additions and 9617 deletions

3
.gitignore vendored
View File

@ -14,7 +14,6 @@
/config.status
/config.sub
/configure
/configure~
/configure.lineno
/configure-stamp
/cov-int/
@ -23,13 +22,11 @@
/isync.spec
/isync-*.tar.gz
/isync-*.tar.gz.asc
/isync-cov.tar.xz
/missing
/patch-stamp
/stamp-h
/stamp-h.in
/stamp-h1
/test-driver
Makefile
Makefile.in

108
AUTHORS
View File

@ -1,101 +1,17 @@
Contact
=======
Oswald Buddenhagen <ossi@users.sf.net>
* Contributor, current maintainer
Theodore Ts'o <tytso@mit.edu>
* Contributor, Debian package co-maintainer
Nicolas Boullis <nboullis@debian.org>
* Debian package maintainer and minor upstream contributions
Michael Elkins <me@mutt.org>
* Original author
Send questions and bug reports to the isync-devel@lists.sourceforge.net
mailing list.
Do _NOT_ report bugs to Michael, not even in a CC: - he is not actively
_DON'T_ report bugs to Michael, not even in a CC: - he is not actively
involved in isync development any more.
Lead Developers
===============
Oswald Buddenhagen <ossi@users.sf.net>
- Current maintainer
Michael Elkins <me@mutt.org>
- Original author
Contributors
============
(Some of these people also contributed bugfixes and optimizations.)
(In chronological order.)
Jeremy Katz <katzj@linuxpower.org>
- UseNamespace & UseSSL* options
Daniel Resare <noa@metamatrix.se>
- Numerous SSL handling improvements
Eivind Eklund <eivind@FreeBSD.org>
- MaxMessages option
Theodore Ts'o <tytso@mit.edu>
- get-cert script
- Maildir UID mapping improvements
- Initial version of partial async IMAP support
Marc Hoersken <info@marc-hoersken.de>
- CopyArrivalDate option
Jack Stone <jwjstone@fastmail.fm>
Jan Synacek <jsynacek@redhat.com>
- SASL support
Jesse Weaver <pianohacker@gmail.com>
- IMAP stream compression support
Anton Khirnov <anton@khirnov.net>
- ClientKey & ClientCertificate options
Michael J Gruber <github@grubix.eu>
- Support for the $Forwarded/Passed flag
Patrick Steinhardt <ps@pks.im>
- UserCmd option
Oliver Runge <oliver.runge@gmail.com>
- UseKeychain option
Georgy Kibardin <georgy@kibardin.name>
- Support for UTF-7 IMAP mailbox names
Honorary Contributors
=====================
(These people contributed patches that were too small or obvious
to claim copyright, or were rewritten from scratch.)
(In alphabetical order.)
Alessandro Ghedini <ghedo@debian.org>
Andreas Grapentin <andreas@grapentin.org>
Aurélien Francillon <aurelien.francillon@eurecom.fr>
Ben Kibbey <bjk@luxsci.net>
Caspar Schutijser <caspar@schutijser.com>
Cedric Ware <cedric.ware__bml@normalesup.org>
Dmitrij D. Czarkoff <czarkoff@gmail.com>
Dmitry Torokhov <dtor@chromium.org>
Felipe Contreras <felipe.contreras@gmail.com>
Felix Janda <felix.janda@posteo.de>
Gergely Risko <gergely@risko.hu>
Sung Pae "guns" <self@sungpae.com>
Helmut Grohne <helmut@subdivi.de>
Hugo Haas <hugo@larve.net>
Jaroslav Suchanek <jaroslav.suchanek@gmail.com>
Jeremie Courreges-Anglas <jca@openbsd.org>
Klemens Nanni <kn@openbsd.org>
Lorenzo Martignoni <lorenzo.martignoni@technologist.com>
Magnus Jonsson <bigfoot@acc.umu.se>
Marcin Niestroj <macius1990w@gmail.com>
Martin Stenberg <martin@gnutiken.se>
Mike Delaney <mdelan@lusars.net>
Nicolas Boullis <nboullis@debian.org>
Nihal Jere <nihal@nihaljere.xyz>
Reimar Döffinger <Reimar.Doeffinger@gmx.de>
Remko Tronçon <remko@el-tramo.be>
sbfnk@users.sf.net
Thomas Roessler <roessler@does-not-exist.org>
Todd T. Fries <todd@fries.net>
Vincent Bernat <vincent@bernat.ch>
Yuri D'Elia <wavexx@thregr.org>

View File

@ -1 +0,0 @@
LICENSES/GPL-2.0-or-later.txt

340
COPYING Normal file
View File

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

41
Dockerfile Normal file
View File

@ -0,0 +1,41 @@
#FROM debian:bookworm-20231030-slim
FROM debian:bullseye-20220801
# need to add
# removed
# libsasl2-modules \
# ca-certificates \
# version pinning is being handled in our from line
# hadolint ignore=DL3008
RUN true && \
apt-get update && \
groupadd --gid 1000 user && \
useradd -m --home-dir /home/user --shell /bin/sh --uid 1000 --gid 1000 user && \
apt-get install -y --no-install-recommends \
libsasl2-2 \
libsasl2-dev \
perl \
libdatetime-format-dateparse-perl \
autoconf \
automake \
zlib1g-dev \
libdb-dev \
libsasl2-dev \
libssl-dev \
gcc \
make \
git \
&& \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
true
# Built with
# apt install build-essential dh-autoreconf git
# apt install libssl-dev
# apt install zlib1g-dev
# apt install libsasl2-dev
WORKDIR /home/user
USER user

View File

@ -1,359 +0,0 @@
Valid-License-Identifier: GPL-2.0
Valid-License-Identifier: GPL-2.0-only
Valid-License-Identifier: GPL-2.0+
Valid-License-Identifier: GPL-2.0-or-later
SPDX-URL: https://spdx.org/licenses/GPL-2.0.html
Usage-Guide:
To use this license in source code, put one of the following SPDX
tag/value pairs into a comment according to the placement
guidelines in the licensing rules documentation.
For 'GNU General Public License (GPL) version 2 only' use:
SPDX-License-Identifier: GPL-2.0
or
SPDX-License-Identifier: GPL-2.0-only
For 'GNU General Public License (GPL) version 2 or any later version' use:
SPDX-License-Identifier: GPL-2.0+
or
SPDX-License-Identifier: GPL-2.0-or-later
License-Text:
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

View File

@ -1,12 +0,0 @@
SPDX-Exception-Identifier: LicenseRef-isync-GPL-exception
SPDX-Licenses: GPL-2.0-or-later
Usage-Guide:
This exception is used together with the above SPDX-License to
allow linking the compiled version of code to non GPL compliant code.
To use this exception add it with the keyword WITH to one of the
identifiers in the SPDX-Licenses tag:
SPDX-License-Identifier: <SPDX-License> WITH LicenseRef-isync-GPL-exception
License-Text:
As a special exception, mbsync may be linked with the OpenSSL library,
despite that library's more restrictive license.

View File

@ -1,10 +1,6 @@
# SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
# SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
# SPDX-License-Identifier: GPL-2.0-or-later
SUBDIRS = src
bin_SCRIPTS = mbsync-get-cert
EXTRA_DIST = LICENSES debian isync.spec $(bin_SCRIPTS)
EXTRA_DIST = debian isync.spec $(bin_SCRIPTS)
LOG_PL = \
use POSIX qw(strftime); \

38
NEWS
View File

@ -1,41 +1,3 @@
[1.5.0]
Changed default config & state locations to follow the XDG basedir spec.
The old locations remain supported.
The reference point for relative local paths in the configuration file
is now the file's containing directory.
Placeholders will be now created for messages exceeding MaxSize even if
they are flagged on the source side.
Renamed the ReNew/--renew/-N options to Upgrade/--upgrade/-u
and Delete/--delete/-d to Gone/--gone/-g.
Superseded SSLVersions option with TLSVersions, and disabled TLS v1.0
and v1.1 by default. Renamed SSLType option to TLSType.
Made the Channel side to expire with MaxMessages configurable.
MaxMessages and MaxSize can be used together now.
Added support for IMAP mailbox names with non-ASCII characters.
Added support for Maildir Paths with suffixes.
The unfiltered list of mailboxes in each Store can be printed now.
A proper summary is now printed prior to exiting.
This includes expunges, which are now included in the progress as well.
Added new sync operation 'Old'.
Added support for mirroring deletions more accurately.
Added --dry-run option.
Added --ext-exit option.
[1.4.0]
The 'isync' compatibility wrapper was removed.

26
TODO
View File

@ -1,23 +1,22 @@
f{,data}sync() usage could be optimized by batching the calls.
make SSL (connect) timeouts produce a bit more than "Unidentified socket error".
automatically resume upon transient errors, e.g. "connection reset by peer"
or timeout after some data was already transmitted.
possibly also try to handle Exchange's "glitches" somehow.
add support for IMAP UTF-7 (for internationalized mailbox names).
uidvalidity lock timeout handling would be a good idea.
should complain when multiple Channels match the same folders.
should complain about nonsensical combinations like Sync Pull + Create Both.
propagate folder deletions even when the folders are non-empty.
- verify that "most" of the folders in the Channel are still there.
- refuse to delete unpropagated messages when trashing on the remote side.
- refuse to delete far side if it has unpropagated messages. symmetry?
add option to suppress complaints about folders that would need creation
(but not deleted ones).
add message expiration based on arrival date (message date would be too
unreliable). MaxAge; probably mutually exclusive to MaxMessages.
@ -26,16 +25,17 @@ add alternative treatments of expired messages. ExpiredMessageMode: Prune
separate folder - ArchiveSuffix, default .archive).
add support for event notification callbacks.
it would be also possible to report more differentiated exit codes, but
that seems too limiting in the general case.
make it possible to have different mailbox names for far and near side in
Patterns.
- use far:near for the pattern
- supporting names with colons requires and extension of the parser to
report which parts of an argument were quoted
- for quoting, use more colons: the longest sequence of colons is the
separator
- this makes Groups mostly useless, as they are mostly a workaround for this
function being missing so far
- this is needed for move detection, which would work only within one Channel
- this supersedes MapInbox
add regexp-based mailbox path rewriting to the drivers. user would provide
expressions for both directions. every transformation would be immediately
@ -48,22 +48,18 @@ also: idling mode.
parallel fetching of multiple mailboxes.
TLS session resumption becomes interesting then as well.
imap_set_msg_flags() & imap_trash_msg(): group commands for efficiency.
imap_set_flags(): group commands for efficiency, don't call back until
imap_commit().
add streaming from fetching to storing.
this is complicated by the IMAP target needing the final size in advance,
which we can't know in a single pass when newline translation is necessary.
handle custom flags (keywords).
this is impeded by there being no Maildir-side standard.
make use of IMAP CONDSTORE extension (rfc4551; CHANGEDSINCE FETCH Modifier);
make use of IMAP QRESYNC extension (rfc5162) to avoid SEARCH to find vanished
messages.
make use of IMAP CAPABILITY APPENDLIMIT= extension (rfc7889; fastmail & gmail).
this is really useful only for IMAP <=> IMAP syncs: saves FETCH BODY.
the message could still become oversized due to conversion.
use MULTIAPPEND and FETCH with multiple messages.
dummy messages resulting from MaxSize should contain a dump of the original
message's MIME structure and its (reasonably sized) text parts.

3
build Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
# Note we assume this is run with podman. Take -u 0:0 out if using docker
docker run -u 0:0 -it --rm -v $(pwd):/make -w /make isync-build make

View File

@ -1,8 +1,4 @@
# SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
# SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
dnl SPDX-License-Identifier: GPL-2.0-or-later
AC_INIT([isync], [1.5.0])
AC_INIT([isync], [1.4.4])
AC_CONFIG_HEADERS([autodefs.h])
AC_CANONICAL_TARGET
@ -83,7 +79,7 @@ if test "x$ob_cv_strftime_z" = x"no"; then
fi
AC_CHECK_HEADERS(poll.h sys/select.h)
AC_CHECK_FUNCS(vasprintf strnlen memrchr timegm fwrite_unlocked)
AC_CHECK_FUNCS(vasprintf strnlen memrchr timegm)
AC_CHECK_LIB(socket, socket, [SOCK_LIBS="-lsocket"])
AC_CHECK_LIB(nsl, inet_ntoa, [SOCK_LIBS="$SOCK_LIBS -lnsl"])

2
debian/copyright vendored
View File

@ -4,7 +4,7 @@ Source: http://isync.sourceforge.net
Files: *
Copyright: 2000-2002, Michael R. Elkins <me@mutt.org>
2002-2022, Oswald Buddenhagen <ossi@users.sf.net>
2002-2017, Oswald Buddenhagen <ossi@users.sf.net>
2004, Theodore Y. Ts'o <tytso@mit.edu>
License: GPL-2+

View File

@ -1,13 +1,25 @@
#!/bin/sh
#
# SPDX-FileCopyrightText: 2003 Theodore Ts'o <tytso@alum.mit.edu>
# SPDX-License-Identifier: GPL-2.0-or-later
#
# This script will extract the necessary certificate from the IMAP server
# It assumes that an attacker isn't trying to spoof you when you connect
# to the IMAP server! You're better off downloading the certificate
# from a trusted source.
#
# Copyright (C) 2003 Theodore Ts'o <tytso@alum.mit.edu>
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
if [ $# != 1 ]; then
echo "Usage: $0 <host>" >&2

8
src/.gitignore vendored
View File

@ -1,14 +1,8 @@
/*_enum.h
/drv_proxy.inc
/mbsync
/mdconvert
/tst_imap_msgs
/tst_imap_utf7
/tst_msg_cvt
/tst_timers
/tmp
/tmp/
.deps/
*.log
*.o
*.trs

View File

@ -1,47 +1,11 @@
# SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
# SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
# SPDX-License-Identifier: GPL-2.0-or-later
mbsync_SOURCES = \
util.c config.c socket.c \
driver.c drv_proxy.c \
drv_imap.c imap_msgs.c imap_utf7.c \
drv_maildir.c \
sync.c sync_state.c sync_msg_cvt.c \
main.c main_sync.c main_list.c
noinst_HEADERS = \
common.h config.h socket.h \
driver.h imap_p.h \
sync.h sync_p.h \
main_p.h
mbsync_SOURCES = main.c sync.c config.c util.c socket.c driver.c drv_imap.c drv_maildir.c drv_proxy.c
mbsync_LDADD = $(DB_LIBS) $(SSL_LIBS) $(SOCK_LIBS) $(SASL_LIBS) $(Z_LIBS) $(KEYCHAIN_LIBS)
noinst_HEADERS = common.h config.h driver.h sync.h socket.h
drv_proxy.$(OBJEXT): drv_proxy.inc
drv_proxy.inc: $(srcdir)/driver.h $(srcdir)/drv_proxy.c $(srcdir)/drv_proxy_gen.pl
perl $(srcdir)/drv_proxy_gen.pl $(srcdir)/driver.h $(srcdir)/drv_proxy.c drv_proxy.inc
ENUM_GEN = $(srcdir)/bit_enum_gen.pl
$(mbsync_OBJECTS): common_enum.h
common_enum.h: common.h $(ENUM_GEN)
perl $(ENUM_GEN) < $< > $@
$(mbsync_OBJECTS): driver_enum.h
driver_enum.h: driver.h $(ENUM_GEN)
perl $(ENUM_GEN) < $< > $@
$(mbsync_OBJECTS): sync_enum.h
sync_enum.h: sync.h $(ENUM_GEN)
perl $(ENUM_GEN) < $< > $@
sync.$(OBJEXT): sync_c_enum.h
sync_c_enum.h: sync.c $(ENUM_GEN)
perl $(ENUM_GEN) < $< > $@
sync.$(OBJEXT) sync_state.$(OBJEXT): sync_p_enum.h
sync_p_enum.h: sync_p.h $(ENUM_GEN)
perl $(ENUM_GEN) < $< > $@
mdconvert_SOURCES = mdconvert.c
mdconvert_LDADD = $(DB_LIBS)
if with_mdconvert
@ -49,26 +13,16 @@ mdconvert_prog = mdconvert
mdconvert_man = mdconvert.1
endif
bin_PROGRAMS = mbsync $(mdconvert_prog)
man_MANS = mbsync.1 $(mdconvert_man)
tst_imap_msgs_SOURCES = tst_imap_msgs.c imap_msgs.c util.c
tst_imap_utf7_SOURCES = tst_imap_utf7.c imap_utf7.c util.c
tst_msg_cvt_SOURCES = tst_msg_cvt.c sync_msg_cvt.c util.c
tst_msg_cvt_CFLAGS = -DQPRINTF_BUFF=10000
check_PROGRAMS = tst_imap_msgs tst_imap_utf7 tst_msg_cvt
TESTS = $(check_PROGRAMS)
EXTRA_PROGRAMS = tst_timers
tst_timers_SOURCES = tst_timers.c util.c
EXTRA_PROGRAMS = tst_timers
bin_PROGRAMS = mbsync $(mdconvert_prog)
man_MANS = mbsync.1 $(mdconvert_man)
exampledir = $(docdir)/examples
example_DATA = mbsyncrc.sample
EXTRA_DIST = bit_enum_gen.pl drv_proxy_gen.pl run-tests.pl $(example_DATA) $(man_MANS)
EXTRA_DIST = drv_proxy_gen.pl run-tests.pl $(example_DATA) $(man_MANS)
CLEANFILES = *_enum.h drv_proxy.inc
CLEANFILES = drv_proxy.inc

View File

@ -1,70 +0,0 @@
#!/usr/bin/perl
#
# SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
# SPDX-License-Identifier: GPL-2.0-or-later
#
# mbsync - mailbox synchronizer
#
use strict;
use warnings;
my $in_enum = 0;
my $conts;
while (<>) {
s,\s*(?://.*)?$,,;
if ($in_enum) {
if (/^\)$/) {
$conts =~ s/\s//g;
$conts =~ s/,$//;
my @vals = split(/,/, $conts);
my ($pfx, $pfx1);
for my $e (@vals) {
if (!defined($pfx)) {
$pfx1 = $pfx = ($e =~ /^([A-Z]+_)/) ? $1 : "";
} elsif (length($pfx)) {
$pfx = "" if ((($e =~ /^([A-Z]+_)/) ? $1 : "") ne $pfx);
}
}
my $bit = 1;
my $bitn = 0;
my (@names, @nameos);
my $nameo = 0;
for my $e (@vals) {
my $bits = ($e =~ s/\((\d+)\)$//) ? $1 : 1;
my $n = substr($e, length($pfx));
if ($bits != 1) {
die("Unsupported field size $bits\n") if ($bits != 2);
print "#define $e(b) ($bit << (b))\n";
push @names, "F-".$n, "N-".$n;
my $nl = length($n) + 3;
push @nameos, $nameo, $nameo + $nl;
$nameo += $nl * 2;
} else {
print "#define $e $bit\n";
push @names, $n;
push @nameos, $nameo;
$nameo += length($n) + 1;
}
$bit <<= $bits;
$bitn += $bits;
}
if (length($pfx)) {
print "#define ${pfx}_NUM_BITS $bitn\n";
}
if (length($pfx1)) {
print "#define ${pfx1}_STRINGS \"".join("\\0", @names)."\"\n";
print "#define ${pfx1}_OFFSETS ".join(", ", @nameos)."\n";
}
print "\n";
$in_enum = 0;
} else {
$conts .= $_;
}
} else {
if (/^BIT_ENUM\($/) {
$conts = "";
$in_enum = 1;
}
}
}

View File

@ -1,8 +1,23 @@
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#ifndef COMMON_H
@ -11,18 +26,10 @@
#include <autodefs.h>
#include <sys/types.h>
#include <assert.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include "common_enum.h"
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
@ -43,22 +50,14 @@ typedef unsigned long ulong;
#define shifted_bit(in, from, to) \
((int)(((uint)(in) / (from > to ? from / to : 1) * (to > from ? to / from : 1)) & to))
#define BIT_ENUM(...)
#define static_assert_bits(pfx, type, field) \
static_assert( pfx##__NUM_BITS <= sizeof(((type){ 0 }).field) * 8, \
stringify(type) "::" stringify(field) " is too small" )
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
# define ATTR_UNUSED __attribute__((unused))
# define ATTR_NORETURN __attribute__((noreturn))
# define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var)))
# define ATTR_OPTIMIZE __attribute__((optimize("2")))
#else
# define ATTR_UNUSED
# define ATTR_NORETURN
# define ATTR_PRINTFLIKE(fmt,var)
# define ATTR_OPTIMIZE
#endif
#if defined(__clang__)
@ -93,72 +92,44 @@ typedef unsigned long ulong;
/* main.c */
enum {
VERYQUIET,
QUIET,
TERSE,
VERBOSE,
};
#define DEBUG_CRASH 0x01
#define DEBUG_MAILDIR 0x02
#define DEBUG_NET 0x04
#define DEBUG_NET_ALL 0x08
#define DEBUG_SYNC 0x10
#define DEBUG_MAIN 0x20
#define DEBUG_DRV 0x40
#define DEBUG_DRV_ALL 0x80
#define DEBUG_ALL (0xFF & ~(DEBUG_NET_ALL | DEBUG_DRV_ALL))
#define QUIET 0x100
#define VERYQUIET 0x200
#define PROGRESS 0x400
#define VERBOSE 0x800
#define KEEPJOURNAL 0x1000
#define ZERODELAY 0x2000
#define FORCEASYNC 0x4000
BIT_ENUM(
DEBUG_MAILDIR,
DEBUG_NET,
DEBUG_NET_ALL,
DEBUG_SYNC,
DEBUG_MAIN,
DEBUG_DRV,
DEBUG_DRV_ALL,
DEBUG_CRASH,
PROGRESS,
DRYRUN,
EXT_EXIT,
ZERODELAY,
KEEPJOURNAL,
FORCEJOURNAL,
FORCEASYNC(2),
FAKEEXPUNGE,
FAKEDUMBSTORE,
)
#define DEBUG_ANY (DEBUG_MAILDIR | DEBUG_NET | DEBUG_SYNC | DEBUG_MAIN | DEBUG_DRV)
#define DEBUG_ALL (DEBUG_ANY | DEBUG_CRASH)
// Global options
extern int Verbosity;
extern int DFlags;
extern int JLimit, JCount;
extern int JLimit;
extern int UseFSync;
extern char FieldDelimiter;
// Global constants (inited by main())
extern int Pid;
extern char Hostname[256];
extern const char *Home;
void countStep( void );
extern uint BufferLimit;
extern int new_total[2], new_done[2];
extern int flags_total[2], flags_done[2];
extern int trash_total[2], trash_done[2];
void stats( void );
/* util.c */
#ifdef DEBUG_FLAG
# define debug(...) \
do { \
if (DFlags & DEBUG_FLAG) \
print( __VA_ARGS__ ); \
} while (0)
# define debugn(...) \
do { \
if (DFlags & DEBUG_FLAG) \
printn( __VA_ARGS__ ); \
} while (0)
#endif
void ATTR_PRINTFLIKE(1, 2) print( const char *, ... );
void ATTR_PRINTFLIKE(1, 2) printn( const char *, ... );
void ATTR_PRINTFLIKE(2, 0) vdebug( int, const char *, va_list va );
void ATTR_PRINTFLIKE(2, 0) vdebugn( int, const char *, va_list va );
void ATTR_PRINTFLIKE(1, 2) info( const char *, ... );
void ATTR_PRINTFLIKE(1, 2) infon( const char *, ... );
void ATTR_PRINTFLIKE(1, 2) progress( const char *, ... );
@ -169,17 +140,6 @@ void ATTR_PRINTFLIKE(1, 0) vsys_error( const char *, va_list va );
void ATTR_PRINTFLIKE(1, 2) sys_error( const char *, ... );
void flushn( void );
char *xvasprintf( const char *fmt, va_list ap );
void xprintf( const char *fmt, ... );
#if !defined(_POSIX_SYNCHRONIZED_IO) || _POSIX_SYNCHRONIZED_IO <= 0
# define fdatasync fsync
#endif
void ATTR_PRINTFLIKE(2, 0) vFprintf( FILE *f, const char *msg, va_list va );
void ATTR_PRINTFLIKE(2, 3) Fprintf( FILE *f, const char *msg, ... );
void Fclose( FILE *f, int safe );
typedef struct string_list {
struct string_list *next;
char string[1];
@ -196,50 +156,16 @@ void *memrchr( const void *s, int c, size_t n );
size_t strnlen( const char *str, size_t maxlen );
#endif
void to_upper( char *str, uint len );
int starts_with( const char *str, int strl, const char *cmp, uint cmpl );
int starts_with_upper( const char *str, int strl, const char *cmp, uint cmpl );
int equals( const char *str, int strl, const char *cmp, uint cmpl );
int equals_upper( const char *str, int strl, const char *cmp, uint cmpl );
#ifndef HAVE_TIMEGM
time_t timegm( struct tm *tm );
#endif
void fmt_bits( uint bits, uint num_bits, const char *bit_str, const int *bit_off, char *buf );
#define BIT_FORMATTER_RET(name, pfx) \
struct name##_str { char str[sizeof(pfx##__STRINGS)]; };
#define BIT_FORMATTER_PROTO(name, pfx, storage) \
storage struct name##_str ATTR_OPTIMIZE /* force RVO */ \
fmt_##name( uint bits )
#define BIT_FORMATTER_IMPL(name, pfx, storage) \
BIT_FORMATTER_PROTO(name, pfx, storage) \
{ \
static const char strings[] = pfx##__STRINGS; \
static const int offsets[] = { pfx##__OFFSETS }; \
\
struct name##_str buf; \
fmt_bits( bits, as(offsets), strings, offsets, buf.str ); \
return buf; \
}
#define BIT_FORMATTER_FUNCTION(name, pfx) \
BIT_FORMATTER_RET(name, pfx) \
BIT_FORMATTER_IMPL(name, pfx, static)
#define DECL_BIT_FORMATTER_FUNCTION(name, pfx) \
BIT_FORMATTER_RET(name, pfx) \
BIT_FORMATTER_PROTO(name, pfx, );
#define DEF_BIT_FORMATTER_FUNCTION(name, pfx) \
BIT_FORMATTER_IMPL(name, pfx, )
void *nfmalloc( size_t sz );
void *nfzalloc( size_t sz );
void *nfcalloc( size_t sz );
void *nfrealloc( void *mem, size_t sz );
char *nfstrndup( const char *str, size_t nchars );
char *nfstrdup( const char *str );
@ -249,9 +175,9 @@ int ATTR_PRINTFLIKE(3, 4) nfsnprintf( char *buf, int blen, const char *fmt, ...
void ATTR_NORETURN oob( void );
void ATTR_NORETURN oom( void );
int map_name( const char *arg, int argl, char **result, uint reserve, const char *in, const char *out );
char *expand_strdup( const char *s );
int mkdir_p( char *path, int len );
int map_name( const char *arg, char **result, uint reserve, const char *in, const char *out );
#define DEFINE_ARRAY_TYPE(T) \
typedef struct { \
@ -325,11 +251,9 @@ typedef struct {
list_head_t links;
void (*cb)( void *aux );
void *aux;
int64_t timeout;
time_t timeout;
} wakeup_t;
void init_timers( void );
int64_t get_now( void );
void init_wakeup( wakeup_t *tmr, void (*cb)( void * ), void *aux );
void conf_wakeup( wakeup_t *tmr, int timeout );
void wipe_wakeup( wakeup_t *tmr );

View File

@ -1,65 +1,40 @@
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2011 Oswald Buddenhagen <ossi@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#define DEBUG_FLAG DEBUG_MAIN
#include "config.h"
#include "sync.h"
#include <assert.h>
#include <unistd.h>
#include <limits.h>
#include <pwd.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__CYGWIN__)
char FieldDelimiter = ';';
#else
char FieldDelimiter = ':';
#endif
DEF_BIT_FORMATTER_FUNCTION(ops, OP)
char *
expand_strdup( const char *s, const conffile_t *cfile )
{
struct passwd *pw;
const char *p, *q;
char *r;
if (*s == '~') {
s++;
if (!*s) {
p = NULL;
q = Home;
} else if (*s == '/') {
p = s;
q = Home;
} else {
if ((p = strchr( s, '/' ))) {
r = nfstrndup( s, (size_t)(p - s) );
pw = getpwnam( r );
free( r );
} else {
pw = getpwnam( s );
}
if (!pw)
return NULL;
q = pw->pw_dir;
}
nfasprintf( &r, "%s%s", q, p ? p : "" );
return r;
} else if (*s != '/') {
nfasprintf( &r, "%.*s%s", cfile->path_len, cfile->file, s );
return r;
} else {
return nfstrdup( s );
}
}
static store_conf_t *stores;
char *
get_arg( conffile_t *cfile, int required, int *comment )
@ -86,15 +61,14 @@ get_arg( conffile_t *cfile, int required, int *comment )
if (escaped && c >= 32) {
escaped = 0;
*t++ = c;
} else if (c == '\\') {
} else if (c == '\\')
escaped = 1;
} else if (c == '"') {
else if (c == '"')
quoted ^= 1;
} else if (!quoted && isspace( (uchar)c )) {
else if (!quoted && isspace( (uchar)c ))
break;
} else {
else
*t++ = c;
}
}
*t = 0;
if (escaped) {
@ -174,7 +148,6 @@ static const struct {
const char *name;
} boxOps[] = {
{ OP_EXPUNGE, "Expunge" },
{ OP_EXPUNGE_SOLO, "ExpungeSolo" },
{ OP_CREATE, "Create" },
{ OP_REMOVE, "Remove" },
};
@ -187,94 +160,53 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
if (!strcasecmp( "Sync", cfile->cmd )) {
arg = cfile->val;
do {
if (!strcasecmp( "Push", arg )) {
do
if (!strcasecmp( "Push", arg ))
*cops |= XOP_PUSH;
} else if (!strcasecmp( "Pull", arg )) {
else if (!strcasecmp( "Pull", arg ))
*cops |= XOP_PULL;
} else if (!strcasecmp( "Upgrade", arg )) {
*cops |= OP_UPGRADE;
} else if (!strcasecmp( "ReNew", arg )) {
cfile->renew_warn = 1;
*cops |= OP_UPGRADE;
} else if (!strcasecmp( "New", arg )) {
else if (!strcasecmp( "ReNew", arg ))
*cops |= OP_RENEW;
else if (!strcasecmp( "New", arg ))
*cops |= OP_NEW;
} else if (!strcasecmp( "Old", arg )) {
*cops |= OP_OLD;
} else if (!strcasecmp( "Gone", arg )) {
*cops |= OP_GONE;
} else if (!strcasecmp( "Delete", arg )) {
cfile->delete_warn = 1;
*cops |= OP_GONE;
} else if (!strcasecmp( "Flags", arg )) {
else if (!strcasecmp( "Delete", arg ))
*cops |= OP_DELETE;
else if (!strcasecmp( "Flags", arg ))
*cops |= OP_FLAGS;
} else if (!strcasecmp( "All", arg ) || !strcasecmp( "Full", arg )) {
*cops |= OP_MASK_TYPE;
} else if (!strcasecmp( "PullUpgrade", arg )) {
conf->ops[N] |= OP_UPGRADE;
} else if (!strcasecmp( "PullReNew", arg )) {
cfile->renew_warn = 1;
conf->ops[N] |= OP_UPGRADE;
} else if (!strcasecmp( "PullNew", arg )) {
else if (!strcasecmp( "PullReNew", arg ))
conf->ops[N] |= OP_RENEW;
else if (!strcasecmp( "PullNew", arg ))
conf->ops[N] |= OP_NEW;
} else if (!strcasecmp( "PullOld", arg )) {
conf->ops[N] |= OP_OLD;
} else if (!strcasecmp( "PullGone", arg )) {
conf->ops[N] |= OP_GONE;
} else if (!strcasecmp( "PullDelete", arg )) {
cfile->delete_warn = 1;
conf->ops[N] |= OP_GONE;
} else if (!strcasecmp( "PullFlags", arg )) {
else if (!strcasecmp( "PullDelete", arg ))
conf->ops[N] |= OP_DELETE;
else if (!strcasecmp( "PullFlags", arg ))
conf->ops[N] |= OP_FLAGS;
} else if (!strcasecmp( "PullFull", arg )) {
conf->ops[N] |= OP_MASK_TYPE;
} else if (!strcasecmp( "PushUpgrade", arg )) {
conf->ops[F] |= OP_UPGRADE;
} else if (!strcasecmp( "PushReNew", arg )) {
cfile->renew_warn = 1;
conf->ops[F] |= OP_UPGRADE;
} else if (!strcasecmp( "PushNew", arg )) {
else if (!strcasecmp( "PushReNew", arg ))
conf->ops[F] |= OP_RENEW;
else if (!strcasecmp( "PushNew", arg ))
conf->ops[F] |= OP_NEW;
} else if (!strcasecmp( "PushOld", arg )) {
conf->ops[F] |= OP_OLD;
} else if (!strcasecmp( "PushGone", arg )) {
conf->ops[F] |= OP_GONE;
} else if (!strcasecmp( "PushDelete", arg )) {
cfile->delete_warn = 1;
conf->ops[F] |= OP_GONE;
} else if (!strcasecmp( "PushFlags", arg )) {
else if (!strcasecmp( "PushDelete", arg ))
conf->ops[F] |= OP_DELETE;
else if (!strcasecmp( "PushFlags", arg ))
conf->ops[F] |= OP_FLAGS;
} else if (!strcasecmp( "PushFull", arg )) {
conf->ops[F] |= OP_MASK_TYPE;
} else if (!strcasecmp( "None", arg ) || !strcasecmp( "Noop", arg )) {
conf->ops[F] |= XOP_TYPE_NOOP;
} else {
else if (!strcasecmp( "All", arg ) || !strcasecmp( "Full", arg ))
*cops |= XOP_PULL|XOP_PUSH;
else if (strcasecmp( "None", arg ) && strcasecmp( "Noop", arg )) {
error( "%s:%d: invalid Sync arg '%s'\n",
cfile->file, cfile->line, arg );
cfile->err = 1;
}
} while ((arg = get_arg( cfile, ARG_OPTIONAL, NULL )));
while ((arg = get_arg( cfile, ARG_OPTIONAL, NULL )));
conf->ops[F] |= XOP_HAVE_TYPE;
} else if (!strcasecmp( "SyncState", cfile->cmd )) {
conf->sync_state = !strcmp( cfile->val, "*" ) ? "*" : expand_strdup( cfile->val, cfile );
} else if (!strcasecmp( "CopyArrivalDate", cfile->cmd )) {
} else if (!strcasecmp( "SyncState", cfile->cmd ))
conf->sync_state = expand_strdup( cfile->val );
else if (!strcasecmp( "CopyArrivalDate", cfile->cmd ))
conf->use_internal_date = parse_bool( cfile );
} else if (!strcasecmp( "MaxMessages", cfile->cmd )) {
else if (!strcasecmp( "MaxMessages", cfile->cmd ))
conf->max_messages = parse_int( cfile );
} else if (!strcasecmp( "ExpireSide", cfile->cmd )) {
arg = cfile->val;
if (!strcasecmp( "Far", arg )) {
conf->expire_side = F;
} else if (!strcasecmp( "Near", arg )) {
conf->expire_side = N;
} else {
error( "%s:%d: invalid ExpireSide argument '%s'\n",
cfile->file, cfile->line, arg );
cfile->err = 1;
}
} else if (!strcasecmp( "ExpireUnread", cfile->cmd )) {
else if (!strcasecmp( "ExpireUnread", cfile->cmd ))
conf->expire_unread = parse_bool( cfile );
} else {
else {
for (i = 0; i < as(boxOps); i++) {
if (!strcasecmp( boxOps[i].name, cfile->cmd )) {
int op = boxOps[i].op;
@ -292,9 +224,7 @@ getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
} else if (!strcasecmp( "Slave", arg )) { // Pre-1.4 legacy
conf->ops[N] |= op;
cfile->ms_warn = 1;
} else if (!strcasecmp( "None", arg )) {
conf->ops[F] |= op * (XOP_EXPUNGE_NOOP / OP_EXPUNGE);
} else {
} else if (strcasecmp( "None", arg )) {
error( "%s:%d: invalid %s arg '%s'\n",
cfile->file, cfile->line, boxOps[i].name, arg );
cfile->err = 1;
@ -334,76 +264,38 @@ getcline( conffile_t *cfile )
return 0;
}
static const char *
channel_str( const char *chan_name )
{
if (!chan_name)
return "on the command line";
if (!*chan_name)
return "in global config section";
static char buf[100];
nfsnprintf( buf, sizeof(buf), "in Channel '%s'", chan_name );
return buf;
}
/* XXX - this does not detect None conflicts ... */
int
merge_ops( int cops, int ops[], const char *chan_name )
merge_ops( int cops, int ops[] )
{
int aops, op;
uint i;
if (!cops && !ops[F] && !ops[N]) // Only to denoise the debug output
return 0;
debug( "merge ops (%s):\n common: %s\n far: %s\n near: %s\n",
channel_str( chan_name ), fmt_ops( cops ).str, fmt_ops( ops[F] ).str, fmt_ops( ops[N] ).str );
aops = ops[F] | ops[N];
if (ops[F] & XOP_HAVE_TYPE) {
if (aops & OP_MASK_TYPE) { // PullNew, etc.
if (ops[F] & XOP_TYPE_NOOP) {
if (aops & OP_MASK_TYPE) {
if (aops & cops & OP_MASK_TYPE) {
cfl:
error( "Conflicting Sync options specified %s.\n", channel_str( chan_name ) );
error( "Conflicting Sync args specified.\n" );
return 1;
}
if (aops & cops & OP_MASK_TYPE) { // Overlapping New, etc.
ovl:
error( "Redundant Sync options specified %s.\n", channel_str( chan_name ) );
return 1;
}
// Mix in non-overlapping Push/Pull or New, etc.
ops[F] |= cops & OP_MASK_TYPE;
ops[N] |= cops & OP_MASK_TYPE;
if (cops & XOP_PULL) {
if (cops & (XOP_PUSH | OP_MASK_TYPE)) {
// Mixing instant effect flags with row/column flags would be confusing,
// so instead everything is instant effect. This implies that mixing
// direction with type would cause overlaps, so PullNew Push Gone, etc.
// is invalid.
// Pull Push covers everything, so makes no sense to combine.
ivl:
error( "Invalid combination of simple and compound Sync options %s.\n",
channel_str( chan_name ) );
return 1;
}
if (ops[N] & OP_DFLT_TYPE)
goto ovl;
ops[N] |= OP_DFLT_TYPE;
} else if (cops & XOP_PUSH) {
if (cops & OP_MASK_TYPE)
goto ivl;
if (ops[F] & OP_DFLT_TYPE)
goto ovl;
ops[F] |= OP_DFLT_TYPE;
} else {
ops[F] |= cops & OP_MASK_TYPE;
ops[N] |= cops & OP_MASK_TYPE;
if (ops[N] & OP_MASK_TYPE)
goto cfl;
ops[N] |= OP_MASK_TYPE;
}
} else if (cops & (OP_MASK_TYPE | XOP_MASK_DIR)) { // Pull New, etc.
if (ops[F] & XOP_TYPE_NOOP)
goto cfl;
if (cops & XOP_PUSH) {
if (ops[F] & OP_MASK_TYPE)
goto cfl;
ops[F] |= OP_MASK_TYPE;
}
} else if (cops & (OP_MASK_TYPE|XOP_MASK_DIR)) {
if (!(cops & OP_MASK_TYPE))
cops |= OP_DFLT_TYPE;
cops |= OP_MASK_TYPE;
else if (!(cops & XOP_MASK_DIR))
cops |= XOP_PULL | XOP_PUSH;
cops |= XOP_PULL|XOP_PUSH;
if (cops & XOP_PULL)
ops[N] |= cops & OP_MASK_TYPE;
if (cops & XOP_PUSH)
@ -413,19 +305,14 @@ merge_ops( int cops, int ops[], const char *chan_name )
for (i = 0; i < as(boxOps); i++) {
op = boxOps[i].op;
if (ops[F] & (op * (XOP_HAVE_EXPUNGE / OP_EXPUNGE))) {
if (((aops | cops) & op) && (ops[F] & (op * (XOP_EXPUNGE_NOOP / OP_EXPUNGE)))) {
error( "Conflicting %s options specified %s.\n", boxOps[i].name, channel_str( chan_name ) );
return 1;
}
if (aops & cops & op) {
error( "Redundant %s options specified %s.\n", boxOps[i].name, channel_str( chan_name ) );
error( "Conflicting %s args specified.\n", boxOps[i].name );
return 1;
}
ops[F] |= cops & op;
ops[N] |= cops & op;
}
}
debug( " => far: %s\n => near: %s\n", fmt_ops( ops[F] ).str, fmt_ops( ops[N] ).str );
return 0;
}
@ -440,39 +327,14 @@ load_config( const char *where )
char *arg, *p;
uint len, max_size;
int cops, gcops, glob_ok, fn, i;
char path[_POSIX_PATH_MAX], path2[_POSIX_PATH_MAX];
char path[_POSIX_PATH_MAX];
char buf[1024];
if (!where) {
int path_len, path_len2;
const char *config_home = getenv( "XDG_CONFIG_HOME" );
if (config_home)
nfsnprintf( path, sizeof(path), "%s/%nisyncrc", config_home, &path_len );
else
nfsnprintf( path, sizeof(path), "%s/.config/%nisyncrc", Home, &path_len );
nfsnprintf( path2, sizeof(path2), "%s/%n.mbsyncrc", Home, &path_len2 );
struct stat st;
int ex = !lstat( path, &st );
int ex2 = !lstat( path2, &st );
if (ex2 && !ex) {
cfile.file = path2;
cfile.path_len = path_len2;
} else {
if (ex && ex2)
warn( "Both %s and %s exist; using the former.\n", path, path2 );
cfile.file = path;
cfile.path_len = path_len;
}
} else {
const char *sl = strrchr( where, '/' );
if (!sl) {
nfsnprintf( path, sizeof(path), "./%n%s", &cfile.path_len, where );
cfile.file = path;
} else {
cfile.path_len = sl - where + 1;
cfile.file = where;
}
}
nfsnprintf( path, sizeof(path), "%s/." EXE "rc", Home );
cfile.file = path;
} else
cfile.file = where;
info( "Reading configuration file %s\n", cfile.file );
@ -486,19 +348,16 @@ load_config( const char *where )
cfile.line = 0;
cfile.err = 0;
cfile.ms_warn = 0;
cfile.renew_warn = 0;
cfile.delete_warn = 0;
cfile.rest = NULL;
gcops = 0;
glob_ok = 1;
global_conf.expire_side = N;
global_conf.expire_unread = -1;
reloop:
while (getcline( &cfile )) {
if (!cfile.cmd)
continue;
for (i = 0; i < N_DRIVERS; i++) {
for (i = 0; i < N_DRIVERS; i++)
if (drivers[i]->parse_store( &cfile, &store )) {
if (store) {
if (!store->max_size)
@ -512,26 +371,27 @@ load_config( const char *where )
glob_ok = 0;
goto reloop;
}
}
if (!strcasecmp( "Channel", cfile.cmd )) {
channel = nfzalloc( sizeof(*channel) );
if (!strcasecmp( "Channel", cfile.cmd ))
{
channel = nfcalloc( sizeof(*channel) );
channel->name = nfstrdup( cfile.val );
channel->max_messages = global_conf.max_messages;
channel->expire_side = global_conf.expire_side;
channel->expire_unread = global_conf.expire_unread;
channel->use_internal_date = global_conf.use_internal_date;
cops = 0;
max_size = UINT_MAX;
while (getcline( &cfile ) && cfile.cmd) {
if (!strcasecmp( "MaxSize", cfile.cmd )) {
if (!strcasecmp( "MaxSize", cfile.cmd ))
max_size = parse_size( &cfile );
} else if (!strcasecmp( "Pattern", cfile.cmd ) ||
!strcasecmp( "Patterns", cfile.cmd )) {
else if (!strcasecmp( "Pattern", cfile.cmd ) ||
!strcasecmp( "Patterns", cfile.cmd ))
{
arg = cfile.val;
do {
do
add_string_list( &channel->patterns, arg );
} while ((arg = get_arg( &cfile, ARG_OPTIONAL, NULL )));
} else if (!strcasecmp( "Far", cfile.cmd )) {
while ((arg = get_arg( &cfile, ARG_OPTIONAL, NULL )));
}
else if (!strcasecmp( "Far", cfile.cmd )) {
fn = F;
goto linkst;
} else if (!strcasecmp( "Master", cfile.cmd )) { // Pre-1.4 legacy
@ -552,13 +412,11 @@ load_config( const char *where )
continue;
}
*p = 0;
for (store = stores; store; store = store->next) {
for (store = stores; store; store = store->next)
if (!strcmp( store->name, cfile.val + 1 )) {
channel->stores[fn] = store;
goto stpcom;
}
}
channel->stores[fn] = (void *)~0;
error( "%s:%d: unknown store '%s'\n",
cfile.file, cfile.line, cfile.val + 1 );
cfile.err = 1;
@ -569,30 +427,31 @@ load_config( const char *where )
} else if (!getopt_helper( &cfile, &cops, channel )) {
error( "%s:%d: keyword '%s' is not recognized in Channel sections\n",
cfile.file, cfile.line, cfile.cmd );
cfile.rest = NULL;
cfile.err = 1;
}
}
if (!channel->stores[F]) {
error( "channel '%s' refers to no far side store\n", channel->name );
cfile.err = 1;
}
if (!channel->stores[N]) {
} else if (!channel->stores[N]) {
error( "channel '%s' refers to no near side store\n", channel->name );
cfile.err = 1;
}
if (merge_ops( cops, channel->ops, channel->name ))
} else if (merge_ops( cops, channel->ops ))
cfile.err = 1;
if (max_size != UINT_MAX && !cfile.err) {
if (!max_size)
max_size = UINT_MAX;
channel->stores[F]->max_size = channel->stores[N]->max_size = max_size;
else {
if (max_size != UINT_MAX) {
if (!max_size)
max_size = UINT_MAX;
channel->stores[F]->max_size = channel->stores[N]->max_size = max_size;
}
*channelapp = channel;
channelapp = &channel->next;
}
*channelapp = channel;
channelapp = &channel->next;
glob_ok = 0;
goto reloop;
} else if (!strcasecmp( "Group", cfile.cmd )) {
}
else if (!strcasecmp( "Group", cfile.cmd ))
{
group = nfmalloc( sizeof(*group) );
group->name = nfstrdup( cfile.val );
*groupapp = group;
@ -611,21 +470,27 @@ load_config( const char *where )
}
while (getcline( &cfile ) && cfile.cmd) {
if (!strcasecmp( "Channel", cfile.cmd ) ||
!strcasecmp( "Channels", cfile.cmd )) {
!strcasecmp( "Channels", cfile.cmd ))
{
arg = cfile.val;
goto addone;
} else {
}
else
{
error( "%s:%d: keyword '%s' is not recognized in Group sections\n",
cfile.file, cfile.line, cfile.cmd );
cfile.rest = NULL;
cfile.err = 1;
}
}
glob_ok = 0;
goto reloop;
} else if (!strcasecmp( "FSync", cfile.cmd )) {
}
else if (!strcasecmp( "FSync", cfile.cmd ))
{
UseFSync = parse_bool( &cfile );
} else if (!strcasecmp( "FieldDelimiter", cfile.cmd )) {
}
else if (!strcasecmp( "FieldDelimiter", cfile.cmd ))
{
if (strlen( cfile.val ) != 1) {
error( "%s:%d: Field delimiter must be exactly one character long\n", cfile.file, cfile.line );
cfile.err = 1;
@ -636,17 +501,20 @@ load_config( const char *where )
cfile.err = 1;
}
}
} else if (!strcasecmp( "BufferLimit", cfile.cmd )) {
}
else if (!strcasecmp( "BufferLimit", cfile.cmd ))
{
BufferLimit = parse_size( &cfile );
if (!BufferLimit) {
error( "%s:%d: BufferLimit cannot be zero\n", cfile.file, cfile.line );
cfile.err = 1;
}
} else if (!getopt_helper( &cfile, &gcops, &global_conf )) {
}
else if (!getopt_helper( &cfile, &gcops, &global_conf ))
{
error( "%s:%d: '%s' is not a recognized section-starting or global keyword\n",
cfile.file, cfile.line, cfile.cmd );
cfile.err = 1;
cfile.rest = NULL;
while (getcline( &cfile ))
if (!cfile.cmd)
goto reloop;
@ -661,30 +529,8 @@ load_config( const char *where )
fclose (cfile.fp);
if (cfile.ms_warn)
warn( "Notice: Master/Slave are deprecated; use Far/Near instead.\n" );
if (cfile.renew_warn)
warn( "Notice: ReNew is deprecated; use Upgrade instead.\n" );
if (cfile.delete_warn)
warn( "Notice: Delete is deprecated; use Gone instead.\n" );
cfile.err |= merge_ops( gcops, global_conf.ops, "" );
if (!global_conf.sync_state) {
const char *state_home = getenv( "XDG_STATE_HOME" );
if (state_home)
nfsnprintf( path, sizeof(path), "%s/isync/", state_home );
else
nfsnprintf( path, sizeof(path), "%s/.local/state/isync/", Home );
nfsnprintf( path2, sizeof(path2), "%s/.mbsync/", Home );
struct stat st;
int ex = !lstat( path, &st );
int ex2 = !lstat( path2, &st );
if (ex2 && !ex) {
global_conf.sync_state = nfstrdup( path2 );
} else {
if (ex && ex2) {
error( "Error: both %s and %s exist; delete one or set SyncState globally.\n", path, path2 );
cfile.err = 1;
}
global_conf.sync_state = nfstrdup( path );
}
}
cfile.err |= merge_ops( gcops, global_conf.ops );
if (!global_conf.sync_state)
global_conf.sync_state = expand_strdup( "~/." EXE "/" );
return cfile.err;
}

View File

@ -1,8 +1,23 @@
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#ifndef CONFIG_H
@ -17,25 +32,20 @@ typedef struct {
int bufl;
int line;
int err;
int ms_warn, renew_warn, delete_warn;
int path_len;
int ms_warn;
char *cmd, *val, *rest;
} conffile_t;
extern char FieldDelimiter;
#define ARG_OPTIONAL 0
#define ARG_REQUIRED 1
char *expand_strdup( const char *s, const conffile_t *cfile );
char *get_arg( conffile_t *cfile, int required, int *comment );
char parse_bool( conffile_t *cfile );
int parse_int( conffile_t *cfile );
uint parse_size( conffile_t *cfile );
int getcline( conffile_t *cfile );
int merge_ops( int cops, int ops[], const char *chan_name );
int merge_ops( int cops, int ops[] );
int load_config( const char *filename );
#endif

View File

@ -1,68 +1,50 @@
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#include "driver.h"
store_conf_t *stores;
#include <stdlib.h>
#include <string.h>
driver_t *drivers[N_DRIVERS] = { &maildir_driver, &imap_driver };
void
cleanup_drivers( void )
uint
count_generic_messages( message_t *msgs )
{
for (int t = 0; t < N_DRIVERS; t++)
drivers[t]->cleanup();
}
// Keep the MESSAGE_FLAGS in sync (grep that)!
const char MsgFlags[] = { 'D', 'F', 'P', 'R', 'S', 'T' };
static void
make_flags( uchar flags, char *buf )
{
uint i, d;
for (i = d = 0; i < as(MsgFlags); i++)
if (flags & (1 << i))
buf[d++] = MsgFlags[i];
buf[d] = 0;
}
flag_str_t
fmt_flags( uchar flags )
{
flag_str_t buf;
make_flags( flags, buf.str );
return buf;
}
flag_str_t
fmt_lone_flags( uchar flags )
{
flag_str_t buf;
if (!flags) {
buf.str[0] = '-';
buf.str[1] = 0;
} else {
make_flags( flags, buf.str );
}
return buf;
uint count = 0;
for (; msgs; msgs = msgs->next)
count++;
return count;
}
void
free_generic_messages( message_t *msgs )
{
while (msgs) {
message_t *tmsg = msgs->next;
message_t *tmsg;
for (; msgs; msgs = tmsg) {
tmsg = msgs->next;
free( msgs->msgid );
free( msgs );
msgs = tmsg;
}
}
@ -91,7 +73,6 @@ parse_generic_store( store_conf_t *store, conffile_t *cfg, const char *type )
store->flat_delim = nfstrdup( cfg->val );
} else {
error( "%s:%d: keyword '%s' is not recognized in %s sections\n", cfg->file, cfg->line, cfg->cmd, type );
cfg->rest = NULL;
cfg->err = 1;
}
}

View File

@ -1,15 +1,29 @@
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#ifndef DRIVER_H
#define DRIVER_H
#include "config.h"
#include "driver_enum.h"
typedef struct driver driver_t;
@ -31,37 +45,27 @@ typedef struct store_conf {
STORE_CONF
} store_conf_t;
extern store_conf_t *stores;
/* For message->flags */
// Keep the MESSAGE_FLAGS in sync (grep that)!
/* Keep the mailbox driver flag definitions in sync: */
/* grep for MAILBOX_DRIVER_FLAG */
/* The order is according to alphabetical maildir flag sort */
BIT_ENUM(
F_DRAFT, // Draft
F_FLAGGED, // Flagged
F_FORWARDED, // Passed
F_ANSWERED, // Replied
F_SEEN, // Seen
F_DELETED, // Trashed
)
extern const char MsgFlags[F__NUM_BITS];
typedef struct { char str[F__NUM_BITS + 1]; } flag_str_t;
flag_str_t ATTR_OPTIMIZE /* force RVO */ fmt_flags( uchar flags );
flag_str_t ATTR_OPTIMIZE /* force RVO */ fmt_lone_flags( uchar flags );
#define F_DRAFT (1<<0) /* Draft */
#define F_FLAGGED (1<<1) /* Flagged */
#define F_FORWARDED (1<<2) /* Passed */
#define F_ANSWERED (1<<3) /* Replied */
#define F_SEEN (1<<4) /* Seen */
#define F_DELETED (1<<5) /* Trashed */
#define NUM_FLAGS 6
/* For message->status */
BIT_ENUM(
M_RECENT, // unsyncable flag; maildir_*() depend on this being bit 0
M_DEAD, // expunged
M_EXPUNGE, // for driver_t->close_box()
M_FLAGS, // flags are valid
// The following are only for IMAP FETCH response parsing
M_DATE,
M_SIZE,
M_BODY,
M_HEADER,
)
#define M_RECENT (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
#define M_DEAD (1<<1) /* expunged */
#define M_FLAGS (1<<2) /* flags fetched */
// The following are only for IMAP FETCH response parsing
#define M_DATE (1<<3)
#define M_SIZE (1<<4)
#define M_BODY (1<<5)
#define M_HEADER (1<<6)
#define TUIDL 12
@ -79,37 +83,26 @@ typedef struct message {
MESSAGE(struct message)
} message_t;
static_assert_bits(F, message_t, flags);
static_assert_bits(M, message_t, status);
// For driver_t->prepare_load_box(), which may amend the passed flags.
// The drivers don't use the first three, but may set them if loading the
// The drivers don't use the first two, but may set them if loading the
// particular range is required to handle some other flag; note that these
// ranges may overlap.
BIT_ENUM(
OPEN_PAIRED, // Paired messages *in* this store.
OPEN_OLD, // Messages that should be already propagated *from* this store.
OPEN_NEW, // Messages (possibly) not yet propagated *from* this store.
OPEN_FIND,
OPEN_FLAGS, // Note that fetch_msg() gets the flags regardless.
OPEN_OLD_SIZE,
OPEN_NEW_SIZE,
OPEN_PAIRED_IDS,
OPEN_APPEND,
OPEN_SETFLAGS,
OPEN_EXPUNGE,
// Expunge only deleted messages we know about. Relies on OPEN_{OLD,NEW,FLAGS}
// being set externally. The driver may unset it if it can't handle it.
OPEN_UID_EXPUNGE,
)
#define OPEN_OLD (1<<0) // Paired messages *in* this store.
#define OPEN_NEW (1<<1) // Messages (possibly) not yet propagated *from* this store.
#define OPEN_FLAGS (1<<2) // Note that fetch_msg() gets the flags regardless.
#define OPEN_NEW_SIZE (1<<4)
#define OPEN_EXPUNGE (1<<5)
#define OPEN_SETFLAGS (1<<6)
#define OPEN_APPEND (1<<7)
#define OPEN_FIND (1<<8)
#define OPEN_OLD_IDS (1<<9)
#define UIDVAL_BAD ((uint)-1)
#define STORE(store) \
store *next; \
driver_t *driver; \
store##_conf *conf; /* foreign */ \
uchar racy_trash;
store##_conf *conf; /* foreign */
typedef struct store {
STORE(struct store)
@ -122,8 +115,6 @@ typedef struct {
uchar flags;
} msg_data_t;
static_assert_bits(F, msg_data_t, flags);
#define DRV_OK 0
/* Message went missing, or mailbox is full, etc. */
#define DRV_MSG_BAD 1
@ -131,12 +122,13 @@ static_assert_bits(F, msg_data_t, flags);
#define DRV_BOX_BAD 2
/* Failed to connect store. */
#define DRV_STORE_BAD 3
/* The command has been cancel_cmds()d or cancel_store()d. */
/* The command has been cancel()ed or cancel_store()d. */
#define DRV_CANCELED 4
/* All memory belongs to the driver's user, unless stated otherwise. */
// All memory passed to driver functions must remain valid until the
// respective result callback is invoked.
// If the driver is NOT DRV_ASYNC, memory owned by the driver returned
// through callbacks MUST remain valid until a related subsequent command
// is invoked, as the proxy driver may deliver these pointers with delay.
/*
This flag says that the driver CAN store messages with CRLFs,
@ -145,7 +137,7 @@ static_assert_bits(F, msg_data_t, flags);
*/
#define DRV_CRLF 1
/*
This flag says that the driver will act upon (Verbosity >= VERBOSE).
This flag says that the driver will act upon (DFlags & VERBOSE).
*/
#define DRV_VERBOSE 2
/*
@ -173,14 +165,9 @@ struct driver {
* return quickly, and must not fail. */
store_t *(*alloc_store)( store_conf_t *conf, const char *label );
// When exp_cb is invoked, the passed message has been expunged;
// its status is M_DEAD now.
// When bad_cb is invoked (at most once per store), the store is fubar;
// call cancel_store() to dispose of it.
void (*set_callbacks)( store_t *ctx,
void (*exp_cb)( message_t *msg, void *aux ),
void (*bad_cb)( void *aux ),
void *aux );
/* When this callback is invoked (at most once per store), the store is fubar;
* call cancel_store() to dispose of it. */
void (*set_bad_callback)( store_t *ctx, void (*cb)( void *aux ), void *aux );
/* Open/connect the store. This may recycle existing server connections. */
void (*connect_store)( store_t *ctx,
@ -242,13 +229,10 @@ struct driver {
* and those named in the excs array (smaller than minuid).
* The driver takes ownership of the excs array.
* Messages starting with finduid need to have the TUID populated when OPEN_FIND is set.
* Messages up to pairuid need to have the Message-Id populated when OPEN_PAIRED_IDS is set.
* Messages up to pairuid need to have the Message-Id populated when OPEN_OLD_IDS is set.
* Messages up to newuid need to have the size populated when OPEN_OLD_SIZE is set;
* likewise messages above newuid when OPEN_NEW_SIZE is set.
* The returned message list remains owned by the driver and remains valid
* until a new box is selected or the store is freed. New messages within
* the specified range may be added to the list as a result of invoking
* other driver functions. */
* The returned message list remains owned by the driver. */
void (*load_box)( store_t *ctx, uint minuid, uint maxuid, uint finduid, uint pairuid, uint newuid, uint_array_t excs,
void (*cb)( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux ), void *aux );
@ -272,8 +256,8 @@ struct driver {
/* Add/remove the named flags to/from the given message. The message may be either
* a pre-fetched one (in which case the in-memory representation is updated),
* or it may be identifed by UID only.
* The operation may be delayed until commit_cmds() is called. */
* or it may be identifed by UID only. The operation may be delayed until commit()
* is called. */
void (*set_msg_flags)( store_t *ctx, message_t *msg, uint uid, int add, int del,
void (*cb)( int sts, void *aux ), void *aux );
@ -284,9 +268,8 @@ struct driver {
/* Expunge deleted messages from the current mailbox and close it.
* There is no need to explicitly close a mailbox if no expunge is needed. */
// If reported is true, the expunge callback was called reliably.
void (*close_box)( store_t *ctx,
void (*cb)( int sts, int reported, void *aux ), void *aux );
void (*cb)( int sts, void *aux ), void *aux );
/* Cancel queued commands which are not in flight yet; they will have their
* callbacks invoked with DRV_CANCELED. Afterwards, wait for the completion of
@ -305,16 +288,15 @@ struct driver {
int (*get_fail_state)( store_conf_t *conf );
};
uint count_generic_messages( message_t * );
void free_generic_messages( message_t * );
void parse_generic_store( store_conf_t *store, conffile_t *cfg, const char *type );
store_t *proxy_alloc_store( store_t *real_ctx, const char *label, int force_async );
store_t *proxy_alloc_store( store_t *real_ctx, const char *label );
#define N_DRIVERS 2
extern driver_t *drivers[N_DRIVERS];
extern driver_t maildir_driver, imap_driver, proxy_driver;
void cleanup_drivers( void );
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,40 @@
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-FileCopyrightText: 2004 Theodore Y. Ts'o <tytso@mit.edu>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2013 Oswald Buddenhagen <ossi@users.sf.net>
* Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#define DEBUG_FLAG DEBUG_MAILDIR
#include "driver.h"
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <time.h>
#include <utime.h>
#if !defined(_POSIX_SYNCHRONIZED_IO) || _POSIX_SYNCHRONIZED_IO <= 0
@ -35,7 +55,6 @@ typedef union maildir_store_conf {
struct {
STORE_CONF
char *path;
char *path_sfx;
char *inbox;
#ifdef USE_DB
int alt_map;
@ -74,12 +93,10 @@ typedef union maildir_store {
// but mailbox totals. also, don't trust them beyond the initial load.
int total_msgs, recent_msgs;
maildir_message_t *msgs;
maildir_message_t **app_msgs; // Only for testing find_new()
wakeup_t lcktmr;
void (*expunge_callback)( message_t *msg, void *aux );
void (*bad_callback)( void *aux );
void *callback_aux;
void *bad_callback_aux;
};
} maildir_store_t;
@ -90,6 +107,21 @@ static struct flock lck;
static int MaildirCount;
static void ATTR_PRINTFLIKE(1, 2)
debug( const char *msg, ... )
{
va_list va;
va_start( va, msg );
vdebug( DEBUG_SYNC, msg, va );
va_end( va );
}
/* Keep the mailbox driver flag definitions in sync: */
/* grep for MAILBOX_DRIVER_FLAG */
/* The order is according to alphabetical maildir flag sort */
static const char Flags[] = { 'D', 'F', 'P', 'R', 'S', 'T' };
static uchar
maildir_parse_flags( const char *info_prefix, const char *base )
{
@ -99,8 +131,8 @@ maildir_parse_flags( const char *info_prefix, const char *base )
flags = 0;
if ((s = strstr( base, info_prefix )))
for (s += 3, i = 0; i < as(MsgFlags); i++)
if (strchr( s, MsgFlags[i] ))
for (s += 3, i = 0; i < as(Flags); i++)
if (strchr( s, Flags[i] ))
flags |= (1 << i);
return flags;
}
@ -122,23 +154,19 @@ static char *
maildir_join_path( maildir_store_conf_t *conf, int in_inbox, const char *box )
{
char *out, *p;
const char *prefix, *infix;
uint pl, il, bl, n;
const char *prefix;
uint pl, bl, n;
char c;
if (in_inbox || conf->sub_style == SUB_MAILDIRPP) {
prefix = conf->inbox;
infix = NULL;
il = 0;
} else {
if (maildir_ensure_path( conf ) < 0)
return NULL;
prefix = conf->path;
infix = conf->path_sfx;
il = strlen( infix ) + 1;
}
pl = strlen( prefix );
for (bl = 0, n = 0; (c = box[bl]); bl++) {
for (bl = 0, n = 0; (c = box[bl]); bl++)
if (c == '/') {
if (conf->sub_style == SUB_UNSET) {
error( "Maildir error: accessing subfolder '%s', but store '%s' does not specify SubFolders style\n",
@ -151,7 +179,6 @@ maildir_join_path( maildir_store_conf_t *conf, int in_inbox, const char *box )
conf->name, box );
return NULL;
}
}
switch (conf->sub_style) {
case SUB_VERBATIM:
n = 0;
@ -162,16 +189,12 @@ maildir_join_path( maildir_store_conf_t *conf, int in_inbox, const char *box )
default: /* SUB_LEGACY and SUB_UNSET */
break;
}
out = nfmalloc( pl + il + bl + n + 1 );
out = nfmalloc( pl + bl + n + 1 );
memcpy( out, prefix, pl );
p = out + pl;
if (conf->sub_style == SUB_MAILDIRPP) {
*p++ = '/';
*p++ = '.';
} else if (il) {
*p++ = '/';
memcpy( p, infix, il - 1 );
p += il - 1;
}
while ((c = *box++)) {
if (c == '/') {
@ -214,7 +237,7 @@ maildir_alloc_store( store_conf_t *gconf, const char *label ATTR_UNUSED )
{
maildir_store_t *ctx;
ctx = nfzalloc( sizeof(*ctx) );
ctx = nfcalloc( sizeof(*ctx) );
ctx->driver = &maildir_driver;
ctx->gen.conf = gconf;
ctx->uvfd = -1;
@ -243,12 +266,10 @@ maildir_connect_store( store_t *gctx,
static void
free_maildir_messages( maildir_message_t *msg )
{
while (msg) {
maildir_message_t *tmsg = msg->next;
for (maildir_message_t *tmsg; (tmsg = msg); msg = tmsg) {
tmsg = msg->next;
free( msg->base );
free( msg->msgid );
free( msg );
msg = tmsg;
}
}
@ -288,20 +309,18 @@ maildir_cleanup_drv( void )
}
static void
maildir_set_callbacks( store_t *gctx, void (*exp_cb)( message_t *, void * ),
void (*bad_cb)( void * ), void *aux )
maildir_set_bad_callback( store_t *gctx, void (*cb)( void *aux ), void *aux )
{
maildir_store_t *ctx = (maildir_store_t *)gctx;
ctx->expunge_callback = exp_cb;
ctx->bad_callback = bad_cb;
ctx->callback_aux = aux;
ctx->bad_callback = cb;
ctx->bad_callback_aux = aux;
}
static void
maildir_invoke_bad_callback( maildir_store_t *ctx )
{
ctx->bad_callback( ctx->callback_aux );
ctx->bad_callback( ctx->bad_callback_aux );
}
static int
@ -372,10 +391,12 @@ maildir_list_maildirpp( maildir_store_t *ctx, int flags, const char *inbox )
return 0;
}
static int maildir_list_inbox( maildir_store_t *ctx, int flags, const char *basePath );
static int maildir_list_path( maildir_store_t *ctx, int flags, const char *inbox );
static int
maildir_list_recurse( maildir_store_t *ctx, int isBox,
const char *inbox, uint inboxLen,
char *suffix, int suffixLen,
maildir_list_recurse( maildir_store_t *ctx, int isBox, int flags, int depth,
const char *inbox, uint inboxLen, const char *basePath, uint basePathLen,
char *path, int pathLen, char *name, int nameLen )
{
DIR *dir;
@ -396,7 +417,7 @@ maildir_list_recurse( maildir_store_t *ctx, int isBox,
closedir( dir );
return -1;
}
if (isBox > 10) {
if (++depth > 10) {
// We do the other checks first to avoid confusing error messages for files.
error( "Maildir error: path %s is too deeply nested. Symlink loop?\n", path );
closedir( dir );
@ -409,19 +430,19 @@ maildir_list_recurse( maildir_store_t *ctx, int isBox,
pl = nfsnprintf( path + pathLen, _POSIX_PATH_MAX - pathLen, "%s", ent );
if (pl == 3 && (!memcmp( ent, "cur", 3 ) || !memcmp( ent, "new", 3 ) || !memcmp( ent, "tmp", 3 )))
continue;
if (suffixLen) {
if (!starts_with( ent, pl, suffix, suffixLen ))
continue;
if (pl == suffixLen) {
error( "Maildir error: empty mailbox name under %s - did you forget the trailing slash?\n", path );
closedir( dir );
return -1;
}
ent += suffixLen;
}
pl += pathLen;
if (inbox && equals( path, pl, inbox, inboxLen )) {
// Inbox nested into Path.
if (maildir_list_inbox( ctx, flags, NULL ) < 0) {
closedir( dir );
return -1;
}
} else if (basePath && equals( path, pl, basePath, basePathLen )) {
// Path nested into Inbox.
if (maildir_list_path( ctx, flags, NULL ) < 0) {
closedir( dir );
return -1;
}
} else {
if (style == SUB_LEGACY) {
if (*ent == '.') {
@ -449,7 +470,7 @@ maildir_list_recurse( maildir_store_t *ctx, int isBox,
add_string_list( &ctx->boxes, name );
path[pl] = 0;
name[nl++] = '/';
if (maildir_list_recurse( ctx, isBox + 1, inbox, inboxLen, NULL, 0, path, pl, name, nl ) < 0) {
if (maildir_list_recurse( ctx, isBox + 1, flags, depth, inbox, inboxLen, basePath, basePathLen, path, pl, name, nl ) < 0) {
closedir( dir );
return -1;
}
@ -460,7 +481,7 @@ maildir_list_recurse( maildir_store_t *ctx, int isBox,
}
static int
maildir_list_inbox( maildir_store_t *ctx )
maildir_list_inbox( maildir_store_t *ctx, int flags, const char *basePath )
{
char path[_POSIX_PATH_MAX], name[_POSIX_PATH_MAX];
@ -470,13 +491,13 @@ maildir_list_inbox( maildir_store_t *ctx )
add_string_list( &ctx->boxes, "INBOX" );
return maildir_list_recurse(
ctx, 1, NULL, 0, NULL, 0,
ctx, 1, flags, 0, NULL, 0, basePath, basePath ? strlen( basePath ) - 1 : 0,
path, nfsnprintf( path, _POSIX_PATH_MAX, "%s/", ctx->conf->inbox ),
name, nfsnprintf( name, _POSIX_PATH_MAX, "INBOX/" ) );
}
static int
maildir_list_path( maildir_store_t *ctx )
maildir_list_path( maildir_store_t *ctx, int flags, const char *inbox )
{
char path[_POSIX_PATH_MAX], name[_POSIX_PATH_MAX];
@ -486,11 +507,9 @@ maildir_list_path( maildir_store_t *ctx )
if (maildir_ensure_path( ctx->conf ) < 0)
return -1;
const char *inbox = ctx->conf->inbox;
return maildir_list_recurse(
ctx, 0, inbox, strlen( inbox ),
ctx->conf->path_sfx, strlen( ctx->conf->path_sfx ),
path, nfsnprintf( path, _POSIX_PATH_MAX, "%s/", ctx->conf->path ),
ctx, 0, flags, 0, inbox, inbox ? strlen( inbox ) : 0, NULL, 0,
path, nfsnprintf( path, _POSIX_PATH_MAX, "%s", ctx->conf->path ),
name, 0 );
}
@ -504,9 +523,9 @@ maildir_list_store( store_t *gctx, int flags,
if (conf->sub_style == SUB_MAILDIRPP
? maildir_list_maildirpp( ctx, flags, conf->inbox ) < 0
: ((((flags & LIST_PATH) || ((flags & LIST_PATH_MAYBE) && conf->path))
&& maildir_list_path( ctx ) < 0) ||
&& maildir_list_path( ctx, flags, conf->inbox ) < 0) ||
((flags & LIST_INBOX)
&& maildir_list_inbox( ctx ) < 0))) {
&& maildir_list_inbox( ctx, flags, conf->path ) < 0))) {
maildir_invoke_bad_callback( ctx );
cb( DRV_CANCELED, NULL, aux );
} else {
@ -533,10 +552,8 @@ maildir_free_scan( msg_t_array_alloc_t *msglist )
uint i;
if (msglist->array.data) {
for (i = 0; i < msglist->array.size; i++) {
for (i = 0; i < msglist->array.size; i++)
free( msglist->array.data[i].base );
free( msglist->array.data[i].msgid );
}
free( msglist->array.data );
}
}
@ -576,6 +593,21 @@ maildir_clear_tmp( char *buf, int bufsz, int bl )
return DRV_OK;
}
static int
make_box_dir( char *buf, int bl )
{
char *p;
if (!mkdir( buf, 0700 ) || errno == EEXIST)
return 0;
p = memrchr( buf, '/', (size_t)bl - 1 );
*p = 0;
if (make_box_dir( buf, (int)(p - buf) ))
return -1;
*p = '/';
return mkdir( buf, 0700 );
}
static int
maildir_validate( const char *box, int create, maildir_store_t *ctx )
{
@ -586,20 +618,20 @@ maildir_validate( const char *box, int create, maildir_store_t *ctx )
bl = nfsnprintf( buf, sizeof(buf) - 4, "%s/", box );
if (stat( buf, &st )) {
if (errno != ENOENT) {
sys_error( "Maildir error: cannot access mailbox '%s'", buf );
sys_error( "Maildir error: cannot access mailbox '%s'", box );
return DRV_BOX_BAD;
}
if (!create)
return DRV_BOX_BAD;
if (mkdir_p( buf, bl - 1 )) {
sys_error( "Maildir error: cannot create mailbox '%s'", buf );
if (make_box_dir( buf, bl )) {
sys_error( "Maildir error: cannot create mailbox '%s'", box );
ctx->conf->failed = FAIL_FINAL;
maildir_invoke_bad_callback( ctx );
return DRV_CANCELED;
}
} else if (!S_ISDIR(st.st_mode)) {
notdir:
error( "Maildir error: '%s' is no valid mailbox\n", buf );
error( "Maildir error: '%s' is no valid mailbox\n", box );
return DRV_BOX_BAD;
}
for (i = 0; i < 3; i++) {
@ -670,11 +702,11 @@ maildir_store_uidval( maildir_store_t *ctx )
n = sprintf( buf, "%u\n%u\n", ctx->uidvalidity, ctx->nuid );
lseek( ctx->uvfd, 0, SEEK_SET );
if (write( ctx->uvfd, buf, (uint)n ) != n || ftruncate( ctx->uvfd, n ) || (UseFSync && fdatasync( ctx->uvfd ))) {
error( "Maildir error: cannot write UIDVALIDITY in %s\n", ctx->path );
error( "Maildir error: cannot write UIDVALIDITY.\n" );
return DRV_BOX_BAD;
}
}
conf_wakeup( &ctx->lcktmr, 2000 );
conf_wakeup( &ctx->lcktmr, 2 );
return DRV_OK;
}
@ -696,7 +728,7 @@ maildir_init_uidval( maildir_store_t *ctx )
static int
maildir_init_uidval_new( maildir_store_t *ctx )
{
notice( "Maildir notice: no UIDVALIDITY in %s, creating new.\n", ctx->path );
notice( "Maildir notice: no UIDVALIDITY, creating new.\n" );
return maildir_init_uidval( ctx );
}
@ -721,14 +753,14 @@ maildir_uidval_lock( maildir_store_t *ctx )
#endif
lck.l_type = F_WRLCK;
if (fcntl( ctx->uvfd, F_SETLKW, &lck )) {
error( "Maildir error: cannot fcntl lock UIDVALIDITY in %s.\n", ctx->path );
error( "Maildir error: cannot fcntl lock UIDVALIDITY.\n" );
return DRV_BOX_BAD;
}
#ifdef USE_DB
if (ctx->usedb) {
if (fstat( ctx->uvfd, &st )) {
sys_error( "Maildir error: cannot fstat UID database in %s", ctx->path );
sys_error( "Maildir error: cannot fstat UID database" );
return DRV_BOX_BAD;
}
if (db_create( &ctx->db, NULL, 0 )) {
@ -762,7 +794,7 @@ maildir_uidval_lock( maildir_store_t *ctx )
* But this would mess up the sync state completely. So better bail out and
* give the user a chance to fix the mailbox. */
if (n) {
error( "Maildir error: cannot read UIDVALIDITY in %s.\n", ctx->path );
error( "Maildir error: cannot read UIDVALIDITY.\n" );
return DRV_BOX_BAD;
}
#endif
@ -770,7 +802,7 @@ maildir_uidval_lock( maildir_store_t *ctx )
}
}
ctx->uvok = 1;
conf_wakeup( &ctx->lcktmr, 2000 );
conf_wakeup( &ctx->lcktmr, 2 );
return DRV_OK;
}
@ -888,6 +920,28 @@ maildir_compare( const void *l, const void *r )
return strcmp( lm->base, rm->base );
}
int is_gmail(const char* name) {
// NOTE: This is not an exact science. For instance, there is a good
// shot that mail sent from containers will end up matching this pattern
char token[16]; // If host name is longer than 12, then we already have our answer
// we expect files in the form of: "1700172103.R12128272304961211247.hostname,U=27:2,S"
// we are looking for "hostname"
// All gmail mails have a hostname in the form of "ff6d55f971cc".
// All are 12 characters long
// All are hexadecimal
if (sscanf(name, "%*[^.].%*[^.].%15[^,]", token) == 1) {
token[14] = '\0';
size_t len = strlen(token);
if (len != 12) return 0;
if (strspn(token, "0123456789abcdef") == len) return 1;
}
// We are here because a) the input format is not as expected,
// or b) the hostname is not gmail
return 0;
}
static int
maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
{
@ -959,9 +1013,25 @@ maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
#endif /* USE_DB */
return DRV_BOX_BAD;
}
const char *filter = getenv("MBSYNC_MAILDIR_IGNORE");
const char *MAGIC_INCLUDE = "^[^\\.]+\\.[^\\.]+\\.[0-9a-f]{12}\\,.*";
char *include = getenv("MBSYNC_MAILDIR_INCLUDE_ONLY");
if (include && strncmp(MAGIC_INCLUDE, include, strlen(MAGIC_INCLUDE)) != 0) {
include = NULL;
error("MBSYNC_MAILDIR_INCLUDE_ONLY can only be '%s'\n", MAGIC_INCLUDE);
}
if (include && filter) {
include = NULL;
error("MBSYNC_MAILDIR_IGNORE cannot be used with MBSYNC_MAILDIR_INCLUDE_ONLY. INCLUDE_ONLY will be ignored\n");
}
while ((e = readdir( d ))) {
if (*e->d_name == '.')
continue;
if (filter && strstr(e->d_name, filter))
continue;
if (include && !is_gmail(e->d_name))
continue;
ctx->total_msgs++;
ctx->recent_msgs += i;
#ifdef USE_DB
@ -1025,10 +1095,11 @@ maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
}
#ifdef USE_DB
if (ctx->usedb) {
if (maildir_uidval_lock( ctx ) != DRV_OK) {
} else if ((ret = ctx->db->cursor( ctx->db, NULL, &dbc, 0 ))) {
if (maildir_uidval_lock( ctx ) != DRV_OK)
;
else if ((ret = ctx->db->cursor( ctx->db, NULL, &dbc, 0 )))
ctx->db->err( ctx->db, ret, "Maildir error: db->cursor()" );
} else {
else {
for (;;) {
if ((ret = dbc->c_get( dbc, &key, &value, DB_NEXT ))) {
if (ret != DB_NOTFOUND)
@ -1059,11 +1130,11 @@ maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
if (uid == entry->uid) {
#if 1
/* See comment in maildir_uidval_lock() why this is fatal. */
error( "Maildir error: duplicate UID %u in %s.\n", uid, ctx->path );
error( "Maildir error: duplicate UID %u.\n", uid );
maildir_free_scan( msglist );
return DRV_BOX_BAD;
#else
notice( "Maildir notice: duplicate UID in %s; changing UIDVALIDITY.\n", ctx->path );
notice( "Maildir notice: duplicate UID; changing UIDVALIDITY.\n");
if ((ret = maildir_init_uid( ctx )) != DRV_OK) {
maildir_free_scan( msglist );
return ret;
@ -1076,8 +1147,7 @@ maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
if (uid > ctx->nuid) {
/* In principle, we could just warn and top up nuid. However, getting into this
* situation might indicate some serious trouble, so let's not make it worse. */
error( "Maildir error: UID %u is beyond highest assigned UID %u in %s.\n",
uid, ctx->nuid, ctx->path );
error( "Maildir error: UID %u is beyond highest assigned UID %u.\n", uid, ctx->nuid );
maildir_free_scan( msglist );
return DRV_BOX_BAD;
}
@ -1121,10 +1191,9 @@ maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
free( entry->base );
entry->base = nfstrndup( buf + bl + 4, (size_t)fnl );
}
int want_size = ((ctx->opts & OPEN_OLD_SIZE) && uid <= ctx->newuid) ||
((ctx->opts & OPEN_NEW_SIZE) && uid > ctx->newuid);
int want_size = ((ctx->opts & OPEN_NEW_SIZE) && uid > ctx->newuid);
int want_tuid = ((ctx->opts & OPEN_FIND) && uid >= ctx->finduid);
int want_msgid = ((ctx->opts & OPEN_PAIRED_IDS) && uid <= ctx->pairuid);
int want_msgid = ((ctx->opts & OPEN_OLD_IDS) && uid <= ctx->pairuid);
if (!want_size && !want_tuid && !want_msgid)
continue;
if (!fnl)
@ -1160,7 +1229,7 @@ maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
break;
if (want_tuid && starts_with( lnbuf, bufl, "X-TUID: ", 8 )) {
if (bufl < 8 + TUIDL) {
error( "Maildir error: malformed X-TUID header in %s\n", buf );
error( "Maildir error: malformed X-TUID header (UID %u)\n", uid );
continue;
}
memcpy( entry->tuid, lnbuf + 8, TUIDL );
@ -1205,15 +1274,15 @@ maildir_init_msg( maildir_store_t *ctx, maildir_message_t *msg, msg_t *entry )
msg->msgid = entry->msgid;
entry->msgid = NULL; /* prevent deletion */
msg->size = entry->size;
msg->srec = NULL;
memcpy( msg->tuid, entry->tuid, TUIDL );
if (entry->recent)
msg->status |= M_RECENT;
if (ctx->opts & OPEN_FLAGS) {
msg->status |= M_FLAGS;
msg->flags = maildir_parse_flags( ctx->conf->info_prefix, msg->base );
} else {
} else
msg->flags = 0;
}
}
static void
@ -1225,7 +1294,6 @@ maildir_app_msg( maildir_store_t *ctx, maildir_message_t ***msgapp, msg_t *entry
*msgapp = &msg->next;
msg->uid = entry->uid;
msg->status = 0;
msg->srec = NULL;
maildir_init_msg( ctx, msg, entry );
}
@ -1237,7 +1305,6 @@ maildir_select_box( store_t *gctx, const char *name )
maildir_cleanup( gctx );
ctx->msgs = NULL;
ctx->app_msgs = &ctx->msgs;
ctx->excs.data = NULL;
ctx->uvfd = -1;
#ifdef USE_DB
@ -1280,7 +1347,7 @@ maildir_open_box( store_t *gctx,
nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", ctx->path );
#ifndef USE_DB
if ((ctx->uvfd = open( uvpath, O_RDWR | O_CREAT, 0600 )) < 0) {
if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) < 0) {
sys_error( "Maildir error: cannot write %s", uvpath );
cb( DRV_BOX_BAD, UIDVAL_BAD, aux );
return;
@ -1291,11 +1358,11 @@ maildir_open_box( store_t *gctx,
nfsnprintf( uvpath, sizeof(uvpath), "%s/.isyncuidmap.db", ctx->path );
if ((ctx->uvfd = open( uvpath, O_RDWR, 0600 )) < 0) {
if (ctx->conf->alt_map) {
if ((ctx->uvfd = open( uvpath, O_RDWR | O_CREAT, 0600 )) >= 0)
if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) >= 0)
goto dbok;
} else {
nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", ctx->path );
if ((ctx->uvfd = open( uvpath, O_RDWR | O_CREAT, 0600 )) >= 0)
if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) >= 0)
goto fnok;
}
sys_error( "Maildir error: cannot write %s", uvpath );
@ -1413,9 +1480,9 @@ maildir_prepare_load_box( store_t *gctx, uint opts )
maildir_store_t *ctx = (maildir_store_t *)gctx;
if (opts & OPEN_SETFLAGS)
opts |= OPEN_PAIRED;
opts |= OPEN_OLD;
if (opts & OPEN_EXPUNGE)
opts |= OPEN_PAIRED | OPEN_OLD | OPEN_NEW | OPEN_FLAGS | OPEN_UID_EXPUNGE;
opts |= OPEN_OLD|OPEN_NEW|OPEN_FLAGS;
ctx->opts = opts;
return opts;
}
@ -1437,7 +1504,6 @@ maildir_load_box( store_t *gctx, uint minuid, uint maxuid, uint finduid, uint pa
ARRAY_SQUEEZE( &excs );
ctx->excs = excs;
assert( !ctx->msgs );
if (maildir_scan( ctx, &msglist ) != DRV_OK) {
cb( DRV_BOX_BAD, NULL, 0, 0, aux );
return;
@ -1445,7 +1511,6 @@ maildir_load_box( store_t *gctx, uint minuid, uint maxuid, uint finduid, uint pa
msgapp = &ctx->msgs;
for (i = 0; i < msglist.array.size; i++)
maildir_app_msg( ctx, &msgapp, msglist.array.data + i );
ctx->app_msgs = msgapp;
maildir_free_scan( &msglist );
cb( DRV_OK, &ctx->msgs->gen, ctx->total_msgs, ctx->recent_msgs, aux );
@ -1461,30 +1526,35 @@ maildir_rescan( maildir_store_t *ctx )
ctx->fresh[0] = ctx->fresh[1] = 0;
if (maildir_scan( ctx, &msglist ) != DRV_OK)
return DRV_BOX_BAD;
debug( "Maildir processing rescan of %s:\n", ctx->path );
for (msgapp = &ctx->msgs, i = 0; (msg = *msgapp) || i < msglist.array.size; ) {
if (!msg) {
debug( " adding new message %u\n", msglist.array.data[i].uid );
#if 0
debug( "adding new message %u\n", msglist.array.data[i].uid );
maildir_app_msg( ctx, &msgapp, msglist.array.data + i );
#else
debug( "ignoring new message %u\n", msglist.array.data[i].uid );
#endif
i++;
} else if (i >= msglist.array.size) {
debug( " purging deleted message %u\n", msg->uid );
debug( "purging deleted message %u\n", msg->uid );
msg->status = M_DEAD;
ctx->expunge_callback( &msg->gen, ctx->callback_aux );
msgapp = &msg->next;
} else if (msglist.array.data[i].uid < msg->uid) {
/* this should not happen, actually */
debug( " adding new message %u\n", msglist.array.data[i].uid );
#if 0
debug( "adding new message %u\n", msglist.array.data[i].uid );
maildir_app_msg( ctx, &msgapp, msglist.array.data + i );
#else
debug( "ignoring new message %u\n", msglist.array.data[i].uid );
#endif
i++;
} else if (msglist.array.data[i].uid > msg->uid) {
debug( " purging deleted message %u\n", msg->uid );
debug( "purging deleted message %u\n", msg->uid );
msg->status = M_DEAD;
ctx->expunge_callback( &msg->gen, ctx->callback_aux );
msgapp = &msg->next;
} else {
debug( " updating message %u\n", msg->uid );
msg->status &= ~(M_FLAGS | M_RECENT);
debug( "updating message %u\n", msg->uid );
msg->status &= ~(M_FLAGS|M_RECENT);
free( msg->base );
free( msg->msgid );
maildir_init_msg( ctx, msg, msglist.array.data + i );
@ -1561,9 +1631,9 @@ maildir_make_flags( char info_delimiter, uchar flags, char *buf )
buf[0] = info_delimiter;
buf[1] = '2';
buf[2] = ',';
for (d = 3, i = 0; i < (int)as(MsgFlags); i++)
for (d = 3, i = 0; i < (int)as(Flags); i++)
if (flags & (1 << i))
buf[d++] = MsgFlags[i];
buf[d++] = Flags[i];
buf[d] = 0;
return d;
}
@ -1576,7 +1646,7 @@ maildir_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
const char *box;
int ret, fd, bl;
uint uid;
char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX], fbuf[as(MsgFlags) + 3], base[128];
char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX], fbuf[NUM_FLAGS + 3], base[128];
bl = nfsnprintf( base, sizeof(base), "%lld.%d_%d.%s", (long long)time( NULL ), Pid, ++MaildirCount, Hostname );
if (!to_trash) {
@ -1605,7 +1675,7 @@ maildir_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
maildir_make_flags( ctx->conf->info_delimiter, data->flags, fbuf );
nfsnprintf( buf, sizeof(buf), "%s/tmp/%s%s", box, base, fbuf );
if ((fd = open( buf, O_WRONLY | O_CREAT | O_EXCL, 0600 )) < 0) {
if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) {
if (errno != ENOENT || !to_trash) {
sys_error( "Maildir error: cannot create %s", buf );
free( data->data );
@ -1617,7 +1687,7 @@ maildir_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
cb( ret, 0, aux );
return;
}
if ((fd = open( buf, O_WRONLY | O_CREAT | O_EXCL, 0600 )) < 0) {
if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) {
sys_error( "Maildir error: cannot create %s", buf );
free( data->data );
cb( DRV_BOX_BAD, 0, aux );
@ -1660,24 +1730,9 @@ maildir_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
cb( DRV_BOX_BAD, 0, aux );
return;
}
if (DFlags & FAKEDUMBSTORE)
uid = 0;
cb( DRV_OK, uid, aux );
}
static void
maildir_find_new_msgs( store_t *gctx, uint newuid,
void (*cb)( int sts, message_t *msgs, void *aux ), void *aux )
{
maildir_store_t *ctx = (maildir_store_t *)gctx;
assert( DFlags & FAKEDUMBSTORE );
ctx->opts |= OPEN_FIND;
ctx->finduid = newuid;
int ret = maildir_rescan( ctx );
cb( ret, &(*ctx->app_msgs)->gen, aux );
}
static void
maildir_set_msg_flags( store_t *gctx, message_t *gmsg, uint uid ATTR_UNUSED, int add, int del,
void (*cb)( int sts, void *aux ), void *aux )
@ -1696,24 +1751,24 @@ maildir_set_msg_flags( store_t *gctx, message_t *gmsg, uint uid ATTR_UNUSED, int
for (;;) {
bl = bbl + nfsnprintf( buf + bbl, _POSIX_PATH_MAX - bbl, "%s/", subdirs[gmsg->status & M_RECENT] );
ol = strlen( msg->base );
if (_POSIX_PATH_MAX - bl < ol + 3 + (int)as(MsgFlags))
if (_POSIX_PATH_MAX - bl < ol + 3 + NUM_FLAGS)
oob();
memcpy( buf + bl, msg->base, (size_t)ol + 1 );
memcpy( nbuf + bl, msg->base, (size_t)ol + 1 );
if ((s = strstr( nbuf + bl, conf->info_prefix ))) {
s += 3;
fl = ol - (s - (nbuf + bl));
for (i = 0; i < as(MsgFlags); i++) {
if ((p = strchr( s, MsgFlags[i] ))) {
for (i = 0; i < as(Flags); i++) {
if ((p = strchr( s, Flags[i] ))) {
if (del & (1 << i)) {
memmove( p, p + 1, (size_t)fl - (size_t)(p - s) );
fl--;
}
} else if (add & (1 << i)) {
for (j = 0; j < fl && MsgFlags[i] > s[j]; j++);
for (j = 0; j < fl && Flags[i] > s[j]; j++);
fl++;
memmove( s + j + 1, s + j, (size_t)(fl - j) );
s[j] = MsgFlags[i];
s[j] = Flags[i];
}
}
tl = ol + 3 + fl;
@ -1791,7 +1846,6 @@ maildir_trash_msg( store_t *gctx, message_t *gmsg,
}
gmsg->status |= M_DEAD;
ctx->total_msgs--;
ctx->expunge_callback( gmsg, ctx->callback_aux );
#ifdef USE_DB
if (ctx->usedb) {
@ -1804,7 +1858,7 @@ maildir_trash_msg( store_t *gctx, message_t *gmsg,
static void
maildir_close_box( store_t *gctx,
void (*cb)( int sts, int reported, void *aux ), void *aux )
void (*cb)( int sts, void *aux ), void *aux )
{
maildir_store_t *ctx = (maildir_store_t *)gctx;
maildir_message_t *msg;
@ -1814,8 +1868,8 @@ maildir_close_box( store_t *gctx,
for (;;) {
retry = 0;
basel = nfsnprintf( buf, sizeof(buf), "%s/", ctx->path );
for (msg = ctx->msgs; msg; msg = msg->next) {
if (!(msg->status & M_DEAD) && (msg->status & M_EXPUNGE)) {
for (msg = ctx->msgs; msg; msg = msg->next)
if (!(msg->status & M_DEAD) && (msg->flags & F_DELETED)) {
nfsnprintf( buf + basel, _POSIX_PATH_MAX - basel, "%s/%s", subdirs[msg->status & M_RECENT], msg->base );
if (unlink( buf )) {
if (errno == ENOENT)
@ -1825,22 +1879,20 @@ maildir_close_box( store_t *gctx,
} else {
msg->status |= M_DEAD;
ctx->total_msgs--;
ctx->expunge_callback( &msg->gen, ctx->callback_aux );
#ifdef USE_DB
if (ctx->db && (ret = maildir_purge_msg( ctx, msg->base )) != DRV_OK) {
cb( ret, 1, aux );
cb( ret, aux );
return;
}
#endif /* USE_DB */
}
}
}
if (!retry) {
cb( DRV_OK, 1, aux );
cb( DRV_OK, aux );
return;
}
if ((ret = maildir_rescan( ctx )) != DRV_OK) {
cb( ret, 1, aux );
if ((ret = maildir_rescan( (maildir_store_t *)gctx )) != DRV_OK) {
cb( ret, aux );
return;
}
}
@ -1878,26 +1930,21 @@ maildir_parse_store( conffile_t *cfg, store_conf_t **storep )
if (strcasecmp( "MaildirStore", cfg->cmd ))
return 0;
store = nfzalloc( sizeof(*store) );
store = nfcalloc( sizeof(*store) );
store->info_delimiter = FieldDelimiter;
store->driver = &maildir_driver;
store->name = nfstrdup( cfg->val );
while (getcline( cfg ) && cfg->cmd) {
if (!strcasecmp( "Inbox", cfg->cmd )) {
store->inbox = expand_strdup( cfg->val, cfg );
} else if (!strcasecmp( "Path", cfg->cmd )) {
store->path = expand_strdup( cfg->val, cfg );
} else if (!strcasecmp( "AltMap", cfg->cmd )) {
while (getcline( cfg ) && cfg->cmd)
if (!strcasecmp( "Inbox", cfg->cmd ))
store->inbox = expand_strdup( cfg->val );
else if (!strcasecmp( "Path", cfg->cmd ))
store->path = expand_strdup( cfg->val );
#ifdef USE_DB
else if (!strcasecmp( "AltMap", cfg->cmd ))
store->alt_map = parse_bool( cfg );
#else
if (parse_bool( cfg )) {
error( "Error: AltMap=true is not supported by this build.\n" );
cfg->err = 1;
}
#endif /* USE_DB */
} else if (!strcasecmp( "InfoDelimiter", cfg->cmd )) {
else if (!strcasecmp( "InfoDelimiter", cfg->cmd )) {
if (strlen( cfg->val ) != 1) {
error( "%s:%d: Info delimiter must be exactly one character long\n", cfg->file, cfg->line );
cfg->err = 1;
@ -1920,28 +1967,13 @@ maildir_parse_store( conffile_t *cfg, store_conf_t **storep )
error( "%s:%d: Unrecognized SubFolders style\n", cfg->file, cfg->line );
cfg->err = 1;
}
} else {
} else
parse_generic_store( &store->gen, cfg, "MaildirStore" );
}
}
if (!store->inbox)
store->inbox = expand_strdup( "~/Maildir", NULL );
if (store->path) {
if (store->sub_style == SUB_MAILDIRPP) {
error( "Maildir store '%s': Setting Path is incompatible with 'SubFolders Maildir++'\n", store->name );
cfg->err = 1;
} else {
uint inboxLen = strlen( store->inbox );
if (starts_with( store->path, -1, store->inbox, inboxLen ) && store->path[inboxLen] == '/') {
error( "Maildir store '%s': Path cannot be nested under Inbox\n", store->name );
cfg->err = 1;
} else {
char *s = strrchr( store->path, '/' );
assert( s ); // due to expand_strdup()
store->path_sfx = s + 1;
*s = 0;
}
}
store->inbox = expand_strdup( "~/Maildir" );
if (store->sub_style == SUB_MAILDIRPP && store->path) {
error( "Maildir store '%s': Setting Path is incompatible with 'SubFolders Maildir++'\n", store->name );
cfg->err = 1;
}
nfasprintf( &store->info_prefix, "%c2,", store->info_delimiter );
nfasprintf( &store->info_stop, "%c,", store->info_delimiter );
@ -1960,7 +1992,7 @@ struct driver maildir_driver = {
maildir_parse_store,
maildir_cleanup_drv,
maildir_alloc_store,
maildir_set_callbacks,
maildir_set_bad_callback,
maildir_connect_store,
maildir_free_store,
maildir_free_store, /* _cancel_, but it's the same */
@ -1978,7 +2010,7 @@ struct driver maildir_driver = {
maildir_load_box,
maildir_fetch_msg,
maildir_store_msg,
maildir_find_new_msgs,
NULL, // find_new_msgs
maildir_set_msg_flags,
maildir_trash_msg,
maildir_close_box,

View File

@ -1,14 +1,29 @@
// SPDX-FileCopyrightText: 2017-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2017 Oswald Buddenhagen <ossi@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#define DEBUG_FLAG DEBUG_DRV
#include "driver.h"
BIT_FORMATTER_FUNCTION(opts, OPEN)
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
typedef struct gen_cmd gen_cmd_t;
@ -20,19 +35,52 @@ typedef union proxy_store {
uint ref_count;
driver_t *real_driver;
store_t *real_store;
gen_cmd_t *pending_cmds, **pending_cmds_append;
gen_cmd_t *done_cmds, **done_cmds_append;
gen_cmd_t *check_cmds, **check_cmds_append;
wakeup_t wakeup;
uint fake_nextuid;
char is_fake; // Was "created" by dry-run
char force_async;
void (*expunge_callback)( message_t *msg, void *aux );
void (*bad_callback)( void *aux );
void *callback_aux;
void *bad_callback_aux;
};
} proxy_store_t;
static void ATTR_PRINTFLIKE(1, 2)
debug( const char *msg, ... )
{
va_list va;
va_start( va, msg );
vdebug( DEBUG_DRV, msg, va );
va_end( va );
}
static void ATTR_PRINTFLIKE(1, 2)
debugn( const char *msg, ... )
{
va_list va;
va_start( va, msg );
vdebugn( DEBUG_DRV, msg, va );
va_end( va );
}
/* Keep the mailbox driver flag definitions in sync: */
/* grep for MAILBOX_DRIVER_FLAG */
/* The order is according to alphabetical maildir flag sort */
static const char Flags[] = { 'D', 'F', 'P', 'R', 'S', 'T' };
static char *
proxy_make_flags( uchar flags, char *buf )
{
uint i, d;
for (d = 0, i = 0; i < as(Flags); i++)
if (flags & (1 << i))
buf[d++] = Flags[i];
buf[d] = 0;
return buf;
}
static void
proxy_store_deref( proxy_store_t *ctx )
{
@ -55,6 +103,17 @@ struct gen_cmd {
GEN_CMD
};
#define GEN_STS_CMD \
GEN_CMD \
int sts;
typedef union {
gen_cmd_t gen;
struct {
GEN_STS_CMD
};
} gen_sts_cmd_t;
static gen_cmd_t *
proxy_cmd_new( proxy_store_t *ctx, uint sz )
{
@ -80,10 +139,10 @@ proxy_wakeup( void *aux )
{
proxy_store_t *ctx = (proxy_store_t *)aux;
gen_cmd_t *cmd = ctx->pending_cmds;
gen_cmd_t *cmd = ctx->done_cmds;
assert( cmd );
if (!(ctx->pending_cmds = cmd->next))
ctx->pending_cmds_append = &ctx->pending_cmds;
if (!(ctx->done_cmds = cmd->next))
ctx->done_cmds_append = &ctx->done_cmds;
else
conf_wakeup( &ctx->wakeup, 0 );
cmd->queued_cb( cmd );
@ -91,22 +150,22 @@ proxy_wakeup( void *aux )
}
static void
proxy_invoke( gen_cmd_t *cmd, int checked, const char *name )
proxy_invoke_cb( gen_cmd_t *cmd, void (*cb)( gen_cmd_t * ), int checked, const char *name )
{
proxy_store_t *ctx = cmd->ctx;
if (ctx->force_async) {
debug( "%s[% 2d] Queue %s%s\n", ctx->label, cmd->tag, name, checked ? " (checked)" : "" );
if (DFlags & FORCEASYNC) {
debug( "%s[% 2d] Callback queue %s%s\n", cmd->ctx->label, cmd->tag, name, checked ? " (checked)" : "" );
cmd->queued_cb = cb;
cmd->next = NULL;
if (checked) {
*ctx->check_cmds_append = cmd;
ctx->check_cmds_append = &cmd->next;
*cmd->ctx->check_cmds_append = cmd;
cmd->ctx->check_cmds_append = &cmd->next;
} else {
*ctx->pending_cmds_append = cmd;
ctx->pending_cmds_append = &cmd->next;
conf_wakeup( &ctx->wakeup, 0 );
*cmd->ctx->done_cmds_append = cmd;
cmd->ctx->done_cmds_append = &cmd->next;
conf_wakeup( &cmd->ctx->wakeup, 0 );
}
} else {
cmd->queued_cb( cmd );
cb( cmd );
proxy_cmd_done( cmd );
}
}
@ -115,8 +174,8 @@ static void
proxy_flush_checked_cmds( proxy_store_t *ctx )
{
if (ctx->check_cmds) {
*ctx->pending_cmds_append = ctx->check_cmds;
ctx->pending_cmds_append = ctx->check_cmds_append;
*ctx->done_cmds_append = ctx->check_cmds;
ctx->done_cmds_append = ctx->check_cmds_append;
ctx->check_cmds_append = &ctx->check_cmds;
ctx->check_cmds = NULL;
conf_wakeup( &ctx->wakeup, 0 );
@ -124,14 +183,15 @@ proxy_flush_checked_cmds( proxy_store_t *ctx )
}
static void
proxy_cancel_queued_cmds( proxy_store_t *ctx )
proxy_cancel_checked_cmds( proxy_store_t *ctx )
{
if (ctx->pending_cmds || ctx->check_cmds) {
// This would involve directly invoking the result callbacks with
// DRV_CANCEL, for which we'd need another set of dispatch functions.
// The autotest doesn't need that, so save the effort.
error( "Fatal: Faking asynchronous cancelation is not supported.\n" );
abort();
gen_cmd_t *cmd;
while ((cmd = ctx->check_cmds)) {
if (!(ctx->check_cmds = cmd->next))
ctx->check_cmds_append = &ctx->check_cmds;
((gen_sts_cmd_t *)cmd)->sts = DRV_CANCELED;
cmd->queued_cb( cmd );
}
}
@ -141,11 +201,8 @@ static @type@proxy_@name@( store_t *gctx )
{
proxy_store_t *ctx = (proxy_store_t *)gctx;
@type@rv;
@pre_invoke@
@indent_invoke@rv = ctx->real_driver->@name@( ctx->real_store );
@post_invoke@
debug( "%sCalled @name@@print_fmt_dry@, ret=@fmt@\n", ctx->label@print_pass_dry@, rv );
@type@rv = ctx->real_driver->@name@( ctx->real_store );
debug( "%sCalled @name@, ret=@fmt@\n", ctx->label, rv );
return rv;
}
//# END
@ -156,13 +213,10 @@ static @type@proxy_@name@( store_t *gctx@decl_args@ )
proxy_store_t *ctx = (proxy_store_t *)gctx;
@pre_print_args@
debug( "%sEnter @name@@print_fmt_dry@@print_fmt_args@\n", ctx->label@print_pass_dry@@print_pass_args@ );
debug( "%sEnter @name@@print_fmt_args@\n", ctx->label@print_pass_args@ );
@print_args@
@type@rv;
@pre_invoke@
@indent_invoke@rv = ctx->real_driver->@name@( ctx->real_store@pass_args@ );
@post_invoke@
debug( "%sLeave @name@, ret=@print_fmt_ret@\n", ctx->label, @print_pass_ret@ );
@type@rv = ctx->real_driver->@name@( ctx->real_store@pass_args@ );
debug( "%sLeave @name@, ret=@fmt@\n", ctx->label, rv );
return rv;
}
//# END
@ -173,43 +227,20 @@ static @type@proxy_@name@( store_t *gctx@decl_args@ )
proxy_store_t *ctx = (proxy_store_t *)gctx;
@pre_print_args@
debug( "%sEnter @name@@print_fmt_dry@@print_fmt_args@\n", ctx->label@print_pass_dry@@print_pass_args@ );
debug( "%sEnter @name@@print_fmt_args@\n", ctx->label@print_pass_args@ );
@print_args@
@pre_invoke@
@indent_invoke@ctx->real_driver->@name@( ctx->real_store@pass_args@ );
@post_invoke@
ctx->real_driver->@name@( ctx->real_store@pass_args@ );
debug( "%sLeave @name@\n", ctx->label );
@action@
}
//# END
//# TEMPLATE CALLBACK_VOID
debug( "%s[% 2d] Callback enter @name@\n", ctx->label, cmd->tag );
@print_cb_args@
//# END
//# TEMPLATE CALLBACK_STS
debug( "%s[% 2d] Callback enter @name@, sts=%d\n", ctx->label, cmd->tag, sts );
//# END
//# TEMPLATE CALLBACK_STS_PRN
debug( "%s[% 2d] Callback enter @name@, sts=%d\n", ctx->label, cmd->tag, sts );
if (sts == DRV_OK) {
@print_cb_args@
}
//# END
//# TEMPLATE CALLBACK_STS_FMT
if (sts == DRV_OK) {
debug( "%s[% 2d] Callback enter @name@, sts=" stringify(DRV_OK) "@print_fmt_cb_args@\n", ctx->label, cmd->tag@print_pass_cb_args@ );
@print_cb_args@
} else {
debug( "%s[% 2d] Callback enter @name@, sts=%d\n", ctx->label, cmd->tag, sts );
}
//# END
//# TEMPLATE CALLBACK
typedef union {
gen_cmd_t gen;
@gen_cmd_t@ gen;
struct {
GEN_CMD
@GEN_CMD@
@decl_cb_state@
void (*callback)( @decl_cb_args@void *aux );
void *callback_aux;
@decl_state@
@ -217,31 +248,24 @@ typedef union {
} @name@_cmd_t;
static void
proxy_@name@_cb( @decl_cb_args@void *aux )
proxy_do_@name@_cb( gen_cmd_t *gcmd )
{
@name@_cmd_t *cmd = (@name@_cmd_t *)aux;
proxy_store_t *ctx = cmd->ctx;
@name@_cmd_t *cmd = (@name@_cmd_t *)gcmd;
@count_step@
@print_cb_args_tpl@
@pre_print_cb_args@
debug( "%s[% 2d] Callback enter @name@@print_fmt_cb_args@\n", cmd->ctx->label, cmd->tag@print_pass_cb_args@ );
@print_cb_args@
cmd->callback( @pass_cb_args@cmd->callback_aux );
debug( "%s[% 2d] Callback leave @name@\n", ctx->label, cmd->tag );
proxy_cmd_done( &cmd->gen );
debug( "%s[% 2d] Callback leave @name@\n", cmd->ctx->label, cmd->tag );
}
static void
proxy_do_@name@( gen_cmd_t *gcmd )
proxy_@name@_cb( @decl_cb_args@void *aux )
{
@name@_cmd_t *cmd = (@name@_cmd_t *)gcmd;
proxy_store_t *ctx = cmd->ctx;
@name@_cmd_t *cmd = (@name@_cmd_t *)aux;
@pre_print_args@
debug( "%s[% 2d] Enter @name@@print_fmt_dry@@print_fmt_args@\n", ctx->label, cmd->tag@print_pass_dry@@print_pass_args@ );
@print_args@
@pre_invoke@
@indent_invoke@ctx->real_driver->@name@( ctx->real_store@pass_args@, proxy_@name@_cb, cmd );
@post_invoke@
debug( "%s[% 2d] Leave @name@\n", ctx->label, cmd->tag );
@save_cb_args@
proxy_invoke_cb( @gen_cmd@, proxy_do_@name@_cb, @checked@, "@name@" );
}
static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@void *aux ), void *aux )
@ -249,118 +273,76 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v
proxy_store_t *ctx = (proxy_store_t *)gctx;
@name@_cmd_t *cmd = (@name@_cmd_t *)proxy_cmd_new( ctx, sizeof(@name@_cmd_t) );
cmd->queued_cb = proxy_do_@name@;
cmd->callback = cb;
cmd->callback_aux = aux;
@assign_state@
proxy_invoke( &cmd->gen, @checked@, "@name@" );
@pre_print_args@
debug( "%s[% 2d] Enter @name@@print_fmt_args@\n", ctx->label, cmd->tag@print_pass_args@ );
@print_args@
ctx->real_driver->@name@( ctx->real_store@pass_args@, proxy_@name@_cb, cmd );
debug( "%s[% 2d] Leave @name@\n", ctx->label, cmd->tag );
proxy_cmd_done( @gen_cmd@ );
}
//# END
//# UNDEFINE list_store_print_fmt_cb_args
//# UNDEFINE list_store_print_pass_cb_args
//# DEFINE list_store_print_cb_args
for (string_list_t *box = boxes; box; box = box->next)
debug( " %s\n", box->string );
if (cmd->sts == DRV_OK) {
for (string_list_t *box = cmd->boxes; box; box = box->next)
debug( " %s\n", box->string );
}
//# END
//# DEFINE select_box_pre_invoke
ctx->is_fake = 0;
//# END
//# DEFINE create_box_driable 1
//# DEFINE create_box_fake_invoke
ctx->is_fake = 1;
//# END
//# DEFINE create_box_counted 1
//# DEFINE open_box_fakeable 1
//# DEFINE open_box_fake_invoke
ctx->fake_nextuid = 1;
//# END
//# DEFINE open_box_fake_cb_args , 1
//# DEFINE get_uidnext_fakeable 1
//# DEFINE get_uidnext_fake_invoke
rv = ctx->fake_nextuid;
//# END
//# DEFINE get_uidnext_post_real_invoke
ctx->fake_nextuid = rv;
//# END
//# DEFINE get_supported_flags_fakeable 1
//# DEFINE get_supported_flags_fake_invoke
rv = 255;
//# END
//# DEFINE confirm_box_empty_fakeable 1
//# DEFINE confirm_box_empty_fake_invoke
rv = 1;
//# END
//# DEFINE delete_box_driable 1
//# DEFINE delete_box_fake_invoke
ctx->is_fake = 0;
//# END
//# DEFINE delete_box_counted 1
//# DEFINE finish_delete_box_driable 1
//# DEFINE finish_delete_box_fake_invoke
rv = DRV_OK;
//# END
//# DEFINE prepare_load_box_print_fmt_args , opts=%s
//# DEFINE prepare_load_box_print_pass_args , fmt_opts( opts ).str
//# DEFINE prepare_load_box_print_fmt_ret %s
//# DEFINE prepare_load_box_print_pass_ret fmt_opts( rv ).str
//# DEFINE load_box_pre_print_args
char ubuf[12];
static char ubuf[12];
//# END
//# DEFINE load_box_print_fmt_args , [%u,%s] (find >= %u, paired <= %u, new > %u)
//# DEFINE load_box_print_pass_args , cmd->minuid, (cmd->maxuid == UINT_MAX) ? "inf" : (nfsnprintf( ubuf, sizeof(ubuf), "%u", cmd->maxuid ), ubuf), cmd->finduid, cmd->pairuid, cmd->newuid
//# DEFINE load_box_print_pass_args , minuid, (maxuid == UINT_MAX) ? "inf" : (nfsnprintf( ubuf, sizeof(ubuf), "%u", maxuid ), ubuf), finduid, pairuid, newuid
//# DEFINE load_box_print_args
if (cmd->excs.size) {
if (excs.size) {
debugn( " excs:" );
for (uint t = 0; t < cmd->excs.size; t++)
debugn( " %u", cmd->excs.data[t] );
for (uint t = 0; t < excs.size; t++)
debugn( " %u", excs.data[t] );
debug( "\n" );
}
//# END
//# DEFINE load_box_fakeable 1
//# DEFINE load_box_fake_cb_args , NULL, 0, 0
//# DEFINE load_box_print_fmt_cb_args , total=%d, recent=%d
//# DEFINE load_box_print_pass_cb_args , total_msgs, recent_msgs
//# DEFINE load_box_print_fmt_cb_args , sts=%d, total=%d, recent=%d
//# DEFINE load_box_print_pass_cb_args , cmd->sts, cmd->total_msgs, cmd->recent_msgs
//# DEFINE load_box_print_cb_args
for (message_t *msg = msgs; msg; msg = msg->next) {
if (msg->status & M_DEAD)
continue;
debug( " uid=%-5u flags=%-4s size=%-6u tuid=%." stringify(TUIDL) "s\n",
msg->uid, (msg->status & M_FLAGS) ? fmt_flags( msg->flags ).str : "?", msg->size, *msg->tuid ? msg->tuid : "?" );
if (cmd->sts == DRV_OK) {
static char fbuf[as(Flags) + 1];
for (message_t *msg = cmd->msgs; msg; msg = msg->next)
debug( " uid=%-5u flags=%-4s size=%-6u tuid=%." stringify(TUIDL) "s\n",
msg->uid, (msg->status & M_FLAGS) ? (proxy_make_flags( msg->flags, fbuf ), fbuf) : "?", msg->size, *msg->tuid ? msg->tuid : "?" );
}
//# END
//# UNDEFINE find_new_msgs_print_fmt_cb_args
//# UNDEFINE find_new_msgs_print_pass_cb_args
//# DEFINE find_new_msgs_print_fmt_cb_args , sts=%d
//# DEFINE find_new_msgs_print_pass_cb_args , cmd->sts
//# DEFINE find_new_msgs_print_cb_args
for (message_t *msg = msgs; msg; msg = msg->next) {
if (msg->status & M_DEAD)
continue;
debug( " uid=%-5u tuid=%." stringify(TUIDL) "s\n", msg->uid, msg->tuid );
if (cmd->sts == DRV_OK) {
for (message_t *msg = cmd->msgs; msg; msg = msg->next)
debug( " uid=%-5u tuid=%." stringify(TUIDL) "s\n", msg->uid, msg->tuid );
}
//# END
//# DEFINE fetch_msg_decl_state
msg_data_t *data;
//# END
//# DEFINE fetch_msg_assign_state
cmd->data = data;
//# END
//# DEFINE fetch_msg_print_fmt_args , uid=%u, want_flags=%s, want_date=%s
//# DEFINE fetch_msg_print_pass_args , cmd->msg->uid, !(cmd->msg->status & M_FLAGS) ? "yes" : "no", cmd->data->date ? "yes" : "no"
//# DEFINE fetch_msg_driable 1
//# DEFINE fetch_msg_fake_invoke
cmd->data->data = strdup( "" );
cmd->data->len = 0;
//# DEFINE fetch_msg_print_pass_args , msg->uid, !(msg->status & M_FLAGS) ? "yes" : "no", data->date ? "yes" : "no"
//# DEFINE fetch_msg_pre_print_cb_args
static char fbuf[as(Flags) + 1];
proxy_make_flags( cmd->data->flags, fbuf );
//# END
//# DEFINE fetch_msg_print_fmt_cb_args , flags=%s, date=%lld, size=%u
//# DEFINE fetch_msg_print_pass_cb_args , fmt_flags( cmd->data->flags ).str, (long long)cmd->data->date, cmd->data->len
//# DEFINE fetch_msg_print_pass_cb_args , fbuf, (long long)cmd->data->date, cmd->data->len
//# DEFINE fetch_msg_print_cb_args
if (DFlags & DEBUG_DRV_ALL) {
if (cmd->sts == DRV_OK && (DFlags & DEBUG_DRV_ALL)) {
printf( "%s=========\n", cmd->ctx->label );
fwrite( cmd->data->data, cmd->data->len, 1, stdout );
printf( "%s=========\n", cmd->ctx->label );
@ -368,78 +350,64 @@ static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@v
}
//# END
//# DEFINE store_msg_pre_print_args
static char fbuf[as(Flags) + 1];
proxy_make_flags( data->flags, fbuf );
//# END
//# DEFINE store_msg_print_fmt_args , flags=%s, date=%lld, size=%u, to_trash=%s
//# DEFINE store_msg_print_pass_args , fmt_flags( cmd->data->flags ).str, (long long)cmd->data->date, cmd->data->len, cmd->to_trash ? "yes" : "no"
//# DEFINE store_msg_print_pass_args , fbuf, (long long)data->date, data->len, to_trash ? "yes" : "no"
//# DEFINE store_msg_print_args
if (DFlags & DEBUG_DRV_ALL) {
printf( "%s>>>>>>>>>\n", ctx->label );
fwrite( cmd->data->data, cmd->data->len, 1, stdout );
fwrite( data->data, data->len, 1, stdout );
printf( "%s>>>>>>>>>\n", ctx->label );
fflush( stdout );
}
//# END
//# DEFINE store_msg_driable 1
//# DEFINE store_msg_fake_cb_args , cmd->to_trash ? 0 : ctx->fake_nextuid++
//# DEFINE store_msg_counted 1
//# DEFINE set_msg_flags_checked 1
//# DEFINE set_msg_flags_pre_print_args
static char fbuf1[as(Flags) + 1], fbuf2[as(Flags) + 1];
proxy_make_flags( add, fbuf1 );
proxy_make_flags( del, fbuf2 );
//# END
//# DEFINE set_msg_flags_print_fmt_args , uid=%u, add=%s, del=%s
//# DEFINE set_msg_flags_print_pass_args , cmd->uid, fmt_flags( cmd->add ).str, fmt_flags( cmd->del ).str
//# DEFINE set_msg_flags_driable 1
//# DEFINE set_msg_flags_counted 1
//# DEFINE set_msg_flags_print_pass_args , uid, fbuf1, fbuf2
//# DEFINE set_msg_flags_checked sts == DRV_OK
//# DEFINE trash_msg_print_fmt_args , uid=%u
//# DEFINE trash_msg_print_pass_args , cmd->msg->uid
//# DEFINE trash_msg_driable 1
//# DEFINE trash_msg_counted 1
//# DEFINE close_box_driable 1
//# DEFINE close_box_fake_cb_args , 0
//# DEFINE close_box_counted 1
//# DEFINE trash_msg_print_pass_args , msg->uid
//# DEFINE commit_cmds_print_args
proxy_flush_checked_cmds( ctx );
//# END
//# DEFINE cancel_cmds_print_cb_args
proxy_cancel_queued_cmds( ctx );
proxy_cancel_checked_cmds( cmd->ctx );
//# END
//# DEFINE free_store_print_args
proxy_cancel_queued_cmds( ctx );
proxy_cancel_checked_cmds( ctx );
//# END
//# DEFINE free_store_action
proxy_store_deref( ctx );
//# END
//# DEFINE cancel_store_print_args
proxy_cancel_queued_cmds( ctx );
proxy_cancel_checked_cmds( ctx );
//# END
//# DEFINE cancel_store_action
proxy_store_deref( ctx );
//# END
#endif
//# SPECIAL set_callbacks
//# SPECIAL set_bad_callback
static void
proxy_set_callbacks( store_t *gctx, void (*exp_cb)( message_t *, void * ),
void (*bad_cb)( void * ), void *aux )
proxy_set_bad_callback( store_t *gctx, void (*cb)( void *aux ), void *aux )
{
proxy_store_t *ctx = (proxy_store_t *)gctx;
ctx->expunge_callback = exp_cb;
ctx->bad_callback = bad_cb;
ctx->callback_aux = aux;
}
static void
proxy_invoke_expunge_callback( message_t *msg, proxy_store_t *ctx )
{
ctx->ref_count++;
debug( "%sCallback enter expunged message %u\n", ctx->label, msg->uid );
ctx->expunge_callback( msg, ctx->callback_aux );
debug( "%sCallback leave expunged message %u\n", ctx->label, msg->uid );
proxy_store_deref( ctx );
ctx->bad_callback = cb;
ctx->bad_callback_aux = aux;
}
static void
@ -447,30 +415,27 @@ proxy_invoke_bad_callback( proxy_store_t *ctx )
{
ctx->ref_count++;
debug( "%sCallback enter bad store\n", ctx->label );
ctx->bad_callback( ctx->callback_aux );
ctx->bad_callback( ctx->bad_callback_aux );
debug( "%sCallback leave bad store\n", ctx->label );
proxy_store_deref( ctx );
}
//# EXCLUDE alloc_store
store_t *
proxy_alloc_store( store_t *real_ctx, const char *label, int force_async )
proxy_alloc_store( store_t *real_ctx, const char *label )
{
proxy_store_t *ctx;
ctx = nfzalloc( sizeof(*ctx) );
ctx = nfcalloc( sizeof(*ctx) );
ctx->driver = &proxy_driver;
ctx->gen.conf = real_ctx->conf;
ctx->ref_count = 1;
ctx->label = label;
ctx->force_async = force_async;
ctx->pending_cmds_append = &ctx->pending_cmds;
ctx->done_cmds_append = &ctx->done_cmds;
ctx->check_cmds_append = &ctx->check_cmds;
ctx->real_driver = real_ctx->driver;
ctx->real_store = real_ctx;
ctx->real_driver->set_callbacks( ctx->real_store,
(void (*)( message_t *, void * ))proxy_invoke_expunge_callback,
(void (*)( void * ))proxy_invoke_bad_callback, ctx );
ctx->real_driver->set_bad_callback( ctx->real_store, (void (*)(void *))proxy_invoke_bad_callback, ctx );
init_wakeup( &ctx->wakeup, proxy_wakeup, ctx );
return &ctx->gen;
}

View File

@ -1,9 +1,23 @@
#!/usr/bin/perl
#
# SPDX-FileCopyrightText: 2017-2022 Oswald Buddenhagen <ossi@users.sf.net>
# SPDX-License-Identifier: GPL-2.0-or-later
#
# mbsync - mailbox synchronizer
# Copyright (C) 2017 Oswald Buddenhagen <ossi@users.sf.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# As a special exception, mbsync may be linked with the OpenSSL library,
# despite that library's more restrictive license.
#
use strict;
@ -77,7 +91,7 @@ while (<$inh>) {
}
close($inh);
$cont =~ s,(?://.*)?\n, ,g;
$cont =~ s,\n, ,g;
$cont =~ s,/\*.*?\*/, ,g;
$cont =~ s,\h+, ,g;
my @ptypes = map { s,^ ,,r } split(/;/, $cont);
@ -126,7 +140,6 @@ for (@ptypes) {
}
push @cmd_table, "proxy_$cmd_name";
next if (defined($special{$cmd_name}));
my $inc_tpl = "";
my %replace;
$replace{'name'} = $cmd_name;
$replace{'type'} = $cmd_type;
@ -135,106 +148,44 @@ for (@ptypes) {
$template = "GETTER";
$replace{'fmt'} = type_to_format($cmd_type);
} else {
my $pass_args;
if ($cmd_type eq "void " && $cmd_args =~ s/, void \(\*cb\)\( (.*)void \*aux \), void \*aux$//) {
my $cmd_cb_args = $1;
$replace{'decl_cb_args'} = $cmd_cb_args;
$replace{'pass_cb_args'} = make_args($cmd_cb_args);
if (length($cmd_cb_args)) {
$replace{'decl_cb_args'} = $cmd_cb_args;
my $r_cmd_cb_args = $cmd_cb_args;
$r_cmd_cb_args =~ s/^int sts, // or die("Callback arguments of $cmd_name don't start with sts.\n");
$r_cmd_cb_args =~ s/^(.*), $/, $1/;
$replace{'print_pass_cb_args'} = make_args($r_cmd_cb_args);
$replace{'print_fmt_cb_args'} = make_format($r_cmd_cb_args);
$inc_tpl = 'CALLBACK_STS';
$replace{'decl_cb_state'} = $r_cmd_cb_args =~ s/, /\;\n/gr;
my $pass_cb_args = make_args($cmd_cb_args);
$replace{'save_cb_args'} = $pass_cb_args =~ s/([^,]+), /cmd->$1 = $1\;\n/gr;
$pass_cb_args =~ s/([^, ]+)/cmd->$1/g;
$replace{'pass_cb_args'} = $pass_cb_args;
$replace{'print_pass_cb_args'} = $pass_cb_args =~ s/(.*), $/, $1/r;
$replace{'print_fmt_cb_args'} = make_format($cmd_cb_args =~ s/(.*), $/, $1/r);
$replace{'gen_cmd_t'} = "gen_sts_cmd_t";
$replace{'GEN_CMD'} = "GEN_STS_CMD\n";
$replace{'gen_cmd'} = "&cmd->gen.gen";
} else {
$inc_tpl = 'CALLBACK_VOID';
$replace{'gen_cmd_t'} = "gen_cmd_t";
$replace{'GEN_CMD'} = "GEN_CMD\n";
$replace{'gen_cmd'} = "&cmd->gen";
}
$pass_args = make_args($cmd_args);
$pass_args =~ s/([^, ]+)/cmd->$1/g;
my $r_cmd_args = $cmd_args =~ s/, (.*)$/$1, /r;
$replace{'decl_state'} = $r_cmd_args =~ s/, /\;\n/gr;
my $r_pass_args = make_args($r_cmd_args);
$replace{'assign_state'} = $r_pass_args =~ s/([^,]+), /cmd->$1 = $1\;\n/gr;
$replace{'checked'} = '0';
$replace{'checked'} //= '0';
$template = "CALLBACK";
} elsif ($cmd_type eq "void ") {
$template = "REGULAR_VOID";
} else {
$pass_args = make_args($cmd_args);
if ($cmd_type eq "void ") {
$template = "REGULAR_VOID";
} else {
$template = "REGULAR";
$replace{'print_fmt_ret'} = type_to_format($cmd_type);
$replace{'print_pass_ret'} = "rv";
}
$template = "REGULAR";
$replace{'fmt'} = type_to_format($cmd_type);
}
$replace{'decl_args'} = $cmd_args;
$replace{'print_pass_args'} = $replace{'pass_args'} = $pass_args;
$replace{'print_pass_args'} = $replace{'pass_args'} = make_args($cmd_args);
$replace{'print_fmt_args'} = make_format($cmd_args);
}
my ($fake_cond, $fake_invoke, $fake_cb_args, $post_invoke) = (undef, "", "", "");
for (keys %defines) {
next if (!/^${cmd_name}_(.*)$/);
my ($key, $val) = ($1, delete $defines{$_});
if ($key eq 'counted') {
$replace{count_step} = "countStep();\n";
} elsif ($key eq 'fakeable') {
$fake_cond = "ctx->is_fake";
$replace{print_pass_dry} = ', '.$fake_cond.' ? " [FAKE]" : ""';
} elsif ($key eq 'driable') {
$fake_cond = "DFlags & DRYRUN";
$replace{print_pass_dry} = ', ('.$fake_cond.') ? " [DRY]" : ""';
} elsif ($key eq 'fake_invoke') {
$fake_invoke = $val;
} elsif ($key eq 'fake_cb_args') {
$fake_cb_args = $val;
} elsif ($key eq 'post_real_invoke') {
$post_invoke = $val;
} else {
$replace{$key} = $val;
}
}
if (defined($fake_cond)) {
$replace{print_fmt_dry} = '%s';
if ($inc_tpl eq 'CALLBACK_STS') {
$fake_invoke .= "proxy_${cmd_name}_cb( DRV_OK${fake_cb_args}, cmd );\n";
} elsif (length($fake_cb_args)) {
die("Unexpected fake callback arguments to $cmd_name\n");
}
my $num_fake = $fake_invoke =~ s/^(?=.)/\t/gsm;
my $num_real = $post_invoke =~ s/^(?=.)/\t/gsm;
my $pre_invoke = "if (".$fake_cond.")";
if ($num_fake > 1 || $num_real) {
$pre_invoke .= " {";
$fake_invoke .= "} else {\n";
$post_invoke .= "}\n";
} else {
$fake_invoke .= "else\n";
}
$replace{pre_invoke} = $pre_invoke."\n".$fake_invoke;
$replace{indent_invoke} = "\t";
$replace{post_invoke} = $post_invoke;
$replace{$1} = delete $defines{$_} if (/^${cmd_name}_(.*)$/);
}
my %used;
my $text = $templates{$template};
if ($inc_tpl) {
if ($inc_tpl eq 'CALLBACK_STS') {
if ($replace{print_fmt_cb_args}) {
$inc_tpl .= '_FMT';
} else {
if ($replace{print_cb_args}) {
$inc_tpl .= '_PRN';
}
# These may be defined but empty; that's not an error.
delete $replace{print_fmt_cb_args};
delete $replace{print_pass_cb_args};
}
}
$text =~ s/^\t\@print_cb_args_tpl\@\n/$templates{$inc_tpl}/sm;
}
$text =~ s/^(\h*)\@(\w+)\@\n/$used{$2} = 1; indent($replace{$2} \/\/ "", $1)/smeg;
$text =~ s/\@(\w+)\@/$used{$1} = 1; $replace{$1} \/\/ ""/eg;
print $outh $text."\n";

View File

@ -1,153 +0,0 @@
// SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
//
// mbsync - mailbox synchronizer
//
#include "imap_p.h"
#ifdef DEBUG_IMAP_MSGS
# define dbg(...) print(__VA_ARGS__)
#else
# define dbg(...) do { } while (0)
#endif
imap_message_t *
imap_new_msg( imap_messages_t *msgs )
{
imap_message_t *msg = nfzalloc( sizeof(*msg) );
*msgs->tail = msg;
msgs->tail = &msg->next;
msgs->count++;
return msg;
}
void
reset_imap_messages( imap_messages_t *msgs )
{
free_generic_messages( &msgs->head->gen );
msgs->head = NULL;
msgs->tail = &msgs->head;
msgs->count = 0;
msgs->cursor_ptr = NULL;
msgs->cursor_seq = 0;
}
static int
imap_compare_msgs( const void *a_, const void *b_ )
{
const imap_message_t *a = *(const imap_message_t * const *)a_;
const imap_message_t *b = *(const imap_message_t * const *)b_;
if (a->uid < b->uid)
return -1;
if (a->uid > b->uid)
return 1;
return 0;
}
void
imap_ensure_relative( imap_messages_t *msgs )
{
if (msgs->cursor_ptr)
return;
uint count = msgs->count;
if (!count)
return;
if (count > 1) {
imap_message_t **t = nfmalloc( sizeof(*t) * count );
imap_message_t *m = msgs->head;
for (uint i = 0; i < count; i++) {
t[i] = m;
m = m->next;
}
qsort( t, count, sizeof(*t), imap_compare_msgs );
imap_message_t *nm = t[0];
msgs->head = nm;
nm->prev = NULL;
uint seq, nseq = nm->seq;
for (uint j = 0; m = nm, seq = nseq, j < count - 1; j++) {
nm = t[j + 1];
m->next = nm;
m->next->prev = m;
nseq = nm->seq;
nm->seq = nseq - seq;
}
msgs->tail = &m->next;
*msgs->tail = NULL;
free( t );
}
msgs->cursor_ptr = msgs->head;
msgs->cursor_seq = msgs->head->seq;
}
void
imap_ensure_absolute( imap_messages_t *msgs )
{
if (!msgs->cursor_ptr)
return;
uint seq = 0;
for (imap_message_t *msg = msgs->head; msg; msg = msg->next) {
seq += msg->seq;
msg->seq = seq;
}
msgs->cursor_ptr = NULL;
msgs->cursor_seq = 0;
}
imap_message_t *
imap_expunge_msg( imap_messages_t *msgs, uint fseq )
{
dbg( "expunge %u\n", fseq );
imap_ensure_relative( msgs );
imap_message_t *ret = NULL, *msg = msgs->cursor_ptr;
if (msg) {
uint seq = msgs->cursor_seq;
for (;;) {
dbg( " now on message %u (uid %u), %sdead\n", seq, msg->uid, (msg->status & M_DEAD) ? "" : "not " );
if (seq == fseq && !(msg->status & M_DEAD)) {
dbg( " => expunging\n" );
msg->status = M_DEAD;
ret = msg;
break;
}
if (seq < fseq) {
dbg( " is below\n" );
if (!msg->next) {
dbg( " no next\n" );
goto done;
}
msg = msg->next;
seq += msg->seq;
} else {
dbg( " is not below\n" );
if (!msg->prev) {
dbg( " no prev\n" );
break;
}
uint pseq = seq - msg->seq;
if (pseq < fseq) {
dbg( " prev too low\n" );
break;
}
seq = pseq;
msg = msg->prev;
}
}
dbg( " => lowering\n" );
assert( msg->seq );
msg->seq--;
seq--;
done:
dbg( " saving cursor on %u (uid %u)\n", seq, msg->uid );
msgs->cursor_ptr = msg;
msgs->cursor_seq = seq;
} else {
dbg( " => no messages\n" );
}
return ret;
}

View File

@ -1,52 +0,0 @@
// SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
//
// mbsync - mailbox synchronizer
//
#ifndef IMAP_P_H
#define IMAP_P_H
#include "driver.h"
//#define DEBUG_IMAP_MSGS
//#define DEBUG_IMAP_UTF7
typedef union imap_message {
message_t gen;
struct {
MESSAGE(union imap_message)
union imap_message *prev; // Used to optimize lookup by seq.
// This is made relative once the fetches complete - to avoid that
// each expunge re-enumerates all subsequent messages. Dead messages
// "occupy" no sequence number themselves, but may still jump a gap.
// Note that use of sequence numbers to address messages in commands
// imposes limitations on permissible pipelining. We don't do that,
// so this is of no concern; however, we might miss the closing of
// a gap, which would result in a tiny performance hit.
uint seq;
};
} imap_message_t;
typedef struct {
imap_message_t *head;
imap_message_t **tail;
// Bulk changes (which is where performance matters) are assumed to be
// reported sequentially (be it forward or reverse), so walking the
// sorted linked list from the previously used message is efficient.
imap_message_t *cursor_ptr;
uint cursor_seq;
uint count;
} imap_messages_t;
imap_message_t *imap_new_msg( imap_messages_t *msgs );
imap_message_t *imap_expunge_msg( imap_messages_t *msgs, uint fseq );
void reset_imap_messages( imap_messages_t *msgs );
void imap_ensure_relative( imap_messages_t *msgs );
void imap_ensure_absolute( imap_messages_t *msgs );
char *imap_utf8_to_utf7( const char *buf );
int imap_utf7_to_utf8( const char *buf, int argl, char *outbuf );
#endif

View File

@ -1,288 +0,0 @@
// SPDX-FileCopyrightText: 2018-2021 Georgy Kibardin <georgy@kibardin.name>
// SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
//
// mbsync - mailbox synchronizer
//
#include "imap_p.h"
#ifdef DEBUG_IMAP_UTF7
# define dbg(...) print(__VA_ARGS__)
#else
# define dbg(...) do { } while (0)
#endif
struct bit_fifo {
unsigned long long value;
uint bits;
};
static void
add_bits( struct bit_fifo *fifo, uint bits, uint size )
{
fifo->value = (fifo->value << size) | bits;
fifo->bits += size;
assert( fifo->bits <= sizeof(fifo->value) * 8 );
}
static uint
eat_bits( struct bit_fifo *fifo, uint size )
{
fifo->bits -= size;
return (fifo->value >> fifo->bits) & ((1LL << size) - 1);
}
static uint
peek_bits( struct bit_fifo *fifo, uint size )
{
return (fifo->value >> (fifo->bits - size)) & ((1LL << size) - 1);
}
static void
add_char( char **p, uint chr )
{
*((*p)++) = (char)chr;
}
static uchar
eat_char( const char **p )
{
return (uchar)*((*p)++);
}
static uint
read_as_utf8( const char **utf8_buf_p )
{
uchar chr = eat_char( utf8_buf_p );
if (chr < 0x80)
return chr;
if ((chr & 0xf8) == 0xf0) {
uchar chr2 = eat_char( utf8_buf_p );
if ((chr2 & 0xc0) != 0x80)
return ~0;
uchar chr3 = eat_char( utf8_buf_p );
if ((chr3 & 0xc0) != 0x80)
return ~0;
uchar chr4 = eat_char( utf8_buf_p );
if ((chr4 & 0xc0) != 0x80)
return ~0;
return ((chr & 0x7) << 18) |
((chr2 & 0x3f) << 12) |
((chr3 & 0x3f) << 6) |
(chr4 & 0x3f);
}
if ((chr & 0xf0) == 0xe0) {
uchar chr2 = eat_char( utf8_buf_p );
if ((chr2 & 0xc0) != 0x80)
return ~0;
uchar chr3 = eat_char( utf8_buf_p );
if ((chr3 & 0xc0) != 0x80)
return ~0;
return ((chr & 0xf) << 12) |
((chr2 & 0x3f) << 6) |
(chr3 & 0x3f);
}
if ((chr & 0xe0) == 0xc0) {
uchar chr2 = eat_char( utf8_buf_p );
if ((chr2 & 0xc0) != 0x80)
return ~0;
return (chr & 0x1f) << 6 |
(chr2 & 0x3f);
}
return ~0;
}
static int
needs_encoding( uint chr )
{
return chr && (chr <= 0x1f || chr >= 0x7f);
}
static uint
utf16_encode( uint chr )
{
chr -= 0x10000;
return (((chr >> 10) + 0xd800) << 16) | ((chr & 0x3ff) + 0xdc00);
}
static uchar
b64_encode( uint chr )
{
assert( chr <= 0x3f );
return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"[chr];
}
char *
imap_utf8_to_utf7( const char *buf )
{
// Size requirements:
// - pass-through: l, 1 => 1
// - all "&": l * 2, 1 => 2
// - 7-bit: (l * 2 * 4 + 2) / 3 + 2, ~ l * 2.7, 1 => 5
// - 3-octet: (l / 3 * 2 * 4 + 2) / 3 + 2, ~ l * 0.9, 3 => 5
// - 4-octet: (l / 4 * 2 * 2 * 4 + 2) / 3 + 2, ~ l * 1.3, 4 => 8
// => worst case: "&" and 7-bit alternating: l * 3.5, 2 => 7
int outsz = strlen( buf ) * 7 / 2 + 3;
char *result = nfmalloc( outsz );
char *outp = result;
struct bit_fifo fifo = { 0, 0 };
int encoding = 0;
uint chr;
do {
chr = read_as_utf8( &buf );
if (chr == ~0U) {
dbg( "Error: invalid UTF-8 string\n" );
free( result );
return NULL;
}
if (needs_encoding( chr )) {
if (!encoding) {
add_char( &outp, '&' );
encoding = 1;
}
if (chr <= 0xffff)
add_bits( &fifo, chr, 16 );
else
add_bits( &fifo, utf16_encode( chr ), 32 );
while (fifo.bits >= 6)
add_char( &outp, b64_encode( eat_bits( &fifo, 6 ) ) );
} else {
if (encoding) {
if (fifo.bits) {
uint trailing_bits = 6 - fifo.bits;
uchar trail = b64_encode( eat_bits( &fifo, fifo.bits ) << trailing_bits );
add_char( &outp, trail );
}
add_char( &outp, '-' );
encoding = 0;
}
add_char( &outp, chr );
if (chr == '&')
add_char( &outp, '-' );
}
} while (chr);
assert( (int)(outp - result) <= outsz );
return result;
}
static void
write_as_utf8( char **outp, uint chr )
{
if (chr <= 0x7f) {
add_char( outp, chr );
} else if (chr <= 0x7ff) {
add_char( outp, (chr >> 6) | 0xc0 );
add_char( outp, (chr & 0x3f) | 0x80 );
} else if (chr <= 0xffff) {
add_char( outp, (chr >> 12) | 0xe0 );
add_char( outp, ((chr >> 6) & 0x3f) | 0x80 );
add_char( outp, (chr & 0x3f) | 0x80 );
} else {
assert( chr <= 0xfffff );
add_char( outp, (chr >> 18) | 0xf0 );
add_char( outp, ((chr >> 12) & 0x3f) | 0x80 );
add_char( outp, ((chr >> 6) & 0x3f) | 0x80 );
add_char( outp, (chr & 0x3f) | 0x80 );
}
}
static int
need_another_16bit( uint bits )
{
return (bits & 0xfc00) == 0xd800;
}
static uint
utf16_decode( uint subject )
{
return 0x10000 + (((subject >> 16) - 0xd800) << 10) + ((subject & 0xffff) - 0xdc00);
}
static uint
b64_decode( uchar chr )
{
static uint lu[128] = {
~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0,
~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0,
~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, ~0, 62, 63, ~0, ~0, ~0,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, ~0, ~0, ~0, ~0, ~0, ~0,
~0, 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, ~0, ~0, ~0, ~0, ~0,
~0, 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, ~0, ~0, ~0, ~0, ~0,
};
return lu[chr];
}
int
imap_utf7_to_utf8( const char *buf, int bufl, char *outbuf )
{
// Size requirements:
// - pass-through: l (shortest worst case)
// - all "&": l / 2, 2 => 1, * .5
// - 7-bit: ((l - 2) * 3 + 1) / 4 / 2, ~ l * .38, 5 => 1, * .2
// - 3-octet: ((l - 2) * 3 + 1) / 4 / 2 * 3, ~ l * 1.13, 5 => 3, * .6 (generic worst case)
// - 4-octet: ((l - 2) * 3 + 1) / 4 / 2 / 2 * 4, ~ l * .75, 8 => 4, * .5
// => reserve bufl * 9 / 8
char *outp = outbuf;
struct bit_fifo fifo = { 0, 0 };
const char *bufe = buf + bufl;
while (buf != bufe) {
uchar chr = *buf++;
if (chr != '&') {
if (chr & 0x80) {
dbg( "Error: 8-bit char %x\n", chr );
return -1;
}
add_char( &outp, chr );
continue;
}
if (buf == bufe) {
dbg( "Error: unterminated shift sequence\n" );
return -1;
}
chr = *buf++;
if (chr == '-') {
add_char( &outp, '&' );
continue;
}
fifo.bits = 0;
do {
if (chr & 0x80) {
dbg( "Error: 8-bit char %x\n", chr );
return -1;
}
uint bits = b64_decode( chr );
if (bits == ~0U) {
dbg( "Error: char %x outside alphabet\n", chr );
return -1;
}
add_bits( &fifo, bits, 6 );
if (fifo.bits >= 16) {
if (need_another_16bit( peek_bits( &fifo, 16 ) )) {
if (fifo.bits >= 32) {
uint utf16 = eat_bits( &fifo, 32 );
if ((utf16 & 0xfc00) != 0xdc00) {
dbg( "Error: unpaired UTF-16 surrogate\n" );
return -1;
}
write_as_utf8( &outp, utf16_decode( utf16 ) );
}
} else {
write_as_utf8( &outp, eat_bits( &fifo, 16 ) );
}
}
if (buf == bufe) {
dbg( "Error: unterminated shift sequence\n" );
return -1;
}
chr = *buf++;
} while (chr != '-');
if (fifo.bits > 6) {
dbg( "Error: incomplete code point\n" );
return -1;
}
}
return (int)(outp - outbuf);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,188 +0,0 @@
// SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
//
// mbsync - mailbox synchronizer
//
#include "main_p.h"
typedef struct store_ent {
struct store_ent *next;
store_conf_t *conf;
} store_ent_t;
typedef struct {
core_vars_t *cvars;
store_conf_t *store;
driver_t *drv;
store_t *ctx;
store_ent_t *storeptr;
int cben, done;
} list_vars_t;
static store_ent_t *
add_store( store_ent_t ***storeapp, store_conf_t *store )
{
store_ent_t *se = nfzalloc( sizeof(*se) );
se->conf = store;
**storeapp = se;
*storeapp = &se->next;
return se;
}
static void do_list_stores( list_vars_t *lvars );
static void list_next_store( list_vars_t *lvars );
void
list_stores( core_vars_t *cvars, char **argv )
{
list_vars_t lvars[1];
store_ent_t *strs = NULL, **strapp = &strs;
store_conf_t *store;
memset( lvars, 0, sizeof(*lvars) );
lvars->cvars = cvars;
if (!stores) {
fputs( "No stores defined.\n", stderr );
cvars->ret = 1;
return;
}
if (!*argv) { // Implicit --all
for (store = stores; store; store = store->next)
add_store( &strapp, store );
} else {
for (; *argv; argv++) {
for (store = stores; store; store = store->next) {
if (!strcmp( store->name, *argv )) {
add_store( &strapp, store );
goto gotstr;
}
}
error( "No store named '%s' defined.\n", *argv );
cvars->ret = 1;
gotstr: ;
}
}
if (cvars->ret)
return;
lvars->storeptr = strs;
do_list_stores( lvars );
main_loop();
}
static void
list_store_bad( void *aux )
{
list_vars_t *lvars = (list_vars_t *)aux;
lvars->drv->cancel_store( lvars->ctx );
lvars->cvars->ret = 1;
list_next_store( lvars );
}
static void
advance_store( list_vars_t *lvars )
{
store_ent_t *nstr = lvars->storeptr->next;
free( lvars->storeptr );
lvars->storeptr = nstr;
}
static void list_store_connected( int sts, void *aux );
static void
do_list_stores( list_vars_t *lvars )
{
while (lvars->storeptr) {
lvars->store = lvars->storeptr->conf;
lvars->drv = lvars->store->driver;
int st = lvars->drv->get_fail_state( lvars->store );
if (st != FAIL_TEMP) {
info( "Skipping %sfailed store %s.\n",
(st == FAIL_WAIT) ? "temporarily " : "", lvars->store->name );
lvars->cvars->ret = 1;
goto next;
}
uint dcaps = lvars->drv->get_caps( NULL );
store_t *ctx = lvars->drv->alloc_store( lvars->store, "" );
if ((DFlags & DEBUG_DRV) || ((DFlags & FORCEASYNC(F)) && !(dcaps & DRV_ASYNC))) {
lvars->drv = &proxy_driver;
ctx = proxy_alloc_store( ctx, "", DFlags & FORCEASYNC(F) );
}
lvars->ctx = ctx;
lvars->drv->set_callbacks( ctx, NULL, list_store_bad, lvars );
info( "Opening store %s...\n", lvars->store->name );
lvars->cben = lvars->done = 0;
lvars->drv->connect_store( lvars->ctx, list_store_connected, lvars );
if (!lvars->done) {
lvars->cben = 1;
return;
}
next:
advance_store( lvars );
}
cleanup_mainloop();
}
static void
list_next_store( list_vars_t *lvars )
{
if (lvars->cben) {
advance_store( lvars );
do_list_stores( lvars );
}
}
static void
list_done_store( list_vars_t *lvars )
{
lvars->done = 1;
lvars->drv->free_store( lvars->ctx );
list_next_store( lvars );
}
static void list_store_listed( int sts, string_list_t *boxes, void *aux );
static void
list_store_connected( int sts, void *aux )
{
list_vars_t *lvars = (list_vars_t *)aux;
switch (sts) {
case DRV_CANCELED:
return;
case DRV_OK:
lvars->drv->list_store( lvars->ctx, LIST_INBOX | LIST_PATH_MAYBE, list_store_listed, lvars );
break;
default:
lvars->cvars->ret = 1;
list_done_store( lvars );
break;
}
}
static void
list_store_listed( int sts, string_list_t *boxes, void *aux )
{
list_vars_t *lvars = (list_vars_t *)aux;
string_list_t *box;
switch (sts) {
case DRV_CANCELED:
return;
case DRV_OK:
printf( "===== %s:\n", lvars->ctx->conf->name );
for (box = boxes; box; box = box->next)
puts( box->string );
break;
default:
lvars->cvars->ret = 1;
break;
}
list_done_store( lvars );
}

View File

@ -1,26 +0,0 @@
// SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
//
// mbsync - mailbox synchronizer
//
#ifndef MAIN_P_H
#define MAIN_P_H
#define DEBUG_FLAG DEBUG_MAIN
#include "sync.h"
typedef struct {
int ret;
int all;
int list;
int list_stores;
int ops[2];
} core_vars_t;
void sync_chans( core_vars_t *cvars, char **argv );
void list_stores( core_vars_t *cvars, char **argv );
void cleanup_mainloop( void );
#endif

View File

@ -1,795 +0,0 @@
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
//
// mbsync - mailbox synchronizer
//
#include "main_p.h"
#define nz(a, b) ((a) ? (a) : (b))
static int ops_any[2], trash_any[2], expunge_any[2];
static int chans_total, chans_done;
static int boxes_total, boxes_done;
static int stats_steps;
static int64_t stats_stamp;
static wakeup_t stats_wakeup;
static void
print_stats( void )
{
char buf[3][80];
char *cs;
static int cols = -1;
if (cols < 0 && (!(cs = getenv( "COLUMNS" )) || !(cols = atoi( cs ))))
cols = 80;
int ll = sprintf( buf[2], "C: %d/%d B: %d/%d", chans_done, chans_total, boxes_done, boxes_total );
int cls = (cols - ll - 10) / 2;
for (int t = 0; t < 2; t++) {
int l = sprintf( buf[t], "+%d/%d *%d/%d #%d/%d -%d/%d",
new_done[t], new_total[t],
flags_done[t], flags_total[t],
trash_done[t], trash_total[t],
expunge_done[t], expunge_total[t] );
if (l > cls)
buf[t][cls - 1] = '~';
}
progress( "\r%s F: %.*s N: %.*s", buf[2], cls, buf[0], cls, buf[1] );
}
static void
stats_timeout( void *aux ATTR_UNUSED )
{
if (stats_steps != -1) {
stats_steps = -1;
print_stats();
}
conf_wakeup( &stats_wakeup, 200 );
}
void
stats( void )
{
if (!(DFlags & PROGRESS))
return;
// If the main loop appears to be running, skip the sync path.
if (stats_steps < 0) {
stats_steps = -2;
return;
}
// Rate-limit the (somewhat) expensive timer queries.
if (++stats_steps < 10)
return;
stats_steps = 0;
int64_t now = get_now();
if (now < stats_stamp + 300)
return;
stats_stamp = now;
print_stats();
}
static void
summary( void )
{
if (Verbosity < TERSE)
return;
if (!boxes_done)
return; // Shut up if we errored out early.
printf( "Processed %d box(es) in %d channel(s)", boxes_done, chans_done );
for (int t = 2; --t >= 0; ) {
if (ops_any[t])
printf( (DFlags & DRYRUN) ?
",\nwould %s %d new message(s) and %d flag update(s)" :
",\n%sed %d new message(s) and %d flag update(s)",
str_hl[t], new_done[t], flags_done[t] );
if (trash_any[t])
printf( (DFlags & DRYRUN) ?
",\nwould move %d %s message(s) to trash" :
",\nmoved %d %s message(s) to trash",
trash_done[t], str_fn[t] );
if (expunge_any[t])
printf( (DFlags & DRYRUN) ?
",\nwould expunge %d message(s) from %s" :
",\nexpunged %d message(s) from %s",
expunge_done[t], str_fn[t] );
}
puts( "." );
}
static int
matches( const char *t, const char *p )
{
for (;;) {
if (!*p)
return !*t;
if (*p == '*') {
p++;
do {
if (matches( t, p ))
return 1;
} while (*t++);
return 0;
} else if (*p == '%') {
p++;
do {
if (*t == '/')
return 0;
if (matches( t, p ))
return 1;
} while (*t++);
return 0;
} else {
if (*p != *t)
return 0;
p++, t++;
}
}
}
static int
is_inbox( const char *name )
{
return starts_with( name, -1, "INBOX", 5 ) && (!name[5] || name[5] == '/');
}
static int
cmp_box_names( const void *a, const void *b )
{
const char *as = *(const char * const *)a;
const char *bs = *(const char * const *)b;
int ai = is_inbox( as );
int bi = is_inbox( bs );
int di = bi - ai;
if (di)
return di;
return strcmp( as, bs );
}
static char **
filter_boxes( string_list_t *boxes, const char *prefix, string_list_t *patterns )
{
char **boxarr = NULL;
uint num = 0, rnum = 0;
uint pfxl = prefix ? strlen( prefix ) : 0;
for (; boxes; boxes = boxes->next) {
if (!starts_with( boxes->string, -1, prefix, pfxl ))
continue;
uint fnot = 1, not;
for (string_list_t *cpat = patterns; cpat; cpat = cpat->next) {
const char *ps = cpat->string;
if (*ps == '!') {
ps++;
not = 1;
} else {
not = 0;
}
if (matches( boxes->string + pfxl, ps )) {
fnot = not;
break;
}
}
if (!fnot) {
if (num + 1 >= rnum)
boxarr = nfrealloc( boxarr, (rnum = (rnum + 10) * 2) * sizeof(*boxarr) );
boxarr[num++] = nfstrdup( boxes->string + pfxl );
boxarr[num] = NULL;
}
}
qsort( boxarr, num, sizeof(*boxarr), cmp_box_names );
return boxarr;
}
static void
merge_actions( channel_conf_t *chan, int ops[], int have, int mask, int def )
{
if (ops[F] & have) {
chan->ops[F] &= ~mask;
chan->ops[F] |= ops[F] & mask;
chan->ops[N] &= ~mask;
chan->ops[N] |= ops[N] & mask;
} else if (!(chan->ops[F] & have)) {
if (global_conf.ops[F] & have) {
chan->ops[F] |= global_conf.ops[F] & mask;
chan->ops[N] |= global_conf.ops[N] & mask;
} else {
chan->ops[F] |= def;
chan->ops[N] |= def;
}
}
}
typedef struct box_ent {
struct box_ent *next;
char *name;
int present[2];
} box_ent_t;
typedef struct chan_ent {
struct chan_ent *next;
channel_conf_t *conf;
box_ent_t *boxes;
int boxlist;
} chan_ent_t;
static chan_ent_t *
add_channel( chan_ent_t ***chanapp, channel_conf_t *chan, int ops[] )
{
chan_ent_t *ce = nfzalloc( sizeof(*ce) );
ce->conf = chan;
merge_actions( chan, ops, XOP_HAVE_TYPE, OP_MASK_TYPE, OP_DFLT_TYPE );
merge_actions( chan, ops, XOP_HAVE_CREATE, OP_CREATE, 0 );
merge_actions( chan, ops, XOP_HAVE_REMOVE, OP_REMOVE, 0 );
merge_actions( chan, ops, XOP_HAVE_EXPUNGE, OP_EXPUNGE, 0 );
merge_actions( chan, ops, XOP_HAVE_EXPUNGE_SOLO, OP_EXPUNGE_SOLO, 0 );
debug( "channel ops (%s):\n far: %s\n near: %s\n",
chan->name, fmt_ops( ops[F] ).str, fmt_ops( ops[N] ).str );
for (int t = 0; t < 2; t++) {
if (!(~ops[t] & (OP_EXPUNGE | OP_EXPUNGE_SOLO))) {
error( "Specified both Expunge and ExpungeSolo for %s of Channel '%s'.\n",
str_fn[t], chan->stores[t]->name );
free( ce );
return NULL;
}
if (chan->ops[t] & OP_MASK_TYPE)
ops_any[t] = 1;
if (chan->ops[t] & (OP_EXPUNGE | OP_EXPUNGE_SOLO)) {
expunge_any[t] = 1;
if (chan->stores[t]->trash ||
(chan->stores[t^1]->trash && chan->stores[t^1]->trash_remote_new))
trash_any[t] = 1;
}
}
**chanapp = ce;
*chanapp = &ce->next;
chans_total++;
return ce;
}
static chan_ent_t *
add_named_channel( chan_ent_t ***chanapp, char *channame, int ops[] )
{
box_ent_t *boxes = NULL, **mboxapp = &boxes, *mbox;
int boxlist = 0;
char *boxp;
if ((boxp = strchr( channame, ':' )))
*boxp++ = 0;
channel_conf_t *chan;
for (chan = channels; chan; chan = chan->next)
if (!strcmp( chan->name, channame ))
goto gotchan;
error( "No channel or group named '%s' defined.\n", channame );
return NULL;
gotchan:
if (boxp) {
if (!chan->patterns) {
error( "Cannot override mailbox in channel '%s' - no Patterns.\n", channame );
return NULL;
}
boxlist = 1;
do {
char *nboxp = strpbrk( boxp, ",\n" );
size_t boxl;
if (nboxp) {
boxl = (size_t)(nboxp - boxp);
*nboxp++ = 0;
} else {
boxl = strlen( boxp );
}
mbox = nfmalloc( sizeof(*mbox) );
if (boxl)
mbox->name = nfstrndup( boxp, boxl );
else
mbox->name = nfstrndup( "INBOX", 5 );
mbox->present[F] = mbox->present[N] = BOX_POSSIBLE;
mbox->next = NULL;
*mboxapp = mbox;
mboxapp = &mbox->next;
boxes_total++;
boxp = nboxp;
} while (boxp);
} else {
if (!chan->patterns)
boxes_total++;
}
chan_ent_t *ce = add_channel( chanapp, chan, ops );
if (!ce)
return NULL;
ce->boxes = boxes;
ce->boxlist = boxlist;
return ce;
}
typedef struct {
int t[2];
core_vars_t *cvars;
channel_conf_t *chan;
driver_t *drv[2];
store_t *ctx[2];
chan_ent_t *chanptr;
box_ent_t *boxptr;
string_list_t *boxes[2];
char *names[2];
int state[2];
int chan_cben, fnlz_cben, box_cben, box_done;
} main_vars_t;
#define AUX &mvars->t[t]
#define MVARS(aux) \
int t = *(int *)aux; \
main_vars_t *mvars = (main_vars_t *)(((char *)(&((int *)aux)[-t])) - offsetof(main_vars_t, t));
static void do_sync_chans( main_vars_t *lvars );
void
sync_chans( core_vars_t *cvars, char **argv )
{
main_vars_t mvars[1];
chan_ent_t *chans = NULL, **chanapp = &chans;
memset( mvars, 0, sizeof(*mvars) );
mvars->t[1] = 1;
mvars->cvars = cvars;
if (!channels) {
fputs( "No channels defined. Try 'man " EXE "'\n", stderr );
cvars->ret = 1;
return;
}
if (cvars->all) {
for (channel_conf_t *chan = channels; chan; chan = chan->next) {
if (!add_channel( &chanapp, chan, cvars->ops ))
cvars->ret = 1;
if (!chan->patterns)
boxes_total++;
}
} else {
for (; *argv; argv++) {
for (group_conf_t *group = groups; group; group = group->next) {
if (!strcmp( group->name, *argv )) {
for (string_list_t *channame = group->channels; channame; channame = channame->next)
if (!add_named_channel( &chanapp, channame->string, cvars->ops ))
cvars->ret = 1;
goto gotgrp;
}
}
if (!add_named_channel( &chanapp, *argv, cvars->ops ))
cvars->ret = 1;
gotgrp: ;
}
}
if (cvars->ret)
return;
if (!chans) {
fputs( "No channel specified. Try '" EXE " -h'\n", stderr );
cvars->ret = 1;
return;
}
mvars->chanptr = chans;
if (!cvars->list && (DFlags & PROGRESS)) {
init_wakeup( &stats_wakeup, stats_timeout, NULL );
stats_timeout( NULL );
}
do_sync_chans( mvars );
main_loop();
if (!cvars->list) {
flushn();
summary();
if (DFlags & EXT_EXIT) {
for (int t = 0; t < 2; t++)
if (new_done[t] || flags_done[t] || trash_done[t] || expunge_done[t])
cvars->ret |= 32 << t;
}
}
}
enum {
ST_FRESH,
ST_CONNECTED,
ST_OPEN,
ST_CANCELING,
ST_CLOSED,
};
static int
check_cancel( main_vars_t *mvars )
{
return mvars->state[F] >= ST_CANCELING || mvars->state[N] >= ST_CANCELING;
}
static void store_connected( int sts, void *aux );
static void store_listed( int sts, string_list_t *boxes, void *aux );
static void sync_opened( main_vars_t *mvars, int t );
static void do_sync_boxes( main_vars_t *lvars );
static void done_sync_dyn( int sts, void *aux );
static void done_sync( int sts, void *aux );
static void finalize_sync( main_vars_t *mvars );
static void
store_bad( void *aux )
{
MVARS(aux)
mvars->drv[t]->cancel_store( mvars->ctx[t] );
mvars->state[t] = ST_CLOSED;
mvars->cvars->ret = 1;
finalize_sync( mvars );
}
static void
advance_chan( main_vars_t *mvars )
{
if (!mvars->cvars->list) {
chans_done++;
stats();
}
chan_ent_t *nchan = mvars->chanptr->next;
free( mvars->chanptr );
mvars->chanptr = nchan;
}
static void
do_sync_chans( main_vars_t *mvars )
{
while (mvars->chanptr) {
stats_steps = 0; // Determine main loop use afresh
mvars->chan = mvars->chanptr->conf;
info( "Channel %s\n", mvars->chan->name );
for (int t = 0; t < 2; t++) {
int st = mvars->chan->stores[t]->driver->get_fail_state( mvars->chan->stores[t] );
if (st != FAIL_TEMP) {
info( "Skipping due to %sfailed %s store %s.\n",
(st == FAIL_WAIT) ? "temporarily " : "", str_fn[t], mvars->chan->stores[t]->name );
goto next;
}
}
uint dcaps[2];
for (int t = 0; t < 2; t++) {
mvars->drv[t] = mvars->chan->stores[t]->driver;
dcaps[t] = mvars->drv[t]->get_caps( NULL );
}
const char *labels[2];
if ((DFlags & DEBUG_DRV) || (dcaps[F] & dcaps[N] & DRV_VERBOSE))
labels[F] = "F: ", labels[N] = "N: ";
else
labels[F] = labels[N] = "";
for (int t = 0; t < 2; t++) {
store_t *ctx = mvars->drv[t]->alloc_store( mvars->chan->stores[t], labels[t] );
if ((DFlags & DEBUG_DRV) || ((DFlags & FORCEASYNC(t)) && !(dcaps[t] & DRV_ASYNC))) {
mvars->drv[t] = &proxy_driver;
ctx = proxy_alloc_store( ctx, labels[t], DFlags & FORCEASYNC(t) );
}
mvars->ctx[t] = ctx;
mvars->drv[t]->set_callbacks( ctx, NULL, store_bad, AUX );
mvars->state[t] = ST_FRESH;
}
mvars->chan_cben = 0;
for (int t = 0; ; t++) {
info( "Opening %s store %s...\n", str_fn[t], mvars->chan->stores[t]->name );
mvars->drv[t]->connect_store( mvars->ctx[t], store_connected, AUX );
if (t || check_cancel( mvars ))
break;
}
if (mvars->state[F] != ST_CLOSED || mvars->state[N] != ST_CLOSED) {
mvars->chan_cben = 1;
return;
}
next:
advance_chan( mvars );
}
cleanup_mainloop();
if (!mvars->cvars->list && (DFlags & PROGRESS))
wipe_wakeup( &stats_wakeup );
}
static void
sync_next_chan( main_vars_t *mvars )
{
if (mvars->chan_cben) {
advance_chan( mvars );
do_sync_chans( mvars );
}
}
static void
store_connected( int sts, void *aux )
{
MVARS(aux)
switch (sts) {
case DRV_CANCELED:
return;
case DRV_OK:
mvars->state[t] = ST_CONNECTED;
if (check_cancel( mvars ))
break;
if (!mvars->chanptr->boxlist && mvars->chan->patterns) {
int cflags = 0;
for (string_list_t *cpat = mvars->chan->patterns; cpat; cpat = cpat->next) {
const char *pat = cpat->string;
if (*pat != '!') {
char buf[8];
int bufl = snprintf( buf, sizeof(buf), "%s%s", nz( mvars->chan->boxes[t], "" ), pat );
int flags = 0;
// Partial matches like "INB*" or even "*" are not considered,
// except implicity when the INBOX lives under Path.
if (starts_with( buf, bufl, "INBOX", 5 )) {
char c = buf[5];
if (!c) {
// User really wants the INBOX.
flags |= LIST_INBOX;
} else if (c == '/') {
// Flattened sub-folders of INBOX actually end up in Path.
if (mvars->ctx[t]->conf->flat_delim[0])
flags |= LIST_PATH;
else
flags |= LIST_INBOX;
} else if (c == '*' || c == '%') {
// It can be both INBOX and Path, but don't require Path to be configured.
flags |= LIST_INBOX | LIST_PATH_MAYBE;
} else {
// It's definitely not the INBOX.
flags |= LIST_PATH;
}
} else {
flags |= LIST_PATH;
}
debug( "pattern '%s' (effective '%s'): %sPath, %sINBOX\n",
pat, buf, (flags & LIST_PATH) ? "" : "no ", (flags & LIST_INBOX) ? "" : "no ");
cflags |= flags;
}
}
mvars->drv[t]->list_store( mvars->ctx[t], cflags, store_listed, AUX );
return;
}
sync_opened( mvars, t );
return;
default:
mvars->cvars->ret = 1;
break;
}
finalize_sync( mvars );
}
static void
store_listed( int sts, string_list_t *boxes, void *aux )
{
MVARS(aux)
int fail = 0;
switch (sts) {
case DRV_CANCELED:
return;
case DRV_OK:
if (check_cancel( mvars ))
break;
for (string_list_t *box = boxes; box; box = box->next) {
if (mvars->ctx[t]->conf->flat_delim[0]) {
string_list_t *nbox;
if (map_name( box->string, -1, (char **)&nbox, offsetof(string_list_t, string), mvars->ctx[t]->conf->flat_delim, "/" ) < 0) {
error( "Error: flattened mailbox name '%s' contains canonical hierarchy delimiter\n", box->string );
fail = 1;
} else {
nbox->next = mvars->boxes[t];
mvars->boxes[t] = nbox;
}
} else {
add_string_list( &mvars->boxes[t], box->string );
}
}
if (fail) {
mvars->cvars->ret = 1;
break;
}
if (mvars->ctx[t]->conf->map_inbox) {
debug( "adding mapped inbox to %s store: %s\n", str_fn[t], mvars->ctx[t]->conf->map_inbox );
add_string_list( &mvars->boxes[t], mvars->ctx[t]->conf->map_inbox );
}
sync_opened( mvars, t );
return;
default:
mvars->cvars->ret = 1;
break;
}
finalize_sync( mvars );
}
static void
sync_opened( main_vars_t *mvars, int t )
{
mvars->state[t] = ST_OPEN;
if (mvars->state[t^1] != ST_OPEN)
return;
if (!mvars->chanptr->boxlist && mvars->chan->patterns) {
mvars->chanptr->boxlist = 2;
char **boxes[2];
boxes[F] = filter_boxes( mvars->boxes[F], mvars->chan->boxes[F], mvars->chan->patterns );
boxes[N] = filter_boxes( mvars->boxes[N], mvars->chan->boxes[N], mvars->chan->patterns );
box_ent_t **mboxapp = &mvars->chanptr->boxes;
for (int mb = 0, sb = 0; ; ) {
char *fname = boxes[F] ? boxes[F][mb] : NULL;
char *nname = boxes[N] ? boxes[N][sb] : NULL;
if (!fname && !nname)
break;
box_ent_t *mbox = nfmalloc( sizeof(*mbox) );
int cmp;
if (!(cmp = !fname - !nname) && !(cmp = cmp_box_names( &fname, &nname ))) {
mbox->name = fname;
free( nname );
mbox->present[F] = mbox->present[N] = BOX_PRESENT;
mb++;
sb++;
} else if (cmp < 0) {
mbox->name = fname;
mbox->present[F] = BOX_PRESENT;
mbox->present[N] = (!mb && !strcmp( mbox->name, "INBOX" )) ? BOX_PRESENT : BOX_ABSENT;
mb++;
} else {
mbox->name = nname;
mbox->present[F] = (!sb && !strcmp( mbox->name, "INBOX" )) ? BOX_PRESENT : BOX_ABSENT;
mbox->present[N] = BOX_PRESENT;
sb++;
}
mbox->next = NULL;
*mboxapp = mbox;
mboxapp = &mbox->next;
boxes_total++;
}
free( boxes[F] );
free( boxes[N] );
if (!mvars->cvars->list)
stats();
}
mvars->boxptr = mvars->chanptr->boxes;
if (mvars->cvars->list && chans_total > 1)
printf( "%s:\n", mvars->chan->name );
mvars->box_done = 0;
do_sync_boxes( mvars );
}
static void
do_sync_boxes( main_vars_t *mvars )
{
mvars->box_cben = 0;
while (mvars->state[F] == ST_OPEN && mvars->state[N] == ST_OPEN) {
if (mvars->chanptr->boxlist) {
box_ent_t *mbox = mvars->boxptr;
if (!mbox)
break;
mvars->boxptr = mbox->next;
mvars->box_done = 0;
if (mvars->chan->boxes[F] || mvars->chan->boxes[N]) {
const char *fpfx = nz( mvars->chan->boxes[F], "" );
const char *npfx = nz( mvars->chan->boxes[N], "" );
if (mvars->cvars->list) {
printf( "%s%s <=> %s%s\n", fpfx, mbox->name, npfx, mbox->name );
continue;
}
nfasprintf( &mvars->names[F], "%s%s", fpfx, mbox->name );
nfasprintf( &mvars->names[N], "%s%s", npfx, mbox->name );
sync_boxes( mvars->ctx, (const char * const *)mvars->names, mbox->present, mvars->chan, done_sync_dyn, mvars );
} else {
if (mvars->cvars->list) {
puts( mbox->name );
continue;
}
mvars->names[F] = mvars->names[N] = mbox->name;
sync_boxes( mvars->ctx, (const char * const *)mvars->names, mbox->present, mvars->chan, done_sync, mvars );
}
} else {
if (mvars->cvars->list) {
printf( "%s <=> %s\n", nz( mvars->chan->boxes[F], "INBOX" ), nz( mvars->chan->boxes[N], "INBOX" ) );
break;
}
if (mvars->box_done)
break;
int present[] = { BOX_POSSIBLE, BOX_POSSIBLE };
sync_boxes( mvars->ctx, mvars->chan->boxes, present, mvars->chan, done_sync, mvars );
}
if (!mvars->box_done) {
mvars->box_cben = 1;
return;
}
}
finalize_sync( mvars );
}
static void
done_sync_dyn( int sts, void *aux )
{
main_vars_t *mvars = (main_vars_t *)aux;
free( mvars->names[F] );
free( mvars->names[N] );
done_sync( sts, aux );
}
static void
done_sync( int sts, void *aux )
{
main_vars_t *mvars = (main_vars_t *)aux;
boxes_done++;
stats();
if (sts) {
mvars->cvars->ret = 1;
if (sts & SYNC_BAD(F))
mvars->state[F] = ST_CLOSED;
if (sts & SYNC_BAD(N))
mvars->state[N] = ST_CLOSED;
}
mvars->box_done = 1;
if (mvars->box_cben)
do_sync_boxes( mvars );
}
static void sync_finalized( void *aux );
static void
finalize_sync( main_vars_t *mvars )
{
if (mvars->chanptr->boxlist) {
box_ent_t *mbox, *nmbox;
for (nmbox = mvars->chanptr->boxes; (mbox = nmbox); ) {
nmbox = mbox->next;
free( mbox->name );
free( mbox );
}
mvars->chanptr->boxes = NULL;
mvars->chanptr->boxlist = 0;
}
mvars->fnlz_cben = 0;
for (int t = 0; t < 2; t++) {
free_string_list( mvars->boxes[t] );
mvars->boxes[t] = NULL;
if (mvars->state[t] == ST_FRESH || mvars->state[t] == ST_OPEN) {
mvars->drv[t]->free_store( mvars->ctx[t] );
mvars->state[t] = ST_CLOSED;
} else if (mvars->state[t] == ST_CONNECTED) {
mvars->state[t] = ST_CANCELING;
mvars->drv[t]->cancel_cmds( mvars->ctx[t], sync_finalized, AUX );
}
}
if (mvars->state[F] != ST_CLOSED || mvars->state[N] != ST_CLOSED) {
mvars->fnlz_cben = 1;
return;
}
sync_next_chan( mvars );
}
static void
sync_finalized( void *aux )
{
MVARS(aux)
mvars->drv[t]->free_store( mvars->ctx[t] );
mvars->state[t] = ST_CLOSED;
if (mvars->state[t^1] != ST_CLOSED)
return;
if (mvars->fnlz_cben)
sync_next_chan( mvars );
}

View File

@ -1,18 +1,31 @@
.\" SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
.\" SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
.\" SPDX-License-Identifier: GPL-2.0-or-later
.\"
.\" mbsync - mailbox synchronizer
.\" Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
.\" Copyright (C) 2002-2004,2011-2015 Oswald Buddenhagen <ossi@users.sf.net>
.\" Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu>
.\"
.\" This program is free software; you can redistribute it and/or modify
.\" it under the terms of the GNU General Public License as published by
.\" the Free Software Foundation; either version 2 of the License, or
.\" (at your option) any later version.
.\"
.\" This program is distributed in the hope that it will be useful,
.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
.\" GNU General Public License for more details.
.\"
.\" You should have received a copy of the GNU General Public License
.\" along with this program. If not, see <http://www.gnu.org/licenses/>.
.\"
.\" As a special exception, mbsync may be linked with the OpenSSL library,
.\" despite that library's more restrictive license.
.
.TH mbsync 1 "2022 Jun 16"
.TH mbsync 1 "2015 Mar 22"
.
.SH NAME
mbsync - synchronize IMAP4 and Maildir mailboxes
.
.SH SYNOPSIS
\fBmbsync\fR [\fIoptions\fR ...] {{\fIchannel\fR[\fB:\fIbox\fR[{\fB,\fR|\fB\\n\fR}...]]|\fIgroup\fR} ...|\fB-a\fR}
.br
\fBmbsync\fR --list-stores [\fIoptions\fR ...] [\fIstore\fR} ...]
.
.SH DESCRIPTION
\fBmbsync\fR is a command line application which synchronizes mailboxes;
@ -34,25 +47,16 @@ Multiple replicas of each mailbox can be maintained.
.TP
\fB-c\fR, \fB--config\fR \fIfile\fR
Read configuration from \fIfile\fR.
By default, the configuration is read from $XDG_CONFIG_HOME/isyncrc, and
if that does not exist, ~/.mbsyncrc is tried in turn.
$XDG_CONFIG_HOME defaults to ~/.config if not set.
By default, the configuration is read from ~/.mbsyncrc.
.TP
\fB-a\fR, \fB--all\fR
Select all configured Channels. Any Channel/Group specifications on the
command line are ignored.
Select all configured channels. Any channel/group specifications on the command
line are ignored.
.TP
\fB-l\fR, \fB--list\fR
Don't synchronize anything, but list all mailboxes in the selected Channels
Don't synchronize anything, but list all mailboxes in the selected channels
and exit.
.TP
\fB-ls\fR, \fB--list-stores\fR
Don't synchronize anything, but list all mailboxes in the selected Stores
and exit.
If no Stores are specified, all configured ones are listed.
These are raw Store contents, not filtered by any Channel's \fBPatterns\fR.
This option may be used to verify each Store's configuration.
.TP
\fB-C\fR[\fBf\fR][\fBn\fR], \fB--create\fR[\fB-far\fR|\fB-near\fR]
Override any \fBCreate\fR options from the config file. See below.
.TP
@ -62,11 +66,11 @@ Override any \fBRemove\fR options from the config file. See below.
\fB-X\fR[\fBf\fR][\fBn\fR], \fB--expunge\fR[\fB-far\fR|\fB-near\fR]
Override any \fBExpunge\fR options from the config file. See below.
.TP
{\fB-n\fR|\fB-o\fR|\fB-u\fR|\fB-g\fR|\fB-f\fR|\fB-0\fR|\fB-F\fR},\
{\fB--new\fR|\fB--old\fR|\fB--upgrade\fR|\fB--gone\fR|\fB--flags\fR|\fB--noop\fR|\fB--full\fR}
{\fB-n\fR|\fB-N\fR|\fB-d\fR|\fB-f\fR|\fB-0\fR|\fB-F\fR},\
{\fB--new\fR|\fB--renew\fR|\fB--delete\fR|\fB--flags\fR|\fB--noop\fR|\fB--full\fR}
.TP
\r{\fB-L\fR|\fB-H\fR}[\fBn\fR][\fBo\fR][\fBu\fR][\fBg\fR][\fBf\fR],\
{\fB--pull\fR|\fB--push\fR}[\fB-new\fR|\fB-old\fR|\fB-upgrade\fR|\fB-gone\fR|\fB-flags\fR]
\r{\fB-L\fR|\fB-H\fR}[\fBn\fR][\fBN\fR][\fBd\fR][\fBf\fR],\
{\fB--pull\fR|\fB--push\fR}[\fB-new\fR|\fB-renew\fR|\fB-delete\fR|\fB-flags\fR]
Override any \fBSync\fR options from the config file. See below.
.TP
\fB-h\fR, \fB--help\fR
@ -75,19 +79,6 @@ Display a summary of command line options.
\fB-v\fR, \fB--version\fR
Display version information.
.TP
\fB-y\fR, \fB--dry-run\fR
Enter simulation mode: the Channel status is queried and all required
operations are determined, but no modifications are actually made
to either the mailboxes or the state files.
.TP
\fB-e\fR, \fB--ext-exit\fR
Return an extended exit code: Add 32 or 64 to the code if any
modifications were made on the far or near side, respectively; these
are not mutually exclusive, so the code may be 96 if changes were both
pushed and pulled.
An error may be reported at the same time, so the code may be for example
65 if some changes were successfully pulled, while others failed.
.TP
\fB-V\fR, \fB--verbose\fR
Enable \fIverbose\fR mode, which displays what is currently happening.
.TP
@ -116,7 +107,7 @@ Without category specification, all categories except net-all are enabled.
.TP
\fB-q\fR, \fB--quiet\fR
Suppress progress counters (this is implicit if stdout is no TTY,
or any debugging categories are enabled), notices, and the summary.
or any debugging categories are enabled) and notices.
If specified twice, suppress warning messages as well.
.
.SH CONFIGURATION
@ -128,8 +119,6 @@ and literal double quotes and backslashes (\fB\\\fR) must be backslash-escaped.
All keywords (including those used as arguments) are case-insensitive.
Bash-like home directory expansion using the tilde (\fB~\fR) is supported
in all options which represent local paths.
The reference point for relative local paths is the configuration file's
containing directory.
There are a few global options, the others apply to particular sections.
Sections begin with a section-starting keyword and are terminated by an empty
line or end of file.
@ -155,7 +144,8 @@ Unix-like forward slashes.
.SS All Stores
These options can be used in all supported Store types.
.br
The term "opposite Store" refers to the other Store within a Channel.
In this context, the term "remote" describes the second Store within a Channel,
and not necessarily a remote server.
.br
The special mailbox \fBINBOX\fR exists in every Store; its physical location
in the file system is Store type specific.
@ -174,17 +164,14 @@ directory.
.TP
\fBMaxSize\fR \fIsize\fR[\fBk\fR|\fBm\fR][\fBb\fR]
Messages larger than \fIsize\fR will have only a small placeholder message
propagated into this Store.
propagated into this Store. To propagate the full message, it must be
flagged in either Store; that can be done retroactively, in which case
the \fBReNew\fR operation needs to be executed instead of \fBNew\fR.
This is useful for avoiding downloading messages with large attachments
unless they are actually needed.
To upgrade the placeholder to the full message, it must be flagged, and
the \fBUpgrade\fR operation executed.
Caveats: Setting a size limit on a Store you never read directly (which is
Caveat: Setting a size limit on a Store you never read directly (which is
typically the case for servers) is not recommended, as you may never
notice that affected messages were not propagated to it.
Also, as flagging is (ab-)used to request an upgrade, changes to the
message's flagging state will not be propagated in either direction until
after the placeholder is upgraded.
.br
\fBK\fR and \fBM\fR can be appended to the size to specify KiBytes resp.
MeBytes instead of bytes. \fBB\fR is accepted but superfluous.
@ -221,20 +208,18 @@ See \fBRECOMMENDATIONS\fR and \fBINHERENT PROBLEMS\fR below.
.TP
\fBTrashNewOnly\fR \fByes\fR|\fBno\fR
When trashing, copy only not yet propagated messages. This makes sense if the
opposite Store has a \fBTrash\fR as well (with \fBTrashNewOnly\fR \fBno\fR).
remote Store has a \fBTrash\fR as well (with \fBTrashNewOnly\fR \fBno\fR).
(Default: \fBno\fR)
.
.TP
\fBTrashRemoteNew\fR \fByes\fR|\fBno\fR
When expunging the opposite Store, copy not yet propagated messages to this
Store's \fBTrash\fR.
When using this, the opposite Store does not need an own \fBTrash\fR at all,
yet all messages are archived.
When expunging the remote Store, copy not yet propagated messages to this
Store's \fBTrash\fR. When using this, the remote Store does not need an own
\fBTrash\fR at all, yet all messages are archived.
(Default: \fBno\fR)
.
.SS Maildir Stores
The reference point for relative \fBPath\fRs is the configuration file's
containing directory.
The reference point for relative \fBPath\fRs is the current working directory.
.P
As \fBmbsync\fR needs UIDs, but no standardized UID storage scheme exists for
Maildir, \fBmbsync\fR supports two schemes, each with its pros and cons.
@ -316,7 +301,7 @@ Define the IMAP4 Account \fIname\fR, opening a section for its parameters.
\fBHost\fR \fIhost\fR
Specify the DNS name or IP address of the IMAP server.
.br
If \fBTunnel\fR is used, this setting is needed only if \fBTLSType\fR is
If \fBTunnel\fR is used, this setting is needed only if \fBSSLType\fR is
not \fBNone\fR and \fBCertificateFile\fR is not used,
in which case the host name is used for certificate subject verification.
.
@ -397,13 +382,13 @@ The list of acceptable authentication mechanisms.
In addition to the mechanisms listed in the SASL registry (link below),
the legacy IMAP \fBLOGIN\fR mechanism is known.
The wildcard \fB*\fR represents all mechanisms that are deemed secure
enough for the current \fBTLSType\fR setting.
enough for the current \fBSSLType\fR setting.
The actually used mechanism is the most secure choice from the intersection
of this list, the list supplied by the server, and the installed SASL modules.
(Default: \fB*\fR)
.
.TP
\fBTLSType\fR {\fBNone\fR|\fBSTARTTLS\fR|\fBIMAPS\fR}
\fBSSLType\fR {\fBNone\fR|\fBSTARTTLS\fR|\fBIMAPS\fR}
Select the connection security/encryption method:
.br
\fBNone\fR - no security.
@ -413,16 +398,14 @@ This is the default when \fBTunnel\fR is set, as tunnels are usually secure.
after connecting the regular IMAP port 143. Most servers support this,
so it is the default (unless a tunnel is used).
.br
\fBIMAPS\fR - security is established by starting TLS negotiation
\fBIMAPS\fR - security is established by starting SSL/TLS negotiation
right after connecting the secure IMAP port 993.
.
.TP
\fBTLSVersions\fR {\fB+\fR|\fB-\fR}{\fB1.0\fR|\fB1.1\fR|\fB1.2\fR|\fB1.3\fR} ...
Add/remove the specified TLS versions to/from the set of acceptable choices.
\fBSSLVersions\fR [\fBSSLv3\fR] [\fBTLSv1\fR] [\fBTLSv1.1\fR] [\fBTLSv1.2\fR] [\fBTLSv1.3\fR]
Select the acceptable SSL/TLS versions.
Use old versions only when the server has problems with newer ones.
Note that new versions are automatically enabled as soon as OpenSSL supports
them, even if \fBmbsync\fR does not recognize them yet.
(Default: All starting with 1.2).
(Default: [\fBTLSv1\fR] [\fBTLSv1.1\fR] [\fBTLSv1.2\fR] [\fBTLSv1.3\fR]).
.
.TP
\fBSystemCertificates\fR \fByes\fR|\fBno\fR
@ -557,7 +540,7 @@ Note that \fBINBOX\fR is not matched by wildcards, unless it lives under
\fBPath\fR.
.br
The mailbox list selected by \fBPatterns\fR can be overridden by a mailbox
list in a Channel reference (a \fBGroup\fR specification or the command line).
list in a channel reference (a \fBGroup\fR specification or the command line).
.br
Example: "\fBPatterns\fR\ \fI%\ !Trash\fR"
.
@ -577,7 +560,7 @@ the actual date of the message) will be deleted first.
Messages that are flagged (marked as important) and (by default) unread
messages will not be automatically deleted.
If \fIcount\fR is 0, the maximum number of messages is \fBunlimited\fR
(Global default: \fI0\fR).
(Default: \fI0\fR).
.
.TP
\fBExpireUnread\fR \fByes\fR|\fBno\fR
@ -587,16 +570,10 @@ This ensures that you never miss new messages even after an extended absence.
However, if your archive contains large amounts of unread messages by design,
treating them as important would practically defeat \fBMaxMessages\fR. In this
case you need to enable this option.
(Global default: \fBno\fR).
(Default: \fBno\fR).
.
.TP
\fBExpireSide\fR \fBFar\fR|\fBNear\fR
Selects on which side messages should be expired when \fBMaxMessages\fR is
configured.
(Global default: \fBNear\fR).
.
.TP
\fBSync\fR {\fBNone\fR|[\fBPull\fR] [\fBPush\fR] [\fBNew\fR] [\fBOld\fR] [\fBUpgrade\fR] [\fBGone\fR] [\fBFlags\fR] [\fBFull\fR]}
\fBSync\fR {\fBNone\fR|[\fBPull\fR] [\fBPush\fR] [\fBNew\fR] [\fBReNew\fR] [\fBDelete\fR] [\fBFlags\fR]|\fBAll\fR}
Select the synchronization operation(s) to perform:
.br
\fBPull\fR - propagate changes from far to near side.
@ -605,15 +582,11 @@ Select the synchronization operation(s) to perform:
.br
\fBNew\fR - propagate newly appeared messages.
.br
\fBOld\fR - propagate previously skipped, failed, and expired messages.
This has a (relatively) high cost and may repeatedly produce error messages,
so it always must be specified explicitly.
.br
\fBUpgrade\fR - upgrade placeholders to full messages. Useful only with
\fBReNew\fR - upgrade placeholders to full messages. Useful only with
a configured \fBMaxSize\fR.
.br
\fBGone\fR - propagate message disappearances. This applies only to messages that
are actually gone, i.e., were expunged. The affected messages in the opposite
\fBDelete\fR - propagate message deletions. This applies only to messages that
are actually gone, i.e., were expunged. The affected messages in the remote
Store are marked as deleted only, i.e., they won't be really deleted until
that Store is expunged.
.br
@ -621,24 +594,23 @@ that Store is expunged.
well; this is particularly interesting if you use \fBmutt\fR with the
maildir_trash option.
.br
\fBFull\fR - alias for "\fBNew\fR\ \fBUpgrade\fR\ \fBGone\fR\ \fBFlags\fR".
\fBAll\fR (\fB--full\fR on the command line) - all of the above.
This is the global default.
.br
\fBNone\fR (\fB--noop\fR on the command line) - don't propagate anything.
Useful if you want to expunge only.
.IP
\fBPull\fR and \fBPush\fR are direction flags, while \fBNew\fR, \fBOld\fR,
\fBUpgrade\fR, \fBGone\fR, and \fBFlags\fR are type flags.
The two flag classes make up a two-dimensional matrix (a table). Its cells are
the individual actions to perform. There are two styles of asserting the cells:
\fBPull\fR and \fBPush\fR are direction flags, while \fBNew\fR, \fBReNew\fR,
\fBDelete\fR and \fBFlags\fR are type flags. The two flag classes make up a
two-dimensional matrix (a table). Its cells are the individual actions to
perform. There are two styles of asserting the cells:
.br
In the first style, the flags select entire rows/colums in the matrix. Only
the cells which are selected both horizontally and vertically are asserted.
Specifying no direction is like specifying both directions, and specifying
no type is like specifying \fBFull\fR.
Specifying no flags from a class is like specifying all flags from this class.
For example, "\fBSync\fR\ \fBPull\fR\ \fBNew\fR\ \fBFlags\fR" will propagate
new messages and flag changes from the far side to the near side,
"\fBSync\fR\ \fBNew\fR\ \fBGone\fR" will propagate message arrivals and
"\fBSync\fR\ \fBNew\fR\ \fBDelete\fR" will propagate message arrivals and
deletions both ways, and "\fBSync\fR\ \fBPush\fR" will propagate all changes
from the near side to the far side.
.br
@ -646,16 +618,13 @@ In the second style, direction flags are concatenated with type flags; every
compound flag immediately asserts a cell in the matrix. In addition to at least
one compound flag, the individual flags can be used as well, but as opposed to
the first style, they immediately assert all cells in their respective
row/column (with the exception of \fBOld\fR). For example,
"\fBSync\fR\ \fBPullNew\fR\ \fBPullGone\fR\ \fBPush\fR" will propagate
row/column. For example,
"\fBSync\fR\ \fBPullNew\fR\ \fBPullDelete\fR\ \fBPush\fR" will propagate
message arrivals and deletions from the far side to the near side and any
changes (except old messages) from the near side to the far side.
.br
changes from the near side to the far side.
Note that it is not allowed to assert a cell in two ways, e.g.
"\fBSync\fR\ \fBPullNew\fR\ \fBPull\fR" and
"\fBSync\fR\ \fBPullNew\fR\ \fBGone\fR\ \fBPush\fR" induce error messages.
.br
\fBNone\fR may not be combined with any other operation.
"\fBSync\fR\ \fBPullNew\fR\ \fBDelete\fR\ \fBPush\fR" induce error messages.
.
.TP
\fBCreate\fR {\fBNone\fR|\fBFar\fR|\fBNear\fR|\fBBoth\fR}
@ -679,26 +648,11 @@ Note that for safety, non-empty mailboxes are never deleted.
.
.TP
\fBExpunge\fR {\fBNone\fR|\fBFar\fR|\fBNear\fR|\fBBoth\fR}
Permanently remove all messages [on the far/near side] which are marked
for deletion.
Mutually exclusive with \fBExpungeSolo\fR for the same side.
Permanently remove all messages [on the far/near side] marked for deletion.
See \fBRECOMMENDATIONS\fR below.
(Global default: \fBNone\fR)
.
.TP
\fBExpungeSolo\fR {\fBNone\fR|\fBFar\fR|\fBNear\fR|\fBBoth\fR}
Permanently remove all messages [on the far/near side] which are both
marked for deletion and have no corresponding message in the opposite
Store.
Together with \fBSync Gone\fR, this allows actual mirroring of
expunges. Note, however, that this makes sense only if nothing else
expunges the other messages which are marked for deletion.
Also note that this does not work for IMAP Stores which do not support
the UIDPLUS extension.
Mutually exclusive with \fBExpunge\fR for the same side.
(Global default: \fBNone\fR)
.
.TP
\fBCopyArrivalDate\fR {\fByes\fR|\fBno\fR}
Selects whether their arrival time should be propagated together with
the messages.
@ -706,12 +660,11 @@ Enabling this makes sense in order to keep the time stamp based message
sorting intact.
Note that IMAP does not guarantee that the time stamp (termed \fBinternal
date\fR) is actually the arrival time, but it is usually close enough.
(Global default: \fBno\fR)
(Default: \fBno\fR)
.
.P
\fBSync\fR, \fBCreate\fR, \fBRemove\fR, \fBExpunge\fR, \fBExpungeSolo\fR,
\fBMaxMessages\fR, \fBExpireUnread\fR, \fBExpireSide\fR,
and \fBCopyArrivalDate\fR
\fBSync\fR, \fBCreate\fR, \fBRemove\fR, \fBExpunge\fR,
\fBMaxMessages\fR, and \fBCopyArrivalDate\fR
can be used before any section for a global effect.
The global settings are overridden by Channel-specific options,
which in turn are overridden by command line switches.
@ -724,17 +677,14 @@ in the near side mailbox itself; this has the advantage that you do not need
to handle the state file separately if you delete the mailbox, but it works
only with Maildir mailboxes, obviously.
Otherwise this is interpreted as a string to prepend to the near side mailbox
name to make up a complete path. Note that you \fBmust\fR append a slash if
you want to specify a directory.
name to make up a complete path.
.br
This option can be used outside any section for a global effect. In this case
the appended string is made up according to the pattern
\fB:\fIfar-store\fB:\fIfar-box\fB_:\fInear-store\fB:\fInear-box\fR
(see also \fBFieldDelimiter\fR below).
.br
(Global default: \fI$XDG_STATE_HOME/isync/\fR, with a fallback to
\fI~/.mbsync/\fR if only that exists.
$XDG_STATE_HOME defaults to ~/.local/state if not set.)
(Global default: \fI~/.mbsync/\fR).
.
.SS Groups
.TP
@ -752,7 +702,7 @@ used as mailbox name separators as well.
.
.TP
\fBChannel\fR[\fBs\fR] \fIchannel\fR[\fB:\fIbox\fR[\fB,\fR...]] ...
Add the specified Channels to the Group. This option can be specified multiple
Add the specified channels to the group. This option can be specified multiple
times within a Group.
.
.SS Global Options
@ -791,33 +741,30 @@ If \fBmbsync\fR's output is connected to a console, it will print progress
counters by default. The output will look like this:
.P
.in +4
C: 1/2 B: 3/4 F: +13/13 *23/42 #0/0 -0/0 N: +0/7 *0/0 #0/0 -0/0
C: 1/2 B: 3/4 F: +13/13 *23/42 #0/0 N: +0/7 *0/0 #0/0
.in -4
.P
This represents the cumulative progress over Channels, boxes, and messages
This represents the cumulative progress over channels, boxes, and messages
affected on the far and near side, respectively.
The message counts represent added messages, messages with updated flags,
trashed messages, and expunged messages, respectively.
and trashed messages, respectively.
No attempt is made to calculate the totals in advance, so they grow over
time as more information is gathered.
.P
Irrespective of output redirection, \fBmbsync\fR will print a summary
of the above in plain language upon completion, except in quiet mode.
.
.SH RECOMMENDATIONS
Make sure your IMAP server does not auto-expunge deleted messages - it is
slow, and semantically somewhat questionable. Specifically, Gmail needs to
be configured not to do it.
.P
By default, \fBmbsync\fR will not delete any messages - expunges are
propagated by marking the messages as deleted in the opposite Store.
By default, \fBmbsync\fR will not delete any messages - deletions are
propagated by marking the messages as deleted on the remote store.
Once you have verified that your setup works, you will typically want to
set \fBExpunge\fR to \fBBoth\fR, so that deletions become effective.
.P
\fBmbsync\fR's built-in trash functionality relies on \fBmbsync\fR doing
the expunging of deleted messages. This is the case when it propagates
deletions of previously propagated messages, and the trash is on the target
Store (typically your IMAP server).
store (typically your IMAP server).
.br
However, when you intend \fBmbsync\fR to trash messages which were not
propagated yet, the MUA must mark the messages as deleted without expunging
@ -842,10 +789,6 @@ Mutt always does that, while mu4e needs to be configured to do it:
.in +4
(setq mu4e-change-filenames-when-moving t)
.in -4
.br
The general expectation is that a completely new filename is generated
as if the message was new, but stripping the \fB,U=\fIxxx\fR infix is
sufficient as well.
.
.SH INHERENT PROBLEMS
Changes done after \fBmbsync\fR has retrieved the message list will not be
@ -860,18 +803,11 @@ There is no risk as long as the IMAP mailbox is accessed by only one client
.
.SH FILES
.TP
\fB$XDG_CONFIG_HOME/isyncrc\fR (usually \fB~/.config/isyncrc\fR)
Default configuration file.
See also the example file in the documentation directory.
.TP
\fB$XDG_STATE_HOME/isync/\fR (usually \fB~/.local/state/isync/\fR)
Directory containing synchronization state files.
.TP
.B ~/.mbsyncrc
Legacy configuration file.
Default configuration file
.TP
.B ~/.mbsync/
Legacy directory containing synchronization state files.
Directory containing synchronization state files
.
.SH SEE ALSO
mdconvert(1), mutt(1), maildir(5)

View File

@ -4,10 +4,6 @@
Expunge None
Create Both
# More sections follow
#
# !!!! Note that empty lines delimit sections !!!!
MaildirStore local
Path ~/Mail/
Trash Trash
@ -36,7 +32,7 @@ Sync PullNew Push
IMAPStore personal
Host host.play.com
Port 6789
TLSType None
RequireSSL no
Channel personal
Far :personal:
@ -57,42 +53,16 @@ Group boxes
Channels work personal remote
# Due to the divergent Path suffixes, it's possible to have
# multiple Stores homed in the same directory.
# You could even put them all directly into $HOME.
MaildirStore local-personal
Path ~/Mail/personal-
Inbox ~/Mail/personal-INBOX
MaildirStore local-work
Path ~/Mail/work-
# Just because.
Inbox ~/Mail/w0rk_InBoX
Channel personal-joined
Far :personal:
Near :local-personal:
Paterns *
Channel work-joined
Far :work:
Near :local-work:
Paterns *
Group joined personal-joined work-joined
IMAPStore st1
Host st1.domain.com
AuthMech CRAM-MD5
# Omit if you want to use the system certificate store.
RequireCRAM yes
CertificateFile ~/.st1-certificate.crt
IMAPStore st2
Host imap.another-domain.com
Path non-standard/
TLSVersions -1.2
RequireSSL no
UseTLSv1 no
Channel rst
Far :st1:somebox
@ -101,7 +71,6 @@ Near :st2:
IMAPAccount server
Host imaps:foo.bar.com
# Omit if you want to use the system certificate store.
CertificateFile ~/.server-certificate.crt
IMAPStore server

View File

@ -1,21 +1,33 @@
.\" SPDX-FileCopyrightText: 2004-2022 Oswald Buddenhagen <ossi@users.sf.net>
.\" SPDX-License-Identifier: GPL-2.0-or-later
.\"
.\" mdconvert - Maildir mailbox UID storage scheme converter
.
.ig
\" mdconvert - Maildir mailbox UID storage scheme converter
\" Copyright (C) 2004 Oswald Buddenhagen <ossi@users.sf.net>
\"
\" This program is free software; you can redistribute it and/or modify
\" it under the terms of the GNU General Public License as published by
\" the Free Software Foundation; either version 2 of the License, or
\" (at your option) any later version.
\"
\" This program is distributed in the hope that it will be useful,
\" but WITHOUT ANY WARRANTY; without even the implied warranty of
\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\" GNU General Public License for more details.
\"
\" You should have received a copy of the GNU General Public License
\" along with this program. If not, see <http://www.gnu.org/licenses/>.
..
.TH mdconvert 1 "2004 Mar 27"
.
..
.SH NAME
mdconvert - Maildir mailbox UID storage scheme converter
.
..
.SH SYNOPSIS
\fBmdconvert\fR [\fIoptions\fR ...] \fImailbox\fR ...
.
..
.SH DESCRIPTION
\fBmdconvert\fR converts Maildir mailboxes between the two UID storage schemes
supported by \fBmbsync\fR. See \fBmbsync\fR's manual page for details on these
schemes.
.
..
.SH OPTIONS
.TP
\fB-a\fR, \fB--alt\fR
@ -30,9 +42,9 @@ Displays a summary of command line options.
.TP
\fB-v\fR, \fB--version\fR
Displays version information.
.
..
.SH SEE ALSO
mbsync(1)
.
..
.SH AUTHOR
Written and maintained by Oswald Buddenhagen <ossi@users.sf.net>.

View File

@ -1,7 +1,19 @@
// SPDX-FileCopyrightText: 2004-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* mdconvert - Maildir UID scheme converter
* Copyright (C) 2004 Oswald Buddenhagen <ossi@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <autodefs.h>
@ -251,9 +263,8 @@ main( int argc, char **argv )
} else if (argv[oint][0] == '-') {
fprintf( stderr, "Unrecognized option '%s'. Try " EXE " -h\n", argv[oint] );
return 1;
} else {
} else
break;
}
}
if (oint == argc) {
fprintf( stderr, "Mailbox specification missing. Try " EXE " -h\n" );

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,34 @@
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-FileCopyrightText: 2004 Theodore Y. Ts'o <tytso@mit.edu>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2008,2010,2011, 2013 Oswald Buddenhagen <ossi@users.sf.net>
* Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#include "socket.h"
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
@ -28,7 +48,6 @@
#endif
enum {
SCK_RESOLVING,
SCK_CONNECTING,
#ifdef HAVE_LIBSSL
SCK_STARTTLS,
@ -383,7 +402,7 @@ socket_start_deflate( conn_t *conn )
{
int result;
conn->in_z = nfzalloc( sizeof(*conn->in_z) );
conn->in_z = nfcalloc( sizeof(*conn->in_z) );
result = inflateInit2(
conn->in_z,
-15 /* Use raw deflate */
@ -393,7 +412,7 @@ socket_start_deflate( conn_t *conn )
abort();
}
conn->out_z = nfzalloc( sizeof(*conn->out_z) );
conn->out_z = nfcalloc( sizeof(*conn->out_z) );
result = deflateInit2(
conn->out_z,
Z_DEFAULT_COMPRESSION, /* Compression level */
@ -408,7 +427,6 @@ socket_start_deflate( conn_t *conn )
}
init_wakeup( &conn->z_fake, z_fake_cb, conn );
conn->readsz = 0; // This optimization makes no sense past this point
}
#endif /* HAVE_LIBZ */
@ -416,7 +434,6 @@ static void socket_fd_cb( int, void * );
static void socket_fake_cb( void * );
static void socket_timeout_cb( void * );
static void socket_resolve( conn_t * );
static void socket_connect_one( conn_t * );
static void socket_connect_next( conn_t * );
static void socket_connect_failed( conn_t * );
@ -424,21 +441,15 @@ static void socket_connected( conn_t * );
static void socket_connect_bail( conn_t * );
static void
socket_register_internal( conn_t *sock, int fd )
socket_open_internal( conn_t *sock, int fd )
{
sock->fd = fd;
fcntl( fd, F_SETFL, O_NONBLOCK );
init_notifier( &sock->notify, fd, socket_fd_cb, sock );
init_wakeup( &sock->fd_fake, socket_fake_cb, sock );
init_wakeup( &sock->fd_timeout, socket_timeout_cb, sock );
}
static void
socket_open_internal( conn_t *sock, int fd )
{
fcntl( fd, F_SETFL, O_NONBLOCK );
socket_register_internal( sock, fd );
}
static void
socket_close_internal( conn_t *sock )
{
@ -449,6 +460,32 @@ socket_close_internal( conn_t *sock )
sock->fd = -1;
}
#ifndef HAVE_IPV6
struct addr_info {
struct addr_info *ai_next;
struct sockaddr_in ai_addr[1];
};
#define freeaddrinfo(ai) free( ai )
static struct addr_info *
init_addrinfo( struct hostent *he )
{
uint naddr = 0;
for (char **addr = he->h_addr_list; *addr; addr++)
naddr++;
struct addr_info *caddr = nfcalloc( naddr * sizeof(struct addrinfo) );
struct addr_info *ret, **caddrp = &ret;
for (char **addr = he->h_addr_list; *addr; addr++, caddr++) {
caddr->ai_addr->sin_family = AF_INET;
memcpy( &caddr->ai_addr->sin_addr.s_addr, *addr, sizeof(struct in_addr) );
*caddrp = caddr;
caddrp = &caddr->ai_next;
}
return ret;
}
#endif
void
socket_connect( conn_t *sock, void (*cb)( int ok, void *aux ) )
{
@ -483,202 +520,77 @@ socket_connect( conn_t *sock, void (*cb)( int ok, void *aux ) )
info( "\vok\n" );
socket_connected( sock );
} else {
socket_resolve( sock );
}
}
static void
pipe_write( int fd, void *buf, int len )
{
do {
int wrote = write( fd, buf, len );
if (wrote < 0) {
perror( "write" );
_exit( 1 );
}
buf = ((char *)buf) + wrote;
len -= wrote;
} while (len);
}
static void
socket_resolve( conn_t *sock )
{
info( "Resolving %s...\n", sock->conf->host );
int pfd[2];
if (pipe( pfd )) {
perror( "pipe" );
exit( 1 );
}
switch (fork()) {
case -1:
perror( "fork" );
exit( 1 );
case 0:
break;
default:
close( pfd[1] );
socket_register_internal( sock, pfd[0] );
sock->state = SCK_RESOLVING;
conf_notifier( &sock->notify, 0, POLLIN );
socket_expect_activity( sock, 1 );
return;
}
#ifdef HAVE_IPV6
struct addrinfo *res, hints = { 0 };
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG;
int gaierr = getaddrinfo( sock->conf->host, NULL, &hints, &res );
pipe_write( pfd[1], &gaierr, sizeof(gaierr) );
if (gaierr)
_exit( 1 );
static_assert( sizeof(((struct addrinfo){ 0 }).ai_family) == sizeof(int), "unexpected size of ai_family" );
static_assert( sizeof(struct in_addr) % sizeof(int) == 0, "unexpected size of struct in_addr" );
static_assert( sizeof(struct in6_addr) % sizeof(int) == 0, "unexpected size of struct in6_addr" );
int nbytes = 0;
for (struct addrinfo *cres = res; cres; cres = cres->ai_next) {
if (cres->ai_family == AF_INET) {
nbytes += sizeof(int) + sizeof(struct in_addr);
} else {
assert( cres->ai_family == AF_INET6 );
nbytes += sizeof(int) + sizeof(struct in6_addr);
int gaierr;
struct addrinfo hints;
memset( &hints, 0, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG;
infon( "Resolving %s... ", conf->host );
if ((gaierr = getaddrinfo( conf->host, NULL, &hints, &sock->addrs ))) {
error( "Error: Cannot resolve server '%s': %s\n", conf->host, gai_strerror( gaierr ) );
socket_connect_bail( sock );
return;
}
}
pipe_write( pfd[1], &nbytes, sizeof(nbytes) );
for (struct addrinfo *cres = res; cres; cres = cres->ai_next) {
pipe_write( pfd[1], &cres->ai_family, sizeof(int) );
if (cres->ai_family == AF_INET)
pipe_write( pfd[1], &((struct sockaddr_in *)cres->ai_addr)->sin_addr, sizeof(struct in_addr) );
else
pipe_write( pfd[1], &((struct sockaddr_in6 *)cres->ai_addr)->sin6_addr, sizeof(struct in6_addr) );
}
info( "\vok\n" );
#else
struct hostent *he = gethostbyname( sock->conf->host );
int herrno = he ? 0 : h_errno;
pipe_write( pfd[1], &herrno, sizeof(herrno) );
if (!he)
_exit( 1 );
static_assert( sizeof(struct in_addr) % sizeof(int) == 0, "unexpected size of struct in_addr" );
int nbytes = 0;
for (char **addr = he->h_addr_list; *addr; addr++)
nbytes += sizeof(struct in_addr);
pipe_write( pfd[1], &nbytes, sizeof(nbytes) );
for (char **addr = he->h_addr_list; *addr; addr++)
pipe_write( pfd[1], *addr, sizeof(struct in_addr) );
#endif
_exit( 0 );
}
struct hostent *he;
static void
pipe_read( int fd, void *buf, int len )
{
do {
int didrd = read( fd, buf, len );
if (didrd < 0) {
sys_error( "read" );
exit( 1 );
infon( "Resolving %s... ", conf->host );
he = gethostbyname( conf->host );
if (!he) {
error( "Error: Cannot resolve server '%s': %s\n", conf->host, hstrerror( h_errno ) );
socket_connect_bail( sock );
return;
}
if (!didrd) {
error( "read: unexpected EOF\n" );
exit( 1 );
}
buf = ((char *)buf) + didrd;
len -= didrd;
} while (len);
}
info( "\vok\n" );
static void
socket_resolve_finalize( conn_t *sock )
{
int errcode;
pipe_read( sock->fd, &errcode, sizeof(errcode) );
if (errcode) {
#ifdef HAVE_IPV6
const char *err = gai_strerror( errcode );
#else
const char *err = hstrerror( errcode );
sock->addrs = init_addrinfo( he );
#endif
error( "Error: Cannot resolve server '%s': %s\n", sock->conf->host, err );
socket_close_internal( sock );
socket_connect_bail( sock );
return;
sock->curr_addr = sock->addrs;
socket_connect_one( sock );
}
int nbytes;
pipe_read( sock->fd, &nbytes, sizeof(nbytes) );
char *addrs = nfmalloc( nbytes );
pipe_read( sock->fd, addrs, nbytes );
sock->curr_addr = sock->addrs = addrs;
sock->addrs_end = addrs + nbytes;
socket_close_internal( sock ); // Get rid of the pipe
socket_connect_one( sock );
}
static void
socket_resolve_timeout( conn_t *sock )
{
error( "Error: Cannot resolve server '%s': timeout.\n", sock->conf->host );
socket_close_internal( sock );
socket_connect_bail( sock );
}
static void
socket_connect_one( conn_t *sock )
{
char *ai = sock->curr_addr;
if (ai == sock->addrs_end) {
int s;
#ifdef HAVE_IPV6
struct addrinfo *ai;
#else
struct addr_info *ai;
#endif
if (!(ai = sock->curr_addr)) {
error( "No working address found for %s\n", sock->conf->host );
socket_connect_bail( sock );
return;
}
union {
struct sockaddr any;
struct sockaddr_in ip4;
#ifdef HAVE_IPV6
struct sockaddr_in6 ip6;
#endif
} addr;
#ifdef HAVE_IPV6
int fam = *(int *)ai;
ai += sizeof(int);
int addr_len;
if (fam == AF_INET6) {
addr_len = sizeof(addr.ip6);
addr.ip6.sin6_addr = *(struct in6_addr *)ai;
addr.ip6.sin6_flowinfo = 0;
addr.ip6.sin6_scope_id = 0;
ai += sizeof(struct in6_addr);
} else {
addr_len = sizeof(addr.ip4);
#else
const int fam = AF_INET;
const int addr_len = sizeof(addr.ip4);
{
#endif
addr.ip4.sin_addr = *(struct in_addr *)ai;
ai += sizeof(struct in_addr);
}
sock->curr_addr = ai;
#ifdef HAVE_IPV6
if (fam == AF_INET6) {
if (ai->ai_family == AF_INET6) {
struct sockaddr_in6 *in6 = ((struct sockaddr_in6 *)ai->ai_addr);
char sockname[64];
inet_ntop( fam, &addr.ip6.sin6_addr, sockname, sizeof(sockname) );
in6->sin6_port = htons( sock->conf->port );
nfasprintf( &sock->name, "%s ([%s]:%hu)",
sock->conf->host, sockname, sock->conf->port );
sock->conf->host, inet_ntop( AF_INET6, &in6->sin6_addr, sockname, sizeof(sockname) ), sock->conf->port );
} else
#endif
{
struct sockaddr_in *in = ((struct sockaddr_in *)ai->ai_addr);
in->sin_port = htons( sock->conf->port );
nfasprintf( &sock->name, "%s (%s:%hu)",
sock->conf->host, inet_ntoa( addr.ip4.sin_addr ), sock->conf->port );
sock->conf->host, inet_ntoa( in->sin_addr ), sock->conf->port );
}
int s = socket( fam, SOCK_STREAM, 0 );
#ifdef HAVE_IPV6
s = socket( ai->ai_family, SOCK_STREAM, 0 );
#else
s = socket( PF_INET, SOCK_STREAM, 0 );
#endif
if (s < 0) {
socket_connect_next( sock );
return;
@ -686,9 +598,11 @@ socket_connect_one( conn_t *sock )
socket_open_internal( sock, s );
infon( "Connecting to %s... ", sock->name );
addr.any.sa_family = fam;
addr.ip4.sin_port = htons( sock->conf->port ); // Aliased for ip6
if (connect( s, &addr.any, addr_len )) {
#ifdef HAVE_IPV6
if (connect( s, ai->ai_addr, ai->ai_addrlen )) {
#else
if (connect( s, ai->ai_addr, sizeof(*ai->ai_addr) )) {
#endif
if (errno != EINPROGRESS) {
socket_connect_failed( sock );
return;
@ -709,6 +623,7 @@ socket_connect_next( conn_t *conn )
sys_error( "Cannot connect to %s", conn->name );
free( conn->name );
conn->name = NULL;
conn->curr_addr = conn->curr_addr->ai_next;
socket_connect_one( conn );
}
@ -722,8 +637,10 @@ socket_connect_failed( conn_t *conn )
static void
socket_connected( conn_t *conn )
{
free( conn->addrs );
conn->addrs = NULL;
if (conn->addrs) {
freeaddrinfo( conn->addrs );
conn->addrs = NULL;
}
conf_notifier( &conn->notify, 0, POLLIN );
socket_expect_activity( conn, 0 );
conn->state = SCK_READY;
@ -733,8 +650,10 @@ socket_connected( conn_t *conn )
static void
socket_cleanup_names( conn_t *conn )
{
free( conn->addrs );
conn->addrs = NULL;
if (conn->addrs) {
freeaddrinfo( conn->addrs );
conn->addrs = NULL;
}
free( conn->name );
conn->name = NULL;
}
@ -820,34 +739,6 @@ do_read( conn_t *sock, char *buf, uint len )
return n;
}
static void
socket_filled( conn_t *conn, uint len )
{
uint off = conn->offset;
uint cnt = conn->bytes + len;
conn->bytes = cnt;
if (conn->wanted) {
// Fulfill as much of the request as still fits into the buffer,
// but avoid chopping up the actual socket reads
if (cnt < conn->wanted && off + cnt < sizeof(conn->buf) - conn->readsz)
return;
} else {
// Need a full line
char *s = conn->buf + off;
char *p = memchr( s + conn->scanoff, '\n', cnt - conn->scanoff );
if (!p) {
conn->scanoff = cnt;
if (off && off + cnt >= sizeof(conn->buf) - conn->readsz) {
memmove( conn->buf, conn->buf + off, cnt );
conn->offset = 0;
}
return;
}
conn->scanoff = (uint)(p - s);
}
conn->read_callback( conn->callback_aux );
}
#ifdef HAVE_LIBZ
static void
socket_fill_z( conn_t *sock )
@ -874,8 +765,10 @@ socket_fill_z( conn_t *sock )
if (!sock->in_z->avail_out)
conf_wakeup( &sock->z_fake, 0 );
if ((len = (uint)((char *)sock->in_z->next_out - buf)))
socket_filled( sock, len );
if ((len = (uint)((char *)sock->in_z->next_out - buf))) {
sock->bytes += len;
sock->read_callback( sock->callback_aux );
}
}
#endif
@ -905,13 +798,8 @@ socket_fill( conn_t *sock )
if ((n = do_read( sock, buf, len )) <= 0)
return;
// IIR filter for tracking average size of bulk reads.
// We use this to optimize the free space at the end of the
// buffer, hence the factor of 1.5.
if (n >= MIN_BULK_READ)
sock->readsz = (sock->readsz * 3 + n * 3 / 2) / 4;
socket_filled( sock, (uint)n );
sock->bytes += (uint)n;
sock->read_callback( sock->callback_aux );
}
}
@ -922,70 +810,44 @@ socket_expect_activity( conn_t *conn, int expect )
conf_wakeup( &conn->fd_timeout, expect ? conn->conf->timeout : -1 );
}
void
socket_expect_eof( conn_t *sock )
int
socket_read( conn_t *conn, char *buf, uint len )
{
#ifdef SSL_OP_IGNORE_UNEXPECTED_EOF // implies HAVE_LIBSSL
if (sock->ssl)
SSL_set_options( sock->ssl, SSL_OP_IGNORE_UNEXPECTED_EOF );
#endif
}
void
socket_expect_bytes( conn_t *conn, uint len )
{
conn->wanted = len;
uint off = conn->offset;
if (off) {
uint cnt = conn->bytes;
if (off + len > sizeof(conn->buf) ||
off + cnt >= sizeof(conn->buf) - conn->readsz) {
memmove( conn->buf, conn->buf + off, cnt );
conn->offset = 0;
}
}
uint n = conn->bytes;
if (!n && conn->state == SCK_EOF)
return -1;
if (n > len)
n = len;
memcpy( buf, conn->buf + conn->offset, n );
if (!(conn->bytes -= n))
conn->offset = 0;
else
conn->offset += n;
return (int)n;
}
char *
socket_read( conn_t *conn, uint min_len, uint max_len, uint *out_len )
socket_read_line( conn_t *b )
{
assert( min_len > 0 );
assert( min_len <= sizeof(conn->buf) );
assert( min_len <= max_len );
char *p, *s;
uint n;
uint off = conn->offset;
uint cnt = conn->bytes;
if (cnt < min_len) {
if (conn->state == SCK_EOF)
return (void *)~0;
return NULL;
}
uint n = (cnt < max_len) ? cnt : max_len;
cnt -= n;
conn->offset = cnt ? off + n : 0;
conn->bytes = cnt;
*out_len = n;
return conn->buf + off;
}
char *
socket_read_line( conn_t *conn )
{
uint off = conn->offset;
uint cnt = conn->bytes;
char *s = conn->buf + off;
char *p = memchr( s + conn->scanoff, '\n', cnt - conn->scanoff );
s = b->buf + b->offset;
p = memchr( s + b->scanoff, '\n', b->bytes - b->scanoff );
if (!p) {
if (conn->state == SCK_EOF)
b->scanoff = b->bytes;
if (b->offset + b->bytes == sizeof(b->buf)) {
memmove( b->buf, b->buf + b->offset, b->bytes );
b->offset = 0;
}
if (b->state == SCK_EOF)
return (void *)~0;
conn->scanoff = cnt;
return NULL;
}
uint n = (uint)(p + 1 - s);
cnt -= n;
conn->offset = cnt ? off + n : 0;
conn->bytes = cnt;
conn->scanoff = 0;
n = (uint)(p + 1 - s);
b->offset += n;
b->bytes -= n;
b->scanoff = 0;
if (p != s && p[-1] == '\r')
p--;
*p = 0;
@ -1206,11 +1068,6 @@ socket_fd_cb( int events, void *aux )
{
conn_t *conn = (conn_t *)aux;
if (conn->state == SCK_RESOLVING) {
socket_resolve_finalize( conn );
return;
}
if ((events & POLLERR) || conn->state == SCK_CONNECTING) {
int soerr;
socklen_t selen = sizeof(soerr);
@ -1273,9 +1130,7 @@ socket_timeout_cb( void *aux )
{
conn_t *conn = (conn_t *)aux;
if (conn->state == SCK_RESOLVING) {
socket_resolve_timeout( conn );
} else if (conn->state == SCK_CONNECTING) {
if (conn->state == SCK_CONNECTING) {
errno = ETIMEDOUT;
socket_connect_failed( conn );
} else {

View File

@ -1,8 +1,23 @@
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#ifndef SOCKET_H
@ -57,7 +72,11 @@ typedef struct {
int fd;
int state;
const server_conf_t *conf; /* needed during connect */
char *addrs, *addrs_end, *curr_addr; // needed during connect; assumed to be int-aligned
#ifdef HAVE_IPV6
struct addrinfo *addrs, *curr_addr; /* needed during connect */
#else
struct addr_info *addrs, *curr_addr; /* needed during connect */
#endif
char *name;
#ifdef HAVE_LIBSSL
SSL *ssl;
@ -95,17 +114,12 @@ typedef struct {
uint offset; /* start of filled bytes in buffer */
uint bytes; /* number of filled bytes in buffer */
uint scanoff; /* offset to continue scanning for newline at, relative to 'offset' */
uint wanted; // try to accumulate that many bytes before calling back; 0 => full line
uint readsz; // average size of bulk reads from the underlying socket, times 1.5
char buf[100000];
#ifdef HAVE_LIBZ
char z_buf[100000];
#endif
} conn_t;
// Shorter reads are assumed to be limited by round-trips.
#define MIN_BULK_READ 1000
/* call this before doing anything with the socket */
static INLINE void socket_init( conn_t *conn,
const server_conf_t *conf,
@ -122,19 +136,14 @@ static INLINE void socket_init( conn_t *conn,
conn->fd = -1;
conn->name = NULL;
conn->write_buf_append = &conn->write_buf;
conn->wanted = 1;
conn->readsz = MIN_BULK_READ * 3 / 2;
}
void socket_connect( conn_t *conn, void (*cb)( int ok, void *aux ) );
void socket_start_tls(conn_t *conn, void (*cb)( int ok, void *aux ) );
void socket_start_deflate( conn_t *conn );
void socket_close( conn_t *sock );
void socket_expect_activity( conn_t *sock, int expect );
void socket_expect_eof( conn_t *sock );
void socket_expect_bytes( conn_t *sock, uint len );
// Don't free return values. These functions never wait.
char *socket_read( conn_t *conn, uint min_len, uint max_len, uint *out_len );
char *socket_read_line( conn_t *conn );
int socket_read( conn_t *sock, char *buf, uint len ); /* never waits */
char *socket_read_line( conn_t *sock ); /* don't free return value; never waits */
typedef enum { KeepOwn = 0, GiveOwn } ownership_t;
typedef struct {
char *buf;

2316
src/sync.c

File diff suppressed because it is too large Load Diff

View File

@ -1,63 +1,59 @@
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#ifndef SYNC_H
#define SYNC_H
#include "driver.h"
#include "sync_enum.h"
#define F 0 // far side
#define N 1 // near side
BIT_ENUM(
OP_NEW,
OP_OLD,
OP_UPGRADE,
OP_GONE,
OP_FLAGS,
OP_EXPUNGE,
OP_EXPUNGE_SOLO,
OP_CREATE,
OP_REMOVE,
XOP_PUSH,
XOP_PULL,
XOP_HAVE_TYPE, // Aka mode; have at least one of dir and type (see below)
// The following must all have the same bit shift from the corresponding OP_* flags.
XOP_HAVE_EXPUNGE,
XOP_HAVE_EXPUNGE_SOLO,
XOP_HAVE_CREATE,
XOP_HAVE_REMOVE,
// ... until here.
XOP_TYPE_NOOP,
// ... and here again from scratch.
XOP_EXPUNGE_NOOP,
XOP_EXPUNGE_SOLO_NOOP,
XOP_CREATE_NOOP,
XOP_REMOVE_NOOP,
)
#define OP_DFLT_TYPE (OP_NEW | OP_UPGRADE | OP_GONE | OP_FLAGS)
#define OP_MASK_TYPE (OP_DFLT_TYPE | OP_OLD) // Asserted in the target side ops
#define XOP_MASK_DIR (XOP_PUSH | XOP_PULL)
DECL_BIT_FORMATTER_FUNCTION(ops, OP)
#define OP_NEW (1<<0)
#define OP_RENEW (1<<1)
#define OP_DELETE (1<<2)
#define OP_FLAGS (1<<3)
#define OP_MASK_TYPE (OP_NEW|OP_RENEW|OP_DELETE|OP_FLAGS) /* asserted in the target ops */
#define OP_EXPUNGE (1<<4)
#define OP_CREATE (1<<5)
#define OP_REMOVE (1<<6)
#define XOP_PUSH (1<<8)
#define XOP_PULL (1<<9)
#define XOP_MASK_DIR (XOP_PUSH|XOP_PULL)
#define XOP_HAVE_TYPE (1<<10)
// The following must all have the same bit shift from the corresponding OP_* flags.
#define XOP_HAVE_EXPUNGE (1<<11)
#define XOP_HAVE_CREATE (1<<12)
#define XOP_HAVE_REMOVE (1<<13)
typedef struct channel_conf {
struct channel_conf *next;
const char *name;
store_conf_t *stores[2];
const char *boxes[2];
const char *sync_state;
char *sync_state;
string_list_t *patterns;
int ops[2];
int max_messages; // For near side only.
int expire_side;
signed char expire_unread;
char use_internal_date;
} channel_conf_t;
@ -72,18 +68,13 @@ extern channel_conf_t global_conf;
extern channel_conf_t *channels;
extern group_conf_t *groups;
extern uint BufferLimit;
extern int new_total[2], new_done[2];
extern int flags_total[2], flags_done[2];
extern int trash_total[2], trash_done[2];
extern int expunge_total[2], expunge_done[2];
extern const char *str_fn[2], *str_hl[2];
#define SYNC_OK 0 /* assumed to be 0 */
#define SYNC_FAIL 1
#define SYNC_BAD(fn) (4<<(fn))
#define SYNC_NOGOOD 16 /* internal */
#define SYNC_CANCELED 32 /* internal */
#define BOX_POSSIBLE -1
#define BOX_ABSENT 0

View File

@ -1,226 +0,0 @@
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
//
// mbsync - mailbox synchronizer
//
#include "sync_p.h"
static void
copy_msg_bytes( char **out_ptr, const char *in_buf, uint *in_idx, uint in_len, int in_cr, int out_cr )
{
char *out = *out_ptr;
uint idx = *in_idx;
if (out_cr != in_cr) {
if (out_cr) {
for (char c, pc = 0; idx < in_len; idx++) {
if (((c = in_buf[idx]) == '\n') && (pc != '\r'))
*out++ = '\r';
*out++ = c;
pc = c;
}
} else {
for (char c, pc = 0; idx < in_len; idx++) {
if (((c = in_buf[idx]) == '\n') && (pc == '\r'))
out--;
*out++ = c;
pc = c;
}
}
} else {
memcpy( out, in_buf + idx, in_len - idx );
out += in_len - idx;
idx = in_len;
}
*out_ptr = out;
*in_idx = idx;
}
char *
copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars )
{
char *in_buf = vars->data.data;
uint in_len = vars->data.len;
uint idx = 0, sbreak = 0, ebreak = 0, break2 = UINT_MAX;
uint lines = 0, hdr_crs = 0, bdy_crs = 0, app_cr = 0, extra = 0;
uint add_subj = 0, fix_tuid = 0, fix_subj = 0, fix_hdr = 0, end_hdr = 0;
if (vars->srec) {
for (;;) {
uint start = idx;
uint line_cr = 0;
uint got_line = 0;
char pc = 0;
while (idx < in_len) {
char c = in_buf[idx++];
if (c == '\n') {
if (pc == '\r')
line_cr = 1;
got_line = 1;
break;
}
pc = c;
}
if (!ebreak && starts_with_upper( in_buf + start, (int)(in_len - start), "X-TUID: ", 8 )) {
extra = (sbreak = start) - (ebreak = idx);
if (!vars->minimal)
break;
continue;
}
if (break2 == UINT_MAX && vars->minimal &&
starts_with_upper( in_buf + start, (int)(in_len - start), "SUBJECT:", 8 )) {
break2 = start + 8;
if (break2 < in_len && in_buf[break2] == ' ')
break2++;
}
hdr_crs += line_cr;
if (got_line) {
lines++;
if (idx - line_cr - 1 != start)
continue;
// Empty line => end of headers
} else {
// The line is incomplete.
if (pc == '\r')
idx--; // For simplicity, move back before trailing CR
if (idx != start) {
// The line is non-empty, so schedule completing it
fix_hdr = 1;
// ... and put our headers after it. (It would seem easier
// to prepend them, as then we could avoid the fixing - but
// the line might be a continuation. We could also prepend
// it to _all_ pre-exiting headers, but then we would risk
// masking an (incorrectly present) leading 'From ' header.)
start = idx;
}
end_hdr = 1;
}
if (!ebreak) {
sbreak = ebreak = start;
fix_tuid = fix_hdr;
fix_hdr = 0;
}
if (vars->minimal) {
in_len = idx;
if (break2 == UINT_MAX) {
break2 = start;
add_subj = 1;
fix_subj = fix_hdr;
fix_hdr = 0;
}
} else {
fix_hdr = 0;
end_hdr = 0;
}
break;
}
app_cr = out_cr && (!in_cr || hdr_crs || !lines);
if (fix_tuid || fix_subj || fix_hdr)
extra += app_cr + 1;
if (end_hdr)
extra += app_cr + 1;
extra += 8 + TUIDL + app_cr + 1;
}
if (out_cr != in_cr) {
for (char pc = 0; idx < in_len; idx++) {
char c = in_buf[idx];
if (c == '\n') {
lines++;
if (pc == '\r')
bdy_crs++;
}
pc = c;
}
extra -= hdr_crs + bdy_crs;
if (out_cr)
extra += lines;
}
uint dummy_msg_len = 0;
char dummy_msg_buf[256];
static const char dummy_pfx[] = "[placeholder] ";
static const char dummy_subj[] = "Subject: [placeholder] (No Subject)";
static const char dummy_msg[] =
"Having a size of %s, this message is over the MaxSize limit.%s"
"Flag it and sync again (Sync mode Upgrade) to fetch its real contents.%s";
static const char dummy_flag[] =
"%s"
"The original message is flagged as important.%s";
if (vars->minimal) {
char sz[32];
if (vars->msg->size < 1024000)
sprintf( sz, "%dKiB", (int)(vars->msg->size >> 10) );
else
sprintf( sz, "%.1fMiB", vars->msg->size / 1048576. );
const char *nl = app_cr ? "\r\n" : "\n";
dummy_msg_len = (uint)sprintf( dummy_msg_buf, dummy_msg, sz, nl, nl );
if (vars->data.flags & F_FLAGGED) {
vars->data.flags &= ~F_FLAGGED;
dummy_msg_len += (uint)sprintf( dummy_msg_buf + dummy_msg_len, dummy_flag, nl, nl );
}
extra += dummy_msg_len;
extra += add_subj ? strlen(dummy_subj) + app_cr + 1 : strlen(dummy_pfx);
}
#define ADD_NL() \
do { \
if (app_cr) \
*out_buf++ = '\r'; \
*out_buf++ = '\n'; \
} while (0)
vars->data.len = in_len + extra;
if (vars->data.len > INT_MAX) {
free( in_buf );
return "is too big after conversion";
}
char *out_buf = vars->data.data = nfmalloc( vars->data.len );
idx = 0;
if (vars->srec) {
if (break2 < sbreak) {
copy_msg_bytes( &out_buf, in_buf, &idx, break2, in_cr, out_cr );
memcpy( out_buf, dummy_pfx, strlen(dummy_pfx) );
out_buf += strlen(dummy_pfx);
}
copy_msg_bytes( &out_buf, in_buf, &idx, sbreak, in_cr, out_cr );
if (fix_tuid)
ADD_NL();
memcpy( out_buf, "X-TUID: ", 8 );
out_buf += 8;
memcpy( out_buf, vars->srec->tuid, TUIDL );
out_buf += TUIDL;
ADD_NL();
idx = ebreak;
if (break2 != UINT_MAX && break2 >= sbreak) {
copy_msg_bytes( &out_buf, in_buf, &idx, break2, in_cr, out_cr );
if (!add_subj) {
memcpy( out_buf, dummy_pfx, strlen(dummy_pfx) );
out_buf += strlen(dummy_pfx);
} else {
if (fix_subj)
ADD_NL();
memcpy( out_buf, dummy_subj, strlen(dummy_subj) );
out_buf += strlen(dummy_subj);
ADD_NL();
}
}
}
copy_msg_bytes( &out_buf, in_buf, &idx, in_len, in_cr, out_cr );
if (vars->minimal) {
if (end_hdr) {
if (fix_hdr)
ADD_NL();
ADD_NL();
}
memcpy( out_buf, dummy_msg_buf, dummy_msg_len );
}
free( in_buf );
return NULL;
}

View File

@ -1,116 +0,0 @@
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
//
// mbsync - mailbox synchronizer
//
#define DEBUG_FLAG DEBUG_SYNC
#include "sync.h"
#include "sync_p_enum.h"
BIT_ENUM(
S_DEAD, // ephemeral: the entry was killed and should be ignored
S_EXPIRE, // the entry is being expired (expire-side message removal scheduled)
S_EXPIRED, // the entry is expired (expire-side message removal confirmed)
S_NEXPIRE, // temporary: new expiration state
S_PENDING, // the entry is new and awaits propagation (possibly a retry)
S_DUMMY(2), // f/n message is only a placeholder
S_SKIPPED, // pre-1.4 legacy: the entry was not propagated (message is too big)
S_GONE(2), // ephemeral: f/n message has been expunged
S_DEL(2), // ephemeral: f/n message would be subject to non-selective expunge
S_DELETE, // ephemeral: flags propagation is a deletion
S_UPGRADE, // ephemeral: upgrading placeholder, do not apply MaxSize
S_PURGE, // ephemeral: placeholder is being nuked
S_PURGED, // ephemeral: placeholder was nuked
)
// This is the persistent status of the sync record, with regard to the journal.
#define S_LOGGED (S_EXPIRE | S_EXPIRED | S_PENDING | S_DUMMY(F) | S_DUMMY(N) | S_SKIPPED)
typedef struct sync_rec {
struct sync_rec *next;
/* string_list_t *keywords; */
uint uid[2];
message_t *msg[2];
ushort status;
uchar flags, pflags, aflags[2], dflags[2];
char tuid[TUIDL];
} sync_rec_t;
static_assert_bits(F, sync_rec_t, flags);
static_assert_bits(S, sync_rec_t, status);
typedef struct {
int t[2];
void (*cb)( int sts, void *aux ), *aux;
char *dname, *jname, *nname, *lname, *box_name[2];
FILE *jfp, *nfp;
sync_rec_t *srecs, **srecadd;
channel_conf_t *chan;
store_t *ctx[2];
driver_t *drv[2];
const char *orig_name[2];
message_t *msgs[2], *new_msgs[2];
uint_array_alloc_t trashed_msgs[2];
int state[2], lfd, ret, existing, replayed, any_expiring;
uint ref_count, nsrecs, opts[2];
uint new_pending[2], flags_pending[2], trash_pending[2];
uint maxuid[2]; // highest UID that was already propagated
uint oldmaxuid[2]; // highest UID that was already propagated before this run
uint newmaxuid[2]; // highest UID that is currently being propagated
uint uidval[2]; // UID validity value
uint newuidval[2]; // UID validity obtained from driver
uint finduid[2]; // TUID lookup makes sense only for UIDs >= this
uint maxxfuid; // highest expired UID on full side
uchar good_flags[2], bad_flags[2], can_crlf[2];
} sync_vars_t;
int prepare_state( sync_vars_t *svars );
int lock_state( sync_vars_t *svars );
int load_state( sync_vars_t *svars );
void save_state( sync_vars_t *svars );
void delete_state( sync_vars_t *svars );
void ATTR_PRINTFLIKE(2, 3) jFprintf( sync_vars_t *svars, const char *msg, ... );
#define JLOG_(pre_commit, log_fmt, log_args, dbg_fmt, ...) \
do { \
if (pre_commit && !(DFlags & FORCEJOURNAL)) { \
debug( "-> (log: " log_fmt ") (" dbg_fmt ")\n", __VA_ARGS__ ); \
} else { \
debug( "-> log: " log_fmt " (" dbg_fmt ")\n", __VA_ARGS__ ); \
jFprintf( svars, log_fmt "\n", deparen(log_args) ); \
} \
} while (0)
#define JLOG3(pre_commit, log_fmt, log_args, dbg_fmt) \
JLOG_(pre_commit, log_fmt, log_args, dbg_fmt, deparen(log_args))
#define JLOG4(pre_commit, log_fmt, log_args, dbg_fmt, dbg_args) \
JLOG_(pre_commit, log_fmt, log_args, dbg_fmt, deparen(log_args), deparen(dbg_args))
#define JLOG_SEL(_1, _2, _3, _4, x, ...) x
#define JLOG(...) JLOG_SEL(__VA_ARGS__, JLOG4, JLOG3, NO_JLOG2, NO_JLOG1)(0, __VA_ARGS__)
#define PC_JLOG(...) JLOG_SEL(__VA_ARGS__, JLOG4, JLOG3, NO_JLOG2, NO_JLOG1)(1, __VA_ARGS__)
void assign_uid( sync_vars_t *svars, sync_rec_t *srec, int t, uint uid );
#define ASSIGN_UID(srec, t, nuid, ...) \
do { \
JLOG( "%c %u %u %u", ("<>"[t], srec->uid[F], srec->uid[N], nuid), __VA_ARGS__ ); \
assign_uid( svars, srec, t, nuid ); \
} while (0)
void assign_tuid( sync_vars_t *svars, sync_rec_t *srec );
int match_tuids( sync_vars_t *svars, int t, message_t *msgs );
sync_rec_t *upgrade_srec( sync_vars_t *svars, sync_rec_t *srec, int t );
typedef struct copy_vars {
void (*cb)( int sts, uint uid, struct copy_vars *vars );
void *aux;
sync_rec_t *srec; /* also ->tuid */
message_t *msg;
msg_data_t data;
int minimal;
} copy_vars_t;
char *copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars );

View File

@ -1,643 +0,0 @@
// SPDX-FileCopyrightText: 2004-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
//
// mbsync - mailbox synchronizer
//
#define DEBUG_FLAG DEBUG_SYNC
#include "sync_p.h"
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <sys/stat.h>
#define JOURNAL_VERSION "5"
const char *str_fn[] = { "far side", "near side" }, *str_hl[] = { "push", "pull" };
BIT_FORMATTER_FUNCTION(sts, S)
static char *
clean_strdup( const char *s )
{
char *cs = nfstrdup( s );
for (uint i = 0; cs[i]; i++)
if (cs[i] == '/')
cs[i] = '!';
return cs;
}
int
prepare_state( sync_vars_t *svars )
{
channel_conf_t *chan = svars->chan;
if (!strcmp( chan->sync_state ? chan->sync_state : global_conf.sync_state, "*" )) {
const char *path = svars->drv[N]->get_box_path( svars->ctx[N] );
if (!path) {
error( "Error: store '%s' does not support in-box sync state\n", chan->stores[N]->name );
return 0;
}
nfasprintf( &svars->dname, "%s/." EXE "state", path );
} else {
char *cnname = clean_strdup( svars->box_name[N] );
if (chan->sync_state) {
nfasprintf( &svars->dname, "%s%s", chan->sync_state, cnname );
} else {
char c = FieldDelimiter;
char *cfname = clean_strdup( svars->box_name[F] );
nfasprintf( &svars->dname, "%s%c%s%c%s_%c%s%c%s", global_conf.sync_state,
c, chan->stores[F]->name, c, cfname, c, chan->stores[N]->name, c, cnname );
free( cfname );
}
free( cnname );
char *s;
if (!(s = strrchr( svars->dname, '/' ))) {
error( "Error: invalid SyncState location '%s'\n", svars->dname );
return 0;
}
// Note that this may be shorter than the configuration value,
// as that may contain a filename prefix.
*s = 0;
if (mkdir_p( svars->dname, s - svars->dname )) {
sys_error( "Error: cannot create SyncState directory '%s'", svars->dname );
return 0;
}
*s = '/';
}
nfasprintf( &svars->jname, "%s.journal", svars->dname );
nfasprintf( &svars->nname, "%s.new", svars->dname );
nfasprintf( &svars->lname, "%s.lock", svars->dname );
return 1;
}
int
lock_state( sync_vars_t *svars )
{
struct flock lck;
if (DFlags & DRYRUN)
return 1;
if (svars->lfd >= 0)
return 1;
memset( &lck, 0, sizeof(lck) );
#if SEEK_SET != 0
lck.l_whence = SEEK_SET;
#endif
#if F_WRLCK != 0
lck.l_type = F_WRLCK;
#endif
if ((svars->lfd = open( svars->lname, O_WRONLY | O_CREAT, 0666 )) < 0) {
sys_error( "Error: cannot create lock file %s", svars->lname );
return 0;
}
if (fcntl( svars->lfd, F_SETLK, &lck )) {
error( "Error: channel :%s:%s-:%s:%s is locked\n",
svars->chan->stores[F]->name, svars->orig_name[F], svars->chan->stores[N]->name, svars->orig_name[N] );
close( svars->lfd );
svars->lfd = -1;
return 0;
}
return 1;
}
static uchar
parse_flags( const char *buf )
{
uchar flags = 0;
for (uint i = 0, d = 0; i < as(MsgFlags); i++) {
if (buf[d] == MsgFlags[i]) {
flags |= (1 << i);
d++;
}
}
return flags;
}
int
load_state( sync_vars_t *svars )
{
sync_rec_t *srec, *nsrec;
FILE *jfp;
uint ll;
uint maxxnuid = 0;
char fbuf[16]; // enlarge when support for keywords is added
char buf[128], buf1[64], buf2[64];
int xt = svars->chan->expire_side;
if ((jfp = fopen( svars->dname, "r" ))) {
if (!lock_state( svars ))
goto jbail;
debug( "reading sync state %s ...\n", svars->dname );
int line = 0;
while (fgets( buf, sizeof(buf), jfp )) {
line++;
if (!(ll = strlen( buf )) || buf[ll - 1] != '\n') {
error( "Error: incomplete sync state header entry at %s:%d\n", svars->dname, line );
jbail:
fclose( jfp );
return 0;
}
if (ll == 1)
goto gothdr;
if (line == 1 && isdigit( buf[0] )) { // Pre-1.1 legacy
if (sscanf( buf, "%63s %63s", buf1, buf2 ) != 2 ||
sscanf( buf1, "%u:%u", &svars->uidval[F], &svars->maxuid[F] ) < 2 ||
sscanf( buf2, "%u:%u:%u", &svars->uidval[N], &maxxnuid, &svars->maxuid[N] ) < 3) {
error( "Error: invalid sync state header in %s\n", svars->dname );
goto jbail;
}
if (maxxnuid && xt != N)
goto sidefail;
goto gothdr;
}
uint uid;
if (sscanf( buf, "%63s %u", buf1, &uid ) != 2) {
error( "Error: malformed sync state header entry at %s:%d\n", svars->dname, line );
goto jbail;
}
if (!strcmp( buf1, "FarUidValidity" ) || !strcmp( buf1, "MasterUidValidity" ) /* Pre-1.4 legacy */) {
svars->uidval[F] = uid;
} else if (!strcmp( buf1, "NearUidValidity" ) || !strcmp( buf1, "SlaveUidValidity" ) /* Pre-1.4 legacy */) {
svars->uidval[N] = uid;
} else if (!strcmp( buf1, "MaxPulledUid" )) {
svars->maxuid[F] = uid;
} else if (!strcmp( buf1, "MaxPushedUid" )) {
svars->maxuid[N] = uid;
} else if (!strcmp( buf1, "MaxExpiredFarUid" ) || !strcmp( buf1, "MaxExpiredMasterUid" ) /* Pre-1.4 legacy */) {
if (xt != N) {
sidefail:
error( "Error: state file %s does not match ExpireSide setting\n", svars->dname );
goto jbail;
}
svars->maxxfuid = uid;
} else if (!strcmp( buf1, "MaxExpiredNearUid" )) {
if (xt != F)
goto sidefail;
svars->maxxfuid = uid;
} else if (!strcmp( buf1, "MaxExpiredSlaveUid" )) { // Pre-1.3 legacy
if (xt != N)
goto sidefail;
maxxnuid = uid;
} else {
error( "Error: unrecognized sync state header entry at %s:%d\n", svars->dname, line );
goto jbail;
}
}
error( "Error: unterminated sync state header in %s\n", svars->dname );
goto jbail;
gothdr:
debug( " uid val %u/%u, max uid %u/%u, max expired %u\n",
svars->uidval[F], svars->uidval[N], svars->maxuid[F], svars->maxuid[N], svars->maxxfuid );
while (fgets( buf, sizeof(buf), jfp )) {
line++;
if (!(ll = strlen( buf )) || buf[--ll] != '\n') {
error( "Error: incomplete sync state entry at %s:%d\n", svars->dname, line );
goto jbail;
}
buf[ll] = 0;
fbuf[0] = 0;
uint t1, t2;
if (sscanf( buf, "%u %u %15s", &t1, &t2, fbuf ) < 2) {
error( "Error: invalid sync state entry at %s:%d\n", svars->dname, line );
goto jbail;
}
srec = nfzalloc( sizeof(*srec) );
srec->uid[F] = t1;
srec->uid[N] = t2;
char *s = fbuf;
if (*s == '<') {
s++;
srec->status = S_DUMMY(F);
} else if (*s == '>') {
s++;
srec->status = S_DUMMY(N);
}
if (*s == '^') { // Pre-1.4 legacy
s++;
srec->status = S_SKIPPED;
} else if (*s == '~' || *s == 'X' /* Pre-1.3 legacy */) {
s++;
srec->status = S_EXPIRE | S_EXPIRED;
} else if (srec->uid[F] == (uint)-1) { // Pre-1.3 legacy
srec->uid[F] = 0;
srec->status = S_SKIPPED;
} else if (srec->uid[N] == (uint)-1) {
srec->uid[N] = 0;
srec->status = S_SKIPPED;
}
srec->flags = parse_flags( s );
debug( " entry (%u,%u,%s,%s)\n", srec->uid[F], srec->uid[N],
fmt_flags( srec->flags ).str, fmt_sts( srec->status ).str );
*svars->srecadd = srec;
svars->srecadd = &srec->next;
svars->nsrecs++;
}
fclose( jfp );
svars->existing = 1;
} else {
if (errno != ENOENT) {
sys_error( "Error: cannot read sync state %s", svars->dname );
return 0;
}
svars->existing = 0;
}
// This is legacy support for pre-1.3 sync states.
if (maxxnuid) {
uint minwuid = UINT_MAX;
for (srec = svars->srecs; srec; srec = srec->next) {
if ((srec->status & (S_DEAD | S_SKIPPED | S_PENDING)) || !srec->uid[F])
continue;
if (srec->status & S_EXPIRED) {
if (!srec->uid[N]) {
// The expired message was already gone.
continue;
}
// The expired message was not expunged yet, so re-examine it.
// This will happen en masse, so just extend the bulk fetch.
} else {
if (srec->uid[N] && maxxnuid >= srec->uid[N]) {
// The non-expired message is in the generally expired range,
// so don't make it contribute to the bulk fetch.
continue;
}
// Usual non-expired message.
}
if (minwuid > srec->uid[F])
minwuid = srec->uid[F];
}
svars->maxxfuid = minwuid - 1;
}
svars->newmaxuid[F] = svars->maxuid[F];
svars->newmaxuid[N] = svars->maxuid[N];
int line = 0;
if ((jfp = fopen( svars->jname, "r" ))) {
if (!lock_state( svars ))
goto jbail;
struct stat st;
if (!stat( svars->nname, &st ) && fgets( buf, sizeof(buf), jfp )) {
debug( "recovering journal ...\n" );
if (!(ll = strlen( buf )) || buf[--ll] != '\n') {
error( "Error: incomplete journal header in %s\n", svars->jname );
goto jbail;
}
buf[ll] = 0;
if (!equals( buf, (int)ll, JOURNAL_VERSION, strlen(JOURNAL_VERSION) )) {
error( "Error: incompatible journal version"
" (got %s, expected " JOURNAL_VERSION ")\n", buf );
goto jbail;
}
srec = NULL;
line = 1;
while (fgets( buf, sizeof(buf), jfp )) {
line++;
if (!(ll = strlen( buf )) || buf[--ll] != '\n') {
error( "Error: incomplete journal entry at %s:%d\n", svars->jname, line );
goto jbail;
}
buf[ll] = 0;
char c;
int tn, bad;
uint t1, t2, t3, t4;
switch ((c = buf[0])) {
case '#':
tn = 0;
bad = (sscanf( buf + 2, "%u %u %n", &t1, &t2, &tn ) < 2) || !tn || (ll - (uint)tn != TUIDL + 2);
break;
case 'N':
case 'F':
case 'T':
case 'P':
case '+':
case '&':
case '-':
case '_':
case '|':
bad = sscanf( buf + 2, "%u %u", &t1, &t2 ) != 2;
break;
case '<':
case '>':
case '*':
case '%':
case '~':
case '^':
bad = sscanf( buf + 2, "%u %u %u", &t1, &t2, &t3 ) != 3;
break;
case '$':
bad = sscanf( buf + 2, "%u %u %u %u", &t1, &t2, &t3, &t4 ) != 4;
break;
default:
error( "Error: unrecognized journal entry at %s:%d\n", svars->jname, line );
goto jbail;
}
if (bad) {
error( "Error: malformed journal entry at %s:%d\n", svars->jname, line );
goto jbail;
}
if (c == 'N') {
svars->maxuid[t1] = svars->newmaxuid[t1] = t2;
debug( " maxuid of %s now %u\n", str_fn[t1], t2 );
} else if (c == 'F') {
svars->finduid[t1] = t2;
debug( " saved UIDNEXT of %s now %u\n", str_fn[t1], t2 );
} else if (c == 'T') {
*uint_array_append( &svars->trashed_msgs[t1] ) = t2;
debug( " trashed %u from %s\n", t2, str_fn[t1] );
} else if (c == '|') {
svars->uidval[F] = t1;
svars->uidval[N] = t2;
debug( " UIDVALIDITYs now %u/%u\n", t1, t2 );
} else if (c == '+') {
srec = nfzalloc( sizeof(*srec) );
srec->uid[F] = t1;
srec->uid[N] = t2;
if (svars->newmaxuid[F] < t1)
svars->newmaxuid[F] = t1;
if (svars->newmaxuid[N] < t2)
svars->newmaxuid[N] = t2;
debug( " new entry(%u,%u)\n", t1, t2 );
srec->status = S_PENDING;
*svars->srecadd = srec;
svars->srecadd = &srec->next;
svars->nsrecs++;
} else {
for (nsrec = srec; srec; srec = srec->next)
if (srec->uid[F] == t1 && srec->uid[N] == t2)
goto syncfnd;
for (srec = svars->srecs; srec != nsrec; srec = srec->next)
if (srec->uid[F] == t1 && srec->uid[N] == t2)
goto syncfnd;
error( "Error: journal entry at %s:%d refers to non-existing sync state entry\n", svars->jname, line );
goto jbail;
syncfnd:
debugn( " entry(%u,%u) ", srec->uid[F], srec->uid[N] );
switch (c) {
case '-':
debug( "killed\n" );
srec->status = S_DEAD;
break;
case '#':
memcpy( srec->tuid, buf + tn + 2, TUIDL );
debug( "TUID now %." stringify(TUIDL) "s\n", srec->tuid );
break;
case '&':
debug( "TUID %." stringify(TUIDL) "s lost\n", srec->tuid );
srec->tuid[0] = 0;
break;
case '<':
debug( "far side now %u\n", t3 );
assign_uid( svars, srec, F, t3 );
break;
case '>':
debug( "near side now %u\n", t3 );
assign_uid( svars, srec, N, t3 );
break;
case '*':
srec->flags = (uchar)t3;
debug( "flags now %s\n", fmt_lone_flags( t3 ).str );
break;
case 'P':
debug( "deleted dummy\n" );
srec->aflags[F] = srec->aflags[N] = 0; // Clear F_DELETED
srec->status = (srec->status & ~S_PURGE) | S_PURGED;
break;
case '%':
srec->pflags = (uchar)t3;
debug( "pending flags now %s\n", fmt_lone_flags( t3 ).str );
break;
case '~':
srec->status = (srec->status & ~S_LOGGED) | t3;
if ((srec->status & S_EXPIRED) && svars->maxxfuid < srec->uid[xt^1])
svars->maxxfuid = srec->uid[xt^1];
debug( "status now %s\n", fmt_sts( srec->status ).str );
break;
case '_':
debug( "has placeholder now\n" );
srec->status = S_PENDING | (!srec->uid[F] ? S_DUMMY(F) : S_DUMMY(N));
break;
case '^':
tn = (srec->status & S_DUMMY(F)) ? F : N;
srec->pflags = (uchar)t3;
debug( "upgrading %s placeholder, dummy's flags %s\n",
str_fn[tn], fmt_lone_flags( t3 ).str );
srec = upgrade_srec( svars, srec, tn );
break;
case '$':
tn = !srec->uid[F] ? F : N;
srec->aflags[tn] = (uchar)t3;
srec->dflags[tn] = (uchar)t4;
debug( "flag update for %s now +%s -%s\n",
str_fn[tn], fmt_flags( t3 ).str, fmt_flags( t4 ).str );
break;
default:
assert( !"Unhandled journal entry" );
}
}
}
}
fclose( jfp );
sort_uint_array( svars->trashed_msgs[F].array );
sort_uint_array( svars->trashed_msgs[N].array );
} else {
if (errno != ENOENT) {
sys_error( "Error: cannot read journal %s", svars->jname );
return 0;
}
}
svars->replayed = line;
return 1;
}
static void
create_state( sync_vars_t *svars )
{
if (!(svars->nfp = fopen( svars->nname, "w" ))) {
sys_error( "Error: cannot create new sync state %s", svars->nname );
exit( 1 );
}
}
void
jFprintf( sync_vars_t *svars, const char *msg, ... )
{
va_list va;
if (!svars->jfp) {
if (DFlags & DRYRUN)
goto dryout;
create_state( svars );
if (!(svars->jfp = fopen( svars->jname, svars->replayed ? "a" : "w" ))) {
sys_error( "Error: cannot create journal %s", svars->jname );
exit( 1 );
}
setlinebuf( svars->jfp );
if (!svars->replayed)
Fprintf( svars->jfp, JOURNAL_VERSION "\n" );
}
va_start( va, msg );
vFprintf( svars->jfp, msg, va );
va_end( va );
dryout:
countStep();
JCount++;
}
void
save_state( sync_vars_t *svars )
{
// If no change was made, the state is also unmodified.
if (!svars->jfp && !svars->replayed)
return;
// jfp is NULL in this case anyway, but we might have replayed.
if (DFlags & DRYRUN)
return;
if (!svars->nfp)
create_state( svars );
Fprintf( svars->nfp,
"FarUidValidity %u\nNearUidValidity %u\nMaxPulledUid %u\nMaxPushedUid %u\n",
svars->uidval[F], svars->uidval[N], svars->maxuid[F], svars->maxuid[N] );
if (svars->maxxfuid)
Fprintf( svars->nfp,
svars->chan->expire_side == N ? "MaxExpiredFarUid %u\n" : "MaxExpiredNearUid %u\n",
svars->maxxfuid );
Fprintf( svars->nfp, "\n" );
for (sync_rec_t *srec = svars->srecs; srec; srec = srec->next) {
if (srec->status & S_DEAD)
continue;
Fprintf( svars->nfp, "%u %u %s%s%s\n", srec->uid[F], srec->uid[N],
(srec->status & S_DUMMY(F)) ? "<" : (srec->status & S_DUMMY(N)) ? ">" : "",
(srec->status & S_SKIPPED) ? "^" : (srec->status & S_EXPIRED) ? "~" : "",
fmt_flags( srec->flags ).str );
}
Fclose( svars->nfp, 1 );
if (svars->jfp)
Fclose( svars->jfp, 0 );
if (!(DFlags & KEEPJOURNAL)) {
// Order is important!
if (rename( svars->nname, svars->dname ))
warn( "Warning: cannot commit sync state %s\n", svars->dname );
else if (unlink( svars->jname ))
warn( "Warning: cannot delete journal %s\n", svars->jname );
}
}
void
delete_state( sync_vars_t *svars )
{
if (DFlags & DRYRUN)
return;
unlink( svars->nname );
unlink( svars->jname );
if (unlink( svars->dname ) || unlink( svars->lname )) {
sys_error( "Error: channel %s: sync state cannot be deleted", svars->chan->name );
svars->ret = SYNC_FAIL;
}
}
void
assign_uid( sync_vars_t *svars, sync_rec_t *srec, int t, uint uid )
{
srec->uid[t] = uid;
if (uid == svars->newmaxuid[t] + 1)
svars->newmaxuid[t] = uid;
if (uid) {
if (srec->status & S_UPGRADE) {
srec->flags = (srec->flags | srec->aflags[t]) & ~srec->dflags[t];
srec->aflags[t] = srec->dflags[t] = 0; // Cleanup after journal replay
} else {
srec->flags = srec->pflags;
}
}
srec->status &= ~(S_PENDING | S_UPGRADE);
srec->tuid[0] = 0;
}
void
assign_tuid( sync_vars_t *svars, sync_rec_t *srec )
{
for (uint i = 0; i < TUIDL; i++) {
uchar c = arc4_getbyte() & 0x3f;
srec->tuid[i] = (char)(c < 26 ? c + 'A' : c < 52 ? c + 'a' - 26 :
c < 62 ? c + '0' - 52 : c == 62 ? '+' : '/');
}
JLOG( "# %u %u %." stringify(TUIDL) "s", (srec->uid[F], srec->uid[N], srec->tuid), "new TUID" );
}
int
match_tuids( sync_vars_t *svars, int t, message_t *msgs )
{
message_t *tmsg, *ntmsg = NULL;
const char *diag;
int num_lost = 0;
for (sync_rec_t *srec = svars->srecs; srec; srec = srec->next) {
if (srec->status & S_DEAD)
continue;
if (!srec->uid[t] && srec->tuid[0]) {
debug( "pair(%u,%u) TUID %." stringify(TUIDL) "s\n", srec->uid[F], srec->uid[N], srec->tuid );
for (tmsg = ntmsg; tmsg; tmsg = tmsg->next) {
if (tmsg->status & M_DEAD)
continue;
if (tmsg->tuid[0] && !memcmp( tmsg->tuid, srec->tuid, TUIDL )) {
diag = (tmsg == ntmsg) ? "adjacently" : "after gap";
goto mfound;
}
}
for (tmsg = msgs; tmsg != ntmsg; tmsg = tmsg->next) {
if (tmsg->status & M_DEAD)
continue;
if (tmsg->tuid[0] && !memcmp( tmsg->tuid, srec->tuid, TUIDL )) {
diag = "after reset";
goto mfound;
}
}
JLOG( "& %u %u", (srec->uid[F], srec->uid[N]), "TUID lost" );
// Note: status remains S_PENDING.
srec->tuid[0] = 0;
num_lost++;
continue;
mfound:
tmsg->srec = srec;
srec->msg[t] = tmsg;
ntmsg = tmsg->next;
ASSIGN_UID( srec, t, tmsg->uid, "TUID matched %s", diag );
}
}
return num_lost;
}
sync_rec_t *
upgrade_srec( sync_vars_t *svars, sync_rec_t *srec, int t )
{
// Create an entry and append it to the current one.
sync_rec_t *nsrec = nfzalloc( sizeof(*nsrec) );
nsrec->next = srec->next;
srec->next = nsrec;
if (svars->srecadd == &srec->next)
svars->srecadd = &nsrec->next;
svars->nsrecs++;
// Move the placeholder to the new entry.
nsrec->uid[t] = srec->uid[t];
srec->uid[t] = 0;
if (srec->msg[t]) { // NULL during journal replay; is assigned later.
nsrec->msg[t] = srec->msg[t];
nsrec->msg[t]->srec = nsrec;
srec->msg[t] = NULL;
}
// Mark the original entry for upgrade.
srec->status = (srec->status & ~(S_DUMMY(F) | S_DUMMY(N))) | S_PENDING | S_UPGRADE;
// Mark the placeholder for nuking.
nsrec->status = S_PURGE | (srec->status & (S_DEL(F) | S_DEL(N)));
nsrec->aflags[t] = F_DELETED;
return nsrec;
}

View File

@ -1,166 +0,0 @@
// SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later
//
// isync test suite
//
#include "imap_p.h"
static imap_messages_t smsgs;
// from driver.c
void
free_generic_messages( message_t *msgs )
{
message_t *tmsg;
for (; msgs; msgs = tmsg) {
tmsg = msgs->next;
// free( msgs->msgid );
free( msgs );
}
}
static void
dump_messages( void )
{
print( "=>" );
uint seq = 0;
for (imap_message_t *msg = smsgs.head; msg; msg = msg->next) {
seq += msg->seq;
if (msg->status & M_DEAD)
print( " (%u:%u)", seq, msg->uid );
else
print( " %u:%u", seq, msg->uid );
}
print( "\n" );
}
static void
init( uint *in )
{
reset_imap_messages( &smsgs );
for (; *in; in++) {
imap_message_t *msg = imap_new_msg( &smsgs );
msg->seq = *in;
// We (ab)use the initial sequence number as the UID. That's not
// exactly realistic, but it's valid, and saves us redundant data.
msg->uid = *in;
}
}
static void
modify( uint *in )
{
for (; *in; in++) {
imap_expunge_msg( &smsgs, *in );
#ifdef DEBUG_IMAP_MSGS
dump_messages();
#endif
}
}
static void
verify( uint *in, const char *name )
{
int fails = 0;
imap_message_t *msg = smsgs.head;
for (;;) {
if (msg && *in && msg->uid == *in) {
if (msg->status & M_DEAD) {
printf( "*** %s: message %u is dead\n", name, msg->uid );
fails++;
} else {
assert( msg->seq );
}
msg = msg->next;
in++;
} else if (*in && (!msg || msg->uid > *in)) {
printf( "*** %s: message %u is missing\n", name, *in );
fails++;
in++;
} else if (msg) {
if (!(msg->status & M_DEAD)) {
printf( "*** %s: excess message %u\n", name, msg->uid );
fails++;
}
msg = msg->next;
} else {
assert( !*in );
break;
}
}
if (fails)
dump_messages();
}
static void
test( uint *ex, uint *out, const char *name )
{
printf( "test %s ...\n", name );
modify( ex );
verify( out, name );
}
int
main( void )
{
static uint arr_0[] = { 0 };
static uint arr_1[] = { 1, 0 };
static uint full_in[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 0 };
init( full_in );
#if 0
static uint nop[] = { 0 };
static uint nop_out[] = { /* 1, */ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, /* 17, */ 18 /*!*/, 0 };
test( nop, nop_out, "self-test" );
#endif
static uint full_ex_fw1[] = { 18, 13, 13, 13, 1, 1, 1, 0 };
static uint full_out_fw1[] = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 17, 0 };
test( full_ex_fw1, full_out_fw1, "full, forward 1" );
static uint full_ex_fw2[] = { 10, 10, 0 };
static uint full_out_fw2[] = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 0 };
test( full_ex_fw2, full_out_fw2, "full, forward 2" );
init( full_in );
static uint full_ex_bw1[] = { 18, 17, 16, 15, 14, 13, 5, 4, 3, 0 };
static uint full_out_bw1[] = { 1, 2, 6, 7, 8, 9, 10, 11, 12, 0 };
test( full_ex_bw1, full_out_bw1, "full, backward 1" );
static uint full_ex_bw2[] = { 2, 1, 0 };
static uint full_out_bw2[] = { 6, 7, 8, 9, 10, 11, 12, 0 };
test( full_ex_bw2, full_out_bw2, "full, backward 2" );
static uint hole_wo1_in[] = { 10, 11, 12, 20, 21, 31, 32, 33, 34, 35, 36, 37, 0 };
init( hole_wo1_in );
static uint hole_wo1_ex_1[] = { 31, 30, 29, 28, 22, 21, 11, 2, 1, 0 };
static uint hole_wo1_out_1[] = { 10, 12, 20, 32, 33, 34, 35, 36, 37, 0 };
test( hole_wo1_ex_1, hole_wo1_out_1, "hole w/o 1, backward" );
init( hole_wo1_in );
static uint hole_wo1_ex_2[] = { 1, 1, 9, 18, 18, 23, 23, 23, 23, 0 };
test( hole_wo1_ex_2, hole_wo1_out_1, "hole w/o 1, forward" );
test( arr_1, hole_wo1_out_1, "hole w/o 1, forward 2" );
static uint hole_wo1_ex_4[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 };
static uint hole_wo1_out_4[] = { 37, 0 };
test( hole_wo1_ex_4, hole_wo1_out_4, "hole w/o 1, forward 3" );
test( arr_1, arr_0, "hole w/o 1, forward 4" );
test( arr_1, arr_0, "hole w/o 1, forward 5" );
static uint hole_w1_in[] = { 1, 10, 11, 12, 0 };
init( hole_w1_in );
static uint hole_w1_ex_1[] = { 11, 10, 2, 1, 0 };
static uint hole_w1_out_1[] = { 12, 0 };
test( hole_w1_ex_1, hole_w1_out_1, "hole w/ 1, backward" );
test( arr_1, hole_w1_out_1, "hole w/ 1, backward 2" );
init( hole_w1_in );
static uint hole_w1_ex_2[] = { 1, 1, 8, 8, 0 };
test( hole_w1_ex_2, hole_w1_out_1, "hole w/ 1, forward" );
static uint hole_w1_ex_4[] = { 1, 1, 1, 1, 1, 1, 1, 0 };
static uint hole_w1_out_4[] = { 12, 0 };
test( hole_w1_ex_4, hole_w1_out_4, "hole w/ 1, forward 2" );
test( arr_1, arr_0, "hole w/ 1, forward 3" );
test( arr_1, arr_0, "hole w/ 1, forward 4" );
return 0;
}

View File

@ -1,116 +0,0 @@
// SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later
//
// isync test suite
//
#include "imap_p.h"
static struct {
const char *utf8, *utf7;
} data[] = {
{ u8"", "" },
{ u8"1", "1" },
{ u8"word", "word" },
{ u8"&", "&-" },
{ NULL, "&" },
{ NULL, "&-&" },
{ u8"&&", "&-&-" },
{ u8"1&1", "1&-1" },
{ u8"&1&", "&-1&-" },
{ u8"\t", "&AAk-" },
{ NULL, "&AAk" },
{ NULL, "&AA-" },
{ NULL, "&*Ak-" },
{ NULL, "&&-" },
{ u8"m\x7f""ll", "m&AH8-ll" },
{ u8"\t&", "&AAk-&-" },
{ u8"\t&\t", "&AAk-&-&AAk-" },
{ u8"&\t", "&-&AAk-" },
{ u8"&\t&", "&-&AAk-&-" },
{ u8"ä", "&AOQ-" },
{ u8"\x83\x84", NULL },
{ u8"\xc3\xc4", NULL },
{ u8"\xc3", NULL },
{ u8"äö", "&AOQA9g-" },
{ u8"äöü", "&AOQA9gD8-" },
{ u8"", "&HgA-" },
{ u8"\xe1\xc8\x80", NULL },
{ u8"\xe1\xb8\xf0", NULL },
{ u8"\xe1\xb8", NULL },
{ u8"\xe1", NULL },
{ u8"Ḁḁ", "&HgAeAQ-" },
{ u8"😂", "&2D3eAg-" },
{ u8"\xf8\x9f\x98\x82", NULL },
{ u8"\xf0\xcf\x98\x82", NULL },
{ u8"\xf0\x9f\xd8\x82", NULL },
{ u8"\xf0\x9f\x98\xe2", NULL },
{ u8"\xf0\x9f\x98", NULL },
{ u8"\xf0\x9f", NULL },
{ u8"\xf0", NULL },
{ NULL, "&2D0-" },
{ u8"😈😎", "&2D3eCNg93g4-" },
{ u8"müll", "m&APw-ll" },
{ u8"", "m&APw-" },
{ u8"über", "&APw-ber" },
};
int
main( void )
{
int ret = 0;
for (uint i = 0; i < as(data); i++) {
if (!data[i].utf8)
continue;
xprintf( "To UTF-7 \"%s\" (\"%!s\") ...\n", data[i].utf8, data[i].utf8 );
char *utf7 = imap_utf8_to_utf7( data[i].utf8 );
if (utf7) {
if (!data[i].utf7) {
xprintf( "Unexpected success: \"%s\" (\"%!s\")\n", utf7, utf7 );
ret = 1;
} else if (strcmp( utf7, data[i].utf7 )) {
xprintf( "Mismatch, got \"%s\" (\"%!s\"), want \"%!s\"\n",
utf7, utf7, data[i].utf7 );
ret = 1;
}
free( utf7 );
} else {
if (data[i].utf7) {
xprintf( "Conversion failure.\n" );
ret = 1;
}
}
}
for (uint i = 0; i < as(data); i++) {
if (!data[i].utf7)
continue;
xprintf( "From UTF-7 \"%!s\" ...\n", data[i].utf7 );
int utf7len = strlen( data[i].utf7 );
char utf8buf[1000];
int utf8len = imap_utf7_to_utf8( data[i].utf7, utf7len, utf8buf );
if (utf8len >= 0) {
if (!data[i].utf8) {
xprintf( "Unexpected success: \"%.*s\" (\"%.*!s\")\n",
utf8len, utf8buf, utf8len, utf8buf );
ret = 1;
} else {
int wantlen = strlen( data[i].utf8 );
if (utf8len != wantlen || memcmp( utf8buf, data[i].utf8, utf8len )) {
xprintf( "Mismatch, got \"%.*s\" (\"%.*!s\"), want \"%s\" (\"%!s\")\n",
utf8len, utf8buf, utf8len, utf8buf, data[i].utf8, data[i].utf8 );
ret = 1;
}
}
assert( utf8len < utf7len * 9 / 8 + 1 );
} else {
if (data[i].utf8) {
xprintf( "Conversion failure.\n" );
ret = 1;
}
}
}
return ret;
}

View File

@ -1,306 +0,0 @@
// SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later
//
// isync test suite
//
#include "sync_p.h"
#define TUID "one two tuid"
static_assert( sizeof(TUID) - 1 == TUIDL, "TUID size mismatch" );
static size_t
strip_cr( char *buf )
{
size_t i, j = 0;
char c, pc = 0;
for (i = 0; (c = buf[i]); i++) {
if (c == '\n' && pc == '\r')
j--;
buf[j++] = c;
pc = c;
}
buf[j] = 0;
return j;
}
#define NL_UNIX 0
#define NL_ANY 1
#define AS_IS 0
#define ADD_TUID 1
#define FULL 0
#define MINIMAL 1
#define REGULAR 0
#define FLAGGED 1
#define OK_HEADER 0
#define PARTIAL_HEADER 1
#define BIG_SIZE 2345687
#define BIG_SIZE_STR "2.2MiB"
#define SEP "============="
static void
test( const char *name, const char *in, int scr, int rscr, const char *out, int tcr, int rtcr, int add_tuid, int minimal, int flagged )
{
assert( !rscr || scr );
assert( !rtcr || tcr );
assert( !minimal || add_tuid );
assert( !flagged || minimal );
printf( "Testing %s, %s (%s) => %s (%s)%s%s%s ...\n", name,
rscr ? "CRLF" : "LF", scr ? "Any" : "Unix", rtcr ? "CRLF" : "LF", tcr ? "Any" : "Unix",
add_tuid ? ", add TUID" : "", minimal ? ", minimal" : "", flagged ? ", flagged" : "" );
sync_rec_t srec;
message_t msg;
copy_vars_t vars;
vars.minimal = minimal;
if (add_tuid) {
vars.srec = &srec;
memcpy( vars.srec->tuid, TUID, TUIDL );
if (minimal) {
vars.msg = &msg;
vars.msg->size = BIG_SIZE;
vars.data.flags = flagged ? F_FLAGGED : 0;
}
} else {
vars.srec = 0;
}
vars.data.data = strdup( in );
vars.data.len = rscr ? strlen( in ) : strip_cr( vars.data.data );
char *orig = strdup( vars.data.data );
const char *err = copy_msg_convert( scr, tcr, &vars );
if (err) {
printf( "FAIL: %s!\n", err );
exit( 1 );
}
if (!rtcr) {
char *tout = strdup( out );
size_t toutl = strip_cr( tout );
if (toutl != vars.data.len || memcmp( vars.data.data, tout, toutl )) {
xprintf( "FAIL!\n"
SEP " Input " SEP "\n%!&s\n"
SEP " Expected output " SEP "\n%!&s\n"
SEP " Output " SEP "\n%.*!&s\n" SEP "\n",
orig, tout, vars.data.len, vars.data.data );
exit( 1 );
}
free( tout );
} else {
size_t outl = strlen( out );
if (outl != vars.data.len || memcmp( vars.data.data, out, outl )) {
xprintf( "FAIL!\n"
SEP " Input " SEP "\n%!&s\n"
SEP " Expected output (%u bytes) " SEP "\n%!&s\n"
SEP " Actual output (%u bytes) " SEP "\n%.*!&s\n" SEP "\n",
orig, outl, out, vars.data.len, vars.data.len, vars.data.data );
exit( 1 );
}
}
free( orig );
free( vars.data.data );
}
static void
tests( const char *name, const char *in, const char *out, int add_tuid, int minimal, int flagged, int hdr_sts )
{
test( name, in, NL_UNIX, NL_UNIX, out, NL_ANY, NL_ANY, add_tuid, minimal, flagged );
test( name, in, NL_ANY, NL_UNIX, out, NL_UNIX, NL_UNIX, add_tuid, minimal, flagged );
test( name, in, NL_ANY, NL_ANY, out, NL_UNIX, NL_UNIX, add_tuid, minimal, flagged );
// Skip if (scr == tcr && !srec), like copy_msg() does.
if (add_tuid) {
test( name, in, NL_UNIX, NL_UNIX, out, NL_UNIX, NL_UNIX, ADD_TUID, minimal, flagged );
if (hdr_sts == OK_HEADER) {
test( name, in, NL_ANY, NL_UNIX, out, NL_ANY, NL_UNIX, ADD_TUID, minimal, flagged );
} else {
// If there are no line breaks to detect the style, the output defaults to CRLF.
test( name, in, NL_ANY, NL_UNIX, out, NL_ANY, NL_ANY, ADD_TUID, minimal, flagged );
}
test( name, in, NL_ANY, NL_ANY, out, NL_ANY, NL_ANY, ADD_TUID, minimal, flagged );
}
}
static void
fulltests( const char *name, const char *in, const char *out, int add_tuid )
{
tests( name, in, out, add_tuid, FULL, REGULAR, OK_HEADER );
}
static void
fulltests_ih( const char *name, const char *in, const char *out, int add_tuid )
{
tests( name, in, out, add_tuid, FULL, REGULAR, PARTIAL_HEADER );
}
static void
mintests( const char *name, const char *in, const char *out, int flagged )
{
tests( name, in, out, ADD_TUID, MINIMAL, flagged, OK_HEADER );
}
static void
mintests_ih( const char *name, const char *in, const char *out, int flagged )
{
tests( name, in, out, ADD_TUID, MINIMAL, flagged, PARTIAL_HEADER );
}
#define FROM "From: de\rvil\r\n"
#define R_TO "To: me"
#define TO R_TO "\r\n"
#define R_IN_TUID "X-TUID: garbage"
#define IN_TUID R_IN_TUID "\r\n"
#define OUT_TUID "X-TUID: " TUID "\r\n"
#define R_SUBJECT "Subject: hell"
#define SUBJECT R_SUBJECT "\r\n"
#define PH_SUBJECT "Subject: [placeholder] hell\r\n"
#define NO_SUBJECT "Subject: [placeholder] (No Subject)\r\n"
#define BODY "\r\nHi,\r\n\r\n...\r\n"
#define PH_BODY "\r\nHaving a size of 2.2MiB, this message is over the MaxSize limit.\r\n" \
"Flag it and sync again (Sync mode Upgrade) to fetch its real contents.\r\n"
#define FLAGGED_PH_BODY PH_BODY "\r\nThe original message is flagged as important.\r\n"
#define scc static const char
int
main( void )
{
scc in_from_to[] = FROM TO BODY;
fulltests( "from / to", in_from_to, in_from_to, AS_IS );
scc out_from_to[] = FROM TO OUT_TUID BODY;
fulltests( "from / to", in_from_to, out_from_to, ADD_TUID );
scc in_from_tuid_to[] = FROM IN_TUID TO BODY;
scc out_from_tuid_to[] = FROM OUT_TUID TO BODY;
fulltests( "from / tuid / to", in_from_tuid_to, out_from_tuid_to, ADD_TUID );
scc out_from_to_ph[] = FROM TO OUT_TUID NO_SUBJECT PH_BODY;
mintests( "from / to", in_from_to, out_from_to_ph, REGULAR );
scc out_from_to_flagged_ph[] = FROM TO OUT_TUID NO_SUBJECT FLAGGED_PH_BODY;
mintests( "from / to", in_from_to, out_from_to_flagged_ph, FLAGGED );
scc out_from_tuid_to_ph[] = FROM OUT_TUID TO NO_SUBJECT PH_BODY;
mintests( "from / tuid / to", in_from_tuid_to, out_from_tuid_to_ph, REGULAR );
scc in_from_subj_to[] = FROM SUBJECT TO BODY;
scc out_from_subj_to[] = FROM PH_SUBJECT TO OUT_TUID PH_BODY;
mintests( "from / subject / to", in_from_subj_to, out_from_subj_to, REGULAR );
scc in_from_subj_tuid_to[] = FROM SUBJECT IN_TUID TO BODY;
scc out_from_subj_tuid_to[] = FROM PH_SUBJECT OUT_TUID TO PH_BODY;
mintests( "from / subject / tuid / to", in_from_subj_tuid_to, out_from_subj_tuid_to, REGULAR );
scc in_subj_from_tuid_to[] = SUBJECT FROM IN_TUID TO BODY;
scc out_subj_from_tuid_to[] = PH_SUBJECT FROM OUT_TUID TO PH_BODY;
mintests( "subject / from / tuid / to", in_subj_from_tuid_to, out_subj_from_tuid_to, REGULAR );
scc in_from_tuid_subj_to[] = FROM IN_TUID SUBJECT TO BODY;
scc out_from_tuid_subj_to[] = FROM OUT_TUID PH_SUBJECT TO PH_BODY;
mintests( "from / tuid / subject / to", in_from_tuid_subj_to, out_from_tuid_subj_to, REGULAR );
scc in_tuid_from_subj_to[] = IN_TUID FROM SUBJECT TO BODY;
scc out_tuid_from_subj_to[] = OUT_TUID FROM PH_SUBJECT TO PH_BODY;
mintests( "tuid / from / subject / to", in_tuid_from_subj_to, out_tuid_from_subj_to, REGULAR );
scc in_from_to_b1[] = FROM TO;
fulltests( "from / to w/o end", in_from_to_b1, in_from_to_b1, AS_IS );
scc out_from_to_b1[] = FROM TO OUT_TUID;
fulltests( "from / to w/o end", in_from_to_b1, out_from_to_b1, ADD_TUID );
scc in_from_tuid_to_b1[] = FROM IN_TUID TO;
scc out_from_tuid_to_b1[] = FROM OUT_TUID TO;
fulltests( "from / tuid / to w/o end", in_from_tuid_to_b1, out_from_tuid_to_b1, ADD_TUID );
scc in_from_to_tuid_b1[] = FROM TO IN_TUID;
scc out_from_to_tuid_b1[] = FROM TO OUT_TUID;
fulltests( "from / to / tuid w/o end", in_from_to_tuid_b1, out_from_to_tuid_b1, ADD_TUID );
mintests( "from / to w/o end", in_from_to_b1, out_from_to_ph, REGULAR );
mintests( "from / tuid / to w/o end", in_from_tuid_to_b1, out_from_tuid_to_ph, REGULAR );
scc in_from_subj_to_b1[] = FROM SUBJECT TO;
mintests( "from / subject / to w/o end", in_from_subj_to_b1, out_from_subj_to, REGULAR );
scc in_from_subj_tuid_to_b1[] = FROM SUBJECT IN_TUID TO;
mintests( "from / subject / tuid / to w/o end", in_from_subj_tuid_to_b1, out_from_subj_tuid_to, REGULAR );
scc in_from_subj_to_tuid_b1[] = FROM SUBJECT TO IN_TUID;
scc out_from_subj_to_tuid_b1[] = FROM PH_SUBJECT TO OUT_TUID PH_BODY;
mintests( "from / subject / to / tuid w/o end", in_from_subj_to_tuid_b1, out_from_subj_to_tuid_b1, REGULAR );
scc in_from_tuid_subj_to_b1[] = FROM IN_TUID SUBJECT TO;
mintests( "from / tuid / subject / to w/o end", in_from_tuid_subj_to_b1, out_from_tuid_subj_to, REGULAR );
scc in_from_tuid_to_subj_b1[] = FROM IN_TUID TO SUBJECT;
scc out_from_tuid_to_subj_b1[] = FROM OUT_TUID TO PH_SUBJECT PH_BODY;
mintests( "from / tuid / to / subject w/o end", in_from_tuid_to_subj_b1, out_from_tuid_to_subj_b1, REGULAR );
scc in_from_to_b2[] = FROM R_TO "\r";
fulltests( "from / to w/o lf", in_from_to_b2, in_from_to_b2, AS_IS );
scc out_from_to_b2[] = FROM TO OUT_TUID "\r";
fulltests( "from / to w/o lf", in_from_to_b2, out_from_to_b2, ADD_TUID );
scc in_from_tuid_to_b2[] = FROM IN_TUID R_TO "\r";
scc out_from_tuid_to_b2[] = FROM OUT_TUID R_TO "\r";
fulltests( "from / tuid / to w/o lf", in_from_tuid_to_b2, out_from_tuid_to_b2, ADD_TUID );
scc in_from_to_tuid_b2[] = FROM TO R_IN_TUID "\r";
fulltests( "from / to / tuid w/o lf", in_from_to_tuid_b2, out_from_to_tuid_b1, ADD_TUID );
mintests( "from / to w/o lf", in_from_to_b2, out_from_to_ph, REGULAR );
mintests( "from / tuid / to w/o lf", in_from_tuid_to_b2, out_from_tuid_to_ph, REGULAR );
scc in_from_subj_to_b2[] = FROM SUBJECT R_TO "\r";
mintests( "from / subject / to w/o lf", in_from_subj_to_b2, out_from_subj_to, REGULAR );
scc in_from_subj_tuid_to_b2[] = FROM SUBJECT IN_TUID R_TO "\r";
mintests( "from / subject / tuid / to w/o lf", in_from_subj_tuid_to_b2, out_from_subj_tuid_to, REGULAR );
scc in_from_subj_to_tuid_b2[] = FROM SUBJECT TO R_IN_TUID "\r";
mintests( "from / subject / to / tuid w/o lf", in_from_subj_to_tuid_b2, out_from_subj_to_tuid_b1, REGULAR );
scc in_from_tuid_subj_to_b2[] = FROM IN_TUID SUBJECT R_TO "\r";
mintests( "from / tuid / subject / to w/o lf", in_from_tuid_subj_to_b2, out_from_tuid_subj_to, REGULAR );
scc in_from_tuid_to_subj_b2[] = FROM IN_TUID TO R_SUBJECT "\r";
mintests( "from / tuid / to / subject w/o lf", in_from_tuid_to_subj_b2, out_from_tuid_to_subj_b1, REGULAR );
scc in_from_to_b3[] = FROM R_TO;
fulltests( "from / to w/o crlf", in_from_to_b3, in_from_to_b3, AS_IS );
fulltests( "from / to w/o crlf", in_from_to_b3, out_from_to_b1, ADD_TUID );
scc in_from_tuid_to_b3[] = FROM IN_TUID R_TO;
scc out_from_tuid_to_b3[] = FROM OUT_TUID R_TO;
fulltests( "from / tuid / to w/o crlf", in_from_tuid_to_b3, out_from_tuid_to_b3, ADD_TUID );
scc in_from_to_tuid_b3[] = FROM TO R_IN_TUID;
fulltests( "from / to / tuid w/o crlf", in_from_to_tuid_b3, out_from_to_tuid_b1, ADD_TUID );
mintests( "from / to w/o crlf", in_from_to_b3, out_from_to_ph, REGULAR );
mintests( "from / tuid / to w/o crlf", in_from_tuid_to_b3, out_from_tuid_to_ph, REGULAR );
scc in_from_subj_to_b3[] = FROM SUBJECT R_TO;
mintests( "from / subject / to w/o crlf", in_from_subj_to_b3, out_from_subj_to, REGULAR );
scc in_from_subj_tuid_to_b3[] = FROM SUBJECT IN_TUID R_TO;
mintests( "from / subject / tuid / to w/o crlf", in_from_subj_tuid_to_b3, out_from_subj_tuid_to, REGULAR );
scc in_from_subj_to_tuid_b3[] = FROM SUBJECT TO R_IN_TUID;
mintests( "from / subject / to / tuid w/o crlf", in_from_subj_to_tuid_b3, out_from_subj_to_tuid_b1, REGULAR );
scc in_from_tuid_subj_to_b3[] = FROM IN_TUID SUBJECT R_TO;
mintests( "from / tuid / subject / to w/o crlf", in_from_tuid_subj_to_b3, out_from_tuid_subj_to, REGULAR );
scc in_from_tuid_to_subj_b3[] = FROM IN_TUID TO R_SUBJECT;
mintests( "from / tuid / to / subject w/o crlf", in_from_tuid_to_subj_b3, out_from_tuid_to_subj_b1, REGULAR );
scc in_to_b1[] = R_TO "\r";
fulltests_ih( "to w/o lf", in_to_b1, in_to_b1, AS_IS );
scc out_to_b1[] = TO OUT_TUID "\r";
fulltests_ih( "to w/o lf", in_to_b1, out_to_b1, ADD_TUID );
scc out_to_b1_ph[] = TO OUT_TUID NO_SUBJECT PH_BODY;
mintests_ih( "to w/o lf", in_to_b1, out_to_b1_ph, REGULAR );
scc in_to_b2[] = R_TO;
fulltests_ih( "to w/o crlf", in_to_b2, in_to_b2, AS_IS );
scc out_to_b2[] = TO OUT_TUID;
fulltests_ih( "to w/o crlf", in_to_b2, out_to_b2, ADD_TUID );
scc out_to_b2_ph[] = TO OUT_TUID NO_SUBJECT PH_BODY;
mintests_ih( "to w/o crlf", in_to_b2, out_to_b2_ph, REGULAR );
scc in_no_hdr[] = BODY;
fulltests( "no header", in_no_hdr, in_no_hdr, AS_IS );
scc out_no_hdr[] = OUT_TUID BODY;
fulltests( "no header", in_no_hdr, out_no_hdr, ADD_TUID );
scc out_no_hdr_ph[] = OUT_TUID NO_SUBJECT PH_BODY;
mintests( "no header", in_no_hdr, out_no_hdr_ph, REGULAR );
scc in_empty[] = "";
fulltests_ih( "empty", in_empty, in_empty, AS_IS );
scc out_empty[] = OUT_TUID;
fulltests_ih( "empty", in_empty, out_empty, ADD_TUID );
scc out_empty_ph[] = OUT_TUID NO_SUBJECT PH_BODY;
mintests_ih( "empty", in_empty, out_empty_ph, REGULAR );
return 0;
}

View File

@ -1,15 +1,38 @@
// SPDX-FileCopyrightText: 2014-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later
//
// isync test suite
//
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2014 Oswald Buddenhagen <ossi@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* Just to satisfy the references in util.c */
int DFlags;
const char *Home;
typedef struct {
int id;
int first, other, morph_at, morph_to;
int64_t start;
time_t start;
wakeup_t timer;
wakeup_t morph_timer;
} tst_t;
@ -18,7 +41,7 @@ static void
timer_start( tst_t *timer, int to )
{
printf( "starting timer %d, should expire after %d\n", timer->id, to );
timer->start = get_now();
time( &timer->start );
conf_wakeup( &timer->timer, to );
}
@ -28,7 +51,7 @@ timed_out( void *aux )
tst_t *timer = (tst_t *)aux;
printf( "timer %d expired after %d, repeat %d\n",
timer->id, (int)(get_now() - timer->start), timer->other );
timer->id, (int)(time( 0 ) - timer->start), timer->other );
if (timer->other >= 0) {
timer_start( timer, timer->other );
} else {
@ -44,7 +67,7 @@ morph_timed_out( void *aux )
tst_t *timer = (tst_t *)aux;
printf( "morphing timer %d after %d\n",
timer->id, (int)(get_now() - timer->start) );
timer->id, (int)(time( 0 ) - timer->start) );
timer_start( timer, timer->morph_to );
}
@ -55,7 +78,6 @@ main( int argc, char **argv )
{
int i;
init_timers();
for (i = 1; i < argc; i++) {
char *val = argv[i];
tst_t *timer = nfmalloc( sizeof(*timer) );

View File

@ -1,29 +1,38 @@
// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins <me@mutt.org>
// SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen <ossi@users.sf.net>
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2011,2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#include "common.h"
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <pwd.h>
#include <sys/time.h>
int Verbosity = TERSE;
int DFlags;
int JLimit, JCount;
int UseFSync = 1;
int Pid;
char Hostname[256];
const char *Home;
static int need_nl, need_del;
static int need_nl;
void
flushn( void )
@ -32,52 +41,38 @@ flushn( void )
putchar( '\n' );
fflush( stdout );
need_nl = 0;
} else if (need_del) {
static const char delstr[] =
" "
" ";
if (need_del > (int)sizeof(delstr) - 1)
need_del = (int)sizeof(delstr) - 1;
// We could use ^[[K instead, but we assume a dumb terminal.
printf( "\r%.*s\r", need_del, delstr );
fflush( stdout );
need_del = 0;
}
}
static void ATTR_PRINTFLIKE(1, 0)
vprint( const char *msg, va_list va )
printn( const char *msg, va_list va )
{
if (*msg == '\v')
msg++;
else
flushn();
vprintf( msg, va );
fflush( stdout );
need_nl = 0;
}
void
print( const char *msg, ... )
vdebug( int cat, const char *msg, va_list va )
{
va_list va;
va_start( va, msg );
vprint( msg, va );
va_end( va );
}
static void ATTR_PRINTFLIKE(1, 0)
vprintn( const char *msg, va_list va )
{
vprint( msg, va );
need_nl = 1;
if (DFlags & cat) {
vprintf( msg, va );
fflush( stdout );
need_nl = 0;
}
}
void
printn( const char *msg, ... )
vdebugn( int cat, const char *msg, va_list va )
{
va_list va;
va_start( va, msg );
vprintn( msg, va );
va_end( va );
if (DFlags & cat) {
vprintf( msg, va );
fflush( stdout );
need_nl = 1;
}
}
void
@ -86,19 +81,10 @@ progress( const char *msg, ... )
va_list va;
va_start( va, msg );
need_del = vprintf( msg, va ) - 1;
vprintf( msg, va );
va_end( va );
fflush( stdout );
}
static void ATTR_PRINTFLIKE(1, 0)
nvprint( const char *msg, va_list va )
{
if (*msg == '\v')
msg++;
else
flushn();
vprint( msg, va );
need_nl = 1;
}
void
@ -106,10 +92,11 @@ info( const char *msg, ... )
{
va_list va;
if (Verbosity >= VERBOSE) {
if (DFlags & VERBOSE) {
va_start( va, msg );
nvprint( msg, va );
printn( msg, va );
va_end( va );
need_nl = 0;
}
}
@ -118,9 +105,9 @@ infon( const char *msg, ... )
{
va_list va;
if (Verbosity >= VERBOSE) {
if (DFlags & VERBOSE) {
va_start( va, msg );
nvprint( msg, va );
printn( msg, va );
va_end( va );
need_nl = 1;
}
@ -131,10 +118,11 @@ notice( const char *msg, ... )
{
va_list va;
if (Verbosity >= TERSE) {
if (!(DFlags & QUIET)) {
va_start( va, msg );
nvprint( msg, va );
printn( msg, va );
va_end( va );
need_nl = 0;
}
}
@ -143,7 +131,7 @@ warn( const char *msg, ... )
{
va_list va;
if (Verbosity >= QUIET) {
if (!(DFlags & VERYQUIET)) {
flushn();
va_start( va, msg );
vfprintf( stderr, msg, va );
@ -185,225 +173,6 @@ sys_error( const char *msg, ... )
va_end( va );
}
// Minimal printf() replacement with custom format sequence(s):
// - %\\s
// Print backslash-escaped string literals. Note that this does not
// automatically add quotes around the printed string, so it is
// possible to concatenate multiple segments.
// - %!s
// Same as %\\s, but non-ASCII characters are (hex-)escaped as well.
// - %!&s
// Same as %!s, but linefeeds are also printed verbatim for legibility.
// TODO: Trade off segments vs. buffer capacity dynamically.
#define QPRINTF_SEGS 16
#ifndef QPRINTF_BUFF
# define QPRINTF_BUFF 1000
#endif
typedef void (*printf_cb)( const char **segs, uint *segls, int nsegs, uint totlen, void *aux );
static void
xvprintf_core( const char *fmt, va_list ap, printf_cb cb, void *cb_aux )
{
int nsegs = 0;
uint totlen = 0;
const char *segs[QPRINTF_SEGS];
uint segls[QPRINTF_SEGS];
char buf[QPRINTF_BUFF];
#define ADD_SEG(p, l) \
do { \
if (nsegs == QPRINTF_SEGS) \
oob(); \
segs[nsegs] = p; \
segls[nsegs++] = l; \
totlen += l; \
} while (0)
char *d = buf;
char *ed = d + sizeof(buf);
const char *s = fmt;
for (;;) {
char c = *fmt;
if (!c || c == '%') {
uint l = fmt - s;
if (l)
ADD_SEG( s, l );
if (!c)
break;
uint maxlen = UINT_MAX;
c = *++fmt;
if (c == '.') {
c = *++fmt;
if (c != '*') {
fputs( "Fatal: unsupported string length specification. Please report a bug.\n", stderr );
abort();
}
maxlen = va_arg( ap, uint );
c = *++fmt;
}
int escaped = 0;
if (c == '\\') {
escaped = 1;
c = *++fmt;
} else if (c == '!') {
escaped = 2;
c = *++fmt;
if (c == '&') {
escaped = 3;
c = *++fmt;
}
}
if (c == 'c') {
if (d + 1 > ed)
oob();
ADD_SEG( d, 1 );
*d++ = (char)va_arg( ap, int );
} else if (c == 's') {
s = va_arg( ap, const char * );
if (escaped) {
char *bd = d;
for (l = 0; l < maxlen && (c = *s); l++, s++) {
if (c == '\\' || c == '"') {
if (d >= ed)
oob();
*d++ = '\\';
} else if (escaped >= 2 && (c < 32 || c > 126)) {
switch (c) {
case '\r': c = 'r'; break;
case '\t': c = 't'; break;
case '\a': c = 'a'; break;
case '\b': c = 'b'; break;
case '\v': c = 'v'; break;
case '\f': c = 'f'; break;
case '\n':
if (escaped == 2) {
c = 'n';
break;
}
if (d + 2 >= ed)
oob();
*d++ = '\\';
*d++ = 'n';
*d++ = c; // Keep the actual line break for legibility.
continue;
default:
d += nfsnprintf( d, ed - d, "\\x%02x", (uchar)c );
continue;
}
if (d >= ed)
oob();
*d++ = '\\';
}
if (d >= ed)
oob();
*d++ = c;
}
l = d - bd;
if (l)
ADD_SEG( bd, l );
} else {
l = strnlen( s, maxlen );
if (l)
ADD_SEG( s, l );
}
} else if (c == 'd') {
l = nfsnprintf( d, ed - d, "%d", va_arg( ap, int ) );
ADD_SEG( d, l );
d += l;
} else if (c == 'u') {
l = nfsnprintf( d, ed - d, "%u", va_arg( ap, uint ) );
ADD_SEG( d, l );
d += l;
} else {
fputs( "Fatal: unsupported format specifier. Please report a bug.\n", stderr );
abort();
}
s = ++fmt;
} else {
fmt++;
}
}
cb( segs, segls, nsegs, totlen, cb_aux );
}
static void
xasprintf_cb( const char **segs, uint *segls, int nsegs, uint totlen, void *aux )
{
char *d = nfmalloc( totlen + 1 );
*(char **)aux = d;
for (int i = 0; i < nsegs; i++) {
memcpy( d, segs[i], segls[i] );
d += segls[i];
}
*d = 0;
}
char *
xvasprintf( const char *fmt, va_list ap )
{
char *out;
xvprintf_core( fmt, ap, xasprintf_cb, &out );
return out;
}
#ifndef HAVE_FWRITE_UNLOCKED
# define flockfile(f)
# define funlockfile(f)
# define fwrite_unlocked(b, l, n, f) fwrite(b, l, n, f)
#endif
static void
xprintf_cb( const char **segs, uint *segls, int nsegs, uint totlen ATTR_UNUSED, void *aux ATTR_UNUSED )
{
flockfile( stdout );
for (int i = 0; i < nsegs; i++)
fwrite_unlocked( segs[i], 1, segls[i], stdout );
funlockfile( stdout );
}
void
xprintf( const char *fmt, ... )
{
va_list va;
va_start( va, fmt );
xvprintf_core( fmt, va, xprintf_cb, NULL );
va_end( va );
}
void
vFprintf( FILE *f, const char *msg, va_list va )
{
int r;
r = vfprintf( f, msg, va );
if (r < 0) {
sys_error( "Error: cannot write file" );
exit( 1 );
}
}
void
Fprintf( FILE *f, const char *msg, ... )
{
va_list va;
va_start( va, msg );
vFprintf( f, msg, va );
va_end( va );
}
void
Fclose( FILE *f, int safe )
{
if ((safe && (fflush( f ) || (UseFSync && fdatasync( fileno( f ) )))) || fclose( f ) == EOF) {
sys_error( "Error: cannot close file" );
exit( 1 );
}
}
void
add_string_list_n( string_list_t **list, const char *str, uint len )
{
@ -473,13 +242,6 @@ strnlen( const char *str, size_t maxlen )
#endif
void
to_upper( char *str, uint len )
{
for (uint i = 0; i < len; i++)
str[i] = toupper( str[i] );
}
int
starts_with( const char *str, int strl, const char *cmp, uint cmpl )
{
@ -488,15 +250,6 @@ starts_with( const char *str, int strl, const char *cmp, uint cmpl )
return ((uint)strl >= cmpl) && !memcmp( str, cmp, cmpl );
}
static int
equals_upper_impl( const char *str, const char *cmp, uint cmpl )
{
for (uint i = 0; i < cmpl; i++)
if (toupper( str[i] ) != cmp[i])
return 0;
return 1;
}
int
starts_with_upper( const char *str, int strl, const char *cmp, uint cmpl )
{
@ -504,7 +257,10 @@ starts_with_upper( const char *str, int strl, const char *cmp, uint cmpl )
strl = strnlen( str, cmpl + 1 );
if ((uint)strl < cmpl)
return 0;
return equals_upper_impl( str, cmp, cmpl );
for (uint i = 0; i < cmpl; i++)
if (str[i] != cmp[i] && toupper( str[i] ) != cmp[i])
return 0;
return 1;
}
int
@ -515,16 +271,6 @@ equals( const char *str, int strl, const char *cmp, uint cmpl )
return ((uint)strl == cmpl) && !memcmp( str, cmp, cmpl );
}
int
equals_upper( const char *str, int strl, const char *cmp, uint cmpl )
{
if (strl < 0)
strl = strnlen( str, cmpl + 1 );
if ((uint)strl != cmpl)
return 0;
return equals_upper_impl( str, cmp, cmpl );
}
#ifndef HAVE_TIMEGM
/*
Converts struct tm to time_t, assuming the data in tm is UTC rather
@ -590,21 +336,6 @@ timegm( struct tm *t )
}
#endif
void
fmt_bits( uint bits, uint num_bits, const char *bit_str, const int *bit_off, char *buf )
{
uint d = 0;
for (uint i = 0, val = 1; i < num_bits; i++, val <<= 1) {
if (bits & val) {
if (d)
buf[d++] = ',';
for (const char *s = bit_str + bit_off[i]; *s; s++)
buf[d++] = *s;
}
}
buf[d] = 0;
}
void
oob( void )
{
@ -643,7 +374,7 @@ nfmalloc( size_t sz )
}
void *
nfzalloc( size_t sz )
nfcalloc( size_t sz )
{
void *ret;
@ -718,23 +449,53 @@ cur_user( void )
}
*/
char *
expand_strdup( const char *s )
{
struct passwd *pw;
const char *p, *q;
char *r;
if (*s == '~') {
s++;
if (!*s) {
p = NULL;
q = Home;
} else if (*s == '/') {
p = s;
q = Home;
} else {
if ((p = strchr( s, '/' ))) {
r = nfstrndup( s, (size_t)(p - s) );
pw = getpwnam( r );
free( r );
} else
pw = getpwnam( s );
if (!pw)
return NULL;
q = pw->pw_dir;
}
nfasprintf( &r, "%s%s", q, p ? p : "" );
return r;
} else
return nfstrdup( s );
}
/* Return value: 0 = ok, -1 = out found in arg, -2 = in found in arg but no out specified */
int
map_name( const char *arg, int l, char **result, uint reserve, const char *in, const char *out )
map_name( const char *arg, char **result, uint reserve, const char *in, const char *out )
{
char *p;
int i, ll, num, inl, outl;
uint i, l, ll, num, inl, outl;
assert( arg );
if (l < 0)
l = strlen( arg );
l = strlen( arg );
assert( in );
inl = strlen( in );
if (!inl) {
copy:
*result = nfmalloc( reserve + l + 1 );
memcpy( *result + reserve, arg, l );
(*result)[reserve + l] = 0;
memcpy( *result + reserve, arg, l + 1 );
return 0;
}
assert( out );
@ -742,8 +503,6 @@ map_name( const char *arg, int l, char **result, uint reserve, const char *in, c
if (equals( in, (int)inl, out, outl ))
goto copy;
for (num = 0, i = 0; i < l; ) {
if (i + inl > l)
goto fout;
for (ll = 0; ll < inl; ll++)
if (arg[i + ll] != in[ll])
goto fout;
@ -752,8 +511,6 @@ map_name( const char *arg, int l, char **result, uint reserve, const char *in, c
continue;
fout:
if (outl) {
if (i + outl > l)
goto fnexti;
for (ll = 0; ll < outl; ll++)
if (arg[i + ll] != out[ll])
goto fnexti;
@ -769,8 +526,6 @@ map_name( const char *arg, int l, char **result, uint reserve, const char *in, c
*result = nfmalloc( reserve + l + num * (outl - inl) + 1 );
p = *result + reserve;
for (i = 0; i < l; ) {
if (i + inl > l)
goto rnexti;
for (ll = 0; ll < inl; ll++)
if (arg[i + ll] != in[ll])
goto rnexti;
@ -785,21 +540,6 @@ map_name( const char *arg, int l, char **result, uint reserve, const char *in, c
return 0;
}
int
mkdir_p( char *path, int len )
{
if (!mkdir( path, 0700 ) || errno == EEXIST)
return 0;
char *p = memrchr( path, '/', (size_t)len );
*p = 0;
if (mkdir_p( path, (int)(p - path) )) {
*p = '/';
return -1;
}
*p = '/';
return mkdir( path, 0700 );
}
static int
compare_uints( const void *l, const void *r )
{
@ -1001,41 +741,10 @@ wipe_notifier( notifier_t *sn )
#endif
}
#if _POSIX_TIMERS - 0 > 0
static clockid_t clkid;
#endif
void
init_timers( void )
{
#if _POSIX_TIMERS - 0 > 0
struct timespec ts;
# ifdef CLOCK_BOOTTIME
if (!clock_gettime( CLOCK_BOOTTIME, &ts )) {
clkid = CLOCK_BOOTTIME;
} else
# endif
# ifdef CLOCK_MONOTONIC_COARSE
if (!clock_gettime( CLOCK_MONOTONIC_COARSE, &ts )) {
clkid = CLOCK_MONOTONIC_COARSE;
} else
# endif
clkid = CLOCK_MONOTONIC;
#endif
}
int64_t
static time_t
get_now( void )
{
#if _POSIX_TIMERS - 0 > 0
struct timespec ts;
clock_gettime( clkid, &ts );
return ts.tv_sec * 1000LL + ts.tv_nsec / 1000000;
#else
struct timeval tv;
gettimeofday( &tv, NULL );
return tv.tv_sec * 1000LL + tv.tv_usec / 1000;
#endif
return time( NULL );
}
static list_head_t timers = { &timers, &timers };
@ -1064,7 +773,7 @@ conf_wakeup( wakeup_t *tmr, int to )
if (tmr->links.next)
list_unlink( &tmr->links );
} else {
int64_t timeout = to;
time_t timeout = to;
if (!to) {
/* We always prepend null timers, to cluster related events. */
succ = timers.next;
@ -1098,20 +807,18 @@ event_wait( void )
int timeout = -1;
if ((head = timers.next) != &timers) {
wakeup_t *tmr = (wakeup_t *)head;
int64_t delta = tmr->timeout;
time_t delta = tmr->timeout;
if (!delta || (delta -= get_now()) <= 0) {
list_unlink( head );
tmr->cb( tmr->aux );
return;
}
timeout = (int)delta;
timeout = (int)delta * 1000;
}
switch (poll( pollfds, npolls, timeout )) {
case 0:
return;
case -1:
if (errno == EINTR)
return;
perror( "poll() failed in event loop" );
abort();
default:
@ -1136,14 +843,14 @@ event_wait( void )
if ((head = timers.next) != &timers) {
wakeup_t *tmr = (wakeup_t *)head;
int64_t delta = tmr->timeout;
time_t delta = tmr->timeout;
if (!delta || (delta -= get_now()) <= 0) {
list_unlink( head );
tmr->cb( tmr->aux );
return;
}
to_tv.tv_sec = delta / 1000;
to_tv.tv_usec = delta * 1000;
to_tv.tv_sec = delta;
to_tv.tv_usec = 0;
timeout = &to_tv;
}
FD_ZERO( &rfds );
@ -1164,8 +871,6 @@ event_wait( void )
case 0:
return;
case -1:
if (errno == EINTR)
return;
perror( "select() failed in event loop" );
abort();
default: