[Exim] 7bit conversion utility

Top Pagina
Delete this message
Reply to this message
Auteur: Torsten Lüttgert
Datum:  
Aan: exim-users
Onderwerp: [Exim] 7bit conversion utility
Hi,

I recently had the problem of the dreaded 7bit conversion (yes, we use mmdf on
one ancient machine, and it cannot be replaced).

This topic was vigorously discussed on this list already, as I could see in the
archives. I don't want to reopen any discussions, but present my little
solution, which is a simple filter I use for the transport to the mmdf box:

trans_box:
driver = smtp
transport_filter = /etc/exim/convert7bit
size_addition = -1

The convert7bit utility was written by me. It scans for chars > 127. If it
finds none, the mail is just piped through. Otherwise, it uses the gmime
library (I used 1.0.5) for the actual conversion. gmime can be found at:

http://spruce.sourceforge.net/gmime/

The source code of convert7bit follows below. If you spot any bugs, don't
hesitate to write :-)

- Torsten (t.luettgert@???)

P.S.: compile it using

gcc -g -Wall -o convert7bit -O3 `gmime-config --cflags` convert7bit.c \
`gmime-config --libs`

------------------------------------------------------------------------------

/* convert7bit.c, (c) 2002 Torsten Lüttgert
*
* This utility is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This utility is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* The GNU General Public License can be found at
* http://www.gnu.org/licenses/gpl.html
*
* You can also obtain it by writing to the Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <gmime/gmime.h>

static int
contains_8bit_chars( const char *s )
{
   if( !s )
      return 0;
   for( ; *s; ++s )
      if( *s < 0 )
         return 1;
   return 0;
}


static int
count_8bit_chars( const char *s )
{
int ret = 0;

   if( !s )
      return 0;
   for( ; *s; ++s )
      if( *s < 0 )
         ++ret;
   return ret;
}


/* utility functions for header modification */

static GList *headers_to_kill = 0;
static GList *headers_to_encode = 0;

static void
check_header( const char *name, const char *value, gpointer data )
{
   if( contains_8bit_chars( name ) ) {
      /* this should NEVER EVER happen !! */
      headers_to_kill = g_list_prepend( headers_to_kill, (char *)name );
      return;
   }else if( contains_8bit_chars( value ) )
      headers_to_encode = g_list_prepend( headers_to_encode, (char *)name );
}


static void
kill_a_header( gpointer data, gpointer user_data )
{
g_mime_header_remove( (GMimeHeader *)user_data, (const char *)data );
}

static void
encode_a_header( gpointer data, gpointer user_data )
{
   const char *orig = g_mime_header_get(
       (GMimeHeader *)user_data, (const char *)data );
   char *enc = g_mime_utils_8bit_header_encode( orig );
   g_mime_header_set( (GMimeHeader *)user_data, (const char *)data, enc );
   g_free(enc);
}


/* utility functions for part modification */

static void
convert_part( GMimePart *part, gpointer data )
{
char *inhalt = g_mime_part_to_string( part );
int cs;

   if( (cs = count_8bit_chars( inhalt )) ) {
      if( 6*cs > strlen(inhalt) ) /* about then, base64 pays off */
         g_mime_part_set_encoding( part, GMIME_PART_ENCODING_BASE64 );
      else
         g_mime_part_set_encoding( part, GMIME_PART_ENCODING_QUOTEDPRINTABLE );
   }


g_free( inhalt );
}


static int
converter( FILE *indesc, FILE *outdesc )
{
GMimeStream *in, *out;
GMimeMessage *msg;

/* assign streams to file descriptors */
in = g_mime_stream_file_new( indesc );
out = g_mime_stream_file_new( outdesc );

/* parse the mail */
msg = g_mime_parser_construct_message( in );

/* convert the message headers, if necessary */
headers_to_kill = headers_to_encode = 0;
g_mime_header_foreach( msg->header->headers, check_header, 0 );

g_list_foreach( headers_to_kill, kill_a_header, msg->header->headers );
g_list_free( headers_to_kill );
g_list_foreach( headers_to_encode, encode_a_header, msg->header->headers );
g_list_free( headers_to_encode );

/* convert parts one by one */
g_mime_message_foreach_part( msg, convert_part, msg );

/* write mail to output stream */
g_mime_message_write_to_stream( msg, out );

/* clean up */
g_mime_object_unref( GMIME_OBJECT(msg) );
g_mime_stream_unref( in );
g_mime_stream_unref( out );

return 1;
}


int
convert_message( FILE *in, FILE *out )
{
int has_8_bit;
unsigned char buf[32768];
size_t rd;
int i;
unsigned char *p;
FILE *tmp;

/* stdio is not seekable, so buffer it */
tmp = tmpfile();

clearerr( in );

   has_8_bit = 0;
   while( !feof(in) && (rd = fread( buf, 1, sizeof(buf), in )) > 0 ) {
      if( fwrite( buf, 1, rd, tmp ) != rd ) {
         fclose(tmp);
         return -1;
      }


      /* pre-check for avoiding 2nd pass if no 8-bit-chars are in there */
      if( !has_8_bit ) {
         for( i=rd, p=buf; i; --i ) {
            if( *(p++) & 128 ) {
               has_8_bit = 1;
               break;
            }
         }
      }
   }


   if( ferror(in) )
      return -1;


fseek( tmp, 0, SEEK_SET );

fclose(in);
in = tmp;

/* do we need to convert? */

   if( !has_8_bit ) {
      /* nope */


      while( (rd = fread( buf, 1, sizeof(buf), in )) > 0 ) {
         if( fwrite( buf, 1, rd, out ) != rd )
            return -1;
      }


      fclose(in);
      fclose(out);


      return 0;
   }


/* conversion using gmime */
return converter( in, out );
}


int
main( int argc, char **argv )
{
int ret;

g_mime_init(0);
ret = convert_message( stdin, stdout );

   if( ret == -1 )
      abort(); /* ouch :-( */


return 0;
}