├── .cvsignore ├── .gitignore ├── AUTHORS ├── COPYING ├── ChangeLog ├── Makefile.am ├── NEWS ├── README ├── TODO ├── acinclude.m4 ├── autogen.sh ├── configure.in ├── debian ├── .cvsignore ├── changelog ├── compat ├── control ├── copyright ├── dirs ├── docs ├── generate-deb ├── patches │ └── 00list ├── rules └── watch ├── get-cert ├── isync.spec.in └── src ├── .cvsignore ├── Makefile.am ├── compat ├── .cvsignore ├── Makefile.am ├── config.c ├── convert.c ├── isync.1 ├── isync.h ├── isyncrc.sample ├── main.c └── util.c ├── config.c ├── drv_imap.c ├── drv_maildir.c ├── isync.h ├── main.c ├── mbsync.1 ├── mbsyncrc.sample ├── mdconvert.1 ├── mdconvert.c ├── run-tests.pl ├── sync.c └── util.c /.cvsignore: -------------------------------------------------------------------------------- 1 | .autoconf_trace 2 | Makefile 3 | Makefile.in 4 | autom4te.cache 5 | aclocal.m4 6 | build-stamp 7 | config.h 8 | config.h.in 9 | config.cache 10 | config.guess 11 | config.log 12 | config.status 13 | config.sub 14 | configure 15 | configure.lineno 16 | configure-stamp 17 | isync.spec 18 | isync-*.tar.gz 19 | patch-stamp 20 | stamp-h 21 | stamp-h.in 22 | stamp-h1 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | INSTALL 2 | Makefile 3 | Makefile.in 4 | aclocal.m4 5 | autom4te.cache/ 6 | config.h 7 | config.h.in 8 | config.log 9 | config.status 10 | configure 11 | depcomp 12 | install-sh 13 | isync.spec 14 | missing 15 | src/.deps/ 16 | src/Makefile 17 | src/Makefile.in 18 | src/compat/.deps/ 19 | src/compat/Makefile 20 | src/compat/Makefile.in 21 | src/compat/config.o 22 | src/compat/convert.o 23 | src/compat/isync 24 | src/compat/main.o 25 | src/compat/util.o 26 | src/config.o 27 | src/drv_imap.o 28 | src/drv_maildir.o 29 | src/main.o 30 | src/mbsync 31 | src/mdconvert 32 | src/mdconvert.o 33 | src/sync.o 34 | src/util.o 35 | stamp-h1 36 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Oswald Buddenhagen 2 | * Contributor, current maintainer 3 | 4 | Theodore Ts'o 5 | * Contributor, Debian package co-maintainer 6 | 7 | Nicolas Boullis 8 | * Debian package maintainer and minor upstream contributions 9 | 10 | Michael Elkins 11 | * Original author 12 | 13 | Send questions and bug reports to the isync-devel@lists.sourceforge.net 14 | mailing list. 15 | 16 | _DON'T_ report bugs to Michael, not even in a CC: - he is not actively 17 | involved in isync development any more. 18 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnym/isync/24f4efdb589aac1c0aef8df0c186f53ad86de44c/ChangeLog -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = src 2 | bin_SCRIPTS = get-cert 3 | EXTRA_DIST = debian isync.spec $(bin_SCRIPTS) 4 | 5 | log: 6 | @perl -p -e "s/^(\\S+)\\s+(\\S.+\\S)\\s+(\\S+)\\s*\$$/\$$1:'\$$2 <\$$3>'\\n/" < ../CVSROOT/accounts > .usermap 7 | @if test -f CVS/Tag; then tag=`cut -c2- CVS/Tag`; else tag=trunk; fi; \ 8 | cvs2cl -U .usermap -F $$tag --no-wrap --separate-header -I ChangeLog -I NEWS -I TODO -I debian/ 9 | @rm -f .usermap ChangeLog.bak 10 | 11 | deb: 12 | CFLAGS="-O2 -mcpu=i686" fakeroot debian/rules binary 13 | 14 | deb-clean: 15 | dh_clean -Xsrc/ 16 | fakeroot debian/rules unpatch 17 | 18 | distdir distclean: deb-clean 19 | 20 | dist-hook: 21 | find $(distdir)/debian \( -name CVS -o -name .cvsignore -o -name .#\*# -o -type l \) -print0 | xargs -0r rm -rf 22 | 23 | dist-sign: dist 24 | gpg -b -a $(PACKAGE)-$(VERSION).tar.gz 25 | 26 | rpm: 27 | make dist 28 | cp $(PACKAGE)-$(VERSION).tar.gz /usr/src/rpm/SOURCES 29 | CFLAGS="-O2 -mcpu=i686" rpm -ba --clean isync.spec 30 | 31 | docdir = $(datadir)/doc/isync 32 | doc_DATA = README TODO NEWS ChangeLog AUTHORS 33 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | [1.0.0] 2 | 3 | Essentially a rewrite. Synchronization state storage concept, configuration 4 | and command line changed entirely. 5 | But you needn't to worry about the upgrade, as a fully automated migration 6 | path is provided, even for users of isync 0.7 and below. 7 | Still, you should re-read the manual to be able to take full advantage of the 8 | new features: 9 | 10 | The supported mailbox types can be freely paired. 11 | A possible application of this is using a local IMAP server to access 12 | mailboxes that are not natively supported yet. 13 | 14 | Message deletions (expunges) are now propagated both ways, so there is no need 15 | for using mutt with maildir_trash any more. 16 | 17 | Additional trash options added. 18 | 19 | `OneToOne' replaced by something more flexible. 20 | 21 | Partial support for IMAP pipelining (streaming, parallelization) added. 22 | Makes flag change propagation much faster - this affects every message that 23 | becomes Seen/Read. 24 | 25 | [0.9] 26 | 27 | Added Tunnel directive to allow the user to specify a shell command to run 28 | to set up an IMAP connection in place of a TCP socket (eg., to run over 29 | an SSH session). 30 | 31 | Added PREAUTH support (useful mostly in conjunction with Tunnel). 32 | 33 | Messages marked deleted are not uploaded when we are going to expunge. 34 | 35 | Locally generated messages are not re-fetched after uploading even if the 36 | UIDPLUS extension is not supported by the server. 37 | 38 | Added `OneToOne' configuration option: ignore any Mailbox specifications 39 | and instead pick up all mailboxes from the local MailDir and remote Folder 40 | and map them 1:1 onto each other according to their names. 41 | 42 | -C now creates both local and remote boxes; -L and -R create only local/remote. 43 | 44 | --quiet is now really quiet. 45 | 46 | [0.8] 47 | 48 | !!! IMPORTANT !!! 49 | 50 | In order to fix the problem where messages copied from one mailbox to 51 | another were not uploaded to the new mailbox, the way Isync stores the UID 52 | for each message needed to be changed. As a result, you _MUST_ delete all 53 | the messages in the local maildir box before using this version. Otherwise 54 | it will upload every message to the server thinking its a new mail. 55 | 56 | [0.7] 57 | 58 | Added `MaxMessages' configuration option to allow tracking of only the most 59 | recently added message in the local mailbox. 60 | 61 | Added --create (-C) command line option to force creation of the local 62 | maildir-style mailbox if it doesn't already exist. 63 | 64 | [0.6] 65 | 66 | Added `Delete' configuration option to correspond to the -d command line 67 | option. 68 | 69 | Added -a (--all) command line option to synchronize all mailboxes. 70 | 71 | [0.5] 72 | 73 | Updated SSL support. 74 | 75 | Added CRAM authentication support. 76 | 77 | Added MailDir configuration option to specify the default location of local 78 | mailboxes when relative paths are used. 79 | 80 | Added support for uploading local messages to the IMAP server. 81 | 82 | Added CopyDeletedTo configuration option to cause isync to move deleted 83 | messages to a particular mailbox on the server when they are expunged. 84 | 85 | [0.4] 86 | 87 | Added MaxSize configuration option to limit downloading of new messages from 88 | the server to less than some threshold. 89 | 90 | More robust --fast option works without using \Recent flags, so the previous 91 | problem with multiple accesses killing these flags is no longer a problem. 92 | 93 | RFC2060 obsoleted RFC822.PEEK, use BODY.PEEK[] instead which does the same 94 | job. 95 | 96 | Don't need to request UID in a FETCH when doing UID FETCH (RFC2060 states 97 | that its automatically returned). 98 | 99 | [0.3] 100 | 101 | Fixed to clean up temp maildir files when the fetch of a new message failed. 102 | 103 | Fixed to not assume order of the flags returned by "UID FETCH" 104 | 105 | Added support for building RPMs. 106 | 107 | [0.2] 108 | 109 | SSL support added. 110 | 111 | [0.1] 112 | 113 | Initial release. 114 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | _ 2 | (_)___ _ _ _ __ ___ 3 | | / __| | | | '_ \ / __| 4 | | \__ \ |_| | | | | (__ 5 | |_|___/\__, |_| |_|\___| 6 | |___/ 7 | isync/mbsync - free (GPL) mailbox synchronization program 8 | http://isync.sf.net/ 9 | 10 | See AUTHORS for contact information. 11 | 12 | ``mbsync'' is a command line application which synchronizes mailboxes; 13 | currently Maildir and IMAP4 mailboxes are supported. New messages, message 14 | deletions and flag changes can be propagated both ways. 15 | ``mbsync'' is suitable for use in IMAP-disconnected mode. 16 | 17 | Synchronization is based on unique message identifiers (UIDs), so no 18 | identification conflicts can occur (as opposed to some other mail 19 | synchronizers). 20 | Synchronization state is kept in one local text file per mailbox pair; 21 | multiple replicas of a mailbox can be maintained. 22 | 23 | isync is the project name, while mbsync is the current executable name; this 24 | change was necessary because of massive changes in the user interface. An 25 | isync executable still exists; it is a compatibility wrapper around mbsync. 26 | 27 | * Features 28 | 29 | * Fine-grained selection of synchronization operations to perform 30 | * Synchronizes single mailboxes or entire mailbox collections 31 | * Partial mirrors possible: keep only the latest messages locally 32 | * Trash functionality: backup messages before removing them 33 | * IMAP features: 34 | * Supports TLS/SSL via imaps: (port 993) and STARTTLS (RFC2595) 35 | * Supports CRAM-MD5 (RFC2195) for authentication 36 | * Supports NAMESPACE (RFC2342) for simplified configuration 37 | * Pipelining for maximum speed (currently only partially implemented) 38 | 39 | * Compatibility 40 | 41 | isync should work fairly well with any IMAP4 compliant server; 42 | particularily efficient with those that support the UIDPLUS and LITERAL+ 43 | extensions. 44 | 45 | Courier 1.4.3 is known to be buggy, version 1.7.3 works fine. 46 | 47 | c-client (UW-IMAP, Pine) is mostly fine, but versions less than 2004a.352 48 | tend to change UIDVALIDITY pretty often when used with unix/mbox mailboxes, 49 | making isync refuse synchronization. 50 | The "cure" is to simply copy the new UIDVALIDITY from the affected 51 | mailbox to mbsync's state file. This is a Bad Hack (TM), but it works - 52 | use at your own risk (if the UIDVALIDITY change was genuine, this will 53 | delete all messages in the affected mailbox - not that this ever 54 | happened to me). 55 | 56 | * Platforms 57 | 58 | At some point, ``isync'' has successfully run on: 59 | Linux, Solaris 2.7, OpenBSD 2.8, FreeBSD 4.3. 60 | 61 | Note that Cygwin cannot be reasonably supported due to restrictions 62 | of the Windows file system. 63 | 64 | * Requirements 65 | 66 | Berkley DB 4.2+ 67 | OpenSSL for TLS/SSL support (optional) 68 | 69 | * Installation 70 | 71 | ./configure 72 | make install 73 | 74 | * Help 75 | 76 | Please see the man page for complete documentation. 77 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | make SSL certificate validation more automatic. 2 | 3 | add deamon mode. primary goal: keep imap password in memory. 4 | 5 | add asynchronous operation to remote mailbox drivers. this is actually 6 | what prevents us from simply using c-client and thus becoming mailsync. 7 | 8 | handle custom flags (keywords). 9 | 10 | fix maildir_{open_store,list} to handle partial names (last char not slash). 11 | 12 | add a way to automatically create and sync subfolders. 13 | 14 | use MULTIAPPEND and FETCH with multiple messages. 15 | 16 | create dummies describing MIME structure of messages bigger than MaxSize. 17 | flagging the dummy would fetch the real message. possibly remove --renew. 18 | 19 | don't SELECT boxes unless really needed; in particular not for appending, 20 | and in write-only mode not before changes are made. 21 | 22 | possibly request message attributes on a per-message basis from the drivers. 23 | considerations: 24 | - record non-existing UID ranges in the sync database, so IMAP FETCHes needn't 25 | to exclude anyway non-existing messages explicitly. 26 | - when detect unborn pairs and orphaned messages being gone? implied by expunge: 27 | with trashing, by local driver, or of messages we deleted in this run. the 28 | remaining cases could be handled by automatic periodical cleanup passes, an 29 | explicit --cleanup action, or be implied by one of the other actions. 30 | - the benefit of this is questionable, as fine-grained requests will result 31 | in sending huge amounts of data, and upstream is often way slower than 32 | downstream. 33 | 34 | maildir: possibly timestamp mails with remote arrival date. 35 | 36 | maybe throw out the ctx->recent stuff - it's used only for one info message. 37 | 38 | possibly use ^[[1m to highlight error messages. 39 | 40 | consider alternative trash implementation: trash only messages we delete, 41 | and trash before marking them deleted in the mailbox. downside: all other 42 | programs have to do the same. and what if the deleted flag is unset? 43 | 44 | items out of scope of purely UID based approach: 45 | - detect message moves between folders 46 | - recovering from UIDVALIDITY change (uw-imap < 2004.352 does this a lot) 47 | -------------------------------------------------------------------------------- /acinclude.m4: -------------------------------------------------------------------------------- 1 | # Add --enable-maintainer-mode option to configure. 2 | # From Jim Meyering 3 | # Change it to enable maintainer mode by default by Nicolas Boullis. 4 | 5 | # Copyright 1996, 1998, 2000, 2001, 2002 Free Software Foundation, Inc. 6 | # Copyright 2004 Nicolas Boullis. 7 | 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2, or (at your option) 11 | # any later version. 12 | 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software Foundation, 20 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 21 | 22 | AC_DEFUN([AM_MAINTAINER_MODE], 23 | [AC_MSG_CHECKING([whether to enable maintainer-specific portions of Makefiles]) 24 | dnl maintainer-mode is enabled by default 25 | AC_ARG_ENABLE(maintainer-mode, 26 | [ --disable-maintainer-mode disable make rules and dependencies not useful 27 | (and sometimes confusing) to the casual installer], 28 | USE_MAINTAINER_MODE=$enableval, 29 | USE_MAINTAINER_MODE=yes) 30 | AC_MSG_RESULT([$USE_MAINTAINER_MODE]) 31 | AM_CONDITIONAL(MAINTAINER_MODE, [test $USE_MAINTAINER_MODE = yes]) 32 | MAINT=$MAINTAINER_MODE_TRUE 33 | AC_SUBST(MAINT)dnl 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | set -e -v 3 | aclocal 4 | autoheader 5 | automake --add-missing 6 | autoconf 7 | -------------------------------------------------------------------------------- /configure.in: -------------------------------------------------------------------------------- 1 | AC_INIT(src/isync.h) 2 | AM_CONFIG_HEADER(config.h) 3 | AM_INIT_AUTOMAKE(isync, 1.1.0) 4 | 5 | AM_MAINTAINER_MODE 6 | 7 | AM_PROG_CC_STDC 8 | if test "$GCC" = yes; then 9 | CFLAGS="$CFLAGS -pipe -W -Wall -Wshadow -Wstrict-prototypes" 10 | fi 11 | 12 | AC_CHECK_HEADERS([sys/filio.h]) 13 | AC_CHECK_FUNCS(vasprintf) 14 | 15 | AC_CHECK_LIB(socket, socket, [SOCK_LIBS="-lsocket"]) 16 | AC_CHECK_LIB(nsl, inet_ntoa, [SOCK_LIBS="$SOCK_LIBS -lnsl"]) 17 | AC_SUBST(SOCK_LIBS) 18 | 19 | m4_ifdef([AS_HELP_STRING], , [m4_define([AS_HELP_STRING], m4_defn([AC_HELP_STRING]))]) 20 | 21 | have_ssl_paths= 22 | AC_ARG_WITH(ssl, 23 | AS_HELP_STRING([--with-ssl[=PATH]], [where to look for SSL [detect]]), 24 | [ob_cv_with_ssl=$withval]) 25 | if test "x$ob_cv_with_ssl" != xno; then 26 | case $ob_cv_with_ssl in 27 | ""|yes) 28 | dnl Detect the pkg-config tool, as it may have extra info about the openssl 29 | dnl installation we can use. I *believe* this is what we are expected to do 30 | dnl on really recent Redhat Linux hosts. 31 | AC_PATH_PROG(PKGCONFIG, pkg-config, no, $PATH:/usr/bin:/usr/local/bin) 32 | if test "$PKGCONFIG" != "no" ; then 33 | AC_MSG_CHECKING([OpenSSL presence with pkg-config]) 34 | if $PKGCONFIG --exists openssl; then 35 | SSL_LIBS=`$PKGCONFIG --libs-only-l openssl` 36 | SSL_LDFLAGS=`$PKGCONFIG --libs-only-L openssl` 37 | SSL_CPPFLAGS=`$PKGCONFIG --cflags-only-I openssl` 38 | have_ssl_paths=yes 39 | AC_MSG_RESULT([found]) 40 | else 41 | AC_MSG_RESULT([not found]) 42 | fi 43 | fi 44 | ;; 45 | *) 46 | SSL_LDFLAGS=-L$ob_cv_with_ssl/lib$libsuff 47 | SSL_CPPFLAGS=-I$ob_cv_with_ssl/include 48 | ;; 49 | esac 50 | if test -z "$have_ssl_paths"; then 51 | sav_LDFLAGS=$LDFLAGS 52 | LDFLAGS="$LDFLAGS $SSL_LDFLAGS" 53 | AC_CHECK_LIB(dl, dlopen, [LIBDL=-ldl]) 54 | AC_CHECK_LIB(crypto, CRYPTO_lock, [LIBCRYPTO=-lcrypto]) 55 | AC_CHECK_LIB(ssl, SSL_connect, 56 | [SSL_LIBS="-lssl $LIBCRYPTO $LIBDL" have_ssl_paths=yes]) 57 | LDFLAGS=$sav_LDFLAGS 58 | fi 59 | 60 | sav_CPPFLAGS=$CPPFLAGS 61 | CPPFLAGS="$CPPFLAGS $SSL_CPPFLAGS" 62 | AC_CHECK_HEADER(openssl/ssl.h, , [have_ssl_paths=]) 63 | CPPFLAGS=$sav_CPPFLAGS 64 | 65 | if test -z "$have_ssl_paths"; then 66 | if test -n "$ob_cv_with_ssl"; then 67 | AC_MSG_ERROR([OpenSSL libs and/or includes were not found where specified]) 68 | fi 69 | else 70 | AC_DEFINE(HAVE_LIBSSL, 1, [if you have the OpenSSL libraries]) 71 | CPPFLAGS="$CPPFLAGS $SSL_CPPFLAGS" 72 | LDFLAGS="$LDFLAGS $SSL_LDFLAGS" 73 | fi 74 | fi 75 | AC_SUBST(SSL_LIBS) 76 | 77 | AC_CACHE_CHECK([for Berkley DB 4.2], ac_cv_berkdb4, 78 | [ac_cv_berkdb4=no 79 | AC_TRY_LINK([#include ], 80 | [DB *db; 81 | db->truncate(db, 0, 0, 0); 82 | db->open(db, 0, "foo", "foo", DB_HASH, DB_CREATE, 0)], 83 | [ac_cv_berkdb4=yes])]) 84 | if test "x$ac_cv_berkdb4" = xno; then 85 | AC_MSG_ERROR([Berkley DB 4.2 not found. 86 | You must install libdb4.2 including the respective development files/headers.]) 87 | fi 88 | 89 | AC_ARG_ENABLE(compat, 90 | AS_HELP_STRING([--disable-compat], [don't include isync compatibility wrapper [no]]), 91 | [ob_cv_enable_compat=$enableval]) 92 | if test "x$ob_cv_enable_compat" != xno; then 93 | AC_CHECK_FUNCS(getopt_long) 94 | fi 95 | AM_CONDITIONAL(with_compat, test "x$ob_cv_enable_compat" != xno) 96 | 97 | AC_OUTPUT(Makefile src/Makefile src/compat/Makefile isync.spec) 98 | 99 | if test -n "$have_ssl_paths"; then 100 | AC_MSG_RESULT([ 101 | Using SSL 102 | ]) 103 | else 104 | AC_MSG_RESULT([ 105 | Not using SSL 106 | ]) 107 | fi 108 | -------------------------------------------------------------------------------- /debian/.cvsignore: -------------------------------------------------------------------------------- 1 | files 2 | isync 3 | isync.postrm.debhelper 4 | isync.substvars 5 | patched 6 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | isync (0.9.2+cvsXXXXXXXX-1) unstable; urgency=low 2 | 3 | * New upstream release. 4 | - Password prompt now includes the mailbox/server (Closes: #92893) 5 | * Theodore Ts'o added as co-maintainer 6 | * Added initial asynchronous flags synchronization patch (Closes: #226222) 7 | * Ignore anything that does not look remotely like a maildir when 8 | collecting mailboxes for OneToOne (from isync CVS) 9 | 10 | -- Theodore Y. Ts'o Sun, 11 Jan 2004 02:38:48 -0500 11 | 12 | isync (0.9.1-4) unstable; urgency=low 13 | 14 | * The "Why do I keep adding such stupid bugs?" release. 15 | * Remove the extra parenthesis that caused UID FETCH syntax errors, 16 | thanks to Niels den Otter for pointing the bug and giving the 17 | solution. (Closes: #224803) 18 | * Use configure's --build and --host options to prevent wrong 19 | optimizations (such as building for sparc64 rather than for sparc). 20 | 21 | -- Nicolas Boullis Wed, 7 Jan 2004 01:06:53 +0100 22 | 23 | isync (0.9.1-3) unstable; urgency=low 24 | 25 | * Do not segfault when using both tunneled end non-tunneled connections, 26 | thanks to Nik A. Melchior for reporting and for his patch. 27 | (Closes: #220667) 28 | * Save uid of messages when interrupted, thanks to Theodore Y. Ts'o for 29 | reporting and for his patch. (Closes: #220346) 30 | * Do not get the sizes of the messages if unneeded (if MaxSize=0). 31 | 32 | -- Nicolas Boullis Thu, 18 Dec 2003 00:55:04 +0100 33 | 34 | isync (0.9.1-2) unstable; urgency=low 35 | 36 | * Add french debconf templates translation, thanks to Christian 37 | Perrier. (Closes: #218118) 38 | 39 | -- Nicolas Boullis Mon, 3 Nov 2003 18:50:56 +0100 40 | 41 | isync (0.9.1-1) unstable; urgency=low 42 | 43 | * New maintainer. (Closes: #180050) 44 | * New upstream release. 45 | - With the new option -R, isync is now able to create non-existent 46 | remote mailboxes. (Closes: #170388) 47 | * Update debian/copyright to match the current copyright: 48 | - Add Oswald Buddenhagen as copyright owner. 49 | - Add special exception for OpenSSL. 50 | * Add support for noopt in $DEB_BUILD_OPTIONS in debian/rules. 51 | * Switch to po-debconf. 52 | * Remove sample.isyncrc from debian/docs: no need to have it both as a 53 | doc and as an example. 54 | * Move package from section non-US/main (?) to mail. (Closes: #154216) 55 | * Update versionned build-dependency on debhelper to >= 4.1.16. 56 | * Bump Standards-Version to 3.6.1. (No change required.) 57 | 58 | -- Nicolas Boullis Tue, 14 Oct 2003 22:02:20 +0200 59 | 60 | isync (0.8-4) unstable; urgency=low 61 | 62 | * Orphaned the package, as I no longer use it. 63 | 64 | -- Joey Hess Thu, 6 Feb 2003 15:46:38 -0500 65 | 66 | isync (0.8-3) unstable; urgency=low 67 | 68 | * New upstream maintainer; updated copyright file web site address, and 69 | watch file. NB: new upstream has not made any new releases yet. 70 | 71 | -- Joey Hess Sat, 1 Feb 2003 16:03:49 -0500 72 | 73 | isync (0.8-2) unstable; urgency=low 74 | 75 | * Only reset debconf question if user chooses to abort upgrade. 76 | Closes: #167363 77 | * Don't open lock files O_EXCL. As seen in upstream cvs. 78 | * Description and build-deps updates. 79 | * Added README.Debian with notes on mutt integration. 80 | 81 | -- Joey Hess Fri, 1 Nov 2002 18:02:44 -0500 82 | 83 | isync (0.8-1) unstable; urgency=low 84 | 85 | * New upstream release. Closes: #134080 86 | 87 | **WARNING** 88 | You need to remove all the messages in your local folder if you were 89 | previously using another version of isync or else you will end up with 90 | duplicate messages on your IMAP server. 91 | 92 | * Has better support for uploading locally added messages. Closes: #120272 93 | * Added a debconf queston with some info about this that lets you abort the 94 | upgrade. 95 | * Added NEWS.Debian with same info. 96 | * New maintainer. 97 | * Removed upstream debianization stuff. 98 | * Updated copyright file. 99 | * Updated to current policy throughout. 100 | * Added uscan watch file. 101 | * Updated build-deps. 102 | * Now that isync needs berkeley databases, go with db4, so I don't have to 103 | transition from db3 later. 104 | * Fix fd leak (forgot to close tmp dir in maildir). Closes: #150762 105 | 106 | -- Joey Hess Tue, 29 Oct 2002 17:02:14 -0500 107 | 108 | isync (0.7-1) unstable; urgency=low 109 | 110 | * New upstream version (Closes: #121312, #92051). 111 | * Rumors say this might fix bugs #102255 and #120272, 112 | but I have no test setup right now, so I'm leaving them open. 113 | * Updated standards-version. 114 | 115 | -- Tommi Virtanen Sat, 5 Jan 2002 16:13:35 +0200 116 | 117 | isync (0.5-1) unstable; urgency=low 118 | 119 | * New upstream version (Closes: #98642). 120 | * Install sample.isyncrc too (Closes: #90464). 121 | 122 | -- Tommi Virtanen Sat, 23 Jun 2001 01:19:07 +0300 123 | 124 | isync (0.4-1) unstable; urgency=low 125 | 126 | * Initial Release. 127 | 128 | -- Tommi Virtanen Sat, 10 Mar 2001 18:43:35 +0200 129 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 4 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: isync 2 | Section: mail 3 | Priority: optional 4 | Maintainer: Nicolas Boullis 5 | Uploaders: Nicolas Boullis , Theodore Y. Ts'o 6 | Standards-Version: 3.6.1 7 | Build-Depends: libssl-dev, debhelper (>= 4.1.16), dpkg-dev (>= 1.9.0), libdb4.2-dev, dpatch 8 | 9 | Package: isync 10 | Architecture: any 11 | Depends: ${shlibs:Depends}, ${misc:Depends} 12 | Suggests: mutt 13 | Description: Synchronize Maildir and IMAP4 mailboxes 14 | A command line application which synchronizes mailboxes; currently 15 | Maildir and IMAP4 mailboxes are supported. 16 | New messages, message deletions and flag changes can be propagated both ways. 17 | It is useful for working in disconnected mode, such as on a laptop or with a 18 | non-permanent internet collection (dIMAP). 19 | . 20 | Features: 21 | * Fine-grained selection of synchronization operations to perform 22 | * Synchronizes single mailboxes or entire mailbox collections 23 | * Partial mirrors possible: keep only the latest messages locally 24 | * Trash functionality: backup messages before removing them 25 | * IMAP features: 26 | * Supports TLS/SSL via imaps: (port 993) and STARTTLS (RFC2595) 27 | * Supports CRAM-MD5 (RFC2195) for authentication 28 | * Supports NAMESPACE (RFC2342) for simplified configuration 29 | * Pipelining for maximum speed (currently only partially implemented) 30 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | This package was debianized by Tommi Virtanen and is now 2 | maintained by Nicolas Boullis . 3 | 4 | It was downloaded from http://isync.sourceforge.net/ 5 | 6 | Upstream Author: Michael R. Elkins , 7 | Oswald Buddenhagen 8 | 9 | Copyright: 10 | 11 | * isync - IMAP4 to maildir mailbox synchronizer 12 | * Copyright (C) 2000-2002 Michael R. Elkins 13 | * Copyright (C) 2002-2006 Oswald Buddenhagen 14 | * 15 | * This program is free software; you can redistribute it and/or modify 16 | * it under the terms of the GNU General Public License as published by 17 | * the Free Software Foundation; either version 2 of the License, or 18 | * (at your option) any later version. 19 | * 20 | * This program is distributed in the hope that it will be useful, 21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | * GNU General Public License for more details. 24 | * 25 | * You should have received a copy of the GNU General Public License 26 | * along with this program; if not, write to the Free Software Foundation, 27 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 28 | * 29 | * As a special exception, isync may be linked with the OpenSSL library, 30 | * despite that library's more restrictive license. 31 | 32 | On Debian systems, the complete text of the GNU General Public 33 | License can be found in /usr/share/common-licenses/GPL 34 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | usr/bin 2 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | NEWS 2 | README 3 | TODO 4 | -------------------------------------------------------------------------------- /debian/generate-deb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Intended to be run from the root of the isync source tree in the repository. 4 | # 5 | VERSION=`dpkg-parsechangelog | sed -ne 's/^Version: \(.*\)-[^-]\+$/\1/p'` 6 | OLDVERSION=$VERSION 7 | 8 | if echo $VERSION | grep +cvsXXXXXXXX; then 9 | DATE=`date +%Y%m%d` 10 | VERSION=`echo $VERSION | sed -e s/+cvsXXXXXXXX/+cvs${DATE}/` 11 | else 12 | if [ ! -f ../isync_$VERSION.orig.tar.gz ]; then 13 | echo isync_$VERSION.orig.tar.gz must be found in the parent directory. 14 | exit 1 15 | fi 16 | fi 17 | rm -rf ../isync-$VERSION 18 | 19 | fakeroot ./debian/rules clean 20 | cp -rl . ../isync-$VERSION 21 | cd ../isync-$VERSION 22 | if [ "$OLDVERSION" != "$VERSION" ]; then 23 | sed -e s/+cvsXXXXXXXX/+cvs${DATE}/ < debian/changelog > debian/changelog.new 24 | mv debian/changelog.new debian/changelog 25 | fi 26 | find . -name CVS -print0 | xargs -0r rm -rf 27 | find . -name .cvsignore -print0 | xargs -0r rm 28 | find . -type l -print0 | xargs -0r rm 29 | find . -name .#\*# -print0 | xargs -0r rm 30 | aclocal 31 | autoheader 32 | automake --add-missing --copy 33 | autoconf 34 | if [ -n "$DOSIGN" ]; then 35 | SIGNOPTS= 36 | else 37 | SIGNOPTS="-us -uc" 38 | fi 39 | dpkg-buildpackage -rfakeroot $SIGNOPTS 40 | -------------------------------------------------------------------------------- /debian/patches/00list: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djnym/isync/24f4efdb589aac1c0aef8df0c186f53ad86de44c/debian/patches/00list -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | PACKAGE=isync 4 | 5 | CFLAGS = -Wall -g 6 | ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) 7 | CFLAGS += -O0 8 | else 9 | CFLAGS += -O2 10 | endif 11 | 12 | DEB_HOST_GNU_TYPE := $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) 13 | DEB_BUILD_GNU_TYPE := $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) 14 | 15 | build: build-stamp 16 | build-stamp: patch-stamp 17 | dh_testdir 18 | ./configure --disable-maintainer-mode --build=$(DEB_BUILD_GNU_TYPE) --host=$(DEB_HOST_GNU_TYPE) --prefix=/usr --mandir=/usr/share/man 19 | $(MAKE) CFLAGS="$(CFLAGS)" 20 | touch build-stamp 21 | 22 | clean: clean1 unpatch 23 | clean1: 24 | dh_testdir 25 | dh_testroot 26 | rm -f build-stamp 27 | -$(MAKE) distclean 28 | dh_clean Makefile config.log config.status 29 | 30 | install: build 31 | dh_testdir 32 | dh_testroot 33 | dh_clean -k 34 | dh_installdirs usr/bin usr/share/man/man1 35 | $(MAKE) DESTDIR=$(CURDIR)/debian/isync install 36 | 37 | binary-indep: build install 38 | 39 | binary-arch: build install 40 | dh_testdir 41 | dh_testroot 42 | dh_installchangelogs ChangeLog 43 | dh_installdocs AUTHORS NEWS README TODO 44 | dh_installexamples src/mbsyncrc.sample src/compat/isyncrc.sample 45 | dh_installman 46 | dh_strip 47 | dh_compress 48 | dh_fixperms 49 | dh_installdeb 50 | dh_shlibdeps 51 | dh_gencontrol 52 | dh_md5sums 53 | dh_builddeb 54 | 55 | binary: binary-indep binary-arch 56 | .PHONY: build clean binary-indep binary-arch binary install clean1 57 | 58 | include /usr/share/dpatch/dpatch.make 59 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=2 2 | http://sourceforge.net/project/showfiles.php?group_id=69662 .*/isync-(.*).tar.gz.* 3 | -------------------------------------------------------------------------------- /get-cert: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script will extract the necessary certificate from the IMAP server 4 | # It assumes that an attacker isn't trying to spoof you when you connect 5 | # to the IMAP server! You're better off downloading the certificate 6 | # from a trusted source. 7 | # 8 | # Copyright (C) 2003 Theodore Ts'o 9 | # This program is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software Foundation, 21 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 22 | # 23 | 24 | if [ $# != 1 ]; then 25 | echo "Usage: $0 " >&2 26 | exit 1 27 | fi 28 | 29 | HOST=$1 30 | 31 | seed=`date '+%s'` 32 | try=0 33 | while :; do 34 | TMPDIR=/tmp/get-cert.$$.$seed 35 | mkdir $TMPDIR 2> /dev/null && break 36 | if [ $try = 1000 ]; then 37 | echo "Cannot create temporary directory." >&2 38 | exit 1 39 | fi 40 | try=`expr $try + 1` 41 | seed=`expr \( \( $seed \* 1103515245 \) + 12345 \) % 2147483648` 42 | done 43 | 44 | TMPFILE=$TMPDIR/get-cert 45 | ERRFILE=$TMPDIR/get-cert-err 46 | CERTFILE=$TMPDIR/cert 47 | 48 | echo QUIT | openssl s_client -connect $HOST:993 -showcerts \ 49 | > $TMPFILE 2> $ERRFILE 50 | sed -e '1,/^-----BEGIN CERTIFICATE-----/d' \ 51 | -e '/^-----END CERTIFICATE-----/,$d' < $TMPFILE > $CERTFILE 52 | 53 | if test -s $CERTFILE ; then 54 | echo -----BEGIN CERTIFICATE----- 55 | cat $CERTFILE 56 | echo -----END CERTIFICATE----- 57 | else 58 | echo "Couldn't retrieve certificate. openssl reported the following errors:" 59 | cat $ERRFILE 60 | fi 61 | 62 | rm -r $TMPDIR 63 | -------------------------------------------------------------------------------- /isync.spec.in: -------------------------------------------------------------------------------- 1 | Summary: Utility to synchronize IMAP mailboxes with local maildir folders 2 | Name: isync 3 | Version: @VERSION@ 4 | Release: 1 5 | Copyright: GPL 6 | Group: Applications/Internet 7 | Source: @PACKAGE@-@VERSION@.tar.gz 8 | URL: http://@PACKAGE@.sf.net/ 9 | Packager: Oswald Buddenhagen 10 | BuildRoot: /var/tmp/%{name}-buildroot 11 | 12 | %description 13 | isync is a command line utility which synchronizes mailboxes; currently 14 | Maildir and IMAP4 mailboxes are supported. 15 | New messages, message deletions and flag changes can be propagated both ways. 16 | It is useful for working in disconnected mode, such as on a laptop or with a 17 | non-permanent internet collection (dIMAP). 18 | 19 | %prep 20 | %setup 21 | %build 22 | ./configure --prefix=/usr 23 | 24 | %install 25 | make DESTDIR=$RPM_BUILD_ROOT install 26 | 27 | %clean 28 | rm -rf $RPM_BUILD_ROOT 29 | 30 | %files 31 | %doc AUTHORS COPYING NEWS README TODO ChangeLog src/mbsyncrc.sample src/compat/isyncrc.sample 32 | /usr/bin/isync 33 | /usr/bin/mbsync 34 | /usr/bin/mdconvert 35 | /usr/bin/get-cert 36 | /usr/man/man1/isync.1.gz 37 | /usr/man/man1/mbsync.1.gz 38 | /usr/man/man1/mdconvert.1.gz 39 | -------------------------------------------------------------------------------- /src/.cvsignore: -------------------------------------------------------------------------------- 1 | .deps 2 | Makefile 3 | Makefile.in 4 | mbsync 5 | mdconvert 6 | tmp 7 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | if with_compat 2 | compat_dir = compat 3 | endif 4 | SUBDIRS = $(compat_dir) 5 | 6 | bin_PROGRAMS = mbsync mdconvert 7 | 8 | mbsync_SOURCES = main.c sync.c config.c util.c drv_imap.c drv_maildir.c 9 | mbsync_LDADD = -ldb $(SSL_LIBS) $(SOCK_LIBS) 10 | noinst_HEADERS = isync.h 11 | 12 | mdconvert_SOURCES = mdconvert.c 13 | mdconvert_LDADD = -ldb 14 | 15 | man_MANS = mbsync.1 mdconvert.1 16 | EXTRA_DIST = run-tests.pl mbsyncrc.sample $(man_MANS) 17 | -------------------------------------------------------------------------------- /src/compat/.cvsignore: -------------------------------------------------------------------------------- 1 | .deps 2 | Makefile 3 | Makefile.in 4 | isync 5 | -------------------------------------------------------------------------------- /src/compat/Makefile.am: -------------------------------------------------------------------------------- 1 | bin_PROGRAMS = isync 2 | 3 | isync_SOURCES = main.c config.c convert.c util.c 4 | isync_LDADD = -ldb 5 | noinst_HEADERS = isync.h 6 | 7 | man_MANS = isync.1 8 | EXTRA_DIST = isyncrc.sample $(man_MANS) 9 | -------------------------------------------------------------------------------- /src/compat/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2004 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 19 | */ 20 | 21 | #include "isync.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | static int local_home, local_root; 33 | 34 | static char * 35 | my_strndup( const char *s, size_t nchars ) 36 | { 37 | char *r = nfmalloc( sizeof(char) * (nchars + 1) ); 38 | memcpy( r, s, nchars ); 39 | r[nchars] = 0; 40 | return r; 41 | } 42 | 43 | char * 44 | expand_strdup( const char *s ) 45 | { 46 | struct passwd *pw; 47 | const char *p, *q; 48 | char *r; 49 | 50 | if (*s == '~') { 51 | s++; 52 | if (!*s) { 53 | p = 0; 54 | q = Home; 55 | } else if (*s == '/') { 56 | p = s + 1; 57 | q = Home; 58 | } else { 59 | if ((p = strchr( s, '/' ))) { 60 | r = my_strndup( s, (int)(p - s) ); 61 | pw = getpwnam( r ); 62 | free( r ); 63 | p++; 64 | } else 65 | pw = getpwnam( s ); 66 | if (!pw) 67 | return 0; 68 | q = pw->pw_dir; 69 | } 70 | nfasprintf( &r, "%s/%s", q, p ? p : "" ); 71 | return r; 72 | } else if (*s != '/' && xmaildir) { 73 | nfasprintf( &r, "%s/%s", xmaildir, s ); 74 | return r; 75 | } else 76 | return nfstrdup( s ); 77 | } 78 | 79 | static int 80 | is_true( const char *val ) 81 | { 82 | return 83 | !strcasecmp( val, "yes" ) || 84 | !strcasecmp( val, "true" ) || 85 | !strcasecmp( val, "on" ) || 86 | !strcmp( val, "1" ); 87 | } 88 | 89 | void 90 | load_config( const char *path, config_t ***stor ) 91 | { 92 | config_t **sstor, *cfg; 93 | FILE *fp; 94 | char *p, *cmd, *val; 95 | int line = 0; 96 | char buf[1024]; 97 | 98 | if (!(fp = fopen( path, "r" ))) { 99 | if (errno != ENOENT) 100 | perror( "fopen" ); 101 | return; 102 | } 103 | if (!Quiet && !Debug && !Verbose) 104 | printf( "Reading configuration file %s\n", path ); 105 | buf[sizeof(buf) - 1] = 0; 106 | cfg = &global; 107 | while (fgets( buf, sizeof(buf) - 1, fp )) { 108 | p = buf; 109 | cmd = next_arg( &p ); 110 | val = next_arg( &p ); 111 | line++; 112 | if (!cmd || *cmd == '#') 113 | continue; 114 | if (!val) { 115 | fprintf( stderr, "%s:%d: parameter missing\n", path, line ); 116 | continue; 117 | } 118 | if (!strcasecmp( "Mailbox", cmd )) { 119 | if (o2o) 120 | break; 121 | cfg = **stor = nfmalloc( sizeof(config_t) ); 122 | *stor = &cfg->next; 123 | memcpy( cfg, &global, sizeof(config_t) ); 124 | if (val[0] == '~' && val[1] == '/') { 125 | val += 2; 126 | local_home = 1; 127 | } else if (!memcmp( val, Home, HomeLen ) && val[HomeLen] == '/') { 128 | val += HomeLen + 1; 129 | local_home = 1; 130 | } else if (val[0] == '/') 131 | local_root = 1; 132 | else 133 | local_home = 1; 134 | /* not expanded at this point */ 135 | cfg->path = nfstrdup( val ); 136 | } else if (!strcasecmp( "OneToOne", cmd )) { 137 | if (boxes) { 138 | forbid: 139 | fprintf( stderr, 140 | "%s:%d: keyword '%s' allowed only in global section\n", 141 | path, line, cmd ); 142 | continue; 143 | } 144 | o2o = is_true( val ); 145 | } else if (!strcasecmp( "Maildir", cmd )) { 146 | if (boxes) 147 | goto forbid; 148 | maildir = nfstrdup( val ); 149 | xmaildir = expand_strdup( val ); 150 | } else if (!strcasecmp( "Folder", cmd )) { 151 | if (boxes) 152 | goto forbid; 153 | folder = nfstrdup( val ); 154 | } else if (!strcasecmp( "Inbox", cmd )) { 155 | if (boxes) 156 | goto forbid; 157 | inbox = nfstrdup( val ); 158 | } else if (!strcasecmp( "Host", cmd )) { 159 | if (!memcmp( "imaps:", val, 6 )) { 160 | val += 6; 161 | cfg->use_imaps = 1; 162 | cfg->port = 993; 163 | cfg->use_sslv2 = 1; 164 | cfg->use_sslv3 = 1; 165 | } 166 | cfg->host = nfstrdup( val ); 167 | } else if (!strcasecmp( "User", cmd )) 168 | cfg->user = nfstrdup( val ); 169 | else if (!strcasecmp( "Pass", cmd )) 170 | cfg->pass = nfstrdup( val ); 171 | else if (!strcasecmp ( "Port", cmd )) 172 | cfg->port = atoi( val ); 173 | else if (!strcasecmp ( "Box", cmd )) 174 | cfg->box = nfstrdup( val ); 175 | else if (!strcasecmp ( "Alias", cmd )) { 176 | if (!boxes) { 177 | fprintf( stderr, 178 | "%s:%d: keyword 'Alias' allowed only in mailbox specification\n", 179 | path, line ); 180 | continue; 181 | } 182 | cfg->alias = nfstrdup( val ); 183 | } else if (!strcasecmp( "MaxSize", cmd )) 184 | cfg->max_size = atol( val ); 185 | else if (!strcasecmp ( "MaxMessages", cmd )) 186 | cfg->max_messages = atol( val ); 187 | else if (!strcasecmp ( "UseNamespace", cmd )) 188 | cfg->use_namespace = is_true( val ); 189 | else if (!strcasecmp ( "CopyDeletedTo", cmd )) 190 | cfg->copy_deleted_to = nfstrdup( val ); 191 | else if (!strcasecmp ( "Tunnel", cmd )) 192 | cfg->tunnel = nfstrdup( val ); 193 | else if (!strcasecmp ( "Expunge", cmd )) 194 | cfg->expunge = is_true( val ); 195 | else if (!strcasecmp( "Delete", cmd )) 196 | cfg->delete = is_true( val ); 197 | else if (!strcasecmp( "CertificateFile", cmd )) 198 | cfg->cert_file = expand_strdup( val ); 199 | else if (!strcasecmp( "RequireSSL", cmd )) 200 | cfg->require_ssl = is_true( val ); 201 | else if (!strcasecmp( "UseSSLv2", cmd )) 202 | cfg->use_sslv2 = is_true( val ); 203 | else if (!strcasecmp( "UseSSLv3", cmd )) 204 | cfg->use_sslv3 = is_true( val ); 205 | else if (!strcasecmp( "UseTLSv1", cmd )) 206 | cfg->use_tlsv1 = is_true( val ); 207 | else if (!strcasecmp( "RequireCRAM", cmd )) 208 | cfg->require_cram = is_true( val ); 209 | else if (buf[0]) 210 | fprintf( stderr, "%s:%d: unknown keyword '%s'\n", path, line, cmd ); 211 | } 212 | fclose( fp ); 213 | if (o2o) { 214 | if (!global.host && !global.tunnel) { 215 | fprintf( stderr, "Neither Host nor Tunnel given to OneToOne. Aborting.\n" ); 216 | exit( 1 ); 217 | } 218 | } else 219 | for (sstor = &boxes; (cfg = *sstor); ) { 220 | if (!cfg->host && !cfg->tunnel) { 221 | fprintf( stderr, "Mailbox '%s' has neither Host nor Tunnel. Skipping.\n", 222 | cfg->alias ? cfg->alias : cfg->path ); 223 | if (&cfg->next == *stor) 224 | *stor = sstor; 225 | *sstor = cfg->next; 226 | continue; 227 | } 228 | sstor = &cfg->next; 229 | } 230 | } 231 | 232 | static const char * 233 | tb( int on ) 234 | { 235 | return on ? "yes" : "no"; 236 | } 237 | 238 | static void 239 | write_imap_server( FILE *fp, config_t *cfg ) 240 | { 241 | config_t *pbox; 242 | char *p, *p2; 243 | int hl, a1, a2, a3, a4; 244 | char buf[128], ubuf[64]; 245 | static int tunnels; 246 | 247 | if (cfg->tunnel) 248 | nfasprintf( (char **)&cfg->old_server_name, "tunnel%d", ++tunnels ); 249 | else if (cfg->host) { 250 | if (sscanf( cfg->host, "%d.%d.%d.%d", &a1, &a2, &a3, &a4 ) == 4) 251 | hl = nfsnprintf( buf, sizeof(buf), "%s", cfg->host ); 252 | else { 253 | /* XXX this does not avoid clashes. add port? */ 254 | p = strrchr( cfg->host, '.' ); 255 | if (!p) 256 | hl = nfsnprintf( buf, sizeof(buf), "%s", cfg->host ); 257 | else { 258 | hl = nfsnprintf( buf, sizeof(buf), "%.*s", p - cfg->host, cfg->host ); 259 | p2 = strrchr( buf, '.' ); 260 | if (p2) 261 | hl = sprintf( buf, "%s", p2 + 1 ); 262 | } 263 | } 264 | if (boxes) /* !o2o */ 265 | for (pbox = boxes; pbox != cfg; pbox = pbox->next) 266 | if (!memcmp( pbox->server_name, buf, hl + 1 )) { 267 | nfasprintf( (char **)&cfg->old_server_name, "%s-%d", buf, ++pbox->old_servers ); 268 | goto gotsrv; 269 | } 270 | cfg->old_server_name = nfstrdup( buf ); 271 | cfg->old_servers = 1; 272 | gotsrv: ; 273 | } else { 274 | fprintf( stderr, "ERROR: Neither host nor tunnel specified for mailbox %s.\n", cfg->path ); 275 | exit( 1 ); 276 | } 277 | 278 | if (cfg->user) 279 | nfsnprintf( ubuf, sizeof(ubuf), "%s@", cfg->user ); 280 | else 281 | ubuf[0] = 0; 282 | if (!cfg->host) 283 | hl = nfsnprintf( buf, sizeof(buf), "%stunnel", ubuf ); 284 | else { 285 | if (cfg->port != (cfg->use_imaps ? 993 : 143)) 286 | hl = nfsnprintf( buf, sizeof(buf), "%s%s_%d", ubuf, cfg->host, cfg->port ); 287 | else 288 | hl = nfsnprintf( buf, sizeof(buf), "%s%s", ubuf, cfg->host ); 289 | } 290 | if (boxes) /* !o2o */ 291 | for (pbox = boxes; pbox != cfg; pbox = pbox->next) 292 | if (!memcmp( pbox->server_name, buf, hl + 1 )) { 293 | nfasprintf( (char **)&cfg->server_name, "%s-%d", buf, ++pbox->servers ); 294 | goto ngotsrv; 295 | } 296 | cfg->server_name = nfstrdup( buf ); 297 | cfg->servers = 1; 298 | ngotsrv: ; 299 | 300 | fprintf( fp, "IMAPAccount %s\n", cfg->server_name ); 301 | if (cfg->tunnel) 302 | fprintf( fp, "Tunnel \"%s\"\n", cfg->tunnel ); 303 | else { 304 | if (cfg->use_imaps) 305 | fprintf( fp, "Host imaps:%s\n", cfg->host ); 306 | else 307 | fprintf( fp, "Host %s\n", cfg->host ); 308 | fprintf( fp, "Port %d\n", cfg->port ); 309 | } 310 | if (cfg->user) 311 | fprintf( fp, "User \"%s\"\n", cfg->user ); 312 | if (cfg->pass) 313 | fprintf( fp, "Pass \"%s\"\n", cfg->pass ); 314 | fprintf( fp, "RequireCRAM %s\nRequireSSL %s\n" 315 | "UseSSLv2 %s\nUseSSLv3 %s\nUseTLSv1 %s\n", 316 | tb(cfg->require_cram), tb(cfg->require_ssl), 317 | tb(cfg->use_sslv2), tb(cfg->use_sslv3), tb(cfg->use_tlsv1) ); 318 | if ((cfg->use_imaps || cfg->use_sslv2 || cfg->use_sslv3 || cfg->use_tlsv1) && 319 | cfg->cert_file) 320 | fprintf( fp, "CertificateFile %s\n", cfg->cert_file ); 321 | fputc( '\n', fp ); 322 | } 323 | 324 | static void 325 | write_imap_store( FILE *fp, config_t *cfg ) 326 | { 327 | if (cfg->stores > 1) 328 | nfasprintf( (char **)&cfg->store_name, "%s-%d", cfg->old_server_name, cfg->stores ); 329 | else 330 | cfg->store_name = cfg->old_server_name; 331 | fprintf( fp, "IMAPStore %s\nAccount %s\n", 332 | cfg->store_name, cfg->server_name ); 333 | if (*folder) 334 | fprintf( fp, "Path \"%s\"\n", folder ); 335 | else 336 | fprintf( fp, "UseNamespace %s\n", tb(cfg->use_namespace) ); 337 | if (inbox) 338 | fprintf( fp, "MapInbox \"%s\"\n", inbox ); 339 | if (cfg->copy_deleted_to) 340 | fprintf( fp, "Trash \"%s\"\n", cfg->copy_deleted_to ); 341 | fputc( '\n', fp ); 342 | } 343 | 344 | static void 345 | write_channel_parm( FILE *fp, config_t *cfg ) 346 | { 347 | if (cfg->max_size) 348 | fprintf( fp, "MaxSize %d\n", cfg->max_size ); 349 | if (cfg->max_messages) 350 | fprintf( fp, "MaxMessages %d\n", cfg->max_messages ); 351 | if (!cfg->delete && !delete) 352 | fputs( "Sync New ReNew Flags\n", fp ); 353 | if (cfg->expunge || expunge) 354 | fputs( "Expunge Both\n", fp ); 355 | fputc( '\n', fp ); 356 | } 357 | 358 | static int 359 | mstrcmp( const char *s1, const char *s2 ) 360 | { 361 | if (s1 == s2) 362 | return 0; 363 | if (!s1 || !s2) 364 | return 1; 365 | return strcmp( s1, s2 ); 366 | } 367 | 368 | void 369 | write_config( int fd ) 370 | { 371 | FILE *fp; 372 | const char *cn, *scn; 373 | config_t *box, *sbox, *pbox; 374 | 375 | if (!(fp = fdopen( fd, "w" ))) { 376 | perror( "fdopen" ); 377 | return; 378 | } 379 | 380 | fprintf( fp, "SyncState *\n\n" ); 381 | if (local_home || o2o) 382 | fprintf( fp, "MaildirStore local\nPath \"%s/\"\nInbox \"%s/INBOX\"\nAltMap %s\n\n", 383 | maildir, maildir, tb( altmap > 0 ) ); 384 | if (local_root) 385 | fprintf( fp, "MaildirStore local_root\nPath /\nAltMap %s\n\n", tb( altmap > 0 ) ); 386 | if (o2o) { 387 | write_imap_server( fp, &global ); 388 | write_imap_store( fp, &global ); 389 | fprintf( fp, "Channel o2o\nMaster :%s:\nSlave :local:\nPattern %%\n", global.store_name ); 390 | write_channel_parm( fp, &global ); 391 | } else { 392 | for (box = boxes; box; box = box->next) { 393 | for (pbox = boxes; pbox != box; pbox = pbox->next) { 394 | if (box->tunnel) { 395 | if (mstrcmp( pbox->tunnel, box->tunnel )) 396 | continue; 397 | } else { 398 | if (mstrcmp( pbox->host, box->host ) || 399 | pbox->use_imaps != box->use_imaps || 400 | pbox->port != box->port) 401 | continue; 402 | } 403 | if (mstrcmp( pbox->user, box->user ) || 404 | mstrcmp( pbox->pass, box->pass )) /* nonsense */ 405 | continue; 406 | if ((box->use_imaps || box->use_sslv2 || 407 | box->use_sslv3 || box->use_tlsv1) && 408 | mstrcmp( pbox->cert_file, box->cert_file )) /* nonsense */ 409 | continue; 410 | if (pbox->use_imaps != box->use_imaps || 411 | pbox->use_sslv2 != box->use_sslv2 || 412 | pbox->use_sslv3 != box->use_sslv3 || 413 | pbox->use_tlsv1 != box->use_tlsv1) 414 | continue; 415 | box->server_name = pbox->server_name; 416 | for (sbox = boxes; sbox != box; sbox = sbox->next) { 417 | if (sbox->server_name != box->server_name || 418 | mstrcmp( sbox->copy_deleted_to, box->copy_deleted_to ) || 419 | (!*folder && sbox->use_namespace != box->use_namespace)) 420 | continue; 421 | box->store_name = sbox->store_name; 422 | goto gotall; 423 | } 424 | box->stores = ++pbox->stores; 425 | goto gotsrv; 426 | } 427 | write_imap_server( fp, box ); 428 | box->stores = 1; 429 | gotsrv: 430 | write_imap_store( fp, box ); 431 | gotall: 432 | if (box->alias) 433 | cn = box->alias; 434 | else { 435 | cn = strrchr( box->path, '/' ); 436 | if (cn) 437 | cn++; 438 | else 439 | cn = box->path; 440 | } 441 | for (sbox = boxes; sbox != box; sbox = sbox->next) { 442 | if (sbox->alias) 443 | scn = sbox->alias; 444 | else { 445 | scn = strrchr( sbox->path, '/' ); 446 | if (scn) 447 | scn++; 448 | else 449 | scn = sbox->path; 450 | } 451 | if (mstrcmp( cn, scn )) 452 | continue; 453 | nfasprintf( (char **)&box->channel_name, "%s-%d", cn, ++sbox->channels ); 454 | goto gotchan; 455 | } 456 | box->channels = 1; 457 | box->channel_name = cn; 458 | gotchan: 459 | if (box->path[0] == '/') 460 | fprintf( fp, "Channel %s\nMaster :%s:%s\nSlave :local_root:%s\n", 461 | box->channel_name, box->store_name, box->box, box->path + 1 ); 462 | else 463 | fprintf( fp, "Channel %s\nMaster :%s:%s\nSlave :local:%s\n", 464 | box->channel_name, box->store_name, box->box, box->path ); 465 | write_channel_parm( fp, box ); 466 | } 467 | 468 | } 469 | 470 | fclose( fp ); 471 | } 472 | 473 | config_t * 474 | find_box( const char *s ) 475 | { 476 | config_t *p; 477 | char *t; 478 | 479 | if (!memcmp( s, Home, HomeLen ) && s[HomeLen] == '/') 480 | s += HomeLen + 1; 481 | for (p = boxes; p; p = p->next) { 482 | if (!strcmp( s, p->path ) || (p->alias && !strcmp( s, p->alias ))) 483 | return p; 484 | /* check to see if the full pathname was specified on the 485 | * command line. 486 | */ 487 | t = expand_strdup( p->path ); 488 | if (!strcmp( s, t )) { 489 | free( t ); 490 | return p; 491 | } 492 | free( t ); 493 | } 494 | return 0; 495 | } 496 | -------------------------------------------------------------------------------- /src/compat/convert.c: -------------------------------------------------------------------------------- 1 | /* 2 | * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2004 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 19 | */ 20 | 21 | #include "isync.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | static const char *subdirs[] = { "cur", "new", "tmp" }; 37 | 38 | static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' }; 39 | 40 | static int 41 | parse_info( const char *s ) 42 | { 43 | unsigned i; 44 | int flags; 45 | 46 | flags = 0; 47 | if (s && *(s + 1) == '2' && *(s + 2) == ',') 48 | for (s += 3, i = 0; i < as(Flags); i++) 49 | if (strchr( s, Flags[i] )) 50 | flags |= (1 << i); 51 | return flags; 52 | } 53 | 54 | typedef struct { 55 | int uid, flags; 56 | } msg_t; 57 | 58 | static int 59 | compare_uids( const void *l, const void *r ) 60 | { 61 | return ((msg_t *)l)->uid - ((msg_t *)r)->uid; 62 | } 63 | 64 | static DBT key, value; 65 | static struct flock lck; 66 | 67 | void 68 | convert( config_t *box ) 69 | { 70 | DIR *d; 71 | struct dirent *e; 72 | char *s, *p, *mboxdir; 73 | FILE *fp; 74 | msg_t *msgs; 75 | DB *db; 76 | int i, ret, fd, uidval, maxuid, bl, uid, rmsgs, nmsgs, uv[2]; 77 | unsigned u; 78 | struct stat sb; 79 | char buf[_POSIX_PATH_MAX], diumname[_POSIX_PATH_MAX], 80 | uvname[_POSIX_PATH_MAX], sname[_POSIX_PATH_MAX], 81 | iuvname[_POSIX_PATH_MAX], imuname[_POSIX_PATH_MAX], 82 | ilname[_POSIX_PATH_MAX], iumname[_POSIX_PATH_MAX]; 83 | 84 | mboxdir = expand_strdup( box->path ); 85 | nfsnprintf( iuvname, sizeof(iuvname), "%s/isyncuidvalidity", mboxdir ); 86 | nfsnprintf( diumname, sizeof(iumname), "%s/.isyncuidmap.db", mboxdir ); 87 | nfsnprintf( uvname, sizeof(uvname), "%s/.uidvalidity", mboxdir ); 88 | if (stat( iuvname, &sb )) { 89 | if (!stat( diumname, &sb )) 90 | altmap++; 91 | else if (!stat( uvname, &sb )) 92 | altmap--; 93 | err1: 94 | free( mboxdir ); 95 | return; 96 | } 97 | for (i = 0; i < 3; i++) { 98 | nfsnprintf( buf, sizeof(buf), "%s/%s", mboxdir, subdirs[i] ); 99 | if (stat( buf, &sb )) { 100 | fprintf( stderr, "ERROR: stat %s: %s (errno %d)\n", buf, 101 | strerror(errno), errno ); 102 | fprintf( stderr, 103 | "ERROR: %s does not appear to be a valid maildir style mailbox\n", 104 | mboxdir ); 105 | goto err1; 106 | } 107 | } 108 | nfsnprintf( iumname, sizeof(iumname), "%s/isyncuidmap.db", mboxdir ); 109 | nfsnprintf( imuname, sizeof(imuname), "%s/isyncmaxuid", mboxdir ); 110 | nfsnprintf( ilname, sizeof(ilname), "%s/isynclock", mboxdir ); 111 | nfsnprintf( sname, sizeof(sname), "%s/.mbsyncstate", mboxdir ); 112 | 113 | if ((fd = open( ilname, O_WRONLY|O_CREAT, 0600 )) < 0) { 114 | perror( ilname ); 115 | goto err1; 116 | } 117 | #if SEEK_SET != 0 118 | lck.l_whence = SEEK_SET; 119 | #endif 120 | #if F_WRLCK != 0 121 | lck.l_type = F_WRLCK; 122 | #endif 123 | if (fcntl( fd, F_SETLKW, &lck )) { 124 | perror( ilname ); 125 | err2: 126 | close( fd ); 127 | goto err1; 128 | } 129 | 130 | if (!(fp = fopen( iuvname, "r" ))) { 131 | perror( iuvname ); 132 | goto err2; 133 | } 134 | fscanf( fp, "%d", &uidval ); 135 | fclose( fp ); 136 | if (!(fp = fopen( imuname, "r" ))) { 137 | perror( imuname ); 138 | goto err2; 139 | } 140 | fscanf( fp, "%d", &maxuid ); 141 | fclose( fp ); 142 | 143 | if (!stat( iumname, &sb )) { 144 | if (db_create( &db, 0, 0 )) { 145 | fputs( "dbcreate failed\n", stderr ); 146 | goto err2; 147 | } 148 | if ((db->open)( db, 0, iumname, 0, DB_HASH, 0, 0 )) { 149 | fputs( "cannot open db\n", stderr ); 150 | db->close( db, 0 ); 151 | goto err2; 152 | } 153 | altmap++; 154 | } else { 155 | db = 0; 156 | altmap--; 157 | } 158 | 159 | msgs = 0; 160 | rmsgs = 0; 161 | nmsgs = 0; 162 | for (i = 0; i < 2; i++) { 163 | bl = nfsnprintf( buf, sizeof(buf), "%s/%s/", mboxdir, subdirs[i] ); 164 | if (!(d = opendir( buf ))) { 165 | perror( "opendir" ); 166 | err4: 167 | if (msgs) 168 | free( msgs ); 169 | if (db) 170 | db->close( db, 0 ); 171 | goto err2; 172 | } 173 | while ((e = readdir( d ))) { 174 | if (*e->d_name == '.') 175 | continue; 176 | s = strchr( e->d_name, ':' ); 177 | if (db) { 178 | key.data = e->d_name; 179 | key.size = s ? (size_t)(s - e->d_name) : strlen( e->d_name ); 180 | if ((ret = db->get( db, 0, &key, &value, 0 ))) { 181 | if (ret != DB_NOTFOUND) 182 | db->err( db, ret, "Maildir error: db->get()" ); 183 | continue; 184 | } 185 | uid = *(int *)value.data; 186 | } else if ((p = strstr( e->d_name, ",U=" ))) 187 | uid = atoi( p + 3 ); 188 | else 189 | continue; 190 | if (nmsgs == rmsgs) { 191 | rmsgs = rmsgs * 2 + 100; 192 | msgs = nfrealloc( msgs, rmsgs * sizeof(msg_t) ); 193 | } 194 | msgs[nmsgs].uid = uid; 195 | msgs[nmsgs++].flags = parse_info( s ); 196 | } 197 | closedir( d ); 198 | } 199 | 200 | qsort( msgs, nmsgs, sizeof(msg_t), compare_uids ); 201 | 202 | if (!(fp = fopen( sname, "w" ))) { 203 | perror( sname ); 204 | goto err4; 205 | } 206 | if (box->max_messages) { 207 | if (!nmsgs) 208 | i = maxuid; 209 | else { 210 | i = nmsgs - box->max_messages; 211 | if (i < 0) 212 | i = 0; 213 | i = msgs[i].uid - 1; 214 | } 215 | } else 216 | i = 0; 217 | fprintf( fp, "%d:%d %d:%d:%d\n", uidval, maxuid, uidval, i, maxuid ); 218 | for (i = 0; i < nmsgs; i++) { 219 | fprintf( fp, "%d %d ", msgs[i].uid, msgs[i].uid ); 220 | for (u = 0; u < as(Flags); u++) 221 | if (msgs[i].flags & (1 << u)) 222 | fputc( Flags[u], fp ); 223 | fputc( '\n', fp ); 224 | } 225 | fclose( fp ); 226 | 227 | if (db) { 228 | key.data = (void *)"UIDVALIDITY"; 229 | key.size = 11; 230 | uv[0] = uidval; 231 | uv[1] = maxuid; 232 | value.data = uv; 233 | value.size = sizeof(uv); 234 | if ((ret = db->put( db, 0, &key, &value, 0 ))) { 235 | db->err( db, ret, "Maildir error: db->put()" ); 236 | goto err4; 237 | } 238 | db->close( db, 0 ); 239 | rename( iumname, diumname ); 240 | } else { 241 | if (!(fp = fopen( uvname, "w" ))) { 242 | perror( uvname ); 243 | goto err4; 244 | } 245 | fprintf( fp, "%d\n%d\n", uidval, maxuid ); 246 | fclose( fp ); 247 | } 248 | 249 | unlink( iuvname ); 250 | unlink( imuname ); 251 | 252 | close( fd ); 253 | unlink( ilname ); 254 | 255 | if (msgs) 256 | free( msgs ); 257 | free( mboxdir ); 258 | return; 259 | } 260 | -------------------------------------------------------------------------------- /src/compat/isync.1: -------------------------------------------------------------------------------- 1 | .ig 2 | \" isync - mbsync wrapper: IMAP4 to Maildir mailbox synchronizer 3 | \" Copyright (C) 2000-2002 Michael R. Elkins 4 | \" Copyright (C) 2002-2004 Oswald Buddenhagen 5 | \" 6 | \" This program is free software; you can redistribute it and/or modify 7 | \" it under the terms of the GNU General Public License as published by 8 | \" the Free Software Foundation; either version 2 of the License, or 9 | \" (at your option) any later version. 10 | \" 11 | \" This program is distributed in the hope that it will be useful, 12 | \" but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | \" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | \" GNU General Public License for more details. 15 | \" 16 | \" You should have received a copy of the GNU General Public License 17 | \" along with this program; if not, write to the Free Software Foundation, 18 | \" Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 19 | \" 20 | .. 21 | .TH isync 1 "2004 Mar 29" 22 | .. 23 | .SH NAME 24 | isync - synchronize IMAP4 and Maildir mailboxes 25 | .. 26 | .SH SYNOPSIS 27 | \fBisync\fR [\fIoptions\fR ...] {\fImailbox\fR ...|\fI-a\fR|\fI-l\fR} 28 | .. 29 | .SH DESCRIPTION 30 | \fBisync\fR is a command line application which synchronizes local 31 | Maildir mailboxes with remote IMAP4 mailboxes, suitable for use in 32 | IMAP-disconnected mode. Multiple copies of the remote IMAP4 mailboxes can 33 | be maintained, and all flags are synchronized. 34 | .br 35 | \fBisync\fR is only a wrapper binary around \fBmbsync\fR to simplify upgrades. 36 | It will automatically migrate the UID mapping from previous versions of 37 | \fBisync\fR (even before 0.8) to the new format, and transparently call 38 | \fBmbsync\fR. If you were using \fBisync\fR version 0.8 or 0.9.x you might 39 | want to use \fBmdconvert\fR to convert the mailboxes to the more efficient 40 | \fBnative\fR UID storage scheme after migrating them. 41 | .. 42 | .SH OPTIONS 43 | .TP 44 | \fB-c\fR, \fB--config\fR \fIfile\fR 45 | Read configuration from \fIfile\fR. 46 | By default, the configuration is read from ~/.isyncrc if it exists. 47 | .TP 48 | \fB-1\fR, \fB--one-to-one\fR 49 | Instead of using the mailbox specifications in ~/.isyncrc, isync will pick up 50 | all mailboxes from the local directory and remote folder and map them 1:1 51 | onto each other according to their names. 52 | .TP 53 | \fB-I\fR, \fB--inbox\fR \fImailbox\fR 54 | Exception to the 1:1 mapping created by -1: the special IMAP mailbox \fIINBOX\fR 55 | is mapped to the local \fImailbox\fR (relative to the maildir). 56 | .TP 57 | \fB-a\fR, \fB--all\fR 58 | Synchronize all mailboxes (either specified in ~/.isyncrc or determined by the 59 | 1:1 mapping). 60 | .TP 61 | \fB-l\fR, \fB--list\fR 62 | Don't synchronize anything, but list all mailboxes and exit. 63 | .TP 64 | \fB-L\fR, \fB--create-local\fR 65 | Automatically create the local Maildir mailbox if it doesn't already 66 | exist. 67 | .TP 68 | \fB-R\fR, \fB--create-remote\fR 69 | Automatically create the remote IMAP mailbox if it doesn't already exist. 70 | .TP 71 | \fB-C\fR, \fB--create\fR 72 | Automatically create any mailboxes if they don't already exist. 73 | This is simply a combination of -L and -R. 74 | .TP 75 | \fB-d\fR, \fB--delete\fR 76 | Causes \fBisync\fR to propagate message deletions. 77 | By default, \fIdead\fR messages are \fBnot\fR deleted. 78 | .TP 79 | \fB-e\fR, \fB--expunge\fR 80 | Causes \fBisync\fR to permanently remove all messages marked for deletion. 81 | By default, \fIdeleted\fR messages are \fBnot\fR expunged. 82 | .TP 83 | \fB-f\fR, \fB--fast\fR 84 | Only fetch new messages existing on the server into the local mailbox. 85 | Message deletions and flag changes will not be propagated. 86 | .TP 87 | \fB-h\fR, \fB--help\fR 88 | Displays a summary of command line options 89 | .TP 90 | \fB-p\fR, \fB--port\fR \fIport\fR 91 | Specifies the port on the IMAP server to connect to (default: 143 for imap, 92 | 993 for imaps) 93 | .TP 94 | \fB-q\fR, \fB--quiet\fR 95 | Suppress informational messages. 96 | If specified twice, suppress warning messages as well. 97 | .TP 98 | \fB-r\fR, \fB--remote\fR \fIbox\fR 99 | Specifies the name of the remote IMAP mailbox to synchronize with 100 | (Default: INBOX) 101 | .TP 102 | \fB-s\fR, \fB--host\fR [\fBimaps:\fR]\fIhost\fR 103 | Specifies the hostname of the IMAP server 104 | .TP 105 | \fB-u\fR, \fB--user\fR \fIuser\fR 106 | Specifies the login name to access the IMAP server (default: $USER) 107 | .TP 108 | \fB-M\fR, \fB--maildir\fR \fIdir\fR 109 | Specifies the location for your local mailboxes. 110 | .TP 111 | \fB-F\fR, \fB--folder\fR \fIfolder\fR/ 112 | Specifies the location for your remote mailboxes. 113 | .TP 114 | \fB-v\fR, \fB--version\fR 115 | Displays \fBisync\fR version information. 116 | .TP 117 | \fB-V\fR, \fB--verbose\fR 118 | Enables \fIverbose\fR mode, which displays the IMAP4 network traffic. 119 | .TP 120 | \fB-D\fR, \fB--debug\fR 121 | Enable printing of \fIdebug\fR messages. 122 | .TP 123 | \fB-w\fR, \fB--write\fR 124 | Don't run \fBmbsync\fR, but instead write a permanent config file for it. 125 | The UID mappings of all configured mailboxes will be migrated. 126 | Note that most command line options that would affect an actual sync operation 127 | will be incorporated into the new config file as well; exceptions are 128 | --fast and --create[-remote|-local]. 129 | The name of the new config file is determined by replacing the last occurrence 130 | of "isync" with "mbsync", or appending ".mbsync" if "isync" was not found. 131 | .TP 132 | \fB-W\fR, \fB--writeto\fR \fIfile\fR 133 | Like \fB-w\fR, but use the specified name for the new config file. 134 | .. 135 | .SH CONFIGURATION 136 | \fBisync\fR by default reads \fI~/.isyncrc\fR to load configuration data. 137 | Each non-empty line of the configuration file that does not start with a 138 | hash mark consists of a command. 139 | The following commands are understood: 140 | .TP 141 | \fBMailbox\fR \fIpath\fR 142 | Defines a local Maildir mailbox. All configuration commands following this 143 | line, up until the next \fIMailbox\fR command, apply to this mailbox only. 144 | .. 145 | .TP 146 | \fBHost\fR [\fBimaps:\fR]\fIname\fR 147 | Defines the DNS name or IP address of the IMAP server. If the hostname is 148 | prefixed with \fBimaps:\fR the connection is assumed to be a SSL connection 149 | to port 993 (though you can change this by placing a \fBPort\fR command 150 | \fBafter\fR the \fBHost\fR command). 151 | Note that modern servers support SSL on the default port 143. 152 | \fBisync\fR will always attempt to use SSL if available. 153 | .. 154 | .TP 155 | \fBPort\fR \fIport\fR 156 | Defines the TCP port number of the IMAP server (Default: 143 for imap, 157 | 993 for imaps) 158 | .. 159 | .TP 160 | \fBBox\fR \fImailbox\fR 161 | Defines the name of the remote IMAP mailbox associated with the local 162 | Maildir mailbox (Default: INBOX) 163 | .. 164 | .TP 165 | \fBUser\fR \fIusername\fR 166 | Defines the login name on the IMAP server (Default: current user) 167 | .. 168 | .TP 169 | \fBPass\fR \fIpassword\fR 170 | Defines the password for \fIusername\fR on the IMAP server. 171 | Note that this option is \fBNOT\fR required. 172 | If no password is specified in the configuration file, \fBisync\fR 173 | will prompt you for it. 174 | .. 175 | .TP 176 | \fBAlias\fR \fIstring\fR 177 | Defines an alias for the mailbox which can be used as a shortcut on the 178 | command line. 179 | .. 180 | .TP 181 | \fBCopyDeletedTo\fR \fImailbox\fR 182 | Specifies the remote IMAP mailbox to copy deleted messages to prior to 183 | expunging (Default: none). 184 | .. 185 | .TP 186 | \fBDelete\fR \fIyes\fR|\fIno\fR 187 | Specifies whether message deletions are propagated. (Default: no). 188 | \fBNOTE:\fR The \fI-d\fR command line option overrides this setting when 189 | set to \fIno\fR. 190 | .. 191 | .TP 192 | \fBExpunge\fR \fIyes\fR|\fIno\fR 193 | Specifies whether deleted messages are expunged. (Default: no). 194 | \fBNOTE:\fR The \fI-e\fR command line option overrides this setting when 195 | set to \fIno\fR. 196 | .. 197 | .TP 198 | \fBMailDir\fR \fIdirectory\fR 199 | Specifies the location of your local mailboxes if a relative path is 200 | specified in a \fIMailbox\fR command (Default: \fI~\fR). 201 | \fBNOTE:\fR This directive is allowed only in the \fIglobal\fR 202 | section (see below). 203 | .. 204 | .TP 205 | \fBFolder\fR \fIdirectory\fR/ 206 | Specifies the location of your IMAP mailboxes 207 | specified in \fIBox\fR commands (Default: \fI""\fR). 208 | \fBNOTE:\fR You \fBmust\fR append the hierarchy delimiter (usually 209 | a slash) to this specification. 210 | \fBNOTE 2:\fR This directive is allowed only in the \fIglobal\fR 211 | section (see below). 212 | .. 213 | .TP 214 | \fBMaxMessages\fR \fIcount\fR 215 | Sets the number of messages \fBisync\fR should keep in the local copy of a 216 | mailbox. 217 | This is useful for mailboxes where you keep a complete archive on the server, 218 | but want to mirror only the last messages (for instance, for mailing lists). 219 | The messages that were the first to arrive in the mailbox (independently of the 220 | actual date of the message) will be deleted first. 221 | Messages that are flagged (marked as important) and recent messages will not be 222 | automatically deleted. 223 | If \fIcount\fR is 0, the maximum number of messages is \fBunlimited\fR. 224 | (Default: 0) 225 | .. 226 | .TP 227 | \fBMaxSize\fR \fIbytes\fR 228 | Messages larger than that many bytes will not be transferred over the wire. 229 | This is useful for weeding out messages with large attachments. 230 | If \fIbytes\fR is 0, the maximum file size is \fBunlimited\fR. 231 | (Default: 0) 232 | .. 233 | .TP 234 | \fBTunnel\fR \fIcommand\fR 235 | Specify a command to run to establish a connection rather than opening a TCP 236 | socket. This allows you to run an IMAP session over an SSH tunnel, for 237 | example. 238 | .TP 239 | \fBUseNamespace\fR \fIyes\fR|\fIno\fR 240 | Selects whether the server's first "personal" NAMESPACE should be prefixed to 241 | mailbox names. Disabling this makes sense for some broken IMAP servers. 242 | This option is meaningless if a \fIFolder\fR was specified. 243 | (Default: \fIyes\fR) 244 | .. 245 | .TP 246 | \fBRequireCRAM\fR \fIyes\fR|\fIno\fR 247 | If set to \fIyes\fR, \fBisync\fR will abort the connection if no CRAM-MD5 248 | authentication is possible. (Default: \fIno\fR) 249 | .. 250 | .TP 251 | \fBRequireSSL\fR \fIyes\fR|\fIno\fR 252 | \fBisync\fR will abort the connection if a TLS/SSL session cannot be 253 | established with the IMAP server. (Default: \fIyes\fR) 254 | .. 255 | .TP 256 | \fBCertificateFile\fR \fIpath\fR 257 | File containing X.509 CA certificates used to verify server identities. 258 | .. 259 | .TP 260 | \fBUseSSLv2\fR \fIyes\fR|\fIno\fR 261 | Should \fBisync\fR use SSLv2 for communication with the IMAP server over SSL? 262 | (Default: \fIyes\fR if the imaps port is used, otherwise \fIno\fR) 263 | .. 264 | .TP 265 | \fBUseSSLv3\fR \fIyes\fR|\fIno\fR 266 | Should \fBisync\fR use SSLv3 for communication with the IMAP server over SSL? 267 | (Default: \fIyes\fR if the imaps port is used, otherwise \fIno\fR) 268 | .. 269 | .TP 270 | \fBUseTLSv1\fR \fIyes\fR|\fIno\fR 271 | Should \fBisync\fR use TLSv1 for communication with the IMAP server over SSL? 272 | (Default: \fIyes\fR) 273 | .. 274 | .TP 275 | \fBOneToOne\fR 276 | \fBisync\fR will ignore any \fIMailbox\fR specifications and instead pick up 277 | all mailboxes from the local \fIMailDir\fR and remote \fIFolder\fR and map 278 | them 1:1 onto each other according to their names. 279 | \fBNOTE:\fR This directive is allowed only in the \fIglobal\fR 280 | section (see below). 281 | .. 282 | .TP 283 | \fBInbox\fR \fImailbox\fR 284 | Exception to the OneToOne mapping: the special IMAP mailbox \fIINBOX\fR 285 | is mapped to the local \fImailbox\fR (relative to the \fIMailDir\fR). 286 | \fBNOTE:\fR This directive is only meaningful in the \fIglobal\fR 287 | section (see below). 288 | .. 289 | .P 290 | Configuration commands that appear prior to the first \fBMailbox\fR 291 | command are considered to be \fIglobal\fR 292 | options which are used as defaults when those specific options are not 293 | specifically set for a defined Mailbox. For example, if you use the same 294 | login name for several IMAP servers, you can put a \fBUser\fR command before 295 | the first \fBMailbox\fR command, and then leave out the \fBUser\fR command 296 | in the sections for each mailbox. 297 | \fBisync\fR will then use the global value by default. 298 | .. 299 | .SH FILES 300 | .TP 301 | .B ~/.isyncrc 302 | Default configuration file 303 | .. 304 | .SH BUGS 305 | The configuration file takes precedence over command line options. 306 | .br 307 | Use -c /dev/null to work around. 308 | .P 309 | See the \fBINHERENT PROBLEMS\fR section in the \fBmbsync\fR man page, too. 310 | .. 311 | .SH SEE ALSO 312 | mbsync(1), mdconvert(1), mutt(1), maildir(5) 313 | .P 314 | Up to date information on \fBisync\fR can be found at http://isync.sf.net/ 315 | .. 316 | .SH AUTHOR 317 | Written by Michael R. Elkins , 318 | .br 319 | maintained by Oswald Buddenhagen . 320 | -------------------------------------------------------------------------------- /src/compat/isync.h: -------------------------------------------------------------------------------- 1 | /* 2 | * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2004 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 19 | */ 20 | 21 | #define _GNU_SOURCE 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | #define as(ar) (sizeof(ar)/sizeof(ar[0])) 29 | 30 | #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) 31 | # define ATTR_UNUSED __attribute__((unused)) 32 | # define ATTR_NORETURN __attribute__((noreturn)) 33 | # define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var))) 34 | #else 35 | # define ATTR_UNUSED 36 | # define ATTR_NORETURN 37 | # define ATTR_PRINTFLIKE(fmt,var) 38 | #endif 39 | 40 | typedef struct config { 41 | struct config *next; 42 | 43 | const char *server_name; 44 | const char *old_server_name; 45 | int servers; 46 | int old_servers; 47 | char *host; 48 | int port; 49 | char *user; 50 | char *pass; 51 | char *tunnel; 52 | unsigned int require_cram:1; 53 | unsigned int require_ssl:1; 54 | unsigned int use_imaps:1; 55 | unsigned int use_sslv2:1; 56 | unsigned int use_sslv3:1; 57 | unsigned int use_tlsv1:1; 58 | char *cert_file; 59 | 60 | const char *store_name; 61 | int stores; 62 | char *copy_deleted_to; 63 | unsigned int use_namespace:1; 64 | 65 | const char *channel_name; 66 | int channels; 67 | const char *alias; 68 | const char *box; 69 | const char *path; /* path relative to .maildir, or absolute path */ 70 | int max_size; 71 | unsigned int max_messages; 72 | unsigned int expunge:1; 73 | unsigned int delete:1; 74 | } config_t; 75 | 76 | extern int Quiet, Verbose, Debug; 77 | 78 | extern const char *Home; 79 | extern int HomeLen; 80 | 81 | extern config_t global, *boxes; 82 | 83 | extern const char *maildir, *xmaildir, *folder, *inbox; 84 | extern int o2o, altmap, delete, expunge; 85 | 86 | /* config.c */ 87 | void load_config( const char *, config_t *** ); 88 | void write_config( int ); 89 | char *expand_strdup( const char * ); 90 | config_t *find_box( const char * ); 91 | 92 | /* convert.c */ 93 | void convert( config_t * ); 94 | 95 | /* util.c */ 96 | char *next_arg( char ** ); 97 | void *nfmalloc( size_t sz ); 98 | void *nfrealloc( void *mem, size_t sz ); 99 | char *nfstrdup( const char *str ); 100 | int nfvasprintf( char **str, const char *fmt, va_list va ); 101 | int nfasprintf( char **str, const char *fmt, ... ); 102 | int nfsnprintf( char *buf, int blen, const char *fmt, ... ); 103 | void ATTR_NORETURN oob( void ); 104 | -------------------------------------------------------------------------------- /src/compat/isyncrc.sample: -------------------------------------------------------------------------------- 1 | # Global configuration section 2 | # Values here are used as defaults for any following Mailbox section that 3 | # doesn't specify it. 4 | 5 | # SSL server certificate file 6 | CertificateFile /etc/ssl/certs/ca-certificates.crt 7 | 8 | # by default, expunge deleted messages (same as -e on command line) 9 | Expunge yes 10 | 11 | # by default delete messages in the local mailbox which no longer exist 12 | # on the server 13 | Delete yes 14 | 15 | # copy deleted messages to the IMAP "Trash" folder 16 | CopyDeletedTo "Trash" 17 | 18 | # my default username, if different from the local username 19 | User me 20 | #Port 143 21 | #Box INBOX 22 | # don't download messages larger than 200K bytes 23 | MaxSize 200000 24 | 25 | ### 26 | ### work mailbox 27 | ### 28 | 29 | Mailbox /home/me/Mail/work 30 | Host work.host.com 31 | Pass xxxxxxxx 32 | # define a shortcut so I can just use "isync work" from the command line 33 | Alias work 34 | # don't auto expunge messages in this box (overridden by -e on command line) 35 | Expunge no 36 | 37 | ### 38 | ### personal mailbox 39 | ### 40 | 41 | Mailbox /home/me/Mail/personal 42 | Host host.play.com 43 | # use a non-default port for this connection 44 | Port 6789 45 | Alias personal 46 | 47 | 48 | ### 49 | ### Remote mailbox over a SSH tunnel 50 | ### 51 | 52 | Mailbox /home/me/Mail/remote 53 | Host host.remote.com 54 | Tunnel "ssh -q host.remote.com /usr/sbin/imapd" 55 | Alias remote 56 | -------------------------------------------------------------------------------- /src/compat/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2004 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 19 | */ 20 | 21 | #include "isync.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #if HAVE_GETOPT_LONG 38 | # define _GNU_SOURCE 39 | # include 40 | struct option Opts[] = { 41 | {"write", 0, NULL, 'w' }, 42 | {"writeto", 0, NULL, 'W' }, 43 | {"all", 0, NULL, 'a' }, 44 | {"list", 0, NULL, 'l'}, 45 | {"config", 1, NULL, 'c'}, 46 | {"create", 0, NULL, 'C'}, 47 | {"create-local", 0, NULL, 'L'}, 48 | {"create-remote", 0, NULL, 'R'}, 49 | {"delete", 0, NULL, 'd'}, 50 | {"expunge", 0, NULL, 'e'}, 51 | {"fast", 0, NULL, 'f'}, 52 | {"help", 0, NULL, 'h'}, 53 | {"remote", 1, NULL, 'r'}, 54 | {"folder", 1, NULL, 'F'}, 55 | {"maildir", 1, NULL, 'M'}, 56 | {"one-to-one", 0, NULL, '1'}, 57 | {"inbox", 1, NULL, 'I'}, 58 | {"host", 1, NULL, 's'}, 59 | {"port", 1, NULL, 'p'}, 60 | {"debug", 0, NULL, 'D'}, 61 | {"quiet", 0, NULL, 'q'}, 62 | {"user", 1, NULL, 'u'}, 63 | {"version", 0, NULL, 'v'}, 64 | {"verbose", 0, NULL, 'V'}, 65 | {0, 0, 0, 0} 66 | }; 67 | #endif 68 | 69 | static void 70 | version( void ) 71 | { 72 | puts( PACKAGE " " VERSION ); 73 | exit( 0 ); 74 | } 75 | 76 | static void 77 | usage( int code ) 78 | { 79 | fputs( 80 | PACKAGE " " VERSION " - mbsync wrapper: IMAP4 to maildir synchronizer\n" 81 | "Copyright (C) 2000-2002 Michael R. Elkins \n" 82 | "Copyright (C) 2002-2004 Oswald Buddenhagen \n" 83 | "usage:\n" 84 | " " PACKAGE " [ flags ] mailbox [mailbox ...]\n" 85 | " " PACKAGE " [ flags ] -a\n" 86 | " " PACKAGE " [ flags ] -l\n" 87 | " -a, --all synchronize all defined mailboxes\n" 88 | " -l, --list list all defined mailboxes and exit\n" 89 | " -L, --create-local create local maildir mailbox if nonexistent\n" 90 | " -R, --create-remote create remote imap mailbox if nonexistent\n" 91 | " -C, --create create both local and remote mailboxes if nonexistent\n" 92 | " -d, --delete delete local msgs that don't exist on the server\n" 93 | " -e, --expunge expunge deleted messages\n" 94 | " -f, --fast only fetch new messages\n" 95 | " -r, --remote BOX remote mailbox\n" 96 | " -F, --folder DIR remote IMAP folder containing mailboxes\n" 97 | " -M, --maildir DIR local directory containing mailboxes\n" 98 | " -1, --one-to-one map every IMAP /box to /box\n" 99 | " -I, --inbox BOX map IMAP INBOX to /BOX (exception to -1)\n" 100 | " -s, --host HOST IMAP server address\n" 101 | " -p, --port PORT server IMAP port\n" 102 | " -u, --user USER IMAP user name\n" 103 | " -c, --config CONFIG read an alternate config file (default: ~/.isyncrc)\n" 104 | " -D, --debug print debugging messages\n" 105 | " -V, --verbose verbose mode (display network traffic)\n" 106 | " -q, --quiet don't display progress info\n" 107 | " -v, --version display version\n" 108 | " -h, --help display this help message\n\n" 109 | "Note that this is a wrapper binary only; the \"real\" isync is named \"mbsync\".\n" 110 | "Options to permanently transform your old isync configuration:\n" 111 | " -w, --write write permanent mbsync configuration\n" 112 | " -W, --writeto FILE write permanent mbsync configuration to FILE\n", 113 | code ? stderr : stdout ); 114 | exit( code ); 115 | } 116 | 117 | static const char * 118 | strrstr( const char *h, const char *n ) 119 | { 120 | char *p = strstr( h, n ); 121 | if (!p) 122 | return 0; 123 | do { 124 | h = p; 125 | p = strstr( h + 1, n ); 126 | } while (p); 127 | return h; 128 | } 129 | 130 | static void 131 | add_arg( char ***args, const char *arg ) 132 | { 133 | int nu = 0; 134 | if (*args) 135 | for (; (*args)[nu]; nu++); 136 | *args = nfrealloc( *args, sizeof(char *) * (nu + 2)); 137 | (*args)[nu] = nfstrdup( arg ); 138 | (*args)[nu + 1] = 0; 139 | } 140 | 141 | #define OP_FAST (1<<2) 142 | #define OP_CREATE_REMOTE (1<<3) 143 | #define OP_CREATE_LOCAL (1<<4) 144 | 145 | int Quiet, Verbose, Debug; 146 | config_t global, *boxes; 147 | const char *maildir, *xmaildir, *folder, *inbox; 148 | int o2o, altmap, delete, expunge; 149 | 150 | const char *Home; 151 | int HomeLen; 152 | 153 | int 154 | main( int argc, char **argv ) 155 | { 156 | config_t *box, **stor; 157 | char *config = 0, *outconfig = 0, **args; 158 | int i, pl, fd, mod, all, list, ops, writeout; 159 | struct stat st; 160 | char path1[_POSIX_PATH_MAX], path2[_POSIX_PATH_MAX]; 161 | 162 | if (!(Home = getenv("HOME"))) { 163 | fputs( "Fatal: $HOME not set\n", stderr ); 164 | return 1; 165 | } 166 | HomeLen = strlen( Home ); 167 | 168 | /* defaults */ 169 | /* XXX the precedence is borked: 170 | it's defaults < cmdline < file instead of defaults < file < cmdline */ 171 | #ifdef BSD 172 | global.user = getenv( "USER" ); 173 | #else 174 | global.user = getenv( "LOGNAME" ); 175 | #endif 176 | global.port = 143; 177 | global.box = "INBOX"; 178 | global.use_namespace = 1; 179 | global.require_ssl = 1; 180 | global.use_tlsv1 = 1; 181 | folder = ""; 182 | maildir = "~"; 183 | xmaildir = Home; 184 | 185 | #define FLAGS "wW:alCLRc:defhp:qu:r:F:M:1I:s:vVD" 186 | 187 | mod = all = list = ops = writeout = Quiet = Verbose = Debug = 0; 188 | #if HAVE_GETOPT_LONG 189 | while ((i = getopt_long( argc, argv, FLAGS, Opts, NULL )) != -1) 190 | #else 191 | while ((i = getopt( argc, argv, FLAGS )) != -1) 192 | #endif 193 | { 194 | switch (i) { 195 | case 'W': 196 | outconfig = optarg; 197 | /* plopp */ 198 | case 'w': 199 | writeout = 1; 200 | break; 201 | case 'l': 202 | list = 1; 203 | /* plopp */ 204 | case 'a': 205 | all = 1; 206 | break; 207 | case '1': 208 | o2o = 1; 209 | mod = 1; 210 | break; 211 | case 'C': 212 | ops |= OP_CREATE_REMOTE|OP_CREATE_LOCAL; 213 | break; 214 | case 'L': 215 | ops |= OP_CREATE_LOCAL; 216 | break; 217 | case 'R': 218 | ops |= OP_CREATE_REMOTE; 219 | break; 220 | case 'c': 221 | config = optarg; 222 | break; 223 | case 'd': 224 | delete = 1; 225 | break; 226 | case 'e': 227 | expunge = 1; 228 | break; 229 | case 'f': 230 | ops |= OP_FAST; 231 | break; 232 | case 'p': 233 | global.port = atoi( optarg ); 234 | mod = 1; 235 | break; 236 | case 'r': 237 | global.box = optarg; 238 | mod = 1; 239 | break; 240 | case 'F': 241 | folder = optarg; 242 | mod = 1; 243 | break; 244 | case 'M': 245 | maildir = optarg; 246 | mod = 1; 247 | break; 248 | case 'I': 249 | inbox = optarg; 250 | mod = 1; 251 | break; 252 | case 's': 253 | #if HAVE_LIBSSL 254 | if (!strncasecmp( "imaps:", optarg, 6 )) { 255 | global.use_imaps = 1; 256 | global.port = 993; 257 | global.use_sslv2 = 1; 258 | global.use_sslv3 = 1; 259 | optarg += 6; 260 | } 261 | #endif 262 | global.host = optarg; 263 | mod = 1; 264 | break; 265 | case 'u': 266 | global.user = optarg; 267 | mod = 1; 268 | break; 269 | case 'D': 270 | Debug = 1; 271 | break; 272 | case 'V': 273 | Verbose = 1; 274 | break; 275 | case 'q': 276 | Quiet++; 277 | break; 278 | case 'v': 279 | version(); 280 | case 'h': 281 | usage( 0 ); 282 | default: 283 | usage( 1 ); 284 | } 285 | } 286 | 287 | if (config) { 288 | if (*config != '/') { 289 | if (!getcwd( path1, sizeof(path1) )) { 290 | fprintf( stderr, "Can't obtain working directory\n" ); 291 | return 1; 292 | } 293 | pl = strlen( path1 ); 294 | nfsnprintf( path1 + pl, sizeof(path1) - pl, "/%s", config ); 295 | config = path1; 296 | } 297 | } else { 298 | nfsnprintf( path1, sizeof(path1), "%s/.isyncrc", Home ); 299 | config = path1; 300 | } 301 | stor = &boxes; 302 | load_config( config, &stor ); 303 | 304 | if (!all && !o2o) 305 | for (i = optind; argv[i]; i++) 306 | if (!(box = find_box( argv[i] ))) { 307 | box = nfmalloc( sizeof(config_t) ); 308 | memcpy( box, &global, sizeof(config_t) ); 309 | box->path = argv[i]; 310 | *stor = box; 311 | stor = &box->next; 312 | mod = 1; 313 | } 314 | 315 | if (writeout) { 316 | all = 1; 317 | if (mod) 318 | fprintf( stderr, 319 | "Warning: command line switches that influence the resulting config file\n" 320 | "have been supplied.\n" ); 321 | } else { 322 | if (!argv[optind] && !all) { 323 | fprintf( stderr, "No mailbox specified. Try isync -h\n" ); 324 | return 1; 325 | } 326 | } 327 | 328 | if (all) { 329 | if (o2o) { 330 | DIR * dir; 331 | struct dirent *de; 332 | 333 | if (!(dir = opendir( xmaildir ))) { 334 | fprintf( stderr, "%s: %s\n", xmaildir, strerror(errno) ); 335 | return 1; 336 | } 337 | while ((de = readdir( dir ))) { 338 | if (*de->d_name == '.') 339 | continue; 340 | nfsnprintf( path2, sizeof(path2), "%s/%s/cur", xmaildir, de->d_name ); 341 | if (stat( path2, &st ) || !S_ISDIR( st.st_mode )) 342 | continue; 343 | global.path = de->d_name; 344 | global.box = (inbox && !strcmp( inbox, global.path )) ? 345 | "INBOX" : global.path; 346 | convert( &global ); 347 | } 348 | closedir( dir ); 349 | } else 350 | for (box = boxes; box; box = box->next) 351 | convert( box ); 352 | } else { 353 | for (i = optind; argv[i]; i++) 354 | if (o2o) { 355 | global.path = argv[i]; 356 | global.box = 357 | (inbox && !strcmp( global.path, inbox )) ? 358 | "INBOX" : global.path; 359 | convert( &global ); 360 | } else 361 | convert( find_box( argv[i] ) ); 362 | } 363 | 364 | if (writeout) { 365 | if (!outconfig) { 366 | const char *p = strrchr( config, '/' ); 367 | if (!p) 368 | p = config; 369 | p = strrstr( p, "isync" ); 370 | if (!p) 371 | nfsnprintf( path2, sizeof(path2), "%s.mbsync", config ); 372 | else 373 | nfsnprintf( path2, sizeof(path2), "%.*smb%s", p - config, config, p + 1 ); 374 | outconfig = path2; 375 | } 376 | if ((fd = creat( outconfig, 0666 )) < 0) { 377 | fprintf( stderr, "Error: cannot write new config %s: %s\n", outconfig, strerror(errno) ); 378 | return 1; 379 | } 380 | } else { 381 | strcpy( path2, "/tmp/mbsyncrcXXXXXX" ); 382 | if ((fd = mkstemp( path2 )) < 0) { 383 | fprintf( stderr, "Can't create temp file\n" ); 384 | return 1; 385 | } 386 | } 387 | write_config( fd ); 388 | 389 | if (writeout) 390 | return 0; 391 | args = 0; 392 | add_arg( &args, "mbsync" ); 393 | if (Verbose) 394 | add_arg( &args, "-V" ); 395 | if (Debug) 396 | add_arg( &args, "-D" ); 397 | for (; Quiet; Quiet--) 398 | add_arg( &args, "-q" ); 399 | add_arg( &args, "-cT" ); 400 | add_arg( &args, path2 ); 401 | if (ops & OP_FAST) 402 | add_arg( &args, "-Ln" ); 403 | if (ops & OP_CREATE_REMOTE) 404 | add_arg( &args, "-Cm" ); 405 | if (ops & OP_CREATE_LOCAL) 406 | add_arg( &args, "-Cs" ); 407 | if (list) 408 | add_arg( &args, "-lC" ); 409 | if (o2o) { 410 | if (all) 411 | add_arg( &args, "o2o" ); 412 | else { 413 | char buf[1024]; 414 | strcpy( buf, "o2o:" ); 415 | strcat( buf, argv[optind] ); 416 | while (argv[++optind]) { 417 | strcat( buf, "," ); 418 | strcat( buf, argv[optind] ); 419 | } 420 | add_arg( &args, buf ); 421 | } 422 | } else { 423 | if (all) 424 | add_arg( &args, "-a" ); 425 | else 426 | for (; argv[optind]; optind++) 427 | add_arg( &args, find_box( argv[optind] )->channel_name ); 428 | } 429 | execvp( args[0], args ); 430 | perror( args[0] ); 431 | return 1; 432 | } 433 | -------------------------------------------------------------------------------- /src/compat/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2004 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 19 | */ 20 | 21 | #include "isync.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | char * 31 | next_arg( char **s ) 32 | { 33 | char *ret; 34 | 35 | if (!s || !*s) 36 | return 0; 37 | while (isspace( (unsigned char) **s )) 38 | (*s)++; 39 | if (!**s) { 40 | *s = 0; 41 | return 0; 42 | } 43 | if (**s == '"') { 44 | ++*s; 45 | ret = *s; 46 | *s = strchr( *s, '"' ); 47 | } else { 48 | ret = *s; 49 | while (**s && !isspace( (unsigned char) **s )) 50 | (*s)++; 51 | } 52 | if (*s) { 53 | if (**s) 54 | *(*s)++ = 0; 55 | if (!**s) 56 | *s = 0; 57 | } 58 | return ret; 59 | } 60 | 61 | #ifndef HAVE_VASPRINTF 62 | static int 63 | vasprintf( char **strp, const char *fmt, va_list ap ) 64 | { 65 | int len; 66 | char tmp[1024]; 67 | 68 | if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = malloc( len + 1 ))) 69 | return -1; 70 | if (len >= (int)sizeof(tmp)) 71 | vsprintf( *strp, fmt, ap ); 72 | else 73 | memcpy( *strp, tmp, len + 1 ); 74 | return len; 75 | } 76 | #endif 77 | 78 | void 79 | oob( void ) 80 | { 81 | fputs( "Fatal: buffer too small. Please report a bug.\n", stderr ); 82 | abort(); 83 | } 84 | 85 | int 86 | nfsnprintf( char *buf, int blen, const char *fmt, ... ) 87 | { 88 | int ret; 89 | va_list va; 90 | 91 | va_start( va, fmt ); 92 | if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen) 93 | oob(); 94 | va_end( va ); 95 | return ret; 96 | } 97 | 98 | static void ATTR_NORETURN 99 | oom( void ) 100 | { 101 | fputs( "Fatal: Out of memory\n", stderr ); 102 | abort(); 103 | } 104 | 105 | void * 106 | nfmalloc( size_t sz ) 107 | { 108 | void *ret; 109 | 110 | if (!(ret = malloc( sz ))) 111 | oom(); 112 | return ret; 113 | } 114 | 115 | void * 116 | nfrealloc( void *mem, size_t sz ) 117 | { 118 | char *ret; 119 | 120 | if (!(ret = realloc( mem, sz )) && sz) 121 | oom(); 122 | return ret; 123 | } 124 | 125 | char * 126 | nfstrdup( const char *str ) 127 | { 128 | char *ret; 129 | 130 | if (!(ret = strdup( str ))) 131 | oom(); 132 | return ret; 133 | } 134 | 135 | int 136 | nfvasprintf( char **str, const char *fmt, va_list va ) 137 | { 138 | int ret = vasprintf( str, fmt, va ); 139 | if (ret < 0) 140 | oom(); 141 | return ret; 142 | } 143 | 144 | int 145 | nfasprintf( char **str, const char *fmt, ... ) 146 | { 147 | int ret; 148 | va_list va; 149 | 150 | va_start( va, fmt ); 151 | ret = nfvasprintf( str, fmt, va ); 152 | va_end( va ); 153 | return ret; 154 | } 155 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mbsync - mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2004 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 19 | * 20 | * As a special exception, mbsync may be linked with the OpenSSL library, 21 | * despite that library's more restrictive license. 22 | */ 23 | 24 | #include "isync.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | driver_t *drivers[N_DRIVERS] = { &maildir_driver, &imap_driver }; 36 | 37 | store_conf_t *stores; 38 | channel_conf_t *channels; 39 | group_conf_t *groups; 40 | int global_ops[2]; 41 | char *global_sync_state; 42 | 43 | int 44 | parse_bool( conffile_t *cfile ) 45 | { 46 | if (!strcasecmp( cfile->val, "yes" ) || 47 | !strcasecmp( cfile->val, "true" ) || 48 | !strcasecmp( cfile->val, "on" ) || 49 | !strcmp( cfile->val, "1" )) 50 | return 1; 51 | if (strcasecmp( cfile->val, "no" ) && 52 | strcasecmp( cfile->val, "false" ) && 53 | strcasecmp( cfile->val, "off" ) && 54 | strcmp( cfile->val, "0" )) 55 | error( "%s:%d: invalid boolean value '%s'\n", 56 | cfile->file, cfile->line, cfile->val ); 57 | return 0; 58 | } 59 | 60 | int 61 | parse_int( conffile_t *cfile ) 62 | { 63 | char *p; 64 | int ret; 65 | 66 | ret = strtol( cfile->val, &p, 10 ); 67 | if (*p) { 68 | error( "%s:%d: invalid integer value '%s'\n", 69 | cfile->file, cfile->line, cfile->val ); 70 | return 0; 71 | } 72 | return ret; 73 | } 74 | 75 | int 76 | parse_size( conffile_t *cfile ) 77 | { 78 | char *p; 79 | int ret; 80 | 81 | ret = strtol (cfile->val, &p, 10); 82 | if (*p == 'k' || *p == 'K') 83 | ret *= 1024, p++; 84 | else if (*p == 'm' || *p == 'M') 85 | ret *= 1024 * 1024, p++; 86 | if (*p == 'b' || *p == 'B') 87 | p++; 88 | if (*p) { 89 | fprintf (stderr, "%s:%d: invalid size '%s'\n", 90 | cfile->file, cfile->line, cfile->val); 91 | return 0; 92 | } 93 | return ret; 94 | } 95 | 96 | static int 97 | getopt_helper( conffile_t *cfile, int *cops, int ops[], char **sync_state ) 98 | { 99 | char *arg; 100 | 101 | if (!strcasecmp( "Sync", cfile->cmd )) { 102 | arg = cfile->val; 103 | do 104 | if (!strcasecmp( "Push", arg )) 105 | *cops |= XOP_PUSH; 106 | else if (!strcasecmp( "Pull", arg )) 107 | *cops |= XOP_PULL; 108 | else if (!strcasecmp( "ReNew", arg )) 109 | *cops |= OP_RENEW; 110 | else if (!strcasecmp( "New", arg )) 111 | *cops |= OP_NEW; 112 | else if (!strcasecmp( "Delete", arg )) 113 | *cops |= OP_DELETE; 114 | else if (!strcasecmp( "Flags", arg )) 115 | *cops |= OP_FLAGS; 116 | else if (!strcasecmp( "PullReNew", arg )) 117 | ops[S] |= OP_RENEW; 118 | else if (!strcasecmp( "PullNew", arg )) 119 | ops[S] |= OP_NEW; 120 | else if (!strcasecmp( "PullDelete", arg )) 121 | ops[S] |= OP_DELETE; 122 | else if (!strcasecmp( "PullFlags", arg )) 123 | ops[S] |= OP_FLAGS; 124 | else if (!strcasecmp( "PushReNew", arg )) 125 | ops[M] |= OP_RENEW; 126 | else if (!strcasecmp( "PushNew", arg )) 127 | ops[M] |= OP_NEW; 128 | else if (!strcasecmp( "PushDelete", arg )) 129 | ops[M] |= OP_DELETE; 130 | else if (!strcasecmp( "PushFlags", arg )) 131 | ops[M] |= OP_FLAGS; 132 | else if (!strcasecmp( "All", arg ) || !strcasecmp( "Full", arg )) 133 | *cops |= XOP_PULL|XOP_PUSH; 134 | else if (strcasecmp( "None", arg ) && strcasecmp( "Noop", arg )) 135 | error( "%s:%d: invalid Sync arg '%s'\n", 136 | cfile->file, cfile->line, arg ); 137 | while ((arg = next_arg( &cfile->rest ))); 138 | ops[M] |= XOP_HAVE_TYPE; 139 | } else if (!strcasecmp( "Expunge", cfile->cmd )) { 140 | arg = cfile->val; 141 | do 142 | if (!strcasecmp( "Both", arg )) 143 | *cops |= OP_EXPUNGE; 144 | else if (!strcasecmp( "Master", arg )) 145 | ops[M] |= OP_EXPUNGE; 146 | else if (!strcasecmp( "Slave", arg )) 147 | ops[S] |= OP_EXPUNGE; 148 | else if (strcasecmp( "None", arg )) 149 | error( "%s:%d: invalid Expunge arg '%s'\n", 150 | cfile->file, cfile->line, arg ); 151 | while ((arg = next_arg( &cfile->rest ))); 152 | ops[M] |= XOP_HAVE_EXPUNGE; 153 | } else if (!strcasecmp( "Create", cfile->cmd )) { 154 | arg = cfile->val; 155 | do 156 | if (!strcasecmp( "Both", arg )) 157 | *cops |= OP_CREATE; 158 | else if (!strcasecmp( "Master", arg )) 159 | ops[M] |= OP_CREATE; 160 | else if (!strcasecmp( "Slave", arg )) 161 | ops[S] |= OP_CREATE; 162 | else if (strcasecmp( "None", arg )) 163 | error( "%s:%d: invalid Create arg '%s'\n", 164 | cfile->file, cfile->line, arg ); 165 | while ((arg = next_arg( &cfile->rest ))); 166 | ops[M] |= XOP_HAVE_CREATE; 167 | } else if (!strcasecmp( "SyncState", cfile->cmd )) 168 | *sync_state = expand_strdup( cfile->val ); 169 | else 170 | return 0; 171 | return 1; 172 | } 173 | 174 | int 175 | getcline( conffile_t *cfile ) 176 | { 177 | char *p; 178 | 179 | while (fgets( cfile->buf, cfile->bufl, cfile->fp )) { 180 | cfile->line++; 181 | p = cfile->buf; 182 | if (!(cfile->cmd = next_arg( &p ))) 183 | return 1; 184 | if (*cfile->cmd == '#') 185 | continue; 186 | if (!(cfile->val = next_arg( &p ))) { 187 | error( "%s:%d: parameter missing\n", cfile->file, cfile->line ); 188 | continue; 189 | } 190 | cfile->rest = p; 191 | return 1; 192 | } 193 | return 0; 194 | } 195 | 196 | /* XXX - this does not detect None conflicts ... */ 197 | int 198 | merge_ops( int cops, int ops[] ) 199 | { 200 | int aops; 201 | 202 | aops = ops[M] | ops[S]; 203 | if (ops[M] & XOP_HAVE_TYPE) { 204 | if (aops & OP_MASK_TYPE) { 205 | if (aops & cops & OP_MASK_TYPE) { 206 | cfl: 207 | error( "Conflicting Sync args specified.\n" ); 208 | return 1; 209 | } 210 | ops[M] |= cops & OP_MASK_TYPE; 211 | ops[S] |= cops & OP_MASK_TYPE; 212 | if (cops & XOP_PULL) { 213 | if (ops[S] & OP_MASK_TYPE) 214 | goto cfl; 215 | ops[S] |= OP_MASK_TYPE; 216 | } 217 | if (cops & XOP_PUSH) { 218 | if (ops[M] & OP_MASK_TYPE) 219 | goto cfl; 220 | ops[M] |= OP_MASK_TYPE; 221 | } 222 | } else if (cops & (OP_MASK_TYPE|XOP_MASK_DIR)) { 223 | if (!(cops & OP_MASK_TYPE)) 224 | cops |= OP_MASK_TYPE; 225 | else if (!(cops & XOP_MASK_DIR)) 226 | cops |= XOP_PULL|XOP_PUSH; 227 | if (cops & XOP_PULL) 228 | ops[S] |= cops & OP_MASK_TYPE; 229 | if (cops & XOP_PUSH) 230 | ops[M] |= cops & OP_MASK_TYPE; 231 | } 232 | } 233 | if (ops[M] & XOP_HAVE_EXPUNGE) { 234 | if (aops & cops & OP_EXPUNGE) { 235 | error( "Conflicting Expunge args specified.\n" ); 236 | return 1; 237 | } 238 | ops[M] |= cops & OP_EXPUNGE; 239 | ops[S] |= cops & OP_EXPUNGE; 240 | } 241 | if (ops[M] & XOP_HAVE_CREATE) { 242 | if (aops & cops & OP_CREATE) { 243 | error( "Conflicting Create args specified.\n" ); 244 | return 1; 245 | } 246 | ops[M] |= cops & OP_CREATE; 247 | ops[S] |= cops & OP_CREATE; 248 | } 249 | return 0; 250 | } 251 | 252 | int 253 | load_config( const char *where, int pseudo ) 254 | { 255 | conffile_t cfile; 256 | store_conf_t *store, **storeapp = &stores; 257 | channel_conf_t *channel, **channelapp = &channels; 258 | group_conf_t *group, **groupapp = &groups; 259 | string_list_t *chanlist, **chanlistapp; 260 | char *arg, *p; 261 | int err, len, cops, gcops, max_size, ms, i; 262 | char path[_POSIX_PATH_MAX]; 263 | char buf[1024]; 264 | 265 | if (!where) { 266 | nfsnprintf( path, sizeof(path), "%s/." EXE "rc", Home ); 267 | cfile.file = path; 268 | } else 269 | cfile.file = where; 270 | 271 | if (!pseudo) 272 | info( "Reading configuration file %s\n", cfile.file ); 273 | 274 | if (!(cfile.fp = fopen( cfile.file, "r" ))) { 275 | perror( "Cannot open config file" ); 276 | return 1; 277 | } 278 | buf[sizeof(buf) - 1] = 0; 279 | cfile.buf = buf; 280 | cfile.bufl = sizeof(buf) - 1; 281 | cfile.line = 0; 282 | 283 | gcops = err = 0; 284 | reloop: 285 | while (getcline( &cfile )) { 286 | if (!cfile.cmd) 287 | continue; 288 | for (i = 0; i < N_DRIVERS; i++) 289 | if (drivers[i]->parse_store( &cfile, &store, &err )) { 290 | if (store) { 291 | if (!store->path) 292 | store->path = ""; 293 | *storeapp = store; 294 | storeapp = &store->next; 295 | *storeapp = 0; 296 | } 297 | goto reloop; 298 | } 299 | if (!strcasecmp( "Channel", cfile.cmd )) 300 | { 301 | channel = nfcalloc( sizeof(*channel) ); 302 | channel->name = nfstrdup( cfile.val ); 303 | cops = 0; 304 | max_size = -1; 305 | while (getcline( &cfile ) && cfile.cmd) { 306 | if (!strcasecmp( "MaxSize", cfile.cmd )) 307 | max_size = parse_size( &cfile ); 308 | else if (!strcasecmp( "MaxMessages", cfile.cmd )) 309 | channel->max_messages = parse_int( &cfile ); 310 | else if (!strcasecmp( "Pattern", cfile.cmd ) || 311 | !strcasecmp( "Patterns", cfile.cmd )) 312 | { 313 | arg = cfile.val; 314 | do 315 | add_string_list( &channel->patterns, arg ); 316 | while ((arg = next_arg( &cfile.rest ))); 317 | } 318 | else if (!strcasecmp( "Master", cfile.cmd )) { 319 | ms = M; 320 | goto linkst; 321 | } else if (!strcasecmp( "Slave", cfile.cmd )) { 322 | ms = S; 323 | linkst: 324 | if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) { 325 | error( "%s:%d: malformed mailbox spec\n", 326 | cfile.file, cfile.line ); 327 | err = 1; 328 | continue; 329 | } 330 | *p = 0; 331 | for (store = stores; store; store = store->next) 332 | if (!strcmp( store->name, cfile.val + 1 )) { 333 | channel->stores[ms] = store; 334 | goto stpcom; 335 | } 336 | error( "%s:%d: unknown store '%s'\n", 337 | cfile.file, cfile.line, cfile.val + 1 ); 338 | err = 1; 339 | continue; 340 | stpcom: 341 | if (*++p) 342 | channel->boxes[ms] = nfstrdup( p ); 343 | } else if (!getopt_helper( &cfile, &cops, channel->ops, &channel->sync_state )) { 344 | error( "%s:%d: unknown keyword '%s'\n", cfile.file, cfile.line, cfile.cmd ); 345 | err = 1; 346 | } 347 | } 348 | if (!channel->stores[M]) { 349 | error( "channel '%s' refers to no master store\n", channel->name ); 350 | err = 1; 351 | } else if (!channel->stores[S]) { 352 | error( "channel '%s' refers to no slave store\n", channel->name ); 353 | err = 1; 354 | } else if (merge_ops( cops, channel->ops )) 355 | err = 1; 356 | else { 357 | if (max_size >= 0) 358 | channel->stores[M]->max_size = channel->stores[S]->max_size = max_size; 359 | *channelapp = channel; 360 | channelapp = &channel->next; 361 | } 362 | } 363 | else if (!strcasecmp( "Group", cfile.cmd )) 364 | { 365 | group = nfmalloc( sizeof(*group) ); 366 | group->name = nfstrdup( cfile.val ); 367 | *groupapp = group; 368 | groupapp = &group->next; 369 | *groupapp = 0; 370 | chanlistapp = &group->channels; 371 | *chanlistapp = 0; 372 | p = cfile.rest; 373 | while ((arg = next_arg( &p ))) { 374 | addone: 375 | len = strlen( arg ); 376 | chanlist = nfmalloc( sizeof(*chanlist) + len ); 377 | memcpy( chanlist->string, arg, len + 1 ); 378 | *chanlistapp = chanlist; 379 | chanlistapp = &chanlist->next; 380 | *chanlistapp = 0; 381 | } 382 | while (getcline( &cfile )) { 383 | if (!cfile.cmd) 384 | goto reloop; 385 | if (!strcasecmp( "Channel", cfile.cmd ) || 386 | !strcasecmp( "Channels", cfile.cmd )) 387 | { 388 | p = cfile.rest; 389 | arg = cfile.val; 390 | goto addone; 391 | } 392 | else 393 | { 394 | error( "%s:%d: unknown keyword '%s'\n", 395 | cfile.file, cfile.line, cfile.cmd ); 396 | err = 1; 397 | } 398 | } 399 | break; 400 | } 401 | else if (!getopt_helper( &cfile, &gcops, global_ops, &global_sync_state )) 402 | { 403 | error( "%s:%d: unknown section keyword '%s'\n", 404 | cfile.file, cfile.line, cfile.cmd ); 405 | err = 1; 406 | while (getcline( &cfile )) 407 | if (!cfile.cmd) 408 | goto reloop; 409 | break; 410 | } 411 | } 412 | fclose (cfile.fp); 413 | err |= merge_ops( gcops, global_ops ); 414 | if (!global_sync_state) 415 | global_sync_state = expand_strdup( "~/." EXE "/" ); 416 | if (!err && pseudo) 417 | unlink( where ); 418 | return err; 419 | } 420 | 421 | void 422 | parse_generic_store( store_conf_t *store, conffile_t *cfg, int *err ) 423 | { 424 | if (!strcasecmp( "Trash", cfg->cmd )) 425 | store->trash = nfstrdup( cfg->val ); 426 | else if (!strcasecmp( "TrashRemoteNew", cfg->cmd )) 427 | store->trash_remote_new = parse_bool( cfg ); 428 | else if (!strcasecmp( "TrashNewOnly", cfg->cmd )) 429 | store->trash_only_new = parse_bool( cfg ); 430 | else if (!strcasecmp( "MaxSize", cfg->cmd )) 431 | store->max_size = parse_size( cfg ); 432 | else if (!strcasecmp( "MapInbox", cfg->cmd )) 433 | store->map_inbox = nfstrdup( cfg->val ); 434 | else { 435 | error( "%s:%d: unknown keyword '%s'\n", cfg->file, cfg->line, cfg->cmd ); 436 | *err = 1; 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /src/isync.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mbsync - mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2006 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 19 | * 20 | * As a special exception, mbsync may be linked with the OpenSSL library, 21 | * despite that library's more restrictive license. 22 | */ 23 | 24 | #define _GNU_SOURCE 25 | 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | #define as(ar) (sizeof(ar)/sizeof(ar[0])) 33 | 34 | #define __stringify(x) #x 35 | #define stringify(x) __stringify(x) 36 | 37 | #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) 38 | # define ATTR_UNUSED __attribute__((unused)) 39 | # define ATTR_NORETURN __attribute__((noreturn)) 40 | # define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var))) 41 | #else 42 | # define ATTR_UNUSED 43 | # define ATTR_NORETURN 44 | # define ATTR_PRINTFLIKE(fmt,var) 45 | #endif 46 | 47 | #define EXE "mbsync" 48 | 49 | typedef struct { 50 | const char *file; 51 | FILE *fp; 52 | char *buf; 53 | int bufl; 54 | int line; 55 | char *cmd, *val, *rest; 56 | } conffile_t; 57 | 58 | #define OP_NEW (1<<0) 59 | #define OP_RENEW (1<<1) 60 | #define OP_DELETE (1<<2) 61 | #define OP_FLAGS (1<<3) 62 | #define OP_MASK_TYPE (OP_NEW|OP_RENEW|OP_DELETE|OP_FLAGS) /* asserted in the target ops */ 63 | #define OP_EXPUNGE (1<<4) 64 | #define OP_CREATE (1<<5) 65 | #define XOP_PUSH (1<<6) 66 | #define XOP_PULL (1<<7) 67 | #define XOP_MASK_DIR (XOP_PUSH|XOP_PULL) 68 | #define XOP_HAVE_TYPE (1<<8) 69 | #define XOP_HAVE_EXPUNGE (1<<9) 70 | #define XOP_HAVE_CREATE (1<<10) 71 | 72 | typedef struct driver driver_t; 73 | 74 | typedef struct store_conf { 75 | struct store_conf *next; 76 | char *name; 77 | driver_t *driver; 78 | const char *path; /* should this be here? its interpretation is driver-specific */ 79 | const char *map_inbox; 80 | const char *trash; 81 | unsigned max_size; /* off_t is overkill */ 82 | unsigned trash_remote_new:1, trash_only_new:1; 83 | } store_conf_t; 84 | 85 | typedef struct string_list { 86 | struct string_list *next; 87 | char string[1]; 88 | } string_list_t; 89 | 90 | #define M 0 /* master */ 91 | #define S 1 /* slave */ 92 | 93 | typedef struct channel_conf { 94 | struct channel_conf *next; 95 | const char *name; 96 | store_conf_t *stores[2]; 97 | const char *boxes[2]; 98 | char *sync_state; 99 | string_list_t *patterns; 100 | int ops[2]; 101 | unsigned max_messages; /* for slave only */ 102 | } channel_conf_t; 103 | 104 | typedef struct group_conf { 105 | struct group_conf *next; 106 | const char *name; 107 | string_list_t *channels; 108 | } group_conf_t; 109 | 110 | /* For message->flags */ 111 | /* Keep the mailbox driver flag definitions in sync! */ 112 | /* The order is according to alphabetical maildir flag sort */ 113 | #define F_DRAFT (1<<0) /* Draft */ 114 | #define F_FLAGGED (1<<1) /* Flagged */ 115 | #define F_ANSWERED (1<<2) /* Replied */ 116 | #define F_SEEN (1<<3) /* Seen */ 117 | #define F_DELETED (1<<4) /* Trashed */ 118 | #define NUM_FLAGS 5 119 | 120 | /* For message->status */ 121 | #define M_RECENT (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */ 122 | #define M_DEAD (1<<1) /* expunged */ 123 | #define M_FLAGS (1<<2) /* flags fetched */ 124 | 125 | typedef struct message { 126 | struct message *next; 127 | struct sync_rec *srec; 128 | /* string_list_t *keywords; */ 129 | size_t size; /* zero implies "not fetched" */ 130 | int uid; 131 | unsigned char flags, status; 132 | } message_t; 133 | 134 | /* For opts, both in store and driver_t->select() */ 135 | #define OPEN_OLD (1<<0) 136 | #define OPEN_NEW (1<<1) 137 | #define OPEN_FLAGS (1<<2) 138 | #define OPEN_SIZE (1<<3) 139 | #define OPEN_CREATE (1<<4) 140 | #define OPEN_EXPUNGE (1<<5) 141 | #define OPEN_SETFLAGS (1<<6) 142 | #define OPEN_APPEND (1<<7) 143 | #define OPEN_FIND (1<<8) 144 | 145 | typedef struct store { 146 | struct store *next; 147 | store_conf_t *conf; /* foreign */ 148 | string_list_t *boxes; /* _list results - own */ 149 | unsigned listed:1; /* was _list already run? */ 150 | 151 | /* currently open mailbox */ 152 | const char *name; /* foreign! maybe preset? */ 153 | char *path; /* own */ 154 | message_t *msgs; /* own */ 155 | int uidvalidity; 156 | unsigned opts; /* maybe preset? */ 157 | /* note that the following do _not_ reflect stats from msgs, but mailbox totals */ 158 | int count; /* # of messages */ 159 | int recent; /* # of recent messages - don't trust this beyond the initial read */ 160 | } store_t; 161 | 162 | typedef struct { 163 | char *data; 164 | int len; 165 | unsigned char flags; 166 | } msg_data_t; 167 | 168 | #define DRV_OK 0 169 | #define DRV_MSG_BAD 1 170 | #define DRV_BOX_BAD 2 171 | #define DRV_STORE_BAD 3 172 | #define DRV_SERVER_BAD 4 173 | #define DRV_CANCELED 5 174 | 175 | /* All memory belongs to the driver's user. */ 176 | 177 | #define DRV_CRLF 1 178 | 179 | #define TUIDL 12 180 | 181 | struct driver { 182 | int flags; 183 | int (*parse_store)( conffile_t *cfg, store_conf_t **storep, int *err ); 184 | void (*cleanup)( void ); 185 | void (*open_store)( store_conf_t *conf, 186 | void (*cb)( store_t *ctx, void *aux ), void *aux ); 187 | void (*disown_store)( store_t *ctx ); 188 | store_t *(*own_store)( store_conf_t *conf ); 189 | void (*cancel_store)( store_t *ctx ); 190 | void (*list)( store_t *ctx, 191 | void (*cb)( int sts, void *aux ), void *aux ); 192 | void (*prepare_paths)( store_t *ctx ); 193 | void (*prepare_opts)( store_t *ctx, int opts ); 194 | int (*select)( store_t *ctx, int minuid, int maxuid, int *excs, int nexcs, 195 | int (*cb)( int sts, void *aux ), void *aux ); 196 | int (*fetch_msg)( store_t *ctx, message_t *msg, msg_data_t *data, 197 | int (*cb)( int sts, void *aux ), void *aux ); 198 | int (*store_msg)( store_t *ctx, msg_data_t *data, int to_trash, 199 | int (*cb)( int sts, int uid, void *aux ), void *aux ); 200 | int (*find_msg)( store_t *ctx, const char *tuid, 201 | int (*cb)( int sts, int uid, void *aux ), void *aux ); 202 | int (*set_flags)( store_t *ctx, message_t *msg, int uid, int add, int del, /* msg can be null, therefore uid as a fallback */ 203 | int (*cb)( int sts, void *aux ), void *aux ); 204 | int (*trash_msg)( store_t *ctx, message_t *msg, /* This may expunge the original message immediately, but it needn't to */ 205 | int (*cb)( int sts, void *aux ), void *aux ); 206 | int (*close)( store_t *ctx, /* IMAP-style: expunge inclusive */ 207 | int (*cb)( int sts, void *aux ), void *aux ); 208 | void (*cancel)( store_t *ctx, /* only not yet sent commands */ 209 | void (*cb)( int sts, void *aux ), void *aux ); 210 | void (*commit)( store_t *ctx ); 211 | }; 212 | 213 | 214 | /* main.c */ 215 | 216 | extern int Pid; 217 | extern char Hostname[256]; 218 | extern const char *Home; 219 | 220 | 221 | /* util.c */ 222 | 223 | #define DEBUG 1 224 | #define VERBOSE 2 225 | #define QUIET 4 226 | #define VERYQUIET 8 227 | #define KEEPJOURNAL 16 228 | 229 | extern int DFlags, Ontty; 230 | 231 | void debug( const char *, ... ); 232 | void debugn( const char *, ... ); 233 | void info( const char *, ... ); 234 | void infon( const char *, ... ); 235 | void warn( const char *, ... ); 236 | void error( const char *, ... ); 237 | 238 | char *next_arg( char ** ); 239 | 240 | void add_string_list( string_list_t **list, const char *str ); 241 | void free_string_list( string_list_t *list ); 242 | 243 | void free_generic_messages( message_t * ); 244 | 245 | void *nfmalloc( size_t sz ); 246 | void *nfcalloc( size_t sz ); 247 | void *nfrealloc( void *mem, size_t sz ); 248 | char *nfstrdup( const char *str ); 249 | int nfvasprintf( char **str, const char *fmt, va_list va ); 250 | int nfasprintf( char **str, const char *fmt, ... ); 251 | int nfsnprintf( char *buf, int blen, const char *fmt, ... ); 252 | void ATTR_NORETURN oob( void ); 253 | 254 | char *expand_strdup( const char *s ); 255 | 256 | void sort_ints( int *arr, int len ); 257 | 258 | void arc4_init( void ); 259 | unsigned char arc4_getbyte( void ); 260 | 261 | /* sync.c */ 262 | 263 | extern const char *str_ms[2], *str_hl[2]; 264 | 265 | #define SYNC_OK 0 /* assumed to be 0 */ 266 | #define SYNC_FAIL 1 267 | #define SYNC_BAD(ms) (2<<(ms)) 268 | #define SYNC_NOGOOD 8 /* internal */ 269 | #define SYNC_CANCELED 16 /* internal */ 270 | 271 | /* All passed pointers must stay alive until cb is called. */ 272 | void sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan, 273 | void (*cb)( int sts, void *aux ), void *aux ); 274 | 275 | /* config.c */ 276 | 277 | #define N_DRIVERS 2 278 | extern driver_t *drivers[N_DRIVERS]; 279 | 280 | extern channel_conf_t *channels; 281 | extern group_conf_t *groups; 282 | extern int global_ops[2]; 283 | extern char *global_sync_state; 284 | 285 | int parse_bool( conffile_t *cfile ); 286 | int parse_int( conffile_t *cfile ); 287 | int parse_size( conffile_t *cfile ); 288 | int getcline( conffile_t *cfile ); 289 | int merge_ops( int cops, int ops[] ); 290 | int load_config( const char *filename, int pseudo ); 291 | void parse_generic_store( store_conf_t *store, conffile_t *cfg, int *err ); 292 | 293 | /* drv_*.c */ 294 | extern driver_t maildir_driver, imap_driver; 295 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mbsync - mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2006 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 19 | * 20 | * As a special exception, mbsync may be linked with the OpenSSL library, 21 | * despite that library's more restrictive license. 22 | */ 23 | 24 | #include "isync.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | int Pid; /* for maildir and imap */ 35 | char Hostname[256]; /* for maildir */ 36 | const char *Home; /* for config */ 37 | 38 | static void 39 | version( void ) 40 | { 41 | puts( PACKAGE " " VERSION ); 42 | exit( 0 ); 43 | } 44 | 45 | static void 46 | usage( int code ) 47 | { 48 | fputs( 49 | PACKAGE " " VERSION " - mailbox synchronizer\n" 50 | "Copyright (C) 2000-2002 Michael R. Elkins \n" 51 | "Copyright (C) 2002-2006 Oswald Buddenhagen \n" 52 | "Copyright (C) 2004 Theodore Ts'o \n" 53 | "usage:\n" 54 | " " EXE " [flags] {{channel[:box,...]|group} ...|-a}\n" 55 | " -a, --all operate on all defined channels\n" 56 | " -l, --list list mailboxes instead of syncing them\n" 57 | " -n, --new propagate new messages\n" 58 | " -d, --delete propagate message deletions\n" 59 | " -f, --flags propagate message flag changes\n" 60 | " -N, --renew propagate previously not propagated new messages\n" 61 | " -L, --pull propagate from master to slave\n" 62 | " -H, --push propagate from slave to master\n" 63 | " -C, --create create mailboxes if nonexistent\n" 64 | " -X, --expunge expunge deleted messages\n" 65 | " -c, --config CONFIG read an alternate config file (default: ~/." EXE "rc)\n" 66 | " -D, --debug print debugging messages\n" 67 | " -V, --verbose verbose mode (display network traffic)\n" 68 | " -q, --quiet don't display progress info\n" 69 | " -v, --version display version\n" 70 | " -h, --help display this help message\n" 71 | "\nIf neither --pull nor --push are specified, both are active.\n" 72 | "If neither --new, --delete, --flags nor --renew are specified, all are active.\n" 73 | "Direction and operation can be concatenated like --pull-new, etc.\n" 74 | "--create and --expunge can be suffixed with -master/-slave. Read the man page.\n" 75 | "\nSupported mailbox formats are: IMAP4rev1, Maildir\n" 76 | "\nCompile time options:\n" 77 | #if HAVE_LIBSSL 78 | " +HAVE_LIBSSL\n" 79 | #else 80 | " -HAVE_LIBSSL\n" 81 | #endif 82 | , code ? stderr : stdout ); 83 | exit( code ); 84 | } 85 | 86 | #ifdef __linux__ 87 | static void 88 | crashHandler( int n ) 89 | { 90 | int dpid; 91 | char pbuf[10], pabuf[20]; 92 | 93 | close( 0 ); 94 | open( "/dev/tty", O_RDWR ); 95 | dup2( 0, 1 ); 96 | dup2( 0, 2 ); 97 | error( "*** " EXE " caught signal %d. Starting debugger ...\n", n ); 98 | switch ((dpid = fork())) { 99 | case -1: 100 | perror( "fork()" ); 101 | break; 102 | case 0: 103 | sprintf( pbuf, "%d", Pid ); 104 | sprintf( pabuf, "/proc/%d/exe", Pid ); 105 | execlp( "gdb", "gdb", pabuf, pbuf, (char *)0 ); 106 | perror( "execlp()" ); 107 | _exit( 1 ); 108 | default: 109 | waitpid( dpid, 0, 0 ); 110 | break; 111 | } 112 | exit( 3 ); 113 | } 114 | #endif 115 | 116 | static int 117 | matches( const char *t, const char *p ) 118 | { 119 | for (;;) { 120 | if (!*p) 121 | return !*t; 122 | if (*p == '*') { 123 | p++; 124 | do { 125 | if (matches( t, p )) 126 | return 1; 127 | } while (*t++); 128 | return 0; 129 | } else if (*p == '%') { 130 | p++; 131 | do { 132 | if (*t == '.' || *t == '/') /* this is "somewhat" hacky ... */ 133 | return 0; 134 | if (matches( t, p )) 135 | return 1; 136 | } while (*t++); 137 | return 0; 138 | } else { 139 | if (*p != *t) 140 | return 0; 141 | p++, t++; 142 | } 143 | } 144 | } 145 | 146 | static string_list_t * 147 | filter_boxes( string_list_t *boxes, string_list_t *patterns ) 148 | { 149 | string_list_t *nboxes = 0, *cpat; 150 | const char *ps; 151 | int not, fnot; 152 | 153 | for (; boxes; boxes = boxes->next) { 154 | fnot = 1; 155 | for (cpat = patterns; cpat; cpat = cpat->next) { 156 | ps = cpat->string; 157 | if (*ps == '!') { 158 | ps++; 159 | not = 1; 160 | } else 161 | not = 0; 162 | if (matches( boxes->string, ps )) { 163 | fnot = not; 164 | break; 165 | } 166 | } 167 | if (!fnot) 168 | add_string_list( &nboxes, boxes->string ); 169 | } 170 | return nboxes; 171 | } 172 | 173 | static void 174 | merge_actions( channel_conf_t *chan, int ops[], int have, int mask, int def ) 175 | { 176 | if (ops[M] & have) { 177 | chan->ops[M] &= ~mask; 178 | chan->ops[M] |= ops[M] & mask; 179 | chan->ops[S] &= ~mask; 180 | chan->ops[S] |= ops[S] & mask; 181 | } else if (!(chan->ops[M] & have)) { 182 | if (global_ops[M] & have) { 183 | chan->ops[M] |= global_ops[M] & mask; 184 | chan->ops[S] |= global_ops[S] & mask; 185 | } else { 186 | chan->ops[M] |= def; 187 | chan->ops[S] |= def; 188 | } 189 | } 190 | } 191 | 192 | typedef struct { 193 | int t[2]; 194 | channel_conf_t *chan; 195 | driver_t *drv[2]; 196 | store_t *ctx[2]; 197 | string_list_t *boxes[2], *cboxes, *chanptr; 198 | const char *names[2]; 199 | char **argv, *boxlist, *boxp; 200 | int oind, ret, multiple, all, list, ops[2], state[2]; 201 | unsigned done:1, skip:1, cben:1; 202 | } main_vars_t; 203 | 204 | #define AUX &mvars->t[t] 205 | #define MVARS(aux) \ 206 | int t = *(int *)aux; \ 207 | main_vars_t *mvars = (main_vars_t *)(((char *)(&((int *)aux)[-t])) - offsetof(main_vars_t, t)); 208 | 209 | #define E_START 0 210 | #define E_OPEN 1 211 | #define E_SYNC 2 212 | 213 | static void sync_chans( main_vars_t *mvars, int ent ); 214 | 215 | int 216 | main( int argc, char **argv ) 217 | { 218 | main_vars_t mvars[1]; 219 | group_conf_t *group; 220 | char *config = 0, *opt, *ochar; 221 | int cops = 0, op, pseudo = 0; 222 | 223 | gethostname( Hostname, sizeof(Hostname) ); 224 | if ((ochar = strchr( Hostname, '.' ))) 225 | *ochar = 0; 226 | Pid = getpid(); 227 | if (!(Home = getenv("HOME"))) { 228 | fputs( "Fatal: $HOME not set\n", stderr ); 229 | return 1; 230 | } 231 | Ontty = isatty( 1 ) && isatty( 2 ); 232 | arc4_init(); 233 | 234 | memset( mvars, 0, sizeof(*mvars) ); 235 | mvars->t[1] = 1; 236 | 237 | for (mvars->oind = 1, ochar = 0; ; ) { 238 | if (!ochar || !*ochar) { 239 | if (mvars->oind >= argc) 240 | break; 241 | if (argv[mvars->oind][0] != '-') 242 | break; 243 | if (argv[mvars->oind][1] == '-') { 244 | opt = argv[mvars->oind++] + 2; 245 | if (!*opt) 246 | break; 247 | if (!strcmp( opt, "config" )) { 248 | if (mvars->oind >= argc) { 249 | error( "--config requires an argument.\n" ); 250 | return 1; 251 | } 252 | config = argv[mvars->oind++]; 253 | } else if (!memcmp( opt, "config=", 7 )) 254 | config = opt + 7; 255 | else if (!strcmp( opt, "all" )) 256 | mvars->all = 1; 257 | else if (!strcmp( opt, "list" )) 258 | mvars->list = 1; 259 | else if (!strcmp( opt, "help" )) 260 | usage( 0 ); 261 | else if (!strcmp( opt, "version" )) 262 | version(); 263 | else if (!strcmp( opt, "quiet" )) { 264 | if (DFlags & QUIET) 265 | DFlags |= VERYQUIET; 266 | else 267 | DFlags |= QUIET; 268 | } else if (!strcmp( opt, "verbose" )) 269 | DFlags |= VERBOSE | QUIET; 270 | else if (!strcmp( opt, "debug" )) 271 | DFlags |= DEBUG | QUIET; 272 | else if (!strcmp( opt, "pull" )) 273 | cops |= XOP_PULL, mvars->ops[M] |= XOP_HAVE_TYPE; 274 | else if (!strcmp( opt, "push" )) 275 | cops |= XOP_PUSH, mvars->ops[M] |= XOP_HAVE_TYPE; 276 | else if (!memcmp( opt, "create", 6 )) { 277 | opt += 6; 278 | op = OP_CREATE|XOP_HAVE_CREATE; 279 | lcop: 280 | if (!*opt) 281 | cops |= op; 282 | else if (!strcmp( opt, "-master" )) 283 | mvars->ops[M] |= op, ochar++; 284 | else if (!strcmp( opt, "-slave" )) 285 | mvars->ops[S] |= op, ochar++; 286 | else 287 | goto badopt; 288 | mvars->ops[M] |= op & (XOP_HAVE_CREATE|XOP_HAVE_EXPUNGE); 289 | } else if (!memcmp( opt, "expunge", 7 )) { 290 | opt += 7; 291 | op = OP_EXPUNGE|XOP_HAVE_EXPUNGE; 292 | goto lcop; 293 | } else if (!strcmp( opt, "no-expunge" )) 294 | mvars->ops[M] |= XOP_HAVE_EXPUNGE; 295 | else if (!strcmp( opt, "no-create" )) 296 | mvars->ops[M] |= XOP_HAVE_CREATE; 297 | else if (!strcmp( opt, "full" )) 298 | mvars->ops[M] |= XOP_HAVE_TYPE|XOP_PULL|XOP_PUSH; 299 | else if (!strcmp( opt, "noop" )) 300 | mvars->ops[M] |= XOP_HAVE_TYPE; 301 | else if (!memcmp( opt, "pull", 4 )) { 302 | op = XOP_PULL; 303 | lcac: 304 | opt += 4; 305 | if (!*opt) 306 | cops |= op; 307 | else if (*opt == '-') { 308 | opt++; 309 | goto rlcac; 310 | } else 311 | goto badopt; 312 | } else if (!memcmp( opt, "push", 4 )) { 313 | op = XOP_PUSH; 314 | goto lcac; 315 | } else { 316 | op = 0; 317 | rlcac: 318 | if (!strcmp( opt, "new" )) 319 | op |= OP_NEW; 320 | else if (!strcmp( opt, "renew" )) 321 | op |= OP_RENEW; 322 | else if (!strcmp( opt, "delete" )) 323 | op |= OP_DELETE; 324 | else if (!strcmp( opt, "flags" )) 325 | op |= OP_FLAGS; 326 | else { 327 | badopt: 328 | error( "Unknown option '%s'\n", argv[mvars->oind - 1] ); 329 | return 1; 330 | } 331 | switch (op & XOP_MASK_DIR) { 332 | case XOP_PULL: mvars->ops[S] |= op & OP_MASK_TYPE; break; 333 | case XOP_PUSH: mvars->ops[M] |= op & OP_MASK_TYPE; break; 334 | default: cops |= op; break; 335 | } 336 | mvars->ops[M] |= XOP_HAVE_TYPE; 337 | } 338 | continue; 339 | } 340 | ochar = argv[mvars->oind++] + 1; 341 | if (!*ochar) { 342 | error( "Invalid option '-'\n" ); 343 | return 1; 344 | } 345 | } 346 | switch (*ochar++) { 347 | case 'a': 348 | mvars->all = 1; 349 | break; 350 | case 'l': 351 | mvars->list = 1; 352 | break; 353 | case 'c': 354 | if (*ochar == 'T') { 355 | ochar++; 356 | pseudo = 1; 357 | } 358 | if (mvars->oind >= argc) { 359 | error( "-c requires an argument.\n" ); 360 | return 1; 361 | } 362 | config = argv[mvars->oind++]; 363 | break; 364 | case 'C': 365 | op = OP_CREATE|XOP_HAVE_CREATE; 366 | cop: 367 | if (*ochar == 'm') 368 | mvars->ops[M] |= op, ochar++; 369 | else if (*ochar == 's') 370 | mvars->ops[S] |= op, ochar++; 371 | else if (*ochar == '-') 372 | ochar++; 373 | else 374 | cops |= op; 375 | mvars->ops[M] |= op & (XOP_HAVE_CREATE|XOP_HAVE_EXPUNGE); 376 | break; 377 | case 'X': 378 | op = OP_EXPUNGE|XOP_HAVE_EXPUNGE; 379 | goto cop; 380 | case 'F': 381 | cops |= XOP_PULL|XOP_PUSH; 382 | case '0': 383 | mvars->ops[M] |= XOP_HAVE_TYPE; 384 | break; 385 | case 'n': 386 | case 'd': 387 | case 'f': 388 | case 'N': 389 | --ochar; 390 | op = 0; 391 | cac: 392 | for (;; ochar++) { 393 | if (*ochar == 'n') 394 | op |= OP_NEW; 395 | else if (*ochar == 'd') 396 | op |= OP_DELETE; 397 | else if (*ochar == 'f') 398 | op |= OP_FLAGS; 399 | else if (*ochar == 'N') 400 | op |= OP_RENEW; 401 | else 402 | break; 403 | } 404 | if (op & OP_MASK_TYPE) 405 | switch (op & XOP_MASK_DIR) { 406 | case XOP_PULL: mvars->ops[S] |= op & OP_MASK_TYPE; break; 407 | case XOP_PUSH: mvars->ops[M] |= op & OP_MASK_TYPE; break; 408 | default: cops |= op; break; 409 | } 410 | else 411 | cops |= op; 412 | mvars->ops[M] |= XOP_HAVE_TYPE; 413 | break; 414 | case 'L': 415 | op = XOP_PULL; 416 | goto cac; 417 | case 'H': 418 | op = XOP_PUSH; 419 | goto cac; 420 | case 'q': 421 | if (DFlags & QUIET) 422 | DFlags |= VERYQUIET; 423 | else 424 | DFlags |= QUIET; 425 | break; 426 | case 'V': 427 | DFlags |= VERBOSE | QUIET; 428 | break; 429 | case 'D': 430 | DFlags |= DEBUG | QUIET; 431 | break; 432 | case 'J': 433 | DFlags |= KEEPJOURNAL; 434 | break; 435 | case 'v': 436 | version(); 437 | case 'h': 438 | usage( 0 ); 439 | default: 440 | error( "Unknown option '-%c'\n", *(ochar - 1) ); 441 | return 1; 442 | } 443 | } 444 | 445 | #ifdef __linux__ 446 | if (DFlags & DEBUG) { 447 | signal( SIGSEGV, crashHandler ); 448 | signal( SIGBUS, crashHandler ); 449 | signal( SIGILL, crashHandler ); 450 | } 451 | #endif 452 | 453 | if (merge_ops( cops, mvars->ops )) 454 | return 1; 455 | 456 | if (load_config( config, pseudo )) 457 | return 1; 458 | 459 | if (!mvars->all && !argv[mvars->oind]) { 460 | fputs( "No channel specified. Try '" EXE " -h'\n", stderr ); 461 | return 1; 462 | } 463 | if (!channels) { 464 | fputs( "No channels defined. Try 'man " EXE "'\n", stderr ); 465 | return 1; 466 | } 467 | 468 | mvars->chan = channels; 469 | if (mvars->all) 470 | mvars->multiple = channels->next != 0; 471 | else if (argv[mvars->oind + 1]) 472 | mvars->multiple = 1; 473 | else 474 | for (group = groups; group; group = group->next) 475 | if (!strcmp( group->name, argv[mvars->oind] )) { 476 | mvars->multiple = 1; 477 | break; 478 | } 479 | mvars->argv = argv; 480 | mvars->cben = 1; 481 | sync_chans( mvars, E_START ); 482 | return mvars->ret; 483 | } 484 | 485 | #define ST_FRESH 0 486 | #define ST_OPEN 1 487 | #define ST_CLOSED 2 488 | 489 | static void store_opened( store_t *ctx, void *aux ); 490 | static void store_listed( int sts, void *aux ); 491 | static void done_sync_dyn( int sts, void *aux ); 492 | static void done_sync( int sts, void *aux ); 493 | 494 | #define nz(a,b) ((a)?(a):(b)) 495 | 496 | static void 497 | sync_chans( main_vars_t *mvars, int ent ) 498 | { 499 | group_conf_t *group; 500 | channel_conf_t *chan; 501 | store_t *store; 502 | string_list_t *mbox, *sbox, **mboxp, **sboxp; 503 | char *channame; 504 | int t; 505 | 506 | if (!mvars->cben) 507 | return; 508 | switch (ent) { 509 | case E_OPEN: goto opened; 510 | case E_SYNC: goto syncone; 511 | } 512 | for (;;) { 513 | mvars->boxlist = 0; 514 | if (!mvars->all) { 515 | if (mvars->chanptr) 516 | channame = mvars->chanptr->string; 517 | else { 518 | for (group = groups; group; group = group->next) 519 | if (!strcmp( group->name, mvars->argv[mvars->oind] )) { 520 | mvars->chanptr = group->channels; 521 | channame = mvars->chanptr->string; 522 | goto gotgrp; 523 | } 524 | channame = mvars->argv[mvars->oind]; 525 | gotgrp: ; 526 | } 527 | if ((mvars->boxlist = strchr( channame, ':' ))) 528 | *mvars->boxlist++ = 0; 529 | for (chan = channels; chan; chan = chan->next) 530 | if (!strcmp( chan->name, channame )) 531 | goto gotchan; 532 | error( "No channel or group named '%s' defined.\n", channame ); 533 | mvars->ret = 1; 534 | goto gotnone; 535 | gotchan: 536 | mvars->chan = chan; 537 | } 538 | merge_actions( mvars->chan, mvars->ops, XOP_HAVE_TYPE, OP_MASK_TYPE, OP_MASK_TYPE ); 539 | merge_actions( mvars->chan, mvars->ops, XOP_HAVE_CREATE, OP_CREATE, 0 ); 540 | merge_actions( mvars->chan, mvars->ops, XOP_HAVE_EXPUNGE, OP_EXPUNGE, 0 ); 541 | 542 | mvars->state[M] = mvars->state[S] = ST_FRESH; 543 | info( "Channel %s\n", mvars->chan->name ); 544 | mvars->boxes[M] = mvars->boxes[S] = mvars->cboxes = 0; 545 | mvars->skip = mvars->cben = 0; 546 | for (t = 0; t < 2; t++) { 547 | mvars->drv[t] = mvars->chan->stores[t]->driver; 548 | if ((store = mvars->drv[t]->own_store( mvars->chan->stores[t] ))) 549 | store_opened( store, AUX ); 550 | } 551 | for (t = 0; t < 2 && !mvars->skip; t++) 552 | if (mvars->state[t] == ST_FRESH) { 553 | info( "Opening %s %s...\n", str_ms[t], mvars->chan->stores[t]->name ); 554 | mvars->drv[t]->open_store( mvars->chan->stores[t], store_opened, AUX ); 555 | } 556 | mvars->cben = 1; 557 | opened: 558 | if (mvars->skip) 559 | goto next; 560 | if (mvars->state[M] != ST_OPEN || mvars->state[S] != ST_OPEN) 561 | return; 562 | 563 | if (mvars->boxlist) 564 | mvars->boxp = mvars->boxlist; 565 | else if (mvars->chan->patterns) { 566 | mvars->boxes[M] = filter_boxes( mvars->ctx[M]->boxes, mvars->chan->patterns ); 567 | mvars->boxes[S] = filter_boxes( mvars->ctx[S]->boxes, mvars->chan->patterns ); 568 | for (mboxp = &mvars->boxes[M]; (mbox = *mboxp); ) { 569 | for (sboxp = &mvars->boxes[S]; (sbox = *sboxp); sboxp = &sbox->next) 570 | if (!strcmp( sbox->string, mbox->string )) { 571 | *sboxp = sbox->next; 572 | free( sbox ); 573 | *mboxp = mbox->next; 574 | mbox->next = mvars->cboxes; 575 | mvars->cboxes = mbox; 576 | goto gotdupe; 577 | } 578 | mboxp = &mbox->next; 579 | gotdupe: ; 580 | } 581 | } 582 | 583 | if (mvars->list && mvars->multiple) 584 | printf( "%s:\n", mvars->chan->name ); 585 | syncml: 586 | mvars->done = mvars->cben = 0; 587 | syncmlx: 588 | if (mvars->boxlist) { 589 | if ((mvars->names[S] = strsep( &mvars->boxp, ",\n" ))) { 590 | if (!*mvars->names[S]) 591 | mvars->names[S] = 0; 592 | if (!mvars->list) { 593 | mvars->names[M] = mvars->names[S]; 594 | sync_boxes( mvars->ctx, mvars->names, mvars->chan, done_sync, mvars ); 595 | goto syncw; 596 | } 597 | puts( nz( mvars->names[S], "INBOX" ) ); 598 | goto syncmlx; 599 | } 600 | } else if (mvars->chan->patterns) { 601 | if ((mbox = mvars->cboxes)) { 602 | mvars->cboxes = mbox->next; 603 | if (!mvars->list) { 604 | mvars->names[M] = mvars->names[S] = mbox->string; 605 | sync_boxes( mvars->ctx, mvars->names, mvars->chan, done_sync_dyn, mvars ); 606 | goto syncw; 607 | } 608 | puts( mbox->string ); 609 | free( mbox ); 610 | goto syncmlx; 611 | } 612 | for (t = 0; t < 2; t++) 613 | if ((mbox = mvars->boxes[t])) { 614 | mvars->boxes[t] = mbox->next; 615 | if ((mvars->chan->ops[1-t] & OP_MASK_TYPE) && (mvars->chan->ops[1-t] & OP_CREATE)) { 616 | if (!mvars->list) { 617 | mvars->names[M] = mvars->names[S] = mbox->string; 618 | sync_boxes( mvars->ctx, mvars->names, mvars->chan, done_sync_dyn, mvars ); 619 | goto syncw; 620 | } 621 | puts( mbox->string ); 622 | } 623 | free( mbox ); 624 | goto syncmlx; 625 | } 626 | } else { 627 | if (!mvars->list) { 628 | sync_boxes( mvars->ctx, mvars->chan->boxes, mvars->chan, done_sync, mvars ); 629 | mvars->skip = 1; 630 | syncw: 631 | mvars->cben = 1; 632 | if (!mvars->done) 633 | return; 634 | syncone: 635 | if (!mvars->skip) 636 | goto syncml; 637 | } else 638 | printf( "%s <=> %s\n", nz( mvars->chan->boxes[M], "INBOX" ), nz( mvars->chan->boxes[S], "INBOX" ) ); 639 | } 640 | 641 | next: 642 | for (t = 0; t < 2; t++) 643 | if (mvars->state[t] == ST_OPEN) { 644 | mvars->drv[t]->disown_store( mvars->ctx[t] ); 645 | mvars->state[t] = ST_CLOSED; 646 | } 647 | if (mvars->state[M] != ST_CLOSED || mvars->state[S] != ST_CLOSED) { 648 | mvars->skip = mvars->cben = 1; 649 | return; 650 | } 651 | free_string_list( mvars->cboxes ); 652 | free_string_list( mvars->boxes[M] ); 653 | free_string_list( mvars->boxes[S] ); 654 | if (mvars->all) { 655 | if (!(mvars->chan = mvars->chan->next)) 656 | break; 657 | } else { 658 | if (mvars->chanptr && (mvars->chanptr = mvars->chanptr->next)) 659 | continue; 660 | gotnone: 661 | if (!mvars->argv[++mvars->oind]) 662 | break; 663 | } 664 | } 665 | for (t = 0; t < N_DRIVERS; t++) 666 | drivers[t]->cleanup(); 667 | } 668 | 669 | static void 670 | store_opened( store_t *ctx, void *aux ) 671 | { 672 | MVARS(aux) 673 | 674 | if (!ctx) { 675 | mvars->state[t] = ST_CLOSED; 676 | mvars->ret = mvars->skip = 1; 677 | return; 678 | } 679 | mvars->ctx[t] = ctx; 680 | if (mvars->skip) { 681 | mvars->state[t] = ST_OPEN; 682 | sync_chans( mvars, E_OPEN ); 683 | return; 684 | } 685 | if (!mvars->boxlist && mvars->chan->patterns && !ctx->listed) 686 | mvars->drv[t]->list( ctx, store_listed, AUX ); 687 | else { 688 | mvars->state[t] = ST_OPEN; 689 | sync_chans( mvars, E_OPEN ); 690 | } 691 | } 692 | 693 | static void 694 | store_listed( int sts, void *aux ) 695 | { 696 | MVARS(aux) 697 | 698 | mvars->state[t] = ST_OPEN; 699 | switch (sts) { 700 | case DRV_OK: 701 | if (mvars->ctx[t]->conf->map_inbox) 702 | add_string_list( &mvars->ctx[t]->boxes, mvars->ctx[t]->conf->map_inbox ); 703 | break; 704 | case DRV_STORE_BAD: 705 | mvars->drv[t]->cancel_store( mvars->ctx[t] ); 706 | mvars->state[t] = ST_CLOSED; 707 | default: 708 | mvars->ret = mvars->skip = 1; 709 | break; 710 | } 711 | sync_chans( mvars, E_OPEN ); 712 | } 713 | 714 | static void 715 | done_sync_dyn( int sts, void *aux ) 716 | { 717 | main_vars_t *mvars = (main_vars_t *)aux; 718 | 719 | free( ((char *)mvars->names[S]) - offsetof(string_list_t, string) ); 720 | done_sync( sts, aux ); 721 | } 722 | 723 | static void 724 | done_sync( int sts, void *aux ) 725 | { 726 | main_vars_t *mvars = (main_vars_t *)aux; 727 | 728 | mvars->done = 1; 729 | if (sts) { 730 | mvars->ret = 1; 731 | if (sts & (SYNC_BAD(M) | SYNC_BAD(S))) { 732 | mvars->skip = 1; 733 | if (sts & SYNC_BAD(M)) 734 | mvars->state[M] = ST_CLOSED; 735 | if (sts & SYNC_BAD(S)) 736 | mvars->state[S] = ST_CLOSED; 737 | } 738 | } 739 | sync_chans( mvars, E_SYNC ); 740 | } 741 | -------------------------------------------------------------------------------- /src/mbsync.1: -------------------------------------------------------------------------------- 1 | .ig 2 | \" mbsync - mailbox synchronizer 3 | \" Copyright (C) 2000-2002 Michael R. Elkins 4 | \" Copyright (C) 2002-2004 Oswald Buddenhagen 5 | \" Copyright (C) 2004 Theodore Y. Ts'o 6 | \" 7 | \" This program is free software; you can redistribute it and/or modify 8 | \" it under the terms of the GNU General Public License as published by 9 | \" the Free Software Foundation; either version 2 of the License, or 10 | \" (at your option) any later version. 11 | \" 12 | \" This program is distributed in the hope that it will be useful, 13 | \" but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | \" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | \" GNU General Public License for more details. 16 | \" 17 | \" You should have received a copy of the GNU General Public License 18 | \" along with this program; if not, write to the Free Software Foundation, 19 | \" Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 20 | \" 21 | \" As a special exception, mbsync may be linked with the OpenSSL library, 22 | \" despite that library's more restrictive license. 23 | .. 24 | .TH mbsync 1 "2004 Mar 27" 25 | .. 26 | .SH NAME 27 | mbsync - synchronize IMAP4 and Maildir mailboxes 28 | .. 29 | .SH SYNOPSIS 30 | \fBmbsync\fR [\fIoptions\fR ...] {{\fIchannel\fR[\fB:\fIbox\fR[{\fB,\fR|\fB\\n\fR}...]]|\fIgroup\fR} ...|\fB-a\fR} 31 | .. 32 | .SH DESCRIPTION 33 | \fBmbsync\fR is a command line application which synchronizes mailboxes; 34 | currently Maildir and IMAP4 mailboxes are supported. 35 | New messages, message deletions and flag changes can be propagated both ways; 36 | the operation set can be selected in a fine-grained manner. 37 | .br 38 | Synchronization is based on unique message identifiers (UIDs), so no 39 | identification conflicts can occur (as opposed to some other mail synchronizers). 40 | OTOH, \fBmbsync\fR is susceptible to UID validity changes (that \fIshould\fR 41 | never happen, but see "Compatibility" in the README). 42 | Synchronization state is kept in one local text file per mailbox pair; 43 | multiple replicas of a mailbox can be maintained. 44 | .. 45 | .SH OPTIONS 46 | .TP 47 | \fB-c\fR, \fB--config\fR \fIfile\fR 48 | Read configuration from \fIfile\fR. 49 | By default, the configuration is read from ~/.mbsyncrc. 50 | .TP 51 | \fB-a\fR, \fB--all\fR 52 | Select all configured channels. Any channel/group specifications on the command 53 | line are ignored. 54 | .TP 55 | \fB-l\fR, \fB--list\fR 56 | Don't synchronize anything, but list all mailboxes in the selected channels 57 | and exit. 58 | .TP 59 | \fB-C\fR[\fBm\fR][\fBs\fR], \fB--create\fR[\fB-master\fR|\fB-slave\fR] 60 | Override any \fBCreate\fR options from the config file. See below. 61 | .TP 62 | \fB-X\fR[\fBm\fR][\fBs\fR], \fB--expunge\fR[\fB-master\fR|\fB-slave\fR] 63 | Override any \fBExpunge\fR options from the config file. See below. 64 | .TP 65 | {\fB-n\fR|\fB-N\fR|\fB-d\fR|\fB-f\fR|\fB-0\fR|\fB-F\fR},\ 66 | {\fB--new\fR|\fB--renew\fR|\fB--delete\fR|\fB--flags\fR|\fB--noop\fR|\fB--full\fR} 67 | .TP 68 | \r{\fB-L\fR|\fB-H\fR}[\fBn\fR][\fBN\fR][\fBd\fR][\fBf\fR],\ 69 | {\fB--pull\fR|\fB--push\fR}[\fB-new\fR|\fB-renew\fR|\fB-delete\fR|\fB-flags\fR] 70 | Override any \fBSync\fR options from the config file. See below. 71 | .TP 72 | \fB-h\fR, \fB--help\fR 73 | Display a summary of command line options. 74 | .TP 75 | \fB-v\fR, \fB--version\fR 76 | Display version information. 77 | .TP 78 | \fB-V\fR, \fB--verbose\fR 79 | Enable \fIverbose\fR mode, which displays the IMAP4 network traffic. 80 | .TP 81 | \fB-D\fR, \fB--debug\fR 82 | Enable printing \fIdebug\fR information. 83 | .TP 84 | \fB-q\fR, \fB--quiet\fR 85 | Suppress informational messages. 86 | If specified twice, suppress warning messages as well. 87 | .. 88 | .SH CONFIGURATION 89 | The configuration file is mandatory; \fBmbsync\fR will not run without it. 90 | Lines starting with a hash mark (\fB#\fR) are comments and are ignored entirely. 91 | Configuration items are keywords followed by one or more arguments; 92 | arguments containing spaces must be enclosed in double quotes (\fB"\fR). 93 | All keywords (including those used as arguments) are case-insensitive. 94 | There are a few global options, the rest applies to particular sections. 95 | Sections are started by a section keyword and are terminated by an empty line 96 | or end of file. 97 | Every section defines an object with a an identifier unique within that 98 | object class. 99 | .P 100 | There are two basic object classes: Stores and Channels. A Store defines 101 | a collection of mailboxes; basically a folder, either local or remote. 102 | A Channel connects two Stores, describing the way the two are synchronized. 103 | .br 104 | There are two auxiliary object classes: Accounts and Groups. An Account 105 | describes the connection part of remote Stores, so a server connection can be 106 | shared between multiple Stores. A Group aggregates multiple Channels to 107 | save typing on the command line. 108 | .. 109 | .SS All Stores 110 | These options can be used in all supported Store types. 111 | .br 112 | In this context, the term "remote" describes the second Store within a Channel, 113 | and not necessarily a remote server. 114 | .br 115 | The special mailbox \fBINBOX\fR exists in every Store; its physical location 116 | in the file system is Store type specific. 117 | .. 118 | .TP 119 | \fBPath\fR \fIpath\fR 120 | The location of the Store in the (server's) file system. 121 | If this is no absolute path, the reference point is Store type specific. 122 | This string is prepended to the mailbox names addressed in this Store, 123 | but is not considered part of them; this is important for \fBPatterns\fR 124 | in the Channels section. 125 | Note that you \fBmust\fR append a slash if you want to specify an entire 126 | directory. 127 | (Default: \fI""\fR) 128 | .. 129 | .TP 130 | \fBMaxSize\fR \fIsize\fR[\fBk\fR|\fBm\fR][\fBb\fR] 131 | Messages larger than that will not be propagated into this Store. 132 | This is useful for weeding out messages with large attachments. 133 | \fBK\fR and \fBM\fR can be appended to the size to specify KiBytes resp. 134 | MeBytes instead of bytes. \fBB\fR is accepted but superfluous. 135 | If \fIsize\fR is 0, the maximum message size is \fBunlimited\fR. 136 | (Default: \fI0\fR) 137 | .. 138 | .TP 139 | \fBMapInbox\fR \fImailbox\fR 140 | Create a virtual mailbox (relative to \fBPath\fR), which is backed by 141 | the \fBINBOX\fR. Makes sense in conjunction with \fBPatterns\fR in the 142 | Channels section. 143 | .. 144 | .TP 145 | \fBTrash\fR \fImailbox\fR 146 | Specifies a mailbox (relative to \fBPath\fR) to copy deleted messages to 147 | prior to expunging. See \fBINHERENT PROBLEMS\fR below. 148 | (Default: none) 149 | .. 150 | .TP 151 | \fBTrashNewOnly\fR \fIyes\fR|\fIno\fR 152 | When trashing, copy only not yet propagated messages. This makes sense if the 153 | remote Store has a \fBTrash\fR as well (with \fBTrashNewOnly\fR \fIno\fR). 154 | (Default: \fIno\fR) 155 | .. 156 | .TP 157 | \fBTrashRemoteNew\fR \fIyes\fR|\fIno\fR 158 | When expunging the remote Store, copy not yet propagated messages to this 159 | Store's \fBTrash\fR. When using this, the remote Store does not need an own 160 | \fBTrash\fR at all, yet all messages are archived. 161 | (Default: \fIno\fR) 162 | .. 163 | .SS Maildir Stores 164 | The reference point for relative \fBPath\fRs is $HOME. 165 | .P 166 | As \fBmbsync\fR needs UIDs, but no standardized UID storage scheme exists for 167 | Maildir, \fBmbsync\fR supports two schemes, each with its pros and cons. 168 | .br 169 | The \fBnative\fR scheme is stolen from the latest Maildir patches to \fBc-client\fR 170 | and is therefore compatible with \fBpine\fR. The UID validity is stored in a 171 | file named .uidvalidity; the UIDs are encoded in the file names of the messages. 172 | .br 173 | The \fBalternative\fR scheme is based on the UID mapping used by \fBisync\fR 174 | versions 0.8 and 0.9.x. The invariant parts of the file names of the messages 175 | are used as keys into a Berkeley database named .isyncuidmap.db, which holds 176 | the UID validity as well. 177 | .br 178 | The \fBnative\fR scheme is faster, more space efficient, endianess independent 179 | and "human readable", but will be disrupted if a message is copied from another 180 | mailbox without getting a new file name; this would result in duplicated UIDs 181 | sooner or later, which in turn results in a UID validity change, making 182 | synchronization fail. 183 | The \fBalternative\fR scheme would fail if a MUA changed a message's file name 184 | in a part \fBmbsync\fR considers invariant; this would be interpreted as a 185 | message deletion and a new message, resulting in unnecessary traffic. 186 | .br 187 | \fBMutt\fR is known to work fine with both schemes. 188 | .br 189 | Use \fBmdconvert\fR to convert mailboxes from one scheme to the other. 190 | .. 191 | .TP 192 | \fBMaildirStore\fR \fIname\fR 193 | Define the Maildir Store \fIname\fR, opening a section for its parameters. 194 | .. 195 | .TP 196 | \fBAltMap\fR \fIyes\fR|\fIno\fR 197 | Use the \fBalternative\fR UID storage scheme for mailboxes in this Store. 198 | This does not affect mailboxes that do already have a UID storage scheme; 199 | use \fBmdconvert\fR to change it. 200 | (Default: \fIno\fR) 201 | .. 202 | .TP 203 | \fBInbox\fR \fIpath\fR 204 | The location of the \fBINBOX\fR. This is \fInot\fR relative to \fBPath\fR. 205 | (Default: \fI~/Maildir\fR) 206 | .. 207 | .SS IMAP4 Accounts 208 | .TP 209 | \fBIMAPAccount\fR \fIname\fR 210 | Define the IMAP4 Account \fIname\fR, opening a section for its parameters. 211 | .. 212 | .TP 213 | \fBHost\fR \fIhost\fR 214 | Specify the DNS name or IP address of the IMAP server. 215 | .. 216 | .TP 217 | \fBPort\fR \fIport\fR 218 | Specify the TCP port number of the IMAP server. (Default: 143 for IMAP, 219 | 993 for IMAPS) 220 | .. 221 | .TP 222 | \fBUser\fR \fIusername\fR 223 | Specify the login name on the IMAP server. (Default: current local user) 224 | .. 225 | .TP 226 | \fBPass\fR \fIpassword\fR 227 | Specify the password for \fIusername\fR on the IMAP server. 228 | Note that this option is \fBNOT\fR required. 229 | If no password is specified in the configuration file, \fBmbsync\fR 230 | will prompt you for it. 231 | .. 232 | .TP 233 | \fBTunnel\fR \fIcommand\fR 234 | Specify a command to run to establish a connection rather than opening a TCP 235 | socket. This allows you to run an IMAP session over an SSH tunnel, for 236 | example. 237 | \fBHost\fR and \fBPort\fR are ignored when \fBTunnel\fR is set. 238 | .. 239 | .TP 240 | \fBRequireCRAM\fR \fIyes\fR|\fIno\fR 241 | If set to \fIyes\fR, \fBmbsync\fR will abort the connection if no CRAM-MD5 242 | authentication is possible. (Default: \fIno\fR) 243 | .. 244 | .TP 245 | \fBUseIMAPS\fR \fIyes\fR|\fIno\fR 246 | If set to \fIyes\fR, the default for \fBPort\fR is changed to 993 and 247 | \fBmbsync\fR will start SSL negotiation immediately after establishing 248 | the connection to the server. 249 | .br 250 | Note that modern servers support SSL on the regular IMAP port 143 via the 251 | STARTTLS extension, which will be used automatically by default. 252 | .. 253 | .TP 254 | \fBRequireSSL\fR \fIyes\fR|\fIno\fR 255 | \fBmbsync\fR will abort the connection if a TLS/SSL session cannot be 256 | established with the IMAP server. (Default: \fIyes\fR) 257 | .. 258 | .TP 259 | \fBCertificateFile\fR \fIpath\fR 260 | File containing X.509 CA certificates used to verify server identities. 261 | This option is \fImandatory\fR if SSL is used. See \fBSSL CERTIFICATES\fR below. 262 | .. 263 | .TP 264 | \fBUseSSLv2\fR \fIyes\fR|\fIno\fR 265 | Use SSLv2 for communication with the IMAP server over SSL? 266 | .br 267 | Note that this option is deprecated for security reasons. 268 | (Default: \fIno\fR) 269 | .. 270 | .TP 271 | \fBUseSSLv3\fR \fIyes\fR|\fIno\fR 272 | Use SSLv3 for communication with the IMAP server over SSL? 273 | (Default: \fIno\fR) 274 | .. 275 | .TP 276 | \fBUseTLSv1\fR \fIyes\fR|\fIno\fR 277 | Use TLSv1 for communication with the IMAP server over SSL? 278 | (Default: \fIyes\fR) 279 | .. 280 | .SS IMAP Stores 281 | The reference point for relative \fBPath\fRs is whatever the server likes it 282 | to be; probably the user's $HOME or $HOME/Mail on that server. The location 283 | of \fBINBOX\fR is up to the server as well and is usually irrelevant. 284 | .TP 285 | \fBIMAPStore\fR \fIname\fR 286 | Define the IMAP4 Store \fIname\fR, opening a section for its parameters. 287 | .. 288 | .TP 289 | \fBAccount\fR \fIaccount\fR 290 | Specify which IMAP4 Account to use. Instead of defining an Account and 291 | referencing it here, it is also possible to specify all the Account options 292 | directly in the Store's section - this makes sense if an Account is used for 293 | one Store only anyway. 294 | .. 295 | .TP 296 | \fBUseNamespace\fR \fIyes\fR|\fIno\fR 297 | Selects whether the server's first "personal" NAMESPACE should be prefixed to 298 | mailbox names. Disabling this makes sense for some broken IMAP servers. 299 | This option is meaningless if a \fBPath\fR was specified. 300 | (Default: \fIyes\fR) 301 | .. 302 | .SS Channels 303 | .TP 304 | \fBChannel\fR \fIname\fR 305 | Define the Channel \fIname\fR, opening a section for its parameters. 306 | .. 307 | .TP 308 | {\fBMaster\fR|\fBSlave\fR} \fB:\fIstore\fB:\fR[\fImailbox\fR] 309 | Specify the Master resp. Slave Store to be connected by this Channel. 310 | If \fImailbox\fR is omitted, \fBINBOX\fR is assumed. 311 | The \fImailbox\fR specification can be overridden by \fBPatterns\fR, which 312 | in turn can be overridden by a mailbox list in a Channel reference (a Group 313 | specification or the command line). 314 | .. 315 | .TP 316 | \fBPattern\fR[\fBs\fR] [\fB!\fR]\fIpattern\fR ... 317 | Instead of synchronizing only one mailbox pair, synchronize all mailboxes 318 | that match the \fIpattern\fR(s). The mailbox names are the same on both 319 | Master and Slave. Patterns are IMAP4 patterns, i.e., \fB*\fR matches anything 320 | and \fB%\fR matches anything up to the next hierarchy delimiter. Prepending 321 | \fB!\fR to a pattern makes it an exclusion. Multiple patterns can be specified 322 | (either by supplying multiple arguments or by using \fBPattern\fR multiple 323 | times); later matches take precedence. 324 | Example: "\fBPatterns\fR\ \fI%\ !Trash\fR" 325 | .. 326 | .TP 327 | \fBMaxSize\fR \fIsize\fR[\fBk\fR|\fBm\fR][\fBb\fR] 328 | Analogous to the homonymous option in the Stores section, but applies equally 329 | to Master and Slave. Note that this actually modifies the Stores, so take care 330 | not to provide conflicting settings if you use the Stores in multiple Channels. 331 | .. 332 | .TP 333 | \fBMaxMessages\fR \fIcount\fR 334 | Sets the maximum number of messages to keep in each Slave mailbox. 335 | This is useful for mailboxes where you keep a complete archive on the server, 336 | but want to mirror only the last messages (for instance, for mailing lists). 337 | The messages that were the first to arrive in the mailbox (independently of 338 | the actual date of the message) will be deleted first. 339 | Messages that are flagged (marked as important) and recent messages will not 340 | be automatically deleted. 341 | If \fIcount\fR is 0, the maximum number of messages is \fBunlimited\fR 342 | (Default: \fI0\fR). 343 | .. 344 | .TP 345 | \fBSync\fR {\fINone\fR|[\fIPull\fR] [\fIPush\fR] [\fINew\fR] [\fIReNew\fR] [\fIDelete\fR] [\fIFlags\fR]|\fIFull\fR} 346 | Select the synchronization operation(s) to perform: 347 | .br 348 | \fBPull\fR - propagate changes from Master to Slave. 349 | .br 350 | \fBPush\fR - propagate changes from Slave to Master. 351 | .br 352 | \fBNew\fR - propagate newly appeared messages. 353 | .br 354 | \fBReNew\fR - previously refused messages are re-evaluated for propagation. 355 | Useful after flagging affected messages in the source Store or enlarging 356 | MaxSize in the destination Store. 357 | .br 358 | \fBDelete\fR - propagate message deletions. This applies only to messages that 359 | are actually gone, i.e., were expunged. The affected messages in the remote 360 | Store are marked as deleted only, i.e., they won't be really deleted until 361 | that Store is expunged. 362 | .br 363 | \fBFlags\fR - propagate flag changes. Note that Deleted/Trashed is a flag as 364 | well; this is particularily interesting if you use \fBmutt\fR with the 365 | maildir_trash option. 366 | .br 367 | \fBAll\fR (\fB--full\fR on the command line) - all of the above. 368 | This is the global default. 369 | .br 370 | \fBNone\fR (\fB--noop\fR on the command line) - don't propagate anything. 371 | Useful if you want to expunge only. 372 | .IP 373 | \fBPull\fR and \fBPush\fR are direction flags, while \fBNew\fR, \fBReNew\fR, 374 | \fBDelete\fR and \fBFlags\fR are type flags. The two flag classes make up a 375 | two-dimensional matrix (a table). Its cells are the individual actions to 376 | perform. There are two styles of asserting the cells: 377 | .br 378 | In the first style, the flags select entire rows/colums in the matrix. Only 379 | the cells which are selected both horizontally and vertically are asserted. 380 | Specifying no flags from a class is like specifying all flags from this class. 381 | For example, "\fBSync\fR\ \fBPull\fR\ \fBNew\fR\ \fBFlags\fR" will propagate 382 | new messages and flag changes from the Master to the Slave, 383 | "\fBSync\fR\ \fBNew\fR\ \fBDelete\fR" will propagate message arrivals and 384 | deletions both ways, and "\fBSync\fR\ \fBPush\fR" will propagate all changes 385 | from the Slave to the Master. 386 | .br 387 | In the second style, direction flags are concatenated with type flags; every 388 | compound flag immediately asserts a cell in the matrix. In addition to at least 389 | one compound flag, the individual flags can be used as well, but as opposed to 390 | the first style, they immediately assert all cells in their respective 391 | row/column. For example, 392 | "\fBSync\fR\ \fBPullNew\fR\ \fBPullDelete\fR\ \fBPush\fR" will propagate 393 | message arrivals and deletions from the Master to the Slave and any changes 394 | from the Slave to the Master. 395 | Note that it is not allowed to assert a cell in two ways, e.g. 396 | "\fBSync\fR\ \fBPullNew\fR\ \fBPull\fR" and 397 | "\fBSync\fR\ \fBPullNew\fR\ \fBDelete\fR\ \fBPush\fR" induce error messages. 398 | .. 399 | .TP 400 | \fBCreate\fR {\fINone\fR|\fIMaster\fR|\fISlave\fR|\fIBoth\fR} 401 | Automatically create missing mailboxes [on the Master/Slave]. 402 | Otherwise print an error message and skip that mailbox pair if a mailbox 403 | does not exist. 404 | (Global default: \fINone\fR) 405 | .. 406 | .TP 407 | \fBExpunge\fR {\fINone\fR|\fIMaster\fR|\fISlave\fR|\fIBoth\fR} 408 | Permanently remove all messages [on the Master/Slave] marked for deletion. 409 | (Global default: \fINone\fR) 410 | .. 411 | .P 412 | \fBSync\fR, \fBCreate\fR and \fBExpunge\fR can be used outside any section for 413 | a global effect. The global settings are overridden by Channel-specific options, 414 | which in turn are overridden by command line switches. 415 | .. 416 | .TP 417 | \fBSyncState\fR {\fB*\fR|\fIpath\fR} 418 | Set the location of this Channel's synchronization state files. \fB*\fR means 419 | that the state should be saved in a file named .mbsyncstate in the 420 | Slave mailbox itself; this has the advantage that you needn't to care for the 421 | state file if you delete the mailbox, but it works only with Maildir mailboxes, 422 | obviously. Otherwise this is interpreted as a string to prepend to the Slave 423 | mailbox name to make up a complete path. 424 | .br 425 | This option can be used outside any section for a global effect. In this case 426 | the appended string is made up according to the pattern 427 | \fB:\fImaster\fB:\fImaster-box\fB_:\fIslave\fB:\fIslave-box\fR. 428 | .br 429 | (Global default: \fI~/.mbsync/\fR). 430 | .. 431 | .SS Groups 432 | .TP 433 | \fBGroup\fR \fIname\fR [\fIchannel\fR[\fB:\fIbox\fR[\fB,\fR...]]] ... 434 | Define the Group \fIname\fR, opening a section for its parameters. 435 | Note that even though Groups have an own namespace, they will "hide" Channels 436 | with the same name on the command line. 437 | .br 438 | One or more Channels can be specified on the same line. 439 | .br 440 | If you supply one or more \fIbox\fRes to a \fIchannel\fR, they will be used 441 | instead of what is specified in the Channel. The same can be done on the command 442 | line, except that there newlines can be used as mailbox name separators as well. 443 | .. 444 | .TP 445 | \fBChannel\fR[\fBs\fR] \fIchannel\fR[\fB:\fIbox\fR[\fB,\fR...]] ... 446 | Add the specified channels to the group. This option can be specified multiple 447 | times within a Group. 448 | .. 449 | .SH SSL CERTIFICATES 450 | [to be done] 451 | .. 452 | .SH INHERENT PROBLEMS 453 | Changes done after \fBmbsync\fR has retrieved the message list will not be 454 | synchronised until the next time \fBmbsync\fR is invoked. 455 | .P 456 | Using \fBTrash\fR on IMAP Stores bears a race condition: messages will be 457 | lost if they are marked as deleted after the message list was retrieved but 458 | before the mailbox is expunged. There is no risk as long as the IMAP mailbox 459 | is not simultaneously accessed by \fBmbsync\fR and another mail client. 460 | .. 461 | .SH FILES 462 | .TP 463 | .B ~/.mbsyncrc 464 | Default configuration file 465 | .TP 466 | .B ~/.mbsync/ 467 | Directory containing synchronization state files 468 | .. 469 | .SH SEE ALSO 470 | mdconvert(1), isync(1), mutt(1), maildir(5) 471 | .P 472 | Up to date information on \fBmbsync\fR can be found at http://isync.sf.net/ 473 | .. 474 | .SH AUTHOR 475 | Written by Michael R. Elkins , 476 | .br 477 | rewritten and maintained by Oswald Buddenhagen , 478 | .br 479 | contributions by Theodore Y. Ts'o . 480 | -------------------------------------------------------------------------------- /src/mbsyncrc.sample: -------------------------------------------------------------------------------- 1 | # Global configuration section 2 | # Values here are used as defaults for any following Channel section that 3 | # doesn't specify them. 4 | Expunge None 5 | Create Both 6 | 7 | MaildirStore local 8 | Path ~/Mail/ 9 | Trash Trash 10 | 11 | 12 | IMAPStore work 13 | Host work.host.com 14 | Pass xxxxxxxx 15 | CertificateFile /etc/ssl/certs/ca-certificates.crt 16 | 17 | Channel work 18 | Master :work: 19 | Slave :local:work 20 | Expunge Slave 21 | Sync PullNew Push 22 | 23 | 24 | IMAPStore personal 25 | Host host.play.com 26 | Port 6789 27 | RequireSSL no 28 | 29 | Channel personal 30 | Master :personal: 31 | Slave :local:personal 32 | Expunge Both 33 | MaxMessages 150 34 | MaxSize 200k 35 | 36 | IMAPStore remote 37 | Tunnel "ssh -q host.remote.com /usr/sbin/imapd" 38 | 39 | Channel remote 40 | Master :remote: 41 | Slave :local:remote 42 | 43 | 44 | Group boxes 45 | Channels work personal remote 46 | 47 | 48 | IMAPStore st1 49 | Host st1.domain.com 50 | RequireCRAM yes 51 | CertificateFile ~/.st1-certificate.crt 52 | 53 | IMAPStore st2 54 | Host imap.another-domain.com 55 | Path non-standard/ 56 | RequireSSL no 57 | UseTLSv1 no 58 | 59 | Channel rst 60 | Master :st1:somebox 61 | Slave :st2: 62 | 63 | 64 | IMAPAccount server 65 | Host imaps:foo.bar.com 66 | CertificateFile ~/.server-certificate.crt 67 | 68 | IMAPStore server 69 | Account server 70 | MapInbox inbox 71 | Trash ~/trash 72 | TrashRemoteNew yes 73 | 74 | MaildirStore mirror 75 | Path ~/Maildir/ 76 | 77 | Channel o2o 78 | Master :server: 79 | Slave :mirror: 80 | Patterns % 81 | 82 | Group partial o2o:inbox,sent-mail,foobar 83 | -------------------------------------------------------------------------------- /src/mdconvert.1: -------------------------------------------------------------------------------- 1 | .ig 2 | \" mdconvert - Maildir mailbox UID storage scheme converter 3 | \" Copyright (C) 2004 Oswald Buddenhagen 4 | \" 5 | \" This program is free software; you can redistribute it and/or modify 6 | \" it under the terms of the GNU General Public License as published by 7 | \" the Free Software Foundation; either version 2 of the License, or 8 | \" (at your option) any later version. 9 | \" 10 | \" This program is distributed in the hope that it will be useful, 11 | \" but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | \" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | \" GNU General Public License for more details. 14 | \" 15 | \" You should have received a copy of the GNU General Public License 16 | \" along with this program; if not, write to the Free Software Foundation, 17 | \" Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 18 | .. 19 | .TH mdconvert 1 "2004 Mar 27" 20 | .. 21 | .SH NAME 22 | mdconvert - Maildir mailbox UID storage scheme converter 23 | .. 24 | .SH SYNOPSIS 25 | \fBmdconvert\fR [\fIoptions\fR ...] \fImailbox\fR ... 26 | .. 27 | .SH DESCRIPTION 28 | \fBmdconvert\fR converts Maildir mailboxes between the two UID storage schemes 29 | supported by \fBmbsync\fR. See \fBmbsync\fR's manual page for details on these 30 | schemes. 31 | .. 32 | .SH OPTIONS 33 | .TP 34 | \fB-a\fR, \fB--alt\fR 35 | Convert to the \fBalternative\fR (Berkeley DB based) UID storage scheme. 36 | .TP 37 | \fB-n\fR, \fB--native\fR 38 | Convert to the \fBnative\fR (file name based) UID storage scheme. 39 | This is the default. 40 | .TP 41 | \fB-h\fR, \fB--help\fR 42 | Displays a summary of command line options. 43 | .TP 44 | \fB-v\fR, \fB--version\fR 45 | Displays version information. 46 | .. 47 | .SH SEE ALSO 48 | mbsync(1) 49 | .. 50 | .SH AUTHOR 51 | Written and maintained by Oswald Buddenhagen . 52 | -------------------------------------------------------------------------------- /src/mdconvert.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mdconvert - Maildir UID scheme converter 3 | * Copyright (C) 2004 Oswald Buddenhagen 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 18 | */ 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | #define EXE "mdconvert" 37 | 38 | static int 39 | nfsnprintf( char *buf, int blen, const char *fmt, ... ) 40 | { 41 | int ret; 42 | va_list va; 43 | 44 | va_start( va, fmt ); 45 | if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen) { 46 | fputs( "Fatal: buffer too small. Please report a bug.\n", stderr ); 47 | abort(); 48 | } 49 | va_end( va ); 50 | return ret; 51 | } 52 | 53 | static const char *subdirs[] = { "cur", "new" }; 54 | static struct flock lck; 55 | static DBT key, value; 56 | 57 | static inline int 58 | convert( const char *box, int altmap ) 59 | { 60 | DB *db; 61 | DIR *d; 62 | struct dirent *e; 63 | const char *u, *ru; 64 | char *p, *s, *dpath, *spath, *dbpath; 65 | int i, n, ret, sfd, dfd, bl, ml, uv[2], uid; 66 | struct stat st; 67 | char buf[_POSIX_PATH_MAX], buf2[_POSIX_PATH_MAX]; 68 | char umpath[_POSIX_PATH_MAX], uvpath[_POSIX_PATH_MAX], tdpath[_POSIX_PATH_MAX]; 69 | 70 | if (stat( box, &st ) || !S_ISDIR(st.st_mode)) { 71 | fprintf( stderr, "'%s' is no Maildir mailbox.\n", box ); 72 | return 1; 73 | } 74 | 75 | nfsnprintf( umpath, sizeof(umpath), "%s/.isyncuidmap.db", box ); 76 | nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", box ); 77 | if (altmap) 78 | dpath = umpath, spath = uvpath, dbpath = tdpath; 79 | else 80 | spath = umpath, dpath = uvpath, dbpath = umpath; 81 | nfsnprintf( tdpath, sizeof(tdpath), "%s.tmp", dpath ); 82 | if ((sfd = open( spath, O_RDWR )) < 0) { 83 | if (errno != ENOENT) 84 | perror( spath ); 85 | return 1; 86 | } 87 | if (fcntl( sfd, F_SETLKW, &lck )) { 88 | perror( spath ); 89 | goto sbork; 90 | } 91 | if ((dfd = open( tdpath, O_RDWR|O_CREAT, 0600 )) < 0) { 92 | perror( tdpath ); 93 | goto sbork; 94 | } 95 | if (db_create( &db, 0, 0 )) { 96 | fputs( "Error: db_create() failed\n", stderr ); 97 | goto tbork; 98 | } 99 | if ((ret = (db->open)( db, 0, dbpath, 0, DB_HASH, DB_CREATE, 0 ))) { 100 | db->err( db, ret, "Error: db->open(%s)", dbpath ); 101 | dbork: 102 | db->close( db, 0 ); 103 | tbork: 104 | unlink( tdpath ); 105 | close( dfd ); 106 | sbork: 107 | close( sfd ); 108 | return 1; 109 | } 110 | key.data = (void *)"UIDVALIDITY"; 111 | key.size = 11; 112 | if (altmap) { 113 | if ((n = read( sfd, buf, sizeof(buf) )) <= 0 || 114 | (buf[n] = 0, sscanf( buf, "%d\n%d", &uv[0], &uv[1] ) != 2)) 115 | { 116 | fprintf( stderr, "Error: cannot read UIDVALIDITY of '%s'.\n", box ); 117 | goto dbork; 118 | } 119 | value.data = uv; 120 | value.size = sizeof(uv); 121 | if ((ret = db->put( db, 0, &key, &value, 0 ))) { 122 | db->err( db, ret, "Error: cannot write UIDVALIDITY for '%s'", box ); 123 | goto dbork; 124 | } 125 | } else { 126 | if ((ret = db->get( db, 0, &key, &value, 0 ))) { 127 | db->err( db, ret, "Error: cannot read UIDVALIDITY of '%s'", box ); 128 | goto dbork; 129 | } 130 | n = sprintf( buf, "%d\n%d\n", ((int *)value.data)[0], ((int *)value.data)[1] ); 131 | if (write( dfd, buf, n ) != n) { 132 | fprintf( stderr, "Error: cannot write UIDVALIDITY for '%s'.\n", box ); 133 | goto dbork; 134 | } 135 | } 136 | 137 | again: 138 | for (i = 0; i < 2; i++) { 139 | bl = nfsnprintf( buf, sizeof(buf), "%s/%s/", box, subdirs[i] ); 140 | if (!(d = opendir( buf ))) { 141 | perror( "opendir" ); 142 | goto dbork; 143 | } 144 | while ((e = readdir( d ))) { 145 | if (*e->d_name == '.') 146 | continue; 147 | nfsnprintf( buf + bl, sizeof(buf) - bl, "%s", e->d_name ); 148 | memcpy( buf2, buf, bl ); 149 | p = strstr( e->d_name, ",U=" ); 150 | if (p) 151 | for (u = p, ru = p + 3; isdigit( (unsigned char)*ru ); ru++); 152 | else 153 | u = ru = strchr( e->d_name, ':' ); 154 | if (u) 155 | ml = u - e->d_name; 156 | else 157 | ru = "", ml = sizeof(buf); 158 | if (altmap) { 159 | if (!p) 160 | continue; 161 | key.data = e->d_name; 162 | key.size = (size_t)(strchr( e->d_name, ',' ) - e->d_name); 163 | uid = atoi( p + 3 ); 164 | value.data = &uid; 165 | value.size = sizeof(uid); 166 | if ((ret = db->put( db, 0, &key, &value, 0 ))) { 167 | db->err( db, ret, "Error: cannot write UID for '%s'", box ); 168 | goto ebork; 169 | } 170 | nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s%s", ml, e->d_name, ru ); 171 | } else { 172 | s = strpbrk( e->d_name, ",:" ); 173 | key.data = e->d_name; 174 | key.size = s ? (size_t)(s - e->d_name) : strlen( e->d_name ); 175 | if ((ret = db->get( db, 0, &key, &value, 0 ))) { 176 | if (ret != DB_NOTFOUND) { 177 | db->err( db, ret, "Error: cannot read UID for '%s'", box ); 178 | goto ebork; 179 | } 180 | continue; 181 | } 182 | uid = *(int *)value.data; 183 | nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s,U=%d%s", ml, e->d_name, uid, ru ); 184 | } 185 | if (rename( buf, buf2 )) { 186 | if (errno == ENOENT) 187 | goto again; 188 | perror( buf ); 189 | ebork: 190 | closedir( d ); 191 | goto dbork; 192 | } 193 | 194 | } 195 | closedir( d ); 196 | } 197 | 198 | db->close( db, 0 ); 199 | close( dfd ); 200 | if (rename( tdpath, dpath )) { 201 | perror( dpath ); 202 | return 1; 203 | } 204 | if (unlink( spath )) 205 | perror( spath ); 206 | close( sfd ); 207 | return 0; 208 | } 209 | 210 | int 211 | main( int argc, char **argv ) 212 | { 213 | int oint, ret, altmap = 0; 214 | 215 | for (oint = 1; oint < argc; oint++) { 216 | if (!strcmp( argv[oint], "-h" ) || !strcmp( argv[oint], "--help" )) { 217 | puts( 218 | "Usage: " EXE " [-a] mailbox...\n" 219 | " -a, --alt convert to alternative (DB based) UID scheme\n" 220 | " -n, --native convert to native (file name based) UID scheme (default)\n" 221 | " -h, --help show this help message\n" 222 | " -v, --version display version" 223 | ); 224 | return 0; 225 | } else if (!strcmp( argv[oint], "-v" ) || !strcmp( argv[oint], "--version" )) { 226 | puts( EXE " " VERSION " - Maildir UID scheme converter" ); 227 | return 0; 228 | } else if (!strcmp( argv[oint], "-a" ) || !strcmp( argv[oint], "--alt" )) { 229 | altmap = 1; 230 | } else if (!strcmp( argv[oint], "-n" ) || !strcmp( argv[oint], "--native" )) { 231 | altmap = 0; 232 | } else if (!strcmp( argv[oint], "--" )) { 233 | oint++; 234 | break; 235 | } else if (argv[oint][0] == '-') { 236 | fprintf( stderr, "Unrecognized option '%s'. Try " EXE " -h\n", argv[oint] ); 237 | return 1; 238 | } else 239 | break; 240 | } 241 | if (oint == argc) { 242 | fprintf( stderr, "Mailbox specification missing. Try " EXE " -h\n" ); 243 | return 1; 244 | } 245 | #if SEEK_SET != 0 246 | lck.l_whence = SEEK_SET; 247 | #endif 248 | #if F_WRLCK != 0 249 | lck.l_type = F_WRLCK; 250 | #endif 251 | ret = 0; 252 | for (; oint < argc; oint++) 253 | ret |= convert( argv[oint], altmap ); 254 | return ret; 255 | } 256 | 257 | -------------------------------------------------------------------------------- /src/run-tests.pl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl -w 2 | # 3 | # Copyright (C) 2006 Oswald Buddenhagen 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 18 | # 19 | 20 | use strict; 21 | use File::Path; 22 | 23 | -d "tmp" or mkdir "tmp"; 24 | chdir "tmp" or die "Cannot enter temp direcory.\n"; 25 | 26 | sub show($$@); 27 | sub test($$); 28 | 29 | ################################################################################ 30 | 31 | # generic syncing tests 32 | my @x01 = ( 33 | [ 8, 34 | 1, 1, "F", 2, 2, "", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "F", 7, 7, "FT", 9, 0, "" ], 35 | [ 8, 36 | 1, 1, "", 2, 2, "F", 3, 3, "F", 4, 4, "", 5, 5, "", 7, 7, "", 8, 8, "", 10, 0, "" ], 37 | [ 8, 0, 0, 38 | 1, 1, "", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "", 6, 6, "", 7, 7, "", 8, 8, "" ], 39 | ); 40 | 41 | #show("01", "01", "", "", ""); 42 | my @X01 = ( 43 | [ "", "", "" ], 44 | [ 10, 45 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "FT", 7, 7, "FT", 9, 9, "", 10, 10, "" ], 46 | [ 10, 47 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 7, 7, "FT", 8, 8, "T", 9, 10, "", 10, 9, "" ], 48 | [ 9, 0, 9, 49 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 0, "", 7, 7, "FT", 0, 8, "", 10, 9, "", 9, 10, "" ], 50 | ); 51 | test(\@x01, \@X01); 52 | 53 | #show("01", "02", "", "", "Expunge Both\n"); 54 | my @X02 = ( 55 | [ "", "", "Expunge Both\n" ], 56 | [ 10, 57 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 9, 9, "", 10, 10, "" ], 58 | [ 10, 59 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 9, 10, "", 10, 9, "" ], 60 | [ 9, 0, 9, 61 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 10, 9, "", 9, 10, "" ], 62 | ); 63 | test(\@x01, \@X02); 64 | 65 | #show("01", "03", "", "", "Expunge Slave\n"); 66 | my @X03 = ( 67 | [ "", "", "Expunge Slave\n" ], 68 | [ 10, 69 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "FT", 7, 7, "FT", 9, 9, "", 10, 10, "" ], 70 | [ 10, 71 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 9, 10, "", 10, 9, "" ], 72 | [ 9, 0, 9, 73 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 0, "T", 6, 0, "", 7, 0, "T", 10, 9, "", 9, 10, "" ], 74 | ); 75 | test(\@x01, \@X03); 76 | 77 | #show("01", "04", "", "", "Sync Pull\n"); 78 | my @X04 = ( 79 | [ "", "", "Sync Pull\n" ], 80 | [ 9, 81 | 1, 1, "F", 2, 2, "", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "F", 7, 7, "FT", 9, 9, "" ], 82 | [ 9, 83 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 7, 7, "FT", 8, 8, "T", 9, 9, "", 10, 0, "" ], 84 | [ 9, 0, 0, 85 | 1, 1, "F", 2, 2, "", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "", 7, 7, "FT", 0, 8, "", 9, 9, "" ], 86 | ); 87 | test(\@x01, \@X04); 88 | 89 | #show("01", "05", "", "", "Sync Flags\n"); 90 | my @X05 = ( 91 | [ "", "", "Sync Flags\n" ], 92 | [ 8, 93 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "F", 7, 7, "FT", 9, 0, "" ], 94 | [ 8, 95 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 7, 7, "FT", 8, 8, "", 10, 0, "" ], 96 | [ 8, 0, 0, 97 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "", 7, 7, "FT", 8, 8, "" ], 98 | ); 99 | test(\@x01, \@X05); 100 | 101 | #show("01", "06", "", "", "Sync Delete\n"); 102 | my @X06 = ( 103 | [ "", "", "Sync Delete\n" ], 104 | [ 8, 105 | 1, 1, "F", 2, 2, "", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "FT", 7, 7, "FT", 9, 0, "" ], 106 | [ 8, 107 | 1, 1, "", 2, 2, "F", 3, 3, "F", 4, 4, "", 5, 5, "", 7, 7, "", 8, 8, "T", 10, 0, "" ], 108 | [ 8, 0, 0, 109 | 1, 1, "", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "", 6, 0, "", 7, 7, "", 0, 8, "" ], 110 | ); 111 | test(\@x01, \@X06); 112 | 113 | #show("01", "07", "", "", "Sync New\n"); 114 | my @X07 = ( 115 | [ "", "", "Sync New\n" ], 116 | [ 10, 117 | 1, 1, "F", 2, 2, "", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "F", 7, 7, "FT", 9, 9, "", 10, 10, "" ], 118 | [ 10, 119 | 1, 1, "", 2, 2, "F", 3, 3, "F", 4, 4, "", 5, 5, "", 7, 7, "", 8, 8, "", 9, 10, "", 10, 9, "" ], 120 | [ 9, 0, 9, 121 | 1, 1, "", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "", 6, 6, "", 7, 7, "", 8, 8, "", 10, 9, "", 9, 10, "" ], 122 | ); 123 | test(\@x01, \@X07); 124 | 125 | #show("01", "08", "", "", "Sync PushFlags PullDelete\n"); 126 | my @X08 = ( 127 | [ "", "", "Sync PushFlags PullDelete\n" ], 128 | [ 8, 129 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "F", 7, 7, "FT", 9, 0, "" ], 130 | [ 8, 131 | 1, 1, "", 2, 2, "F", 3, 3, "F", 4, 4, "", 5, 5, "", 7, 7, "", 8, 8, "T", 10, 0, "" ], 132 | [ 8, 0, 0, 133 | 1, 1, "", 2, 2, "F", 3, 3, "F", 4, 4, "", 5, 5, "", 6, 6, "", 7, 7, "", 0, 8, "" ], 134 | ); 135 | test(\@x01, \@X08); 136 | 137 | # size restriction tests 138 | 139 | my @x10 = ( 140 | [ 0, 141 | 1, 0, "", 2, 0, "*" ], 142 | [ 0, 143 | 3, 0, "*" ], 144 | [ 0, 0, 0, 145 | ], 146 | ); 147 | 148 | #show("10", "11", "MaxSize 1k\n", "MaxSize 1k\n", ""); 149 | my @X11 = ( 150 | [ "MaxSize 1k\n", "MaxSize 1k\n", "" ], 151 | [ 2, 152 | 1, 1, "", 2, 2, "*" ], 153 | [ 2, 154 | 3, 1, "*", 1, 2, "" ], 155 | [ 2, 0, 1, 156 | -1, 1, "", 1, 2, "", 2, -1, "" ], 157 | ); 158 | test(\@x10, \@X11); 159 | 160 | my @x20 = @X11[1,2,3]; 161 | 162 | #show("20", "11", "MaxSize 1k\n", "MaxSize 1k\n", ""); # sic! - 11 163 | test(\@x20, \@X11); 164 | 165 | #show("20", "22", "", "MaxSize 1k\n", ""); 166 | my @X22 = ( 167 | [ "", "MaxSize 1k\n", "" ], 168 | [ 3, 169 | 1, 1, "", 2, 2, "*", 3, 3, "*" ], 170 | [ 2, 171 | 3, 1, "*", 1, 2, "" ], 172 | [ 2, 0, 1, 173 | 3, 1, "", 1, 2, "", 2, -1, "" ], 174 | ); 175 | test(\@x20, \@X22); 176 | 177 | # expiration tests 178 | 179 | my @x30 = ( 180 | [ 0, 181 | 1, 0, "F", 2, 0, "", 3, 0, "", 4, 0, "", 5, 0, "" ], 182 | [ 0, 183 | ], 184 | [ 0, 0, 0, 185 | ], 186 | ); 187 | 188 | #show("30", "31", "", "", "MaxMessages 3\n"); 189 | my @X31 = ( 190 | [ "", "", "MaxMessages 3\n" ], 191 | [ 5, 192 | 1, 1, "F", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "" ], 193 | [ 5, 194 | 1, 1, "F", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "" ], 195 | [ 5, 0, 0, 196 | 1, 1, "F", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "" ], 197 | ); 198 | test(\@x30, \@X31); 199 | 200 | my @x40 = @X31[1,2,3]; 201 | 202 | #show("40", "41", "", "", "MaxMessages 3\nExpunge Both\n"); 203 | my @X41 = ( 204 | [ "", "", "MaxMessages 3\nExpunge Both\n" ], 205 | [ 5, 206 | 1, 1, "F", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "" ], 207 | [ 5, 208 | 1, 1, "F", 3, 3, "", 4, 4, "", 5, 5, "" ], 209 | [ 5, 2, 0, 210 | 1, 1, "F", 3, 3, "", 4, 4, "", 5, 5, "" ], 211 | ); 212 | test(\@x40, \@X41); 213 | 214 | my @x50 = ( 215 | [ 5, 216 | 1, 1, "F", 2, 2, "F", 3, 3, "", 4, 4, "", 5, 5, "" ], 217 | [ 5, 218 | 1, 1, " ", 2, 2, "T", 3, 3, "", 4, 4, "", 5, 5, "" ], 219 | [ 5, 2, 0, 220 | 1, 1, "F", 2, 2, "X", 3, 3, "", 4, 4, "", 5, 5, "" ], 221 | ); 222 | 223 | #show("50", "51", "", "", "MaxMessages 3\nExpunge Both\n"); 224 | my @X51 = ( 225 | [ "", "", "MaxMessages 3\nExpunge Both\n" ], 226 | [ 5, 227 | 1, 1, "", 2, 2, "F", 3, 3, "", 4, 4, "", 5, 5, "" ], 228 | [ 5, 229 | 2, 2, "F", 3, 3, "", 4, 4, "", 5, 5, "" ], 230 | [ 5, 2, 0, 231 | 2, 2, "F", 3, 3, "", 4, 4, "", 5, 5, "" ], 232 | ); 233 | test(\@x50, \@X51); 234 | 235 | 236 | ################################################################################ 237 | 238 | chdir ".."; 239 | rmdir "tmp"; 240 | print "OK.\n"; 241 | exit 0; 242 | 243 | 244 | sub qm($) 245 | { 246 | shift; 247 | s/\\/\\\\/g; 248 | s/\"/\\"/g; 249 | s/\"/\\"/g; 250 | s/\n/\\n/g; 251 | return $_; 252 | } 253 | 254 | # $global, $master, $slave 255 | sub writecfg($$$) 256 | { 257 | open(FILE, ">", ".mbsyncrc") or 258 | die "Cannot open .mbsyncrc.\n"; 259 | print FILE 260 | "MaildirStore master 261 | Path ./ 262 | Inbox ./master 263 | ".shift()." 264 | MaildirStore slave 265 | Path ./ 266 | Inbox ./slave 267 | ".shift()." 268 | Channel test 269 | Master :master: 270 | Slave :slave: 271 | SyncState * 272 | ".shift(); 273 | close FILE; 274 | } 275 | 276 | sub killcfg() 277 | { 278 | unlink ".mbsyncrc"; 279 | } 280 | 281 | # $options 282 | sub runsync($) 283 | { 284 | # open FILE, "valgrind -q --log-fd=3 ../mbsync ".shift()." -c .mbsyncrc test 3>&2 2>&1 |"; 285 | open FILE, "../mbsync -D ".shift()." -c .mbsyncrc test 2>&1 |"; 286 | my @out = ; 287 | close FILE or push(@out, $! ? "*** error closing mbsync: $!\n" : "*** mbsync exited with signal ".($?&127).", code ".($?>>8)."\n"); 288 | return $?, @out; 289 | } 290 | 291 | 292 | # $path 293 | sub readbox($) 294 | { 295 | my $bn = shift; 296 | 297 | (-d $bn) or 298 | die "No mailbox '$bn'.\n"; 299 | (-d $bn."/tmp" and -d $bn."/new" and -d $bn."/cur") or 300 | die "Invalid mailbox '$bn'.\n"; 301 | open(FILE, "<", $bn."/.uidvalidity") or die "Cannot read UID validity of mailbox '$bn'.\n"; 302 | my $dummy = ; 303 | chomp(my $mu = ); 304 | close FILE; 305 | my %ms = (); 306 | for my $d ("cur", "new") { 307 | opendir(DIR, $bn."/".$d) or next; 308 | for my $f (grep(!/^\.\.?$/, readdir(DIR))) { 309 | my ($uid, $flg, $num); 310 | if ($f =~ /^\d+\.\d+_\d+\.[-[:alnum:]]+,U=(\d+):2,(.*)$/) { 311 | ($uid, $flg) = ($1, $2); 312 | } elsif ($f =~ /^\d+\.\d+_(\d+)\.[-[:alnum:]]+:2,(.*)$/) { 313 | ($uid, $flg) = (0, $2); 314 | } else { 315 | print STDERR "unrecognided file name '$f' in '$bn'.\n"; 316 | exit 1; 317 | } 318 | open(FILE, "<", $bn."/".$d."/".$f) or die "Cannot read message '$f' in '$bn'.\n"; 319 | my $sz = 0; 320 | while () { 321 | /^Subject: (\d+)$/ && ($num = $1); 322 | $sz += length($_); 323 | } 324 | close FILE; 325 | if (!defined($num)) { 326 | print STDERR "message '$f' in '$bn' has no identifier.\n"; 327 | exit 1; 328 | } 329 | @{ $ms{$num} } = ($uid, $flg.($sz>1000?"*":"")); 330 | } 331 | } 332 | return ($mu, %ms); 333 | } 334 | 335 | # $boxname 336 | sub showbox($) 337 | { 338 | my ($bn) = @_; 339 | 340 | my ($mu, %ms) = readbox($bn); 341 | print " [ $mu,\n "; 342 | my $frst = 1; 343 | for my $num (sort {my ($ca, $cb) = ($ms{$a}[0], $ms{$b}[0]); ($ca?$ca:$a+1000) <=> ($cb?$cb:$b+1000)} keys %ms) { 344 | if ($frst) { 345 | $frst = 0; 346 | } else { 347 | print ", "; 348 | } 349 | print "$num, $ms{$num}[0], \"$ms{$num}[1]\""; 350 | } 351 | print " ],\n"; 352 | } 353 | 354 | # $filename 355 | sub showstate($) 356 | { 357 | my ($fn) = @_; 358 | 359 | if (!open(FILE, "<", $fn)) { 360 | print STDERR " Cannot read sync state $fn: $!\n"; 361 | return; 362 | } 363 | $_ = ; 364 | if (!defined $_) { 365 | print STDERR " Missing sync state header.\n"; 366 | close FILE; 367 | return; 368 | } 369 | if (!/^1:(\d+) 1:(\d+):(\d+)\n$/) { 370 | print STDERR " Malformed sync state header '$_'.\n"; 371 | close FILE; 372 | return; 373 | } 374 | print " [ $1, $2, $3,\n "; 375 | my $frst = 1; 376 | for () { 377 | if ($frst) { 378 | $frst = 0; 379 | } else { 380 | print ", "; 381 | } 382 | if (!/^(-?\d+) (-?\d+) (.*)\n$/) { 383 | print "??, ??, \"??\""; 384 | } else { 385 | print "$1, $2, \"$3\""; 386 | } 387 | } 388 | print " ],\n"; 389 | close FILE; 390 | } 391 | 392 | # $filename 393 | sub showchan($) 394 | { 395 | my ($fn) = @_; 396 | 397 | showbox("master"); 398 | showbox("slave"); 399 | showstate($fn); 400 | } 401 | 402 | sub show($$@) 403 | { 404 | my ($sx, $tx, @sfx) = @_; 405 | my @sp; 406 | eval "\@sp = \@x$sx"; 407 | mkchan($sp[0], $sp[1], @{ $sp[2] }); 408 | print "my \@x$sx = (\n"; 409 | showchan("slave/.mbsyncstate"); 410 | print ");\n"; 411 | &writecfg(@sfx); 412 | runsync(""); 413 | killcfg(); 414 | print "my \@X$tx = (\n"; 415 | print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ],\n"; 416 | showchan("slave/.mbsyncstate"); 417 | print ");\n"; 418 | print "test(\\\@x$sx, \\\@X$tx);\n\n"; 419 | rmtree "slave"; 420 | rmtree "master"; 421 | } 422 | 423 | # $boxname, $maxuid, @msgs 424 | sub mkbox($$@) 425 | { 426 | my ($bn, $mu, @ms) = @_; 427 | 428 | rmtree($bn); 429 | (mkdir($bn) and mkdir($bn."/tmp") and mkdir($bn."/new") and mkdir($bn."/cur")) or 430 | die "Cannot create mailbox $bn.\n"; 431 | open(FILE, ">", $bn."/.uidvalidity") or die "Cannot create UID validity for mailbox $bn.\n"; 432 | print FILE "1\n$mu\n"; 433 | close FILE; 434 | while (@ms) { 435 | my ($num, $uid, $flg) = (shift @ms, shift @ms, shift @ms); 436 | if ($uid) { 437 | $uid = ",U=".$uid; 438 | } else { 439 | $uid = ""; 440 | } 441 | my $big = $flg =~ s/\*//; 442 | open(FILE, ">", $bn."/cur/0.1_".$num.".local".$uid.":2,".$flg) or 443 | die "Cannot create message $num in mailbox $bn.\n"; 444 | print FILE "From: foo\nTo: bar\nDate: Thu, 1 Jan 1970 00:00:00 +0000\nSubject: $num\n\n".(("A"x50)."\n")x($big*30); 445 | close FILE; 446 | } 447 | } 448 | 449 | # \@master, \@slave, @syncstate 450 | sub mkchan($$@) 451 | { 452 | my ($m, $s, @t) = @_; 453 | &mkbox("master", @{ $m }); 454 | &mkbox("slave", @{ $s }); 455 | open(FILE, ">", "slave/.mbsyncstate") or 456 | die "Cannot create sync state.\n"; 457 | print FILE "1:".shift(@t)." 1:".shift(@t).":".shift(@t)."\n"; 458 | while (@t) { 459 | print FILE shift(@t)." ".shift(@t)." ".shift(@t)."\n"; 460 | } 461 | close FILE; 462 | } 463 | 464 | # $config, $boxname, $maxuid, @msgs 465 | sub ckbox($$$@) 466 | { 467 | my ($bn, $MU, @MS) = @_; 468 | 469 | my ($mu, %ms) = readbox($bn); 470 | if ($mu != $MU) { 471 | print STDERR "MAXUID mismatch for '$bn'.\n"; 472 | return 1; 473 | } 474 | while (@MS) { 475 | my ($num, $uid, $flg) = (shift @MS, shift @MS, shift @MS); 476 | if (!defined $ms{$num}) { 477 | print STDERR "No message $bn:$num.\n"; 478 | return 1; 479 | } 480 | if ($ms{$num}[0] ne $uid) { 481 | print STDERR "UID mismatch for $bn:$num.\n"; 482 | return 1; 483 | } 484 | if ($ms{$num}[1] ne $flg) { 485 | print STDERR "Flag mismatch for $bn:$num.\n"; 486 | return 1; 487 | } 488 | delete $ms{$num}; 489 | } 490 | if (%ms) { 491 | print STDERR "Excess messages in '$bn': ".join(", ", sort({$a <=> $b } keys(%ms))).".\n"; 492 | return 1; 493 | } 494 | return 0; 495 | } 496 | 497 | # $filename, @syncstate 498 | sub ckstate($@) 499 | { 500 | my ($fn, @T) = @_; 501 | open(FILE, "<", $fn) or die "Cannot read sync state $fn.\n"; 502 | my $l = ; 503 | chomp(my @ls = ); 504 | close FILE; 505 | if (!defined $l) { 506 | print STDERR "Sync state header missing.\n"; 507 | return 1; 508 | } 509 | chomp($l); 510 | my $xl = "1:".shift(@T)." 1:".shift(@T).":".shift(@T); 511 | if ($l ne $xl) { 512 | print STDERR "Sync state header mismatch: '$l' instead of '$xl'.\n"; 513 | return 1; 514 | } else { 515 | for $l (@ls) { 516 | $xl = shift(@T)." ".shift(@T)." ".shift(@T); 517 | if ($l ne $xl) { 518 | print STDERR "Sync state entry mismatch: '$l' instead of '$xl'.\n"; 519 | return 1; 520 | } 521 | } 522 | } 523 | return 0; 524 | } 525 | 526 | # \@master, \@slave, @syncstate 527 | sub ckchan($$@) 528 | { 529 | my ($M, $S, @T) = @_; 530 | my $rslt = ckstate("slave/.mbsyncstate.new", @T); 531 | $rslt |= &ckbox("master", @{ $M }); 532 | $rslt |= &ckbox("slave", @{ $S }); 533 | return $rslt; 534 | } 535 | 536 | sub printbox($$@) 537 | { 538 | my ($bn, $mu, @ms) = @_; 539 | 540 | print " [ $mu,\n "; 541 | my $frst = 1; 542 | while (@ms) { 543 | if ($frst) { 544 | $frst = 0; 545 | } else { 546 | print ", "; 547 | } 548 | print shift(@ms).", ".shift(@ms).", \"".shift(@ms)."\""; 549 | } 550 | print " ],\n"; 551 | } 552 | 553 | # @syncstate 554 | sub printstate(@) 555 | { 556 | my (@t) = @_; 557 | 558 | print " [ ".shift(@t).", ".shift(@t).", ".shift(@t).",\n "; 559 | my $frst = 1; 560 | while (@t) { 561 | if ($frst) { 562 | $frst = 0; 563 | } else { 564 | print ", "; 565 | } 566 | print shift(@t).", ".shift(@t).", \"".shift(@t)."\""; 567 | } 568 | print " ],\n"; 569 | close FILE; 570 | } 571 | 572 | # \@master, \@slave, @syncstate 573 | sub printchan($$@) 574 | { 575 | my ($m, $s, @t) = @_; 576 | 577 | &printbox("master", @{ $m }); 578 | &printbox("slave", @{ $s }); 579 | printstate(@t); 580 | } 581 | 582 | sub test($$) 583 | { 584 | my ($sx, $tx) = @_; 585 | 586 | mkchan($$sx[0], $$sx[1], @{ $$sx[2] }); 587 | &writecfg(@{ $$tx[0] }); 588 | my ($xc, @ret) = runsync("-J"); 589 | if ($xc) { 590 | print "Input:\n"; 591 | printchan($$sx[0], $$sx[1], @{ $$sx[2] }); 592 | print "Options:\n"; 593 | print " [ ".join(", ", map('"'.qm($_).'"', @{ $$tx[0] }))." ]\n"; 594 | print "Expected result:\n"; 595 | printchan($$tx[1], $$tx[2], @{ $$tx[3] }); 596 | print "Debug output:\n"; 597 | print @ret; 598 | exit 1; 599 | } 600 | if (ckchan($$tx[1], $$tx[2], @{ $$tx[3] })) { 601 | print "Input:\n"; 602 | printchan($$sx[0], $$sx[1], @{ $$sx[2] }); 603 | print "Options:\n"; 604 | print " [ ".join(", ", map('"'.qm($_).'"', @{ $$tx[0] }))." ]\n"; 605 | print "Expected result:\n"; 606 | printchan($$tx[1], $$tx[2], @{ $$tx[3] }); 607 | print "Actual result:\n"; 608 | showchan("slave/.mbsyncstate.new"); 609 | print "Debug output:\n"; 610 | print @ret; 611 | exit 1; 612 | } 613 | open(FILE, "<", "slave/.mbsyncstate.journal") or 614 | die "Cannot read journal.\n"; 615 | my @nj = ; 616 | close FILE; 617 | @ret = runsync(""); 618 | killcfg(); 619 | if (ckstate("slave/.mbsyncstate", @{ $$tx[3] })) { 620 | print "Options:\n"; 621 | print " [ ".join(", ", map('"'.qm($_).'"', @{ $$tx[0] }))." ]\n"; 622 | print "Old State:\n"; 623 | printstate(@{ $$sx[2] }); 624 | print "Journal:\n".join("", @nj)."\n"; 625 | print "Expected New State:\n"; 626 | printstate(@{ $$tx[3] }); 627 | print "New State:\n"; 628 | showstate("slave/.mbsyncstate"); 629 | print "Debug output:\n"; 630 | print @ret; 631 | exit 1; 632 | } 633 | rmtree "slave"; 634 | rmtree "master"; 635 | } 636 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mbsync - mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2006 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 19 | * 20 | * As a special exception, mbsync may be linked with the OpenSSL library, 21 | * despite that library's more restrictive license. 22 | */ 23 | 24 | #include "isync.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | int DFlags, Ontty; 34 | static int need_nl; 35 | 36 | void 37 | debug( const char *msg, ... ) 38 | { 39 | va_list va; 40 | 41 | if (DFlags & DEBUG) { 42 | va_start( va, msg ); 43 | vprintf( msg, va ); 44 | va_end( va ); 45 | fflush( stdout ); 46 | need_nl = 0; 47 | } 48 | } 49 | 50 | void 51 | debugn( const char *msg, ... ) 52 | { 53 | va_list va; 54 | 55 | if (DFlags & DEBUG) { 56 | va_start( va, msg ); 57 | vprintf( msg, va ); 58 | va_end( va ); 59 | fflush( stdout ); 60 | need_nl = Ontty; 61 | } 62 | } 63 | 64 | void 65 | info( const char *msg, ... ) 66 | { 67 | va_list va; 68 | 69 | if (!(DFlags & QUIET)) { 70 | va_start( va, msg ); 71 | vprintf( msg, va ); 72 | va_end( va ); 73 | fflush( stdout ); 74 | need_nl = 0; 75 | } 76 | } 77 | 78 | void 79 | infon( const char *msg, ... ) 80 | { 81 | va_list va; 82 | 83 | if (!(DFlags & QUIET)) { 84 | va_start( va, msg ); 85 | vprintf( msg, va ); 86 | va_end( va ); 87 | fflush( stdout ); 88 | need_nl = Ontty; 89 | } 90 | } 91 | 92 | void 93 | warn( const char *msg, ... ) 94 | { 95 | va_list va; 96 | 97 | if (!(DFlags & VERYQUIET)) { 98 | if (need_nl) { 99 | putchar( '\n' ); 100 | need_nl = 0; 101 | } 102 | va_start( va, msg ); 103 | vfprintf( stderr, msg, va ); 104 | va_end( va ); 105 | } 106 | } 107 | 108 | void 109 | error( const char *msg, ... ) 110 | { 111 | va_list va; 112 | 113 | if (need_nl) { 114 | putchar( '\n' ); 115 | need_nl = 0; 116 | } 117 | va_start( va, msg ); 118 | vfprintf( stderr, msg, va ); 119 | va_end( va ); 120 | } 121 | 122 | char * 123 | next_arg( char **s ) 124 | { 125 | char *ret; 126 | 127 | if (!s || !*s) 128 | return 0; 129 | while (isspace( (unsigned char) **s )) 130 | (*s)++; 131 | if (!**s) { 132 | *s = 0; 133 | return 0; 134 | } 135 | if (**s == '"') { 136 | ++*s; 137 | ret = *s; 138 | *s = strchr( *s, '"' ); 139 | } else { 140 | ret = *s; 141 | while (**s && !isspace( (unsigned char) **s )) 142 | (*s)++; 143 | } 144 | if (*s) { 145 | if (**s) 146 | *(*s)++ = 0; 147 | if (!**s) 148 | *s = 0; 149 | } 150 | return ret; 151 | } 152 | 153 | void 154 | add_string_list( string_list_t **list, const char *str ) 155 | { 156 | string_list_t *elem; 157 | int len; 158 | 159 | len = strlen( str ); 160 | elem = nfmalloc( sizeof(*elem) + len ); 161 | elem->next = *list; 162 | *list = elem; 163 | memcpy( elem->string, str, len + 1 ); 164 | } 165 | 166 | void 167 | free_string_list( string_list_t *list ) 168 | { 169 | string_list_t *tlist; 170 | 171 | for (; list; list = tlist) { 172 | tlist = list->next; 173 | free( list ); 174 | } 175 | } 176 | 177 | void 178 | free_generic_messages( message_t *msgs ) 179 | { 180 | message_t *tmsg; 181 | 182 | for (; msgs; msgs = tmsg) { 183 | tmsg = msgs->next; 184 | free( msgs ); 185 | } 186 | } 187 | 188 | #ifndef HAVE_VASPRINTF 189 | static int 190 | vasprintf( char **strp, const char *fmt, va_list ap ) 191 | { 192 | int len; 193 | char tmp[1024]; 194 | 195 | if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = malloc( len + 1 ))) 196 | return -1; 197 | if (len >= (int)sizeof(tmp)) 198 | vsprintf( *strp, fmt, ap ); 199 | else 200 | memcpy( *strp, tmp, len + 1 ); 201 | return len; 202 | } 203 | #endif 204 | 205 | void 206 | oob( void ) 207 | { 208 | fputs( "Fatal: buffer too small. Please report a bug.\n", stderr ); 209 | abort(); 210 | } 211 | 212 | int 213 | nfsnprintf( char *buf, int blen, const char *fmt, ... ) 214 | { 215 | int ret; 216 | va_list va; 217 | 218 | va_start( va, fmt ); 219 | if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen) 220 | oob(); 221 | va_end( va ); 222 | return ret; 223 | } 224 | 225 | static void ATTR_NORETURN 226 | oom( void ) 227 | { 228 | fputs( "Fatal: Out of memory\n", stderr ); 229 | abort(); 230 | } 231 | 232 | void * 233 | nfmalloc( size_t sz ) 234 | { 235 | void *ret; 236 | 237 | if (!(ret = malloc( sz ))) 238 | oom(); 239 | return ret; 240 | } 241 | 242 | void * 243 | nfcalloc( size_t sz ) 244 | { 245 | void *ret; 246 | 247 | if (!(ret = calloc( sz, 1 ))) 248 | oom(); 249 | return ret; 250 | } 251 | 252 | void * 253 | nfrealloc( void *mem, size_t sz ) 254 | { 255 | char *ret; 256 | 257 | if (!(ret = realloc( mem, sz )) && sz) 258 | oom(); 259 | return ret; 260 | } 261 | 262 | char * 263 | nfstrdup( const char *str ) 264 | { 265 | char *ret; 266 | 267 | if (!(ret = strdup( str ))) 268 | oom(); 269 | return ret; 270 | } 271 | 272 | int 273 | nfvasprintf( char **str, const char *fmt, va_list va ) 274 | { 275 | int ret = vasprintf( str, fmt, va ); 276 | if (ret < 0) 277 | oom(); 278 | return ret; 279 | } 280 | 281 | int 282 | nfasprintf( char **str, const char *fmt, ... ) 283 | { 284 | int ret; 285 | va_list va; 286 | 287 | va_start( va, fmt ); 288 | ret = nfvasprintf( str, fmt, va ); 289 | va_end( va ); 290 | return ret; 291 | } 292 | 293 | /* 294 | static struct passwd * 295 | cur_user( void ) 296 | { 297 | char *p; 298 | struct passwd *pw; 299 | uid_t uid; 300 | 301 | uid = getuid(); 302 | if ((!(p = getenv("LOGNAME")) || !(pw = getpwnam( p )) || pw->pw_uid != uid) && 303 | (!(p = getenv("USER")) || !(pw = getpwnam( p )) || pw->pw_uid != uid) && 304 | !(pw = getpwuid( uid ))) 305 | { 306 | fputs ("Cannot determinate current user\n", stderr); 307 | return 0; 308 | } 309 | return pw; 310 | } 311 | */ 312 | 313 | static char * 314 | my_strndup( const char *s, size_t nchars ) 315 | { 316 | char *r = nfmalloc( nchars + 1 ); 317 | memcpy( r, s, nchars ); 318 | r[nchars] = 0; 319 | return r; 320 | } 321 | 322 | char * 323 | expand_strdup( const char *s ) 324 | { 325 | struct passwd *pw; 326 | const char *p, *q; 327 | char *r; 328 | 329 | if (*s == '~') { 330 | s++; 331 | if (!*s) { 332 | p = 0; 333 | q = Home; 334 | } else if (*s == '/') { 335 | p = s; 336 | q = Home; 337 | } else { 338 | if ((p = strchr( s, '/' ))) { 339 | r = my_strndup( s, (int)(p - s) ); 340 | pw = getpwnam( r ); 341 | free( r ); 342 | } else 343 | pw = getpwnam( s ); 344 | if (!pw) 345 | return 0; 346 | q = pw->pw_dir; 347 | } 348 | nfasprintf( &r, "%s%s", q, p ? p : "" ); 349 | return r; 350 | } else 351 | return nfstrdup( s ); 352 | } 353 | 354 | static int 355 | compare_ints( const void *l, const void *r ) 356 | { 357 | return *(int *)l - *(int *)r; 358 | } 359 | 360 | void 361 | sort_ints( int *arr, int len ) 362 | { 363 | qsort( arr, len, sizeof(int), compare_ints ); 364 | } 365 | 366 | 367 | static struct { 368 | unsigned char i, j, s[256]; 369 | } rs; 370 | 371 | void 372 | arc4_init( void ) 373 | { 374 | int i, fd; 375 | unsigned char j, si, dat[128]; 376 | 377 | if ((fd = open( "/dev/urandom", O_RDONLY )) < 0 && (fd = open( "/dev/random", O_RDONLY )) < 0) { 378 | error( "Fatal: no random number source available.\n" ); 379 | exit( 3 ); 380 | } 381 | if (read( fd, dat, 128 ) != 128) { 382 | error( "Fatal: cannot read random number source.\n" ); 383 | exit( 3 ); 384 | } 385 | close( fd ); 386 | 387 | for (i = 0; i < 256; i++) 388 | rs.s[i] = i; 389 | for (i = j = 0; i < 256; i++) { 390 | si = rs.s[i]; 391 | j += si + dat[i & 127]; 392 | rs.s[i] = rs.s[j]; 393 | rs.s[j] = si; 394 | } 395 | rs.i = rs.j = 0; 396 | 397 | for (i = 0; i < 256; i++) 398 | arc4_getbyte(); 399 | } 400 | 401 | unsigned char 402 | arc4_getbyte( void ) 403 | { 404 | unsigned char si, sj; 405 | 406 | rs.i++; 407 | si = rs.s[rs.i]; 408 | rs.j += si; 409 | sj = rs.s[rs.j]; 410 | rs.s[rs.i] = sj; 411 | rs.s[rs.j] = si; 412 | return rs.s[(si + sj) & 0xff]; 413 | } 414 | --------------------------------------------------------------------------------