diff --git a/ChangeLog b/ChangeLog index ece16a5..041d865 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,553 +1,553 @@ -LibPST 0.6.8 (2008-xx-xx) +LibPST 0.6.8 (2008-03-05) =============================== * Initial version of pst2dii to convert to Summation dii load file format. * Changes for Fedora packaging (#434727) LibPST 0.6.7 (2008-02-16) =============================== * Work around bogus 7c.b5 blocks in some messages that have been read. They appear to have attachments, but of some unknown format. Before the message was read, it did not have any attachments. * Use autoscan to cleanup our autoconf system. * Use autoconf to detect when we need to use our XGetopt files and other header files. * More fields, including BCC. * Fix missing LE32_CPU byte swapping for FILETIME types. LibPST 0.6.6 (2008-01-31) =============================== * More code cleanup, removing unnecessary null terminations on binary buffers. All pst file reads now go thru one function. Logging all pst reads to detect cases where we read the same data multiple times - discovers node sizes are actually 512 bytes. * Switch from cvs to mercurial source control. LibPST 0.6.5 (2008-01-22) =============================== * More code cleanup, removing obsolete code. All the boolean flags of type 0xb have length 4, so these are all 32 bits in the file. Libpst treats them all as 16 bits, but at least we are consistent. * More fields decoded - for example, see We should be able to use that data for much more complete decoding. * Move the rpm group to Applications/Productivity consistent with Evolution. LibPST 0.6.4 (2008-01-19) =============================== * More fixes for Outlook 2003 64 bit parsing. We observed cases of compressed RTF bodies (type 0x1009) with zero length. * Document type 0x0101 descriptor blocks and process them. * Fix large file support - we need to include config.h before any standard headers. * Merge following changes from svn snapshot from Alioth: * Add new fields to appointment for recurring events (SourceForge #304198) * Map IPM.Task items to PST_TYPE_TASK. * Applied patch to remove compiler warnings, thanks! (SourceForge #304314) * Fix crash with unknown reference type * Fix more memory issues detected by valgrind * lspst - add usage mesage and option parsing using getopt (SourceForge #304199) * Fix crash caused by invalid free calls * Fix crash when email subject is empty * Fix memory and information leak in hex debug dump LibPST 0.6.3 (2008-01-13) =============================== * More type consistency issues found by splint. LibPST 0.6.2 (2008-01-12) =============================== * More fixes for Outlook 2003 64 bit parsing. * All buffer sizes changed to size_t, all file offsets changed to off_t, all function names start with pst_, many other type consistency issues found by splint. Many changes to #llx in debug printing for 64 bit items. All id values are now uint64_t. LibPST 0.6.1 (2008-01-06) =============================== * Outlook 2003 64 bit parsing. Some documentation from Alexander Grau and patches from Sean Loaring . * fix from Antonio Palama for email items that happen to have item->contact non null, and were being processed as contacts. * Add large file support so we can read .pst files larger than 2gb. * Change lspst to be similar to readpst, properly using recursion to walk the tree, and testing item types. Add a man page for lspst. LibPST 0.5.12 (2007-10-02) =============================== * security fix from Brad Hards for buffer overruns in liv-zemple decoding for corrupted or malicious pst files. LibPST 0.5.11 (2007-08-24) =============================== * fix from Stevens Miller for unitialized variable. LibPST 0.5.10 (2007-08-20) =============================== * fix yet more valgrind errors - finally have a clean memory check. * restructure readpst.c for proper recursive tree walk. * buffer overrun test was backwards, introduced at 0.5.6 * fix broken email attachments, introduced at 0.5.6 LibPST 0.5.9 (2007-08-12) =============================== * fix more valgrind errors. LibPST 0.5.8 (2007-08-10) =============================== * fix more valgrind errors. lzfu_decompress needs to return the actual buffer size, since the lz header overestimates the size. This caused base64_encode to encode undefined bytes into the email attachment. LibPST 0.5.7 (2007-08-09) =============================== * fix valgrind errors, using uninitialized data. * improve debug logging and readpstlog for indented listings. * cleanup documentation. LibPST 0.5.6 (2007-07-15) =============================== * Fix to allow very small pst files with only one node in the tree. We were mixing signed/unsigned types in comparisons. * More progress decoding the basic structure 7c blocks. Many four byte values may be ID2 indices with data outside the buffer. * Start using doxygen to generate internal documentation. LibPST 0.5.5 (2007-07-10) =============================== * merge the following changes from Joe Nahmias version: * Lots of memory fixes. Thanks to Nigel Horne for his assistance tracking these down! * Fixed creation of vCards from contacts, thanks to Nigel Horne for his help with this! * fix for MIME multipart/alternative attachments. * added -c options to readpst manpage. * use 8.3 attachment filename if long filename isn't available. * new -b option to skip rtf-body.rtf attachments. * fix format of From header lines in mbox files. * Add more appointment fields, thanks to Chris Halls for tracking them down! LibPST 0.5.4 (2006-02-25) =============================== * patches from Arne, adding MH mode, remove leading zeros from the generated numbered filenames starting with one rather than zero. Miscellaneous code cleanup. * document the "7c" descriptor block format. LibPST 0.5.3 (2006-02-20) =============================== * switch to gnu autoconf/automake. This breaks the MS VC++ projects since the source code is now in the src subdirectory. * documentation switched to xml, building man pages and html from the master xml copy. * include rpm .spec file for building src and binary rpms. LibPST 0.5.2 (2006-02-18) =============================== * Added pst2ldif to convert the contacts to ldif format for import into ldap databases. * Major changes to libpst.c to properly use the node depth values from the b-tree nodes. We also use the item count values in the nodes rather than trying to guess how many items are active. * Cleanup whitespace - using tabs for every four columns. LibPST 0.5.1 (17 November 2004) =============================== Well, alot has happened since the last release of libpst. Release / Management: * The project has forked! The new maintainer is Joseph Nahmias. * We have changed hosting sites, thanks to sourceforge for hosting to this point. From this point forward we will be using alioth.debian.org. * The project is now using SubVersioN for source control. You can get the latest code by running: svn co svn://svn.debian.org/svn/libpst/trunk . * See for more information. Code Changes: * Added lspst program to list items in a PST. Still incomplete. * Added vim folding markers to readpst.c * avoid the pseudo-prologue that MS prepends to the email headers * fix build on msvc, since it doesn't have sys/param.h * Re-vamped Makefile: * Only define CFLAGS in Makefileif missing * fixed {un,}install targets in Makefile * Fixed up build process in Makefile * Added mozilla conversion script from David Binard * Fixed bogus creation of readpst.log on every invocation * escaped dashes and apostrophe in manpages * Updated TODO * added manpages from debian pkg * fix escaped-string length count to consider '\n', thanks to Paul Bakker . * ensure there's a blank line between header and body patch from (SourceForge #890745). * Apply accumulated endian-related patches * Removed unused files, upstream's debian/ dir -- Joe Nahmias LibPST v0.5 =========== It is with GREAT relief that I bring you version 0.5 of the LibPST tools! Through great difficulties, this tool has survived and expanded to become even better. The changes are as follows: * RTF support. We can now decompress RTF bodies in emails, and are saved as attachments * Better support in reading the indexes. Fixed many bugs with them * Improved reliability. "Now we are getting somewhere!" * Improved compiling. Hopefully we won't be hitting too many compile errors now. * vCard handling. Contacts are now exported as vCard entries. * vEvent handling. Support has begun on exporting Calendar entries as events * Support for Journal entries has also begun If you have any problems with this release, don't hesitate to contact me. These changes come to you, as always, free under the GPL license!! What a wonderful thing it is. It does mean that you can write your own program off of this library and distribute it also for free. However, anyone with commercial interests for developing applications they will be charging for are encouraged to get in touch with me, as I am sure we can come to some arrangement. Dave Smith LibPST v0.4.3 ============= Bug fix release. No extra functionality Dave Smith LibPST v0.4.2 ============= The debug system has had an overhaul. The debug messages are no longer printed to the screen when they are enabled. They are dumped to a binary file. There is another utility called "readlog" that I have written to handle these log files. It should make it easier to selectively view bits of a log file. It also shows the position that the log message was printed from. There is a new switch in readpst. It is -d. It enables the user to specify the log file which the binary log is written to. If the switch isn't used, the default file of "readpst.log" is used. The code is now Visual C++ compatible. It has compiled on Visual C++ .net Standard edition, and produces the readpst.exe file. Use the project file included in this distribution. There have been minor improvements elsewhere too. LibPST v0.4.1 ============= Fixed a couple more bugs. Is it me or do bugs just insert themselves in random, hard to find places! Cured a few problems with regard to emails with multiple embeded items. They are not fully re-created using Mime-types, but are accessible with the -S switch (which saves everything as seperate items) Fixed a problem reading the first index. Back sliders are now detected. (ie when the value following the current one is smaller, not bigger!) Added some error messages when we try and read outside of the PST file, this was causing a few problems before, cause the return value wasn't always checked, so it was possible to be reading random data, and trying to make sense of it! Anyway, if you find any problems, don't hesitate to mail me Dave Smith LibPST v0.4 =========== Fixed a nasty bug that occasionally corrupted attachments. Another bug with regard to reading of indexes (also occasional). Another output method has been added which is called "Seperate". It is activated with the -S switch. It operates in the following manor: |--Inbox-->000000 | 000001 | 000002 |--Sentmail-->0000000 | 0000001 | 0000002 All the emails are stored in seperate files counting from 0 upwards, in a folder named as the PST folder. When an email has an attachment, it is saved as a seperate file. The filename for the attachment is made up of 2 parts, the first is the email number to which it belongs, the second is its filename. The should now be runnable on big-endian machines, if the define.h file is first modified. The #define LITTLE_ENDIAN must be commented out, and the #define BIG_ENDIAN must be uncommented. More verbose error messages have been added. Apparently people got confused when the program stopped for no visible reason. This has now been resolved. Thanks for the continued support of all people involved. Dave Smith Libpst v0.3.4 ============= Several more fixes. An Infinite loop and incorrect interpreting of item index attributes. Work has started on making the code executable on big endian CPUs. At present it should work with Linux on these CPUs, but I would appreciate it if you could provide feedback with regard to it's performance. I am also working with some other people at make it operate on Solaris. A whole load more items are now recognized by the Item records. With more items in Emails and Folders. I haven't got to the Contacts yet. Anyway, this is what I would call a minor feature enhancment and bugfix release. Dave Smith LibPST v0.3.3 ============= Fixed several items. Mainly memory leaks. Loads of them! oops.. I have added a new program, mainly of debugging, which when passed an ID value and a pst file, will extract and decrypt that ID from the pst file. I don't see it being a huge attraction, or of much use to most people, but it is another example of writing an application to use the libpst interface. Another fix was in the reading of the item index. This has hopefully now been corrected. The result of this bug was that not all the emails in a folder were converted. Hopefully you should have more luck now. Dave Smith LibPST v0.3.2 ============= Quick bugfix release. There was a bug in the decryption of the basic encryption that outlook uses. One byte, 0x6c, was incorrectly decrypted to 0x6c instead of 0xcd. This release fixes this bug. Sorry... LibPST v0.3.1 ============= Minor improvements. Fixed bug when linking multiple blocks together, so now the linking blocks are not "encrypted" when trying to read them. LibPST v0.3 =========== A lot of bug fixing has been done for this release. Testing has been done on the creation of the files by readpst. Better handling of large binaries being extracted from the PST file has been implemented. Quite a few reports have come in about not being able to compile on Darwin. This could be down to using macros with variable parameter lists. This has now been changed to use C functions with variable parameters. I hope this fixes a lot of problems. Added support for recreating the folder structure into normal directories. For Instance: Personal Folders |-Inbox | |-Jokes | |-Meetings |-Send Items each folder containing an mbox file with the correct emails for that folder. Dave Smith LibPST v0.3 beta1 ================= Again, a shed load of enhancements. More work has been done on the mime creation. A bug has been fixed that was letting part of the attachments that were created disappear. A major enhancement is that "compressible encryption" support has been added. This was an incredibly simple method to use. It is basically a ceasar cipher. It has been noted by several users already that the PST password that Outlook uses, serves *no purpose*. It is not used to encrypt the PST, it is mearly stored there. This means that the readpst application is able to convert PST files without knowing the password. Microsoft have some explaning to do! Output files are now not overwritten if they already exist. This means that if you have two folders in your PST file named "fred", the first one encountered will be named "fred" and the second one will be named "fred00000001". As you can see, there is enough room there for many duplicate names! Output filenames are now restricted. Any "/" or "\" characters in the name are replaced with "_". If you find that there are any other characters that need to be changed, could you please make me aware! Thanks to Berry Wizard for help with supporting the encryption. Thanks to Auke Kok, Carolus Walraven and Yogesh Kumar Guatam for providing debugging information and testing. Dave Smith LibPST v0.2 beta1 ================= Hello once more... Attachments are now re-created in mime format. The method is very crude and could be prone to over generalisation. Please test this version, and if attachments are not recreated correctly, please send me the email (complete message source) of the original and converted. Cheers. I hope this will work for everyone who uses this program, but reality can be very different! Let us see how it goes... Dave Smith LibPST v0.2 alpha1 =========== Hello! Some improvements. The internal code has been changed so that attachments are now processed and loaded into the structures. The readpst program is not finished yet. It needs to convert these binary structs into mime data. At present it just saves them to the current directory, overwriting any previous files with the attachment name. Improvements over previous version: * KMail output is supported - if the "-k" flag is specified, all the directory hierarchy is created using the KMail standard * Lots of bugs and memory leaks fixed Usage: ReadPST v0.2alpha1 implementing LibPST v0.2alpha1 Usage: ./readpst [OPTIONS] {PST FILENAME} OPTIONS: -h - Help. This screen -k - KMail. Output in kmail format -o - Output Dir. Directory to write files to. CWD is changed *after* opening pst file -V - Version. Display program version If you want to view lots of debug output, modify a line in "define.h" from "//#define DEBUG_ALL" to "#define DEBUG_ALL". It would then be advisable to pipe all output to a log file: ./readpst -o out pst_file &> logfile Dave Smith LibPST v0.1 =========== Hi Folks! This has been a long, hard slog, but I now feel that I have got somewhere useful. The included program "main" is able to read an Outlook PST file and dump the emails into mbox files, separating each folder into a different mbox file. All the mbox files are stored in the current directory and no attempt is yet made to organise these files into a directory hierarchy. This would not be too difficult to achieve though. Email attachments are not yet handled, neither are Contacts. There is no pretty interface yet, but you can convert a PST file in the following manner ./main {path to PST file} This is very much a work in progress, but I thought I should release this code so that people can lose their conception that outlook files will never be converted to Linux. I am intending that the code I am writing will be developed into greater applications to provide USEFUL tools for accessing and converting PST files into a variety of formats. One point I feel I should make is that Outlook, by default, creates "Compressible Encryption" PST files. I have not, as yet, attempted to write any decryption routines, so you will not be able to convert these files. However, if you create a new PST file and choose not to make an encrypted one, you can copy all your emails into this new one and then convert the unencrypted one. I hope you enjoy, Dave Smith diff --git a/Makefile.am b/Makefile.am index 8d8df61..f1bee01 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,5 @@ SUBDIRS = src man html info +htmldir = ${datadir}/doc/@PACKAGE@-@VERSION@ +html_DATA = AUTHORS COPYING ChangeLog NEWS README CLEANFILES = xml/libpst xml/Makefile EXTRA_DIST = Doxyfile libpst.html.tar.gz libpst.spec $(wildcard xml/M*) $(wildcard xml/h*) $(wildcard xml/lib*) diff --git a/NEWS b/NEWS index 36d4c56..64a5710 100644 --- a/NEWS +++ b/NEWS @@ -1,19 +1,19 @@ -0.6.8 2008-02-26 Initial version of pst2dii to convert to Summation dii load file format. +0.6.8 2008-03-05 Initial version of pst2dii to convert to Summation dii load file format. 0.6.7 2008-02-16 Ignore unknown attachments on some read messages; autoconf cleanup. 0.6.6 2008-01-31 Code cleanup, switch from cvs to mercurial source control. 0.6.5 2008-01-22 Code cleanup, rpm group Applications/Productivity. 0.6.4 2008-01-19 More fixes for 64 bit format, merge changes from svn Alioth. 0.6.3 2008-01-13 More type consistency issues found by splint. 0.6.2 2008-01-12 More fixes for 64 bit format, consistent types size_t, off_t, etc. 0.6.1 2008-01-06 Outlook 2003 64 bit format and fix for bogus contacts. 0.5.12 2007-10-02 security fix for possible buffer overruns in liv-zemple decoding 0.5.11 2007-08-24 fix for unitialized variable 0.5.10 2007-08-20 fix yet more valgrind errors, restructure readpst recursive walk, backwards overrun test 0.5.9 2007-08-12 fix more valgrind errors, pst2ldif wrote undefined data 0.5.8 2007-08-10 lzfu_decompress/base64_encode encoded random data into attachment 0.5.7 2007-08-09 fix valgrind errors, using uninitialized data 0.5.6 2007-07-15 handle small pst files, better decoding of 7c blocks 0.5.5 2007-07-10 merge changes from Joe Nahmias version 0.5.4 2006-02-25 add MH mode, generated filenames with no leading zeros 0.5.3 2006-02-20 switch to gnu autoconf/automake 0.5.2 2006-02-18 add pst2ldif, fix btree processing in libpst.c diff --git a/configure.in b/configure.in index d00ccca..9c49ad6 100644 --- a/configure.in +++ b/configure.in @@ -1,85 +1,88 @@ AC_PREREQ(2.59) AC_INIT(libpst,0.6.8,carl@five-ten-sg.com) AC_CONFIG_SRCDIR([config.h.in]) AC_CONFIG_HEADER([config.h]) AM_INIT_AUTOMAKE($PACKAGE_NAME,$PACKAGE_VERSION) # Checks for programs. my_build_dii=yes AC_PATH_PROG(CONVERT, convert) -if test "x$CONVERT" = x ; then +if test "x$CONVERT" = "x" ; then AC_MSG_WARN([convert not found. pst2dii disabled]) my_build_dii=no fi AM_CONDITIONAL(BUILD_DII, test $my_build_dii = yes) # Checks for programs. AC_PROG_CXX AC_PROG_CC AC_PROG_CPP AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_RANLIB AC_SYS_LARGEFILE # Checks for header files. AC_CHECK_HEADER([unistd.h], AM_CONDITIONAL(NEED_XGETOPT, [test yes = no ]), AM_CONDITIONAL(NEED_XGETOPT, [test yes = yes]) ) AC_HEADER_DIRENT AC_HEADER_STDC AC_CHECK_HEADERS([fcntl.h limits.h malloc.h netinet/in.h stdint.h stdlib.h string.h sys/param.h wchar.h]) +if test "$my_build_dii" = "yes"; then + AC_CHECK_HEADERS([gd.h]) +fi # Checks for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL AC_C_CONST AC_TYPE_OFF_T AC_TYPE_SIZE_T AC_STRUCT_TM # Checks for library functions. AC_FUNC_LSTAT AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK AC_FUNC_MALLOC AC_FUNC_MKTIME AC_FUNC_REALLOC AC_FUNC_STRFTIME AC_FUNC_VPRINTF AC_CHECK_FUNCS([memchr memmove memset strcasecmp strchr strdup strerror strpbrk strrchr strstr strtol]) # The following lines adds the --enable-pst-debug option to configure: # # Give the user the choice to enter one of these: # --enable-pst-debug # --enable-pst-debug=yes # --enable-pst-debug=no # AC_MSG_CHECKING([whether we are forcing debug dump file creation]) AC_ARG_ENABLE(pst-debug, AC_HELP_STRING([--enable-pst-debug], [force debug dump file creation]), [if test "${enable_pst_debug}" = "no" ; then AC_MSG_RESULT([no]) else AC_DEFINE(DEBUG_ALL, 1, Define to 1 to force debug dump file creation) AC_MSG_RESULT([yes]) fi], # Default value for configure AC_MSG_RESULT([no]) ) AC_OUTPUT( \ Makefile \ libpst.spec \ html/Makefile \ info/Makefile \ man/Makefile \ src/Makefile \ src/version.h \ src/pst2dii.cpp \ xml/Makefile \ xml/libpst \ ) diff --git a/libpst.spec.in b/libpst.spec.in index df61979..4e8c45d 100644 --- a/libpst.spec.in +++ b/libpst.spec.in @@ -1,57 +1,59 @@ -%define localstatedir /var/lib - Summary: Utilities to convert Outlook .pst files to other formats Name: @PACKAGE@ Version: @VERSION@ Release: 1%{?dist} License: GPLv2+ Group: Applications/Productivity Source: http://www.five-ten-sg.com/%{name}/packages/%{name}-%{version}.tar.gz BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) URL: http://www.five-ten-sg.com/%{name}/ Requires: ImageMagick BuildRequires: ImageMagick freetype-devel gd-devel libjpeg-devel zlib-devel %description The Libpst utilities include readpst which can convert email messages to both mbox and MH mailbox formats, pst2ldif which can convert the contacts to .ldif format for import into ldap databases, and pst2dii which can convert email messages to the DII load file format used by Summation. %prep %setup -q + %build %configure make %{?_smp_mflags} + %install rm -rf $RPM_BUILD_ROOT make DESTDIR=$RPM_BUILD_ROOT install %clean rm -rf $RPM_BUILD_ROOT + %files %defattr(-,root,root,-) %{_bindir}/* %{_mandir}/man1/* %{_mandir}/man5/* -%doc AUTHORS COPYING ChangeLog NEWS README +%docdir %{_datadir}/doc/%{name}-%{version} +%{_datadir}/doc/%{name}-%{version} %changelog -* Tue Feb 26 2008 Carl Byington - 0.6.8 +* Wed Mar 05 2008 Carl Byington - 0.6.8 - Initial version of pst2dii to convert to Summation dii load file format - changes for Fedora packaging guidelines (#434727) * Tue Jul 10 2007 Carl Byington - 0.5.5 - merge changes from Joe Nahmias version * Sun Feb 19 2006 Carl Byington - 0.5.3 - initial spec file using autoconf and http://www.fedora.us/docs/rpm-packaging-guidelines.html diff --git a/package b/package deleted file mode 100644 index 0a3f5ae..0000000 --- a/package +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash - -find . -name '*~' | while read a; do rm -f "$a"; done - -# setup environment -T=`grep AC_INIT configure.in | cut -d'(' -f2` -NAME=`echo $T | cut -d, -f1` -VER=`echo $T | cut -d, -f2` -BALL=$NAME-$VER.tar.gz -web=/home/httpd/html/510sg/$NAME -distlog=/tmp/distcheck -repo=/usr/hg-repository/$NAME -oldone=$web/packages/$BALL - -if [ -f $oldone ]; then - echo "$oldone already exists, did you update the version in configure.in?" - exit 0 -fi - -# check for uncommitted changes -a=$(hg status) -if [ -n "$a" ]; then - hg status - echo "fix those first with 'hg commit'" - exit 0 -fi - -# check for changesets pending push to global repository -hg outgoing $repo | grep 'no changes found' >/dev/null -if [ $? -ne 0 ]; then - hg outgoing $repo - echo "fix those first with 'hg push $repo'" - exit 0 -fi - -# get a new clean copy from source control -orig=$(pwd) -pack=$orig/../$NAME-package -[ -d "$pack" ] && rm -rf "$pack" -mkdir "$pack" -cd "$pack" -hg clone $repo -cd $NAME - -# build the package -mkdir -p $web -chown --recursive root:root * -make -f *cvs -./configure >/dev/null -(cd xml; make; make distclean) -cp -a html/*html $web -make distcheck >$distlog 2>&1 - -if [ $? -eq 0 ]; then - if [ -f $BALL ]; then - # expand locally to see the tarball - rm -rf junk - mkdir junk - cd junk - tar xfz ../$BALL - cd $NAME-$VER - ./configure >/dev/null - make >/dev/null - cd .. - cd .. - - # build rpm on target - target5=pmg2 - scp $BALL $target5:/tmp - ssh $target5 "cd /tmp; rpmbuild -ta $BALL" - - # build rpm on target - target4=host62 - scp $BALL $target4:/tmp - ssh $target4 "cd /tmp; rpmbuild -ta $BALL" - - # add packages to the web site - wp=$web/packages - wp4=$wp/centos4 - wp5=$wp/centos5 - mkdir -p $wp4 $wp5 - rp=/usr/src/redhat - mv -f $BALL $wp - scp $target4:$rp/SRPMS/$NAME-$VER*rpm $wp - scp $target4:$rp/RPMS/i386/$NAME-$VER*rpm $wp4 - scp $target5:$rp/RPMS/i386/$NAME-$VER*rpm $wp5 - (cd $web; chown --recursive web:web *; ls -alR) - rpm -ql -p $wp4/$NAME-$VER*6.rpm - rpm -ql -p $wp5/$NAME-$VER*6.rpm - fi -else - tail -10 $distlog -fi - -# throw away the packaging directory -cd "$pack/.." -rm -rf $NAME-package -cd "$orig" diff --git a/regression/regression-tests.bash b/regression/regression-tests.bash index 9d80572..ec10c05 100644 --- a/regression/regression-tests.bash +++ b/regression/regression-tests.bash @@ -1,51 +1,52 @@ #!/bin/bash val="valgrind --leak-check=full" val='' for i in {1..10}; do rm -rf output$i mkdir output$i done hash=$(md5sum ams.pst) pre="$hash bates-" $val ../src/pst2dii -f /usr/share/fonts/bitstream-vera/VeraMono.ttf -B "$pre" -o output1 -O mydii -d dumper ams.pst ../src/readpstlog -f I dumper >ams.log $val ../src/pst2dii -f /usr/share/fonts/bitstream-vera/VeraMono.ttf -B "bates-" -o output2 -O mydii2 -d dumper sample_64.pst ../src/readpstlog -f I dumper >sample_64.log $val ../src/pst2dii -f /usr/share/fonts/bitstream-vera/VeraMono.ttf -B "bates-" -o output3 -O mydii3 -d dumper test.pst ../src/readpstlog -f I dumper >test.log ../src/pst2dii -f /usr/share/fonts/bitstream-vera/VeraMono.ttf -B "bates-" -o output4 -O mydii4 -d dumper big_mail.pst ../src/readpstlog -f I dumper >big_mail.log +exit $val ../src/pst2ldif -b 'o=ams-cc.com, c=US' -c 'newPerson' ams.pst >ams.err 2>&1 $val ../src/readpst -cv -o output1 ams.pst >out1.err 2>&1 $val ../src/readpst -cl -r -o output2 ams.pst >out2.err 2>&1 $val ../src/readpst -S -o output3 ams.pst >out3.err 2>&1 $val ../src/readpst -M -o output4 ams.pst >out4.err 2>&1 $val ../src/readpst -o output5 -d dumper mbmg.archive.pst >out5.err 2>&1 ../src/readpstlog -f I dumper >mbmg.archive.log $val ../src/readpst -o output6 -d dumper test.pst >out6.err 2>&1 ../src/readpstlog -f I dumper >test.log $val ../src/readpst -cv -o output7 -d dumper sample_64.pst >out7.err 2>&1 ../src/readpstlog -f I dumper >sample_64.log $val ../src/readpst -cv -o output8 -d dumper big_mail.pst >out8.err 2>&1 ../src/readpstlog -f I dumper >big_mail.log $val ../src/readpst -cv -o output9 -d dumper Single2003-read.pst >out9.err 2>&1 ../src/readpstlog -f I dumper >Single2003-read.log $val ../src/readpst -cv -o output10 -d dumper Single2003-unread.pst >out10.err 2>&1 ../src/readpstlog -f I dumper >Single2003-unread.log $val ../src/lspst -d dumper ams.pst >out11.err 2>&1 ../src/readpstlog -f I dumper >ams.log rm -f dumper diff --git a/src/pst2dii.cpp.in b/src/pst2dii.cpp.in index c355760..47af210 100644 --- a/src/pst2dii.cpp.in +++ b/src/pst2dii.cpp.in @@ -1,733 +1,742 @@ /* Copyright (c) 2008 Carl Byington - 510 Software Group, released under the GPL version 2 or any later version at your choice available at http://www.fsf.org/licenses/gpl.txt Based on readpst.c by David Smith */ -#include "gd.h" +#include #include #include #include #include #include using namespace std; extern "C" { #include "define.h" #include "libstrfunc.h" #include "libpst.h" #include "common.h" #include "timeconv.h" #include "lzfu.h" } struct file_ll { string name; int32_t stored_count; int32_t email_count; int32_t skip_count; int32_t type; file_ll() { stored_count = 0; email_count = 0; skip_count = 0; type = 0; }; }; // global settings char* convert = "@CONVERT@"; // fully qualified path of the convert program from image magick char* prog_name = NULL; // our arg0 name char* bates_prefix = ""; // string to prefix bates numbers int bates_index = 0; // current bates sequence char* output_directory = "."; char* output_file = "load.dii"; char* font_file = NULL; int bates_color = 0xff0000; // color of bates header stamp int email_sequence = 0; // current pdf sequence number char pdf_name[PATH_MAX]; // current pdf file name FILE* dii_file = NULL; // the output dii load file pst_file pstfile; // the input pst file // pdf writer globals bool pdf_open = false; // is pdf writer started char* pst_folder; // current folder name int page_sequence; // current page number string conversion; // conversion command vector png_names; // png writer globals bool png_open = false; // is current page open int line_height; // in pixels int char_width; // in pixels int col_number, col_max; // in characters int line_number, line_max; // lines per page int x_position, y_position; // in pixels int black, red; // text colors gdImagePtr image; // current gd image const int DPI = 300; const double sz = 10.0; const int margin = DPI/2; const int LINE_SIZE = 2000; const int PAGE_WIDTH = DPI*17/2; const int PAGE_HEIGHT = DPI*11; // max size of the c_time char*. It will store the date of the email #define C_TIME_SIZE 500 static void open_png(); static void close_png(); static int version(); static int version() { printf("pst2dii v%s\n", VERSION); #if BYTE_ORDER == BIG_ENDIAN printf("Big Endian implementation being used.\n"); #elif BYTE_ORDER == LITTLE_ENDIAN printf("Little Endian implementation being used.\n"); #else # error "Byte order not supported by this library" #endif #ifdef __GNUC__ printf("GCC %d.%d : %s %s\n", __GNUC__, __GNUC_MINOR__, __DATE__, __TIME__); #endif return 0; } static int usage(); static int usage() { version(); printf("Usage: %s [OPTIONS] {PST FILENAME}\n", prog_name); printf("OPTIONS:\n"); printf("\t-B bates-prefix \t- Set the bates prefix string\n"); printf("\t-O dii-output-file\t- Set the dii load file output filename\n"); printf("\t-V \t- Version. Display program version\n"); printf("\t-b bates-number \t- Set the starting bates sequence number\n"); printf("\t-c bates-color \t- Specify the color of the bates stamps as 6 digit hex\n"); printf("\t-d filename \t- Debug to file. This is a binary log. Use readpstlog to print it.\n"); printf("\t-f ttf-font-file \t- Set the font file\n"); printf("\t-h \t- Help. This screen\n"); printf("\t-o dirname \t- Output directory to write files to.\n"); return 0; } static char *removeCR (char *c); static char *removeCR (char *c) { // converts /r/n to /n char *a, *b; DEBUG_ENT("removeCR"); a = b = c; while (*a != '\0') { *b = *a; if (*a != '\r') b++; a++; } *b = '\0'; DEBUG_RET(); return c; } // The sole purpose of this function is to bypass the pseudo-header prologue // that Microsoft Outlook inserts at the beginning of the internet email // headers for emails stored in their "Personal Folders" files. static char *skip_header_prologue(char *headers); static char *skip_header_prologue(char *headers) { const char *bad = "Microsoft Mail Internet Headers"; if (strncmp(headers, bad, strlen(bad)) == 0) { // Found the offensive header prologue char *pc = strchr(headers, '\n'); return pc + 1; } return headers; } static void check_filename(string &fname); static void check_filename(string &fname) { char *t = strdup(fname.c_str()); DEBUG_ENT("check_filename"); if (!t) { DEBUG_RET(); return; } char *tt = t; bool fixed = false; while (t = strpbrk(t, " /\\:")) { // while there are characters in the second string that we don't want *t = '_'; //replace them with an underscore fixed = true; } if (fixed) fname = string(tt); free(tt); DEBUG_RET(); } static string write_separate_attachment(string fname, pst_item_attach* current_attach, int attach_num, pst_file* pst); static string write_separate_attachment(string fname, pst_item_attach* current_attach, int attach_num, pst_file* pst) { FILE *fp = NULL; int x = 0; char *temp = NULL; // If there is a long filename (filename2) use that, otherwise // use the 8.3 filename (filename1) char *attach_filename = (current_attach->filename2) ? current_attach->filename2 : current_attach->filename1; DEBUG_ENT("write_separate_attachment"); check_filename(fname); const char* f_name = fname.c_str(); DEBUG_EMAIL(("dirname=%s, pathname=%s, filename=%s\n", output_directory, f_name, attach_filename)); int len = strlen(output_directory) + 1 + strlen(f_name) + 15; if (!attach_filename) { // generate our own (dummy) filename for the attachement temp = (char*)xmalloc(len); sprintf(temp, "%s/%s_attach%i", output_directory, f_name, attach_num); } else { // have an attachment name, make sure it's unique temp = (char*)xmalloc(len+strlen(attach_filename)); do { if (fp) fclose(fp); if (x == 0) sprintf(temp, "%s/%s_%s", output_directory, f_name, attach_filename); else sprintf(temp, "%s/%s_%s-%i", output_directory, f_name, attach_filename, x); } while ((fp = fopen(temp, "r")) && ++x < 99999999); if (x > 99999999) { DIE(("error finding attachment name. exhausted possibilities to %s\n", temp)); } } DEBUG_EMAIL(("Saving attachment to %s\n", temp)); if (!(fp = fopen(temp, "wb"))) { WARN(("write_separate_attachment: Cannot open attachment save file \"%s\"\n", temp)); } else { if (current_attach->data) fwrite(current_attach->data, 1, current_attach->size, fp); else { (void)pst_attach_to_file(pst, current_attach, fp); } fclose(fp); } string rc(temp); if (temp) free(temp); DEBUG_RET(); return rc; } static void print_pdf_short(char *line, int len, int color); static void print_pdf_short(char *line, int len, int color) { if (line_number >= line_max) { close_png(); open_png(); } int brect[8]; gdFTStringExtra strex; strex.flags = gdFTEX_RESOLUTION; strex.linespacing = 1.20; strex.charmap = 0; strex.hdpi = DPI; strex.vdpi = DPI; char x = line[len]; line[len] = '\0'; char *p; char *l = line; while (p = strchr(l, '&')) { *p = '\0'; char *err = gdImageStringFTEx(image, &brect[0], color, font_file, sz, 0.0, x_position, y_position, l, &strex); if (err) printf(err); x_position += (brect[2]-brect[6]); l = p+1; err = gdImageStringFTEx(image, &brect[0], color, font_file, sz, 0.0, x_position, y_position, "&", &strex); if (err) printf(err); x_position += (brect[2]-brect[6]); } char *err = gdImageStringFTEx(image, &brect[0], color, font_file, sz, 0.0, x_position, y_position, l, &strex); if (err) printf(err); line[len] = x; x_position += (brect[2]-brect[6]); col_number += len; } +static void new_line(); +static void new_line() +{ + y_position += line_height; + line_number += 1; + x_position = margin; + col_number = 0; +} + + static void print_pdf_single(char *line, int color); static void print_pdf_single(char *line, int color) { + while (*line == '\t') { + char blanks[5]; + memset(blanks, ' ', 5); + print_pdf_short(blanks, 4, color); + line++; + if (col_number >= col_max) new_line(); + } int n = strlen(line); while (n) { int m = col_max - col_number; // number of chars that will fit on this line m = (n > m) ? m : n; print_pdf_short(line, m, color); line += m; n -= m; - if (n) { - y_position += line_height; - line_number += 1; - x_position = margin; - col_number = 0; - } + if (n) new_line(); } } static void print_pdf_only(char *line, int color); static void print_pdf_only(char *line, int color) { char *p; while (p = strchr(line, '\n')) { *p = '\0'; print_pdf_single(line, color); *p = '\n'; line = p+1; - y_position += line_height; - line_number += 1; - x_position = margin; - col_number = 0; + new_line(); } print_pdf_single(line, color); } static void print_pdf(char *line); static void print_pdf(char *line) { fwrite(line, 1, strlen(line), dii_file); print_pdf_only(line, black); } static void open_png() { if (!png_open) { png_open = true; int brect[8]; image = gdImageCreate(PAGE_WIDTH, PAGE_HEIGHT); int w = gdImageColorAllocate(image, 255, 255, 255); // background color first one allocated black = gdImageColorAllocate(image, 0, 0, 0); int r = (bates_color & 0xff0000) >> 16; int g = (bates_color & 0x00ff00) >> 8; int b = (bates_color & 0x0000ff); red = gdImageColorAllocate(image, r, g, b); gdFTStringExtra strex; strex.flags = gdFTEX_RESOLUTION; strex.linespacing = 1.20; strex.charmap = 0; strex.hdpi = DPI; strex.vdpi = DPI; char line[LINE_SIZE]; char *err = gdImageStringFTEx(NULL, &brect[0], black, font_file, sz, 0.0, margin, margin, "LMgqQ", &strex); if (err) printf(err); line_height = (brect[3]-brect[7]) * 12/10; char_width = (brect[2]-brect[6]) / 5; col_number = 0; col_max = (PAGE_WIDTH - margin*2) / char_width; line_number = 0; line_max = (PAGE_HEIGHT - margin*2) / line_height; x_position = margin; y_position = margin + line_height; snprintf(line, sizeof(line), "%s%06d\n", bates_prefix, bates_index++); print_pdf_only(line, red); print_pdf_only(pst_folder, red); } } static void close_png() { if (png_open) { png_open = false; char fn[PATH_MAX]; snprintf(fn, sizeof(fn), "page%d.png", ++page_sequence); FILE *pngout = fopen(fn, "wb"); if (pngout) { gdImagePng(image, pngout); fclose(pngout); } gdImageDestroy(image); // free memory png_names.push_back(fn); conversion += string(" ") + fn; } } static void open_pdf(char *line); static void open_pdf(char *line) { pst_folder = line; page_sequence = 0; conversion = string(convert); png_names.clear(); open_png(); snprintf(pdf_name, sizeof(pdf_name), "dii%06d", ++email_sequence); fprintf(dii_file, "\n@T %s\n", pdf_name); snprintf(pdf_name, sizeof(pdf_name), "%s/dii%06d.pdf", output_directory, email_sequence); } static void close_pdf(); static void close_pdf() { close_png(); conversion += string(" ") + pdf_name; system(conversion.c_str()); for (vector::iterator i=png_names.begin(); i!=png_names.end(); i++) { remove((*i).c_str()); } fprintf(dii_file, "@D %s\n", pdf_name); } static void write_simple(char *tag, const char *value); static void write_simple(char *tag, const char *value) { if (value) fprintf(dii_file, "@%s %s\n", tag, value); } static void write_simple(char *tag, string value); static void write_simple(char *tag, string value) { fprintf(dii_file, "@%s %s\n", tag, value.c_str()); } static void write_simple(char *tag, const char *value, const char *value2); static void write_simple(char *tag, const char *value, const char *value2) { if (value) { if (value2) fprintf(dii_file, "@%s \"%s\" <%s>\n", tag, value, value2); else fprintf(dii_file, "@%s \"%s\"\n", tag, value); } } static string extract_header(char *headers, char *field); static string extract_header(char *headers, char *field) { string rc; int len = strlen(field) + 4; char f[len]; snprintf(f, len, "\n%s: ", field); char *p = strstr(headers, f); if (p) { p += strlen(f); char *n = strchr(p, '\n'); if (n) { *n = '\0'; rc = string(p); *n = '\n'; } else { rc = string(p); } } return rc; } static void write_normal_email(file_ll &f, pst_item* item, pst_file* pst); static void write_normal_email(file_ll &f, pst_item* item, pst_file* pst) { DEBUG_ENT("write_normal_email"); char *soh = NULL; // real start of headers. if (item->email->header) { // some of the headers we get from the file are not properly defined. // they can contain some email stuff too. We will cut off the header // when we see a \n\n or \r\n\r\n removeCR(item->email->header); char *temp = strstr(item->email->header, "\n\n"); if (temp) { DEBUG_EMAIL(("Found body text in header\n")); temp[1] = '\0'; // stop after first \n } soh = skip_header_prologue(item->email->header); } char folder_line[LINE_SIZE]; char line[LINE_SIZE]; // reset pdf writer to new file int bates = bates_index; // save starting index snprintf(folder_line, sizeof(folder_line), "pst folder = %s\n", f.name.c_str()); open_pdf(folder_line); // start printing this email fprintf(dii_file, "@FOLDERNAME %s\n", f.name.c_str()); string myfrom = extract_header(soh, "From"); string myto = extract_header(soh, "To"); string mycc = extract_header(soh, "Cc"); string mybcc = extract_header(soh, "Bcc"); if (myfrom.empty()) write_simple("FROM", item->email->outlook_sender_name, item->email->sender_address); else write_simple("FROM", myfrom); if (myto.empty()) write_simple("TO", item->email->sentto_address, item->email->recip_address); else write_simple("TO", myto); if (mycc.empty()) write_simple("CC", item->email->cc_address); else write_simple("CC", mycc); if (mybcc.empty()) write_simple("BCC", item->email->bcc_address); else write_simple("BCC", mybcc); if (item->email->sent_date) { time_t t = fileTimeToUnixTime(item->email->sent_date, NULL); char c_time[C_TIME_SIZE]; strftime(c_time, C_TIME_SIZE, "%F", gmtime(&t)); write_simple("DATESENT", c_time); strftime(c_time, C_TIME_SIZE, "%T+0000", gmtime(&t)); write_simple("TIMESENT", c_time); } if (item->email->arrival_date) { time_t t = fileTimeToUnixTime(item->email->arrival_date, NULL); char c_time[C_TIME_SIZE]; strftime(c_time, C_TIME_SIZE, "%F", gmtime(&t)); write_simple("DATERCVD", c_time); strftime(c_time, C_TIME_SIZE, "%T+0000", gmtime(&t)); write_simple("TIMERCVD", c_time); } if (item->email->subject) { write_simple("SUBJECT", item->email->subject->subj); } write_simple("MSGID", item->email->messageid); if (item->email->flag) { write_simple("READ", (item->email->flag & 1) ? "Y" : "N"); } DEBUG_EMAIL(("About to print Header\n")); fprintf(dii_file, "@HEADER\n"); if (item && item->email && item->email->subject && item->email->subject->subj) { DEBUG_EMAIL(("item->email->subject->subj = %s\n", item->email->subject->subj)); } if (soh) { // Now, write out the header... print_pdf(soh); int len = strlen(soh); if (!len || (soh[len-1] != '\n')) { snprintf(line, sizeof(line), "\n"); print_pdf(line); } } else { //make up our own headers char *temp = item->email->outlook_sender; if (!temp) temp = ""; snprintf(line, sizeof(line), "From: \"%s\" <%s>\n", item->email->outlook_sender_name, temp); print_pdf(line); if (item->email->subject) { snprintf(line, sizeof(line), "Subject: %s\n", item->email->subject->subj); } else { snprintf(line, sizeof(line), "Subject: \n"); } print_pdf(line); snprintf(line, sizeof(line), "To: %s\n", item->email->sentto_address); print_pdf(line); if (item->email->cc_address) { snprintf(line, sizeof(line), "Cc: %s\n", item->email->cc_address); print_pdf(line); } if (item->email->sent_date) { time_t em_time = fileTimeToUnixTime(item->email->sent_date, 0); char c_time[C_TIME_SIZE]; strftime(c_time, C_TIME_SIZE, "%a, %d %b %Y %H:%M:%S %z", gmtime(&em_time)); snprintf(line, sizeof(line), "Date: %s\n", c_time); print_pdf(line); } } snprintf(line, sizeof(line), "\n"); print_pdf_only(line, black); fprintf(dii_file, "@HEADER-END\n"); DEBUG_EMAIL(("About to print Body\n")); fprintf(dii_file, "@EMAIL-BODY\n"); if (item->email->body) { removeCR(item->email->body); print_pdf(item->email->body); } else if (item->email->htmlbody) { removeCR(item->email->htmlbody); print_pdf(item->email->htmlbody); } else if (item->email->encrypted_body || item->email->encrypted_htmlbody) { print_pdf("The body of this email is encrypted. This isn't supported yet, but the body is now an attachment\n"); } fprintf(dii_file, "@EMAIL-END\n"); int attach_num = 0; for (pst_item_attach* current_attach = item->attach; current_attach; current_attach = current_attach->next) { DEBUG_EMAIL(("Attempting Attachment encoding\n")); if (!current_attach->data) { DEBUG_EMAIL(("Data of attachment is NULL!. Size is supposed to be %i\n", current_attach->size)); } string an = write_separate_attachment(f.name, current_attach, ++attach_num, pst); fprintf(dii_file, "@EATTACH %s\n", an.c_str()); } close_pdf(); fprintf(dii_file, "@BATESBEG %d\n", bates); fprintf(dii_file, "@BATESEND %d\n", bates_index-1); DEBUG_RET(); } static void create_enter_dir(file_ll &f, file_ll *parent, pst_item *item); static void create_enter_dir(file_ll &f, file_ll *parent, pst_item *item) { f.email_count = 0; f.skip_count = 0; f.type = item->type; f.stored_count = (item->folder) ? item->folder->email_count : 0; f.name = ((parent) ? parent->name + "/" : "") + string(item->file_as); } static void close_enter_dir(file_ll &f); static void close_enter_dir(file_ll &f) { } static void process(pst_item *outeritem, file_ll *parent, pst_desc_ll *d_ptr); static void process(pst_item *outeritem, file_ll *parent, pst_desc_ll *d_ptr) { file_ll ff; pst_item *item = NULL; DEBUG_ENT("process"); create_enter_dir(ff, parent, outeritem); while (d_ptr) { if (d_ptr->desc) { item = pst_parse_item(&pstfile, d_ptr); DEBUG_INFO(("item pointer is %p\n", item)); if (item) { if (item->message_store) { // there should only be one message_store, and we have already done it DIE(("main: A second message_store has been found. Sorry, this must be an error.\n")); } if (item->folder && d_ptr->child ) { //if this is a non-empty folder other than deleted items, we want to recurse into it fprintf(stderr, "entering folder %s\n", item->file_as); process(item, &ff, d_ptr->child); } else if (item->email && (item->type == PST_TYPE_NOTE || item->type == PST_TYPE_REPORT)) { ff.email_count++; DEBUG_MAIN(("main: Processing Email\n")); if ((ff.type != PST_TYPE_NOTE) && (ff.type != PST_TYPE_REPORT)) { DEBUG_MAIN(("main: I have an email, but the folder isn't an email folder. Processing anyway\n")); } write_normal_email(ff, item, &pstfile); } pst_freeItem(item); } else { ff.skip_count++; DEBUG_MAIN(("main: A NULL item was seen\n")); } } d_ptr = d_ptr->next; } close_enter_dir(ff); DEBUG_RET(); } int main(int argc, char **argv) { pst_desc_ll *d_ptr; char *fname = NULL; char c; char *d_log = NULL; prog_name = argv[0]; pst_item *item = NULL; while ((c = getopt(argc, argv, "B:b:c:d:f:o:O:Vh"))!= -1) { switch (c) { case 'B': bates_prefix = optarg; break; case 'b': bates_index = atoi(optarg); break; case 'c': bates_color = (int)strtol(optarg, (char**)NULL, 16); break; case 'f': font_file = optarg; break; case 'o': output_directory = optarg; break; case 'O': output_file = optarg; break; case 'd': d_log = optarg; break; case 'h': usage(); exit(0); break; case 'V': version(); exit(0); break; default: usage(); exit(1); break; } } if (argc > optind) { fname = argv[optind]; } else { usage(); exit(2); } #ifdef DEBUG_ALL // force a log file if (!d_log) d_log = "pst2dii.log"; #endif DEBUG_INIT(d_log); DEBUG_REGISTER_CLOSE(); DEBUG_ENT("main"); RET_DERROR(pst_open(&pstfile, fname), 1, ("Error opening File\n")); RET_DERROR(pst_load_index(&pstfile), 2, ("Index Error\n")); pst_load_extended_attributes(&pstfile); d_ptr = pstfile.d_head; // first record is main record item = (pst_item*)pst_parse_item(&pstfile, d_ptr); if (!item || !item->message_store) { DEBUG_RET(); DIE(("main: Could not get root record\n")); } d_ptr = pst_getTopOfFolders(&pstfile, item); if (!d_ptr) { DEBUG_RET(); DIE(("Top of folders record not found. Cannot continue\n")); } dii_file = fopen(output_file, "wb"); if (dii_file) { process(item, NULL, d_ptr->child); // do the children of TOPF pst_freeItem(item); pst_close(&pstfile); fclose(dii_file); } DEBUG_RET(); return 0; }