/*------------------------------------------------------------------------
Module: mime_alter.c
Author: Paul L Daniels / Steven Coutts
Project: ripMIME
State: development
Creation Date: 01/05/2001
Description: MIME-alter is an additional module in the mime.[ch] object, which will contain various functions which will -alter- mime-packs.
------------------------------------------------------------------------*/
// 0908 10H57 - Added ability for disclaimer to be actual text, as well as
// a filename. I just detect for a \n or \r in the string, if detected
// disclaimer is considered as "text"
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <regex.h>
#include "ffget.h"
#include "pldstr.h"
#include "strstack.h"
#include "boundary-stack.h"
#include "MIME_headers.h"
#include "filename-filters.h"
#include "mime_alter.h"
#include "qpe.h"
#include "logger.h"
#define AM_1K_BUFFER_SIZE 1024
#define AM_DISCLAIMER_FILENAME_LENGTH_MAX 256;
#define AM_DNORMAL (glb.debug > 0)
#define AM_VERBOSE (glb.verbose > 0)
#define DAM if (glb.debug > 0)
#define VAM if (glb.verbose > 0)
/* Globals */
char *AM_scratch_buffer[AM_1K_BUFFER_SIZE]; /* allocate a static array of 1024 pointers to char */
//06/03/2002 - replaced AM_setup_table() with this compile-time array. Saves a few startup cycles.
unsigned char AM_encode64[64]={
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,\
81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102,\
103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,\
119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47 \
};
#define AM_HEADERBUFFER_MAX 100
#define AM_HEADERBUFFER_ITEM_SIZE 1024
struct AM_globals {
int debug; // Low level debugging
int verbose; /* do we talk as we walk */
int paranoid; /* set paranoid to yes! */
int HTML_too; /* Add footer to the HTML email too */
int force_for_bad_html; /** Force insertion of HTML disclaimer even when we can't find the end **/
int multipart_insert; /* Should we insert into emails which are embedded into another */
int nullify_all; /* Remove ALL filename'd attachments */
int alter_signed; /* Do we alter signed emails ? */
int header_long_search; /* do we search through email bodies for more headers, like qmail bounces */
char *disclaimer_plain;
int disclaimer_plain_type;
char *disclaimer_HTML;
int disclaimer_HTML_type;
#ifdef ALTERMIME_PRETEXT
char *pretext_plain;
int pretext_plain_type;
char *pretext_HTML;
int pretext_HTML_type;
int pretext_insert;
#endif
char *headerbuffer[ AM_HEADERBUFFER_MAX ]; // 100 lines for the header buffers
int headerbuffermax;
};
static struct AM_globals glb;
/*-----------------------------------------------------------------\
Function Name : AM_version
Returns Type : int
----Parameter List
1. void ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_version( void )
{
fprintf(stderr,"alterMIME: %s", LIBAM_VERSION);
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_init
Returns Type : int
----Parameter List
1. void ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_init( void )
{
glb.debug=0; /* Low level debugging */
glb.verbose=0; /* do we talk as we walk */
glb.paranoid=1; /* set paranoid to yes! */
glb.HTML_too=1; /* Add footer to the HTML email too */
glb.multipart_insert=0; /* do not insert into multipart attachments */
glb.nullify_all=0; /* Remove ALL filename'd attachments */
glb.alter_signed=0; /* Do we alter signed emails ? - default is NO */
glb.header_long_search=1; /* Search into email bodies for more attachments */
glb.disclaimer_plain=NULL;
glb.disclaimer_plain_type=AM_DISCLAIMER_TYPE_NONE;
glb.disclaimer_HTML=NULL;
glb.disclaimer_HTML_type=AM_DISCLAIMER_TYPE_NONE;
#ifdef ALTERMIME_PRETEXT
glb.pretext_plain=NULL;
glb.pretext_plain_type=AM_PRETEXT_TYPE_NONE;
glb.pretext_HTML=NULL;
glb.pretext_HTML_type=AM_PRETEXT_TYPE_NONE;
glb.pretext_insert = 0;
#endif
glb.headerbuffermax=0;
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_done
Returns Type : int
----Parameter List
1. void ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_done( void )
{
if (glb.disclaimer_plain != NULL) free(glb.disclaimer_plain);
if (glb.disclaimer_HTML != NULL) free(glb.disclaimer_HTML);
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_set_force_for_bad_html
Returns Type : int
----Parameter List
1. int level ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_set_force_for_bad_html( int level )
{
glb.force_for_bad_html = level;
return glb.force_for_bad_html;
}
/*-----------------------------------------------------------------\
Function Name : AM_set_pretext_insert
Returns Type : int
----Parameter List
1. int level ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
#ifdef ALTERMIME_PRETEXT
int AM_set_pretext_insert( int level )
{
glb.pretext_insert = level;
return glb.pretext_insert;
}
#endif
/*-----------------------------------------------------------------\
Function Name : AM_set_debug
Returns Type : int
----Parameter List
1. int level ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_set_debug( int level )
{
glb.debug = level;
AM_set_verbose( level );
MIMEH_set_debug(level);
return level;
}
/*-----------------------------------------------------------------\
Function Name : AM_hbuffer_reset
Returns Type : int
----Parameter List
1. void ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_hbuffer_reset( void )
{
int i;
for (i = 0; i < glb.headerbuffermax; i++)
{
if (glb.headerbuffer[i]) free(glb.headerbuffer[i]);
}
glb.headerbuffermax=0;
return 0;
}
/*-----------------------------------------------------------------\
Function Name : *AM_hbuffer_add
Returns Type : char
----Parameter List
1. char *headerline,
2. FILE *f ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
char *AM_hbuffer_add( char *headerline, FILE *f )
{
int i;
if (glb.headerbuffermax == AM_HEADERBUFFER_MAX)
{
if (glb.headerbuffer[0])
{
fprintf(f,"%s",glb.headerbuffer[0]);
free(glb.headerbuffer[0]);
}
for (i = 0; i < (AM_HEADERBUFFER_MAX-1); i++)
{
glb.headerbuffer[i] = glb.headerbuffer[i+1];
}
glb.headerbuffermax = AM_HEADERBUFFER_MAX -1;
}
glb.headerbuffer[glb.headerbuffermax] = malloc(sizeof(char) *(AM_HEADERBUFFER_ITEM_SIZE +1));
if (!glb.headerbuffer[glb.headerbuffermax])
{
LOGGER_log("alterMIME: AM_hbuffer_add: Error: cannot allocate %d bytes for new header", AM_HEADERBUFFER_ITEM_SIZE +1);
return NULL;
}
strncpy(glb.headerbuffer[glb.headerbuffermax], headerline, AM_HEADERBUFFER_ITEM_SIZE);
glb.headerbuffermax++;
return glb.headerbuffer[glb.headerbuffermax-1];
}
/*------------------------------------------------------------------------
Procedure: AM_hbuffer_getmax ID:1
Purpose:
Input:
Output:
Errors:
------------------------------------------------------------------------*/
int AM_hbuffer_getmax( void )
{
return glb.headerbuffermax;
}
/*------------------------------------------------------------------------
Procedure: AM_hbuffer_getline ID:1
Purpose:
Input:
Output:
Errors:
------------------------------------------------------------------------*/
char *AM_hbuffer_getline( int index )
{
if ((index >=0)&&(index < glb.headerbuffermax))
return glb.headerbuffer[index];
else return NULL;
}
/*------------------------------------------------------------------------
Procedure: AM_set_glb.paranoid ID:1
Purpose:
Input:
Output:
Errors:
------------------------------------------------------------------------*/
int AM_set_paranoid( int level )
{
glb.paranoid = level;
return glb.paranoid;
}
/*-----------------------------------------------------------------\
Function Name : AM_set_header_long_search
Returns Type : int
----Parameter List
1. int level ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_set_header_long_search( int level )
{
glb.header_long_search = level;
return glb.header_long_search;
}
/*------------------------------------------------------------------------
Procedure: AM_set_glb.verbose ID:1
Purpose:
Input:
Output:
Errors:
------------------------------------------------------------------------*/
int AM_set_verbose( int level )
{
glb.verbose = level;
return glb.verbose;
}
/*------------------------------------------------------------------------
Procedure: AM_set_HTMLtoo ID:1
Purpose:
Input:
Output:
Errors:
------------------------------------------------------------------------*/
int AM_set_HTMLtoo( int level )
{
glb.HTML_too = level;
return glb.HTML_too;
}
/*------------------------------------------------------------------------
Procedure: AM_set_multipart_insert ID:1
Purpose: Set the multipart-insertion of dislcaimers to level. Multipart disclaimers are not on by default
because they would imply inserting disclaimers into previously forwarded messages.
Input: level
Output:
Errors:
------------------------------------------------------------------------*/
int AM_set_multipart_insert( int level )
{
glb.multipart_insert = level;
return glb.multipart_insert;
}
/*-----------------------------------------------------------------\
Function Name : *AM_set_disclaimer_plain
Returns Type : char
----Parameter List
1. char *filename,
2. int disclaimer_type ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
Sets the disclaimer text and text-type for Plain-text disclaimers.
What we mean here is, if the disclaimer_type is 'TEXT' then we
don't interpret the contents of .disclaimer_plain as a filename
rather, we take it literally as the text to use for our disclaimer
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
char *AM_set_disclaimer_plain( char *filename, int disclaimer_type )
{
glb.disclaimer_plain = strdup( filename );
glb.disclaimer_plain_type = disclaimer_type;
return glb.disclaimer_plain;
}
/*-----------------------------------------------------------------\
Function Name : *AM_set_disclaimer_HTML
Returns Type : char
----Parameter List
1. char *filename,
2. int disclaimer_type ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
char *AM_set_disclaimer_HTML( char *filename, int disclaimer_type )
{
glb.disclaimer_HTML = strdup( filename );
glb.disclaimer_HTML_type = disclaimer_type;
return glb.disclaimer_HTML;
}
/*-----------------------------------------------------------------\
Function Name : *AM_set_pretext_plain
Returns Type : char
----Parameter List
1. char *filename,
2. int pretext_type ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
#ifdef ALTERMIME_PRETEXT
int AM_set_pretext_plain( char *filename, int pretext_type )
{
glb.pretext_plain = strdup( filename );
glb.pretext_plain_type = pretext_type;
return 0;
}
#endif
/*-----------------------------------------------------------------\
Function Name : *AM_set_pretext_HTML
Returns Type : char
----Parameter List
1. char *filename,
2. int pretext_type ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
#ifdef ALTERMIME_PRETEXT
int AM_set_pretext_HTML( char *filename, int pretext_type )
{
glb.pretext_HTML = strdup( filename );
glb.pretext_HTML_type = pretext_type;
return 0;
}
#endif
/*------------------------------------------------------------------------
Procedure: AM_set_nullifyall ID:1
Purpose:
Input:
Output:
Errors:
------------------------------------------------------------------------*/
int AM_set_nullifyall( int level )
{
glb.nullify_all = level;
return glb.nullify_all;
}
/*------------------------------------------------------------------------
Procedure: AM_set_altersigned ID:1
Purpose: Turns on or off the option to alter signed email messages.
Default is to be -off-.
Input:
Output:
Errors:
------------------------------------------------------------------------*/
int AM_set_altersigned( int level )
{
glb.alter_signed = level;
return glb.alter_signed;
}
/*-----------------------------------------------------------------\
Function Name : AM_ntorn
Returns Type : int
----Parameter List
1. char *in,
2. FILE *out ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_ntorn( char *in, FILE *out )
{
char *p = in;
char lastchar=' ';
while (*p)
{
if ((*p == '\n')&&(lastchar != '\r')) fprintf(out, "\r");
fprintf(out, "%c", *p);
lastchar = *p;
p++;
}
return 0;
}
/*------------------------------------------------------------------------
Procedure: AM_base64_encodef ID:1
Purpose: encode to BASE64 an input stream to an output stream
Input: FILE *fin: Input stream
FILE *fout: Output stream
Output:
Errors:
------------------------------------------------------------------------*/
int AM_base64_encodef( FILE *fin, FILE *fout )
{
unsigned char inbuf[3];
unsigned char outbuf[4];
int cc;
int byte_count;
if (!fin)
{
LOGGER_log("AM_base64_encodef: Error: input file stream not open, please use AM_base64_encode(infile,outfile)");
return -1;
}
if (!fout)
{
LOGGER_log("AM_base64_encodef: Error: output file stream not open, please use AM_base64_encode(infile,outfile)");
return -1;
}
cc = 0; /* Character Count = 0 */
while ((byte_count = fread(inbuf, 1, 3, fin))) /* while we get more than 0 bytes */
{
/* Split 3 bytes into 4 bytes by taking 6bit blocks out of the 24 bit (3x8bit)
* array */
outbuf[0] = AM_encode64[((inbuf[0] & 0xFC) >> 2)];
outbuf[1] = AM_encode64[((inbuf[0] & 0x03) << 4) | ((inbuf[1] & 0xF0) >> 4)];
outbuf[2] = AM_encode64[((inbuf[1] & 0x0F) << 2) | ((inbuf[2] & 0xC0) >> 6)];
outbuf[3] = AM_encode64[(inbuf[2] & 0x3F)];
/* if we didn't get a full 3 bytes from the file read, then we need to then
* pad the output with '=' (base64 padding char) */
if ( byte_count < 3 )
{
if ( byte_count == 2 ) outbuf[3] = '=';
if ( byte_count == 1 ) outbuf[3] = outbuf[2] = '=';
fwrite(outbuf, 1, 4, fout); /* write out the buffer */
fwrite( "\n", 1, 1, fout ); /* as this is the last line, put a trailing \n */
break; /* exit out of the while loop */
}
fwrite(outbuf, 1, 4, fout); /* normal operation buffer-write */
cc += 4; /* increment the output char count by 4 */
if (cc > 76) /* if we have more than 76 chars, then put in a \n */
{
fwrite( "\n", 1, 1, fout ); /* write the \n out */
cc = 0; /* reset the output char count */
} /* if bytecount == 3 */
} /* while more data to read in */
return 0;
}
/*------------------------------------------------------------------------
Procedure: AM_base64_encode ID:1
Purpose: Encodes a given input stream into BASE64 and outputs to a given
output stream
Input: char *enc_fname: Input filename of the stream to encode
char *out_fname: Output filename of the stream to send to
Output:
Errors:
------------------------------------------------------------------------*/
int AM_base64_encode( char *enc_fname, char *out_fname )
{
FILE *fin, *fout;
fin = fopen( enc_fname, "rb" );
if (!fin)
{
LOGGER_log("AM_base64_encode: Cannot open \"%s\" for reading.(%s)",enc_fname,strerror(errno));
return -1;
}
fout = fopen ( out_fname, "wb" );
if (!fout)
{
LOGGER_log("AM_base64_encode: Cannot open \"%s\" for writing.(%s)",out_fname,strerror(errno));
fclose(fin);
return -1;
}
AM_base64_encodef( fin, fout ); /* encode and output */
fclose(fin); /* close the input file */
fclose(fout); /* close the output file */
return 0;
}
/*------------------------------------------------------------------------
Procedure: AM_add_section1 ID:1
Purpose: Reads the main headers off an email and places the values it finds for
content-type, encoding and other such into the dd record
Input: struct AM_disclaimer_details *dd : pointer to the record where all the details will go
FFGET_FILE *f : Open stream which we're reading the headers from
FILE *newf : Open stream which we're dumping the headers we read back into.
Output:
Errors:
------------------------------------------------------------------------*/
int AM_read_headers(struct AM_disclaimer_details *dd, FFGET_FILE *f, FILE *newf )
{
struct MIMEH_header_info hinfo;
DAM LOGGER_log("%s:%d:AM_read_headers:DEBUG: Starting to read headers", FL );
dd->isfile = 0;
dd->ishtml = 0;
memset( &hinfo, '\0', sizeof(struct MIMEH_header_info));
hinfo.uudec_name[0] = '\0';
if (FFGET_feof(f) != 0) return -1;
// MIMEH_set_headers_save( newf ); // 20040309-2243:PLD
MIMEH_set_doubleCR_save(0);
MIMEH_set_header_longsearch(0); /** Turn off qmail searching, is not applicable here **/
MIMEH_set_headers_original_save_to_file( newf );
MIMEH_parse_headers( f, &hinfo );
MIMEH_set_headers_original_save_to_file( NULL );
MIMEH_set_doubleCR_save(1);
MIMEH_set_headers_nosave();
/* Copy over our information */
dd->content_type = hinfo.content_type;
dd->content_encoding = hinfo.content_transfer_encoding;
if (hinfo.boundary_located > 0) {
dd->boundary_found = 1;
snprintf( dd->boundary, sizeof(dd->boundary), "%s", hinfo.boundary );
}
if (strlen(hinfo.filename) > 0) dd->isfile = 1;
if (strlen(hinfo.name) > 0) dd->isfile = 1;
/**
** list here any content transfer encodings you
** don't want to have disclaimers inserted into
**/
switch (hinfo.content_transfer_encoding) {
case _CTRANS_ENCODING_B64:
case _CTRANS_ENCODING_UUENCODE:
case _CTRANS_ENCODING_BINARY:
dd->isfile = 1;
break;
}
switch (hinfo.content_type) {
case _CTYPE_MULTIPART_REPORT:
case _CTYPE_RFC822:
dd->isfile = 1;
break;
}
/** If our content-type is of HTML, then we'll mark that we're in a HTML
** section. NOTE - this doesn't mean that the section isn't a file!
** so, we have to check later on to see that dd.isfile == 0 still.
**/
if (dd->content_type == _CTYPE_TEXT_HTML) dd->ishtml = 1;
if (hinfo.content_type == _CTYPE_MULTIPART_SIGNED)
{
DAM LOGGER_log("%s:%d:AM_read_headers:DEBUG: Email is signed, return SIGNED_EMAIL",FL);
return AM_RETURN_SIGNED_EMAIL;
}
DAM LOGGER_log("%s:%d:AM_read_headers:DEBUG: Exit ( Header read section ).\n\t-- isfile=%d ishtml=%d boundaryfound=%d\n\n", FL, dd->isfile, dd->ishtml, dd->boundary_found );
return 0;
}
/*------------------------------------------------------------------------
Procedure: AM_disclaimer_load_text ID:1
Purpose: Loads the text from the file given in fname, and mallocs the required amount
of memory to place the entire contents of the file into textptr.
Input: char *fname : The file name from which to read the dislcaimer text
char **textptr : a derefernced pointer to the buffer variable which will hold the text.
Output:
Errors:
------------------------------------------------------------------------*/
int AM_disclaimer_load_text( char *fname, char **textptr )
{
FILE *f;
struct stat st;
if (0 == stat(fname, &st))
{
*textptr = malloc(st.st_size +1); // We have to add 1 so that we delimit the data with a \0
if (textptr)
{
memset(*textptr, '\0', (st.st_size +1));
f = fopen(fname,"r");
if (!f)
{
LOGGER_log("%s:%d:ERROR - Cannot open %s for reading (%s)",__FILE__,__LINE__,fname,strerror(errno));
textptr = NULL;
return 0;
}
fread(*textptr, 1, st.st_size, f);
DAM LOGGER_log("Disclaimer Loaded:\n%s",*textptr);
fclose(f);
}
}
else
{
LOGGER_log("%s:%d:ERROR - Cannot stat '%s' (%s)",__FILE__,__LINE__,fname,strerror(errno));
}
return 0;
}
int AM_disclaimer_html_perform_insertion( struct AM_disclaimer_details *dd, FFGET_FILE *f, FILE *newf )
{
if (dd->content_encoding == _CTRANS_ENCODING_QP) {
char *qp_data;
size_t qp_data_size;
char *data_to_use;
if (dd->disclaimer_text_HTML == NULL) data_to_use = dd->disclaimer_text_plain;
else data_to_use = dd->disclaimer_text_HTML;
qp_data_size = strlen(data_to_use) *3 +1;
qp_data = malloc( qp_data_size *sizeof(char));
if (qp_data == NULL) {
LOGGER_log("%s:%d:AM_disclaimer_html_perform_insertion:DEBUG: Error trying to allocate %d bytes of memory for QP encoded disclaimer",FL, qp_data_size);
return -1;
}
qp_encode( qp_data, qp_data_size, data_to_use, strlen(data_to_use));
DAM LOGGER_log("%s:%d:AM_disclaimer_html_perform_insertion:DEBUG: Inserting QP encoded disclaimer",FL);
if (dd->disclaimer_text_HTML == NULL) {
fprintf(newf,"<br><pre>=\r\n");
AM_ntorn(qp_data, newf);
fprintf(newf,"</pre><br>=\r\n");
} else {
fprintf(newf,"<br>=\r\n");
AM_ntorn(qp_data, newf);
fprintf(newf,"<br>=\r\n");
}
/** cleanup **/
if (qp_data) free(qp_data);
/** Quoted printable email segment, so we have to encode everything **/
} else {
if (dd->disclaimer_text_HTML == NULL)
{
fprintf(newf,"<br><pre>\r\n");
AM_ntorn(dd->disclaimer_text_plain, newf);
fprintf(newf,"</pre><br>\r\n");
}
else
{
fprintf(newf,"<br>\r\n");
AM_ntorn(dd->disclaimer_text_HTML, newf);
fprintf(newf,"<br>\r\n");
}
} /** quotedprintable / non encoding selector **/
return 0;
}
/*------------------------------------------------------------------------
Procedure: AM_add_disclaimer_insert_html ID:1
Purpose: Inserts a disclaimer with <PRE> tagging into a HTML block of text just prior to the
end of the HTML. Assumes that it has been placed at the start of the HTML block
Input: AM_disclaimer_details *dd : Disclaimer tracking information
FFGET_FILE *f : Input file
FILE *newf : Output file
Output:
Errors:
------------------------------------------------------------------------*/
int AM_add_disclaimer_insert_html( struct AM_disclaimer_details *dd, FFGET_FILE *f, FILE *newf )
{
char boundary[ AM_1K_BUFFER_SIZE +1];
int boundary_length = 0;
char line[ AM_1K_BUFFER_SIZE +1];
char lline[ AM_1K_BUFFER_SIZE +1];
char *prebody, *tmpbody;
DAM LOGGER_log("%s:%d:AM_add_disclaimer_insert_html:DEBUG: Starting to attempt to insert HTML disclaimer",FL);
if (dd->boundary_found == 1)
{
snprintf(boundary, sizeof(boundary), "--%s", dd->boundary);
boundary_length = strlen(boundary);
}
while (FFGET_fgets(line, AM_1K_BUFFER_SIZE, f))
{
/** 20041019-1135:PLD: Applied diff as contributed by Tim (datalore)
**
** If we reached the end of the boundary, check if force html insertion is
** enabled. If so, force the html disclaimer into the message.
**/
if(dd->boundary_found == 1 && strncmp(boundary, line, boundary_length) == 0)
{
DAM LOGGER_log("%s:%d:AM_add_disclaimer_insert_html: End of boundary reached before html disclamer was added...",FL);
if (glb.force_for_bad_html == 1)
{
DAM LOGGER_log("%s:%d:Forcing insertion of html disclaimer into non valid html body...",FL);
dd->html_inserted = 1;
AM_disclaimer_html_perform_insertion( dd, f, newf );
}
// write the boundary line
fprintf(newf, "%s", line);
// stop searching/adding/whatever
break;
}
/** not at end of boundary, so search for body/html tag **/
/** End of patch as supplied by Tim (datalore) **/
strcpy(lline,line);
PLD_strlower(lline);
// Look for either a BODY or HTML closing tag.
prebody = strstr(lline,"</body");
if (!prebody) prebody = strstr(lline,"</html");
// If we found one of the tags, then insert our disclaimer aruond about
// here.
if (prebody != NULL)
{
DAM LOGGER_log("%s:%d:AM_add_disclaimer_insert_html:DEBUG: prebody = %s",FL,prebody);
DAM LOGGER_log("%s:%d:AM_add_disclaimer_insert_html:DEBUG: Inserting html-body disclaimer",FL);
dd->html_inserted = 1;
// prepare to print out the original up to the </body> or </html> part
tmpbody = line +(prebody -lline);
*tmpbody = '\0';
// save to file the first part of the line segment
fprintf(newf,"%s\r\n",line);
AM_disclaimer_html_perform_insertion( dd, f, newf );
fprintf(newf,"<%s",(tmpbody+1));
break;
} else {
fprintf(newf,"%s",line);
}
} // While FFGET_fgets()
return dd->html_inserted;
}
/*-----------------------------------------------------------------\
Function Name : AM_add_disclaimer_cleanup
Returns Type : int
----Parameter List
1. FILE *mp,
2. FILE *newf,
3. char *mpacktmp,
4. char *mpackname ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_add_disclaimer_cleanup( FILE *mp, FILE *newf, char *mpacktmp, char *mpackname )
{
/** If the input file wasn't stdin, then we will
** need to close the input and output files and
** rename the old to the new **/
if (strcmp(mpackname,"-"))
{
// Close our files
fclose(mp);
fclose(newf);
// rename the files
rename(mpacktmp, mpackname);
}
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_add_disclaimer_flush
Returns Type : int
----Parameter List
1. FFGET_FILE *f,
2. FILE *newf,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_add_disclaimer_flush( FFGET_FILE *f, FILE *newf )
{
char line[AM_1K_BUFFER_SIZE+1]="";
if ( ! FFGET_feof( f ) )
{
DAM LOGGER_log("%s:%d:AM_add_disclaimer_flush:DEBUG: Appending remaining of file",FL);
while (FFGET_fgets(line, AM_1K_BUFFER_SIZE, f))
{
fprintf(newf,"%s",line);
}
DAM LOGGER_log("%s:%d:AM_add_disclaimer_flush:DEBUG: Done appending.",FL);
}
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_read_to_boundary
Returns Type : int
----Parameter List
1. FFGET_FILE *infile, Source data file
2. FILE *outf , File to send data to
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
Copies data over from the current MIME segment from infile to outf.
Copying stops when a boundary line is found.
The boundary line is written to the output file.
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_read_to_boundary( FFGET_FILE *infile, FILE *outf, char *buffer, size_t buffer_size )
{
while (FFGET_fgets(buffer, buffer_size, infile))
{
fprintf(outf,"%s",buffer);
if ( (BS_cmp(buffer,strlen(buffer))==1) )
{
DAM LOGGER_log("%s:%d:AM_read_to_boundary:DEBUG: Boundary hit while reading MIME segment, breaking out of while loop",FL);
break;
}
}
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_load_disclaimers
Returns Type : int
----Parameter List
1. struct AM_disclaimer_details *dd ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
Load the disclaimers into the disclaimer structure.
Returns -1 if neither of the disclaimers could be
loaded.
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_load_disclaimers( struct AM_disclaimer_details *dd )
{
int dud_html=0, dud_text=0;
dd->disclaimer_text_HTML = NULL;
dd->disclaimer_text_plain = NULL;
if ((glb.disclaimer_plain != NULL )&&(glb.disclaimer_plain_type != AM_DISCLAIMER_TYPE_NONE))
{
if (glb.disclaimer_plain_type == AM_DISCLAIMER_TYPE_FILENAME)
{
AM_disclaimer_load_text( glb.disclaimer_plain, &(dd->disclaimer_text_plain) );
}
else
{
dd->disclaimer_text_plain = glb.disclaimer_plain;
}
} else {
// LOGGER_log("AM_add_disclaimer: Plain-text disclaimer has not been setup correctly\n");
dud_text=1;
}
if ((glb.disclaimer_HTML != NULL )&&(glb.disclaimer_HTML_type != AM_DISCLAIMER_TYPE_NONE))
{
if (glb.disclaimer_HTML_type == AM_DISCLAIMER_TYPE_FILENAME)
{
AM_disclaimer_load_text( glb.disclaimer_HTML, &(dd->disclaimer_text_HTML) );
}
else
{
dd->disclaimer_text_HTML = glb.disclaimer_HTML;
}
} else {
// LOGGER_log("AM_add_disclaimer: HTML-text disclaimer has not been setup correctly\n");
dud_html=1;
}
// If both our disclaiemrs are 'dud's, then we should just return quietly
if ((dud_html == 1)&&(dud_text == 1))
{
LOGGER_log("%s:%d:AM_load_disclaimers: Neither the Plain-text or HTML disclaimer were valid to insert, skipping disclaimer-insertion routine\n");
return -1;
}
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_add_disclaimer_plain_text
Returns Type : int
----Parameter List
1. FFGET_FILE *f,
2. FILE *newf,
3. struct AM_disclaimer_details *dd ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_add_disclaimer_no_boudary( FFGET_FILE *f, FILE *newf, struct AM_disclaimer_details *dd )
{
char line[AM_1K_BUFFER_SIZE+1]="";
DAM LOGGER_log("%s:%d:AM_add_disclaimer_no_boudary:DEBUG: Inserting disclaimer into a non-boundary email",FL);
// If we have a HTML email body, then insert the HTML based
// disclaimer, or, the plain-text one with <PRE>...</pre>
// tagging
switch (dd->content_type) {
case _CTYPE_TEXT_HTML:
DAM LOGGER_log("%s:%d:AM_add_disclaimer_no_boudary:DEBUG: Conditions right for HTML disclaimer to be added",FL);
AM_add_disclaimer_insert_html( dd, f, newf );
if ((glb.verbose)&&( dd->html_inserted == 0 ))
{
LOGGER_log("WARNING: Could not insert HTML disclaimer into email\n");
}
break;
default:
// If we have a plain-text email body, then just get to the end of the file
// and append the disclaimer to the end.
DAM LOGGER_log("%s:%d:AM_add_disclaimer_insert_html:DEBUG: Seeking to the end of the file for plain-text insertion...",FL);
/** Read to the end of the file **/
AM_read_to_boundary( f, newf, line, AM_1K_BUFFER_SIZE );
DAM LOGGER_log("%s:%d:AM_add_disclaimer_no_boudary:DEBUG: About to write disclaimer '%s'",FL,dd->disclaimer_text_plain);
fprintf(newf,"%s",dd->disclaimer_text_plain);
dd->text_inserted = 1;
DAM LOGGER_log("%s:%d:AM_add_disclaimer_no_boudary:DEBUG: Disclaimer now written to file",FL);
break;
}
DAM LOGGER_log("%s:%d:AM_add_disclaimer_no_boudary:DEBUG: Done, text-inserted=%d, html-inserted=%d"
,FL
,dd->text_inserted
,dd->html_inserted
);
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_insert_disclaimer_into_segment
Returns Type : int
----Parameter List
1. FFGET_FILE *f,
2. FILE *newf,
3. struct AM_disclaimer_details *dd ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_insert_disclaimer_into_segment( FFGET_FILE *f, FILE *newf, struct AM_disclaimer_details *dd )
{
char line[AM_1K_BUFFER_SIZE+1]="";
int last_boundary_written = -1;
int result = 0;
int insert_success = 0;
DAM LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: Inserting disclaimer into segment",FL);
if ( (0 == dd->text_inserted) \
&& (dd->content_type == _CTYPE_TEXT_PLAIN ) \
&& (dd->isfile == 0) \
)
{
DAM LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: Conditions right to insert disclaimer\n",FL);
DAM LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: Reading first segment looking for boundary line\n",FL);
// Read and write data until we locate the boundary line.
// NOTE - we -deliberately- break before writing the boundary
// line because we want to insert the disclaimer /before/
// we write the boundary line.
while (FFGET_fgets(line, AM_1K_BUFFER_SIZE, f))
{
last_boundary_written = 0;
if ( BS_cmp(line,strlen(line))==1 )
{
DAM LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: Boundary hit",FL);
break;
}
fprintf( newf, "%s", line );
last_boundary_written = 1;
}
if ( FFGET_feof( f ) ) return -1;
DAM LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: Inserting TEXT disclaimer (%s)\n", FL, dd->disclaimer_text_plain);
if (dd->content_encoding == _CTRANS_ENCODING_QP)
{
/** convert the disclaimer into QP **/
char *qp_data;
size_t qp_data_size;
qp_data_size = strlen(dd->disclaimer_text_plain) *3 +1;
qp_data = malloc( qp_data_size *sizeof(char));
if (qp_data == NULL) {
LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: Error trying to allocate %d bytes of memory for QP encoded disclaimer",FL, qp_data_size);
return -1;
}
qp_encode( qp_data, qp_data_size, dd->disclaimer_text_plain, strlen(dd->disclaimer_text_plain));
DAM LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: Inserting QP encoded disclaimer",FL);
fprintf( newf, "%s\n", qp_data );
/** cleanup **/
if (qp_data) free(qp_data);
} else {
/** Normal plain-text insertion **/
fprintf( newf, "%s\n", dd->disclaimer_text_plain );
}
dd->text_inserted = 1; /** Our disclaimer has been inserted, so set the flag **/
insert_success = 1;
DAM LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: TEXT disclaimer is inserted, now flushing file output",FL);
fflush(newf);
if ( last_boundary_written == 0 )
{
fprintf( newf, "%s", line );
last_boundary_written = 1;
}
} else {
DAM LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: Conditions not right to insert a TEXT disclaimer",FL);
}
// Add in the HTML disclaimer (if wanted)
DAM LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: glb.HTML_too=%d, dd->html_inserted=%d, dd->content_type=%d, dd->isfile=%d"
,FL
,glb.HTML_too
,dd->html_inserted
,dd->content_type
,dd->isfile
);
if ( ( glb.HTML_too ) \
&& ( 0 == dd->html_inserted ) \
&& ( dd->content_type == _CTYPE_TEXT_HTML )\
&& ( dd->isfile == 0 ) \
)
{
DAM LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: Conditions right for HTML disclaimer to be added",FL);
result = AM_add_disclaimer_insert_html( dd, f, newf );
if ((glb.verbose)&&(0 == result))
{
LOGGER_log("WARNING: Could not insert HTML disclaimer into email");
} else {
dd->html_inserted = 1;
insert_success = 1;
}
} else {
DAM LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: Conditions not right to insert HTML disclaimer",FL);
}
/** If we weren't able to insert a disclaimer, then read through to the end of the segment **/
if (insert_success == 0) {
while (FFGET_fgets(line, AM_1K_BUFFER_SIZE, f)) {
last_boundary_written = 0;
fprintf( newf, "%s", line );
if ( BS_cmp(line,strlen(line))==1 ) {
DAM LOGGER_log("%s:%d:AM_insert_disclaimer_into_segment:DEBUG: Boundary hit, breaking out",FL);
break;
}
}
}
return 0;
}
/*------------------------------------------------------------------------
Procedure: AM_add_disclaimer ID:1
Purpose: Adds a disclaimer to [potentially] plain-text, mixed/multipart and HTML emails.
Input: char *mpackname: Mailpack/email name we're modifying
char *disclaimer: either the text, or the filename of the text we want to insert
int istext: 0 = dislciamer is text, 1 = disclaimer is filename
Output:
Errors:
------------------------------------------------------------------------*/
int AM_add_disclaimer( char *mpackname )
{
FILE *mp, *newf;
FFGET_FILE f;
char line[AM_1K_BUFFER_SIZE+1]="";
char mpacktmp[AM_1K_BUFFER_SIZE+1]="";
char mpackold[AM_1K_BUFFER_SIZE+1]="";
struct AM_disclaimer_details dd;
int result = 0;
int segment_read = 0;
/* create our temp filename */
snprintf(mpacktmp,AM_1K_BUFFER_SIZE, "%s.tmp",mpackname);
snprintf(mpackold,AM_1K_BUFFER_SIZE, "%s.old",mpackname);
if (strcmp(mpackname,"-")==0)
{
mp = stdin;
newf = stdout;
} else {
/* Initialise our files */
newf = fopen(mpacktmp,"w");
mp = fopen(mpackname,"r");
}
/* Initialise the Boundary-stack globals */
BS_init();
/* Allow 10 boundaries to be stored */
BS_set_hold_limit(10);
// Test file statuses... and hop out if we had troubles with either
//
if (!newf)
{
LOGGER_log("AM_add_disclaimer: Cannot open %s, %s",mpacktmp,strerror(errno));
return -1;
}
if (!mp)
{
LOGGER_log("AM_add_disclaimer: Cannot open %s, %s",mpacktmp,strerror(errno));
return -1;
}
// Set up the disclaimer text as required to be inserted into the file
//
result = AM_load_disclaimers( &dd );
if (result == -1) return 0; // Just exit if we couldn't load the disclaimers.
// Initialise our variables
dd.content_type = _CTYPE_UNKNOWN;
dd.content_encoding = _CTRANS_ENCODING_UNKNOWN;
dd.boundary_found = 0;
memset(dd.boundary, 0, sizeof(dd.boundary));
dd.ishtml = 0;
dd.isfile = 0;
dd.text_inserted = 0;
dd.html_inserted = 0;
VAM LOGGER_log("Attempting to add disclaimer");
FFGET_setstream(&f, mp);
// The process....
//
// First, we read through the email until we at least come across the traditional
// true-blank seperator line between the headers and the body of the email.
// Once we have found that, we can then choose how we're going to detect our first
// disclaimer position based on what we discovered in the headers (ie, did we
// find a boundary specifier? are we using plain-text here, or does the email have
// HTML in it?
//
// If we have just a plain text email, then we wait till we reach the end of the email
// and we just append the disclaimer
//
// If we [most likely] dont have a plain email, then we need to wait until we find
// the boundary line, then, insert the disclaimer there.
//
// Next, we need to check to see if the NEXT section is the HTML version of the first
// section (well, we dont /really/ compare them, but we check the headers to see if
// it has things like text/html and no filename
// If the section is HTML and non-filenamed, we once more wait until the boundary then
// we add in a HTML version of the disclaimer.
//
// Sounds easy ?
//
// Note - It should be pointed out also, that alterMIME will not insert disclaimers into what
// are just forwarded emails [ Content-Type: message/rfc822 ].
//
// Note - the boundary is ONLY taken from the main headers. This is to prevent us
// attempting to dive in deeper into the email adding disclaimers to parts which
// were not generated by the last mail user agent.
// Section 1 - read in the MAIN headers until a true-blank.
// This will give us some indication of what to expect in the email, be it a multipart
// a file, or a plain text email.
//
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Reading main headers",FL);
result = AM_read_headers( &dd, &f, newf );
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Main Headers have been read",FL);
// Check to see if we can actually insert into this
// email's encoding.
//
if (dd.content_encoding == _CTRANS_ENCODING_B64)
{
AM_add_disclaimer_flush( &f, newf );
/** 20050626-0111: Patch to remove tmp file on failure supplied by Carlos Velasco **/
fclose(newf);
remove(mpacktmp);
/** endpatch **/
VAM LOGGER_log("Email is BASE64 encoded, we cannot insert into these emails\n");
return AM_RETURN_B64_ENCODED_EMAIL;
}
// Check to see if the email is signed
//
//
if ((AM_RETURN_SIGNED_EMAIL == result)||(-1 == result))
{
AM_add_disclaimer_flush( &f, newf );
fclose(mp);
fclose(newf);
VAM LOGGER_log("Email is signed with a crypto, we do not alter these emails\n");
return AM_RETURN_SIGNED_EMAIL;
}
// If we have no boundary, and we have either text or no content-type
// then we can just append the disclaimer to the /end/ of the email
//&&((dd.content_type >= _CTYPE_TEXT_START && dd.content_type <= _CTYPE_TEXT_END ))
if (
(0 == dd.boundary_found)
&&(0 == dd.isfile)
)
{
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Inserting disclaimer into an email with no boundary",FL);
result = AM_add_disclaimer_no_boudary( &f, newf, &dd );
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Inserting done, txt-inserted=%d html-inserted=%d"
,FL
,dd.text_inserted
,dd.html_inserted
);
}
// BOUNDARY LIMITED EMAILS -----------------
//
// These emails are somewhat /harder/ to insert attachments into
// namely because of the profound number of convoulted ways in which
// email clients can forward/reply/mangle emails.
//
// We use a 'WHILE' loop here just so that we can cheat a bit when
// trying to /exit/ out of the processing prematurely when we run
// out of input (as happens at the end of the input file.
//
// By using a 'WHILE', we can just use 'break', rather than resorting
// to other ugly methods :)
if ( dd.boundary_found == 1 )
{
do {
segment_read = 0;
if (FFGET_feof(&f)) break;
// If we've found a boundary and a text content section...
//
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Inserting into a MIME email\n",FL);
// Step 1 - first read off any spurilous data prior to the first
// MIME block, this typically includes things like
// "If you can read this, then your email client is not MIME
// compliant" (etc).
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Reading spurilous data before first MIME segment\n",FL);
AM_read_to_boundary( &f, newf, line, AM_1K_BUFFER_SIZE );
if (FFGET_feof(&f))
{
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: End of file is reached, pulling out early",FL);
break;
}
// Read SEGMENT 1 headers - and attempt to insert disclaimer
if (AM_read_headers( &dd, &f, newf ) == 0)
{
/** Did we stumble on a boundary, and was it a non-RFC822 form
** (ie, not likely a forwarded email ) **/
if ((dd.boundary_found == 1)&&(dd.content_type != _CTYPE_RFC822))
{
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Located a new non-RFC822 boundary, adding to list\n",FL);
}
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: inserting into SEGMENT 1",FL);
AM_insert_disclaimer_into_segment( &f, newf, &dd );
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Finished inserting into SEGMENT 1",FL);
} else {
DAM LOGGER_log("%s:%d:AM_add_disclaimer: Cannot read headers for SEGMENT 1",FL);
}
// Read SEGMENT 2 headers - and attempt to insert disclaimer
if (AM_read_headers( &dd, &f, newf ) == 0)
{
/** Did we stumble on a boundary, and was it a non-RFC822 form
** (ie, not likely a forwarded email ) **/
if ((dd.boundary_found == 1)&&(dd.content_type != _CTYPE_RFC822))
{
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Located a new non-RFC822 boundary, adding to list\n",FL);
}
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: inserting into SEGMENT 2",FL);
AM_insert_disclaimer_into_segment( &f, newf, &dd );
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Finished inserting into SEGMENT 2",FL);
} else {
DAM LOGGER_log("%s:%d:AM_add_disclaimer: Cannot read headers for SEGMENT 2",FL);
}
// Read SEGMENT 3 headers - and attempt to insert disclaimer, typically we do this
// to handle TXT + HTML combo emails
if (AM_read_headers( &dd, &f, newf ) == 0)
{
/** Did we stumble on a boundary, and was it a non-RFC822 form
** (ie, not likely a forwarded email ) **/
if ((dd.boundary_found == 1)&&(dd.content_type != _CTYPE_RFC822))
{
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Located a new non-RFC822 boundary, adding to list\n",FL);
}
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: inserting into SEGMENT 3",FL);
AM_insert_disclaimer_into_segment( &f, newf, &dd );
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Finished inserting into SEGMENT 3",FL);
} else {
DAM LOGGER_log("%s:%d:AM_add_disclaimer: Cannot read headers for SEGMENT 3",FL);
}
} while (0);
}
// Clean up any remaining lines in the email
AM_add_disclaimer_flush( &f, newf );
VAM LOGGER_log("Done.\n");
AM_add_disclaimer_cleanup( mp, newf, mpacktmp, mpackname);
// Free the malloc'd text if required
if ((glb.disclaimer_plain_type == AM_DISCLAIMER_TYPE_FILENAME)&&(dd.disclaimer_text_plain))
{
free(dd.disclaimer_text_plain);
}
if ((glb.disclaimer_HTML_type == AM_DISCLAIMER_TYPE_FILENAME)&&(dd.disclaimer_text_HTML))
{
free(dd.disclaimer_text_HTML);
}
DAM LOGGER_log("%s:%d:AM_add_disclaimer:DEBUG: Inserting done, txt-inserted=%d html-inserted=%d"
,FL
,dd.text_inserted
,dd.html_inserted
);
return result;
}
/*------------------------------------------------------------------------
Procedure: AM_filename_fix ID:1
Purpose: Converts a MIME header
Input: line: string with the filename
removed_prefix: filename prefix to use
removed_count: the number of attachments we've processed / ID
Output:
Errors:
------------------------------------------------------------------------*/
int AM_filename_fix( char *line, char *removed_prefix, int removed_count )
{
char *pos;
char lline[AM_1K_BUFFER_SIZE];
/* process the current line, as it contains the filename, and replace the filename
* with a -removed- one */
pos = strrchr(line,'='); /* locate where the filename starts */
pos++; /* move one char along */
*pos = '\0'; /* terminate the string */
sprintf(lline,"%s\"%s%d\"\n",line,removed_prefix,removed_count); /* create a new string, in lline (scratch pad)*/
strcpy(line,lline);
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_headers_remove_header
Returns Type : int
----Parameter List
1. char *headers,
2. char *header_name ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_headers_remove_header( char *headers, char *header_name )
{
char delimeter_size;
char *segment_start, *segment_end;
char *p;
if (AM_DNORMAL)LOGGER_log("%s:%d:AM_headers_remove_header:DEBUG: Removing %s", FL, header_name );
delimeter_size=1;
if (strstr(headers,"\n\r") != NULL) delimeter_size=2;
if (strstr(headers,"\r\n") != NULL) delimeter_size=2;
segment_start = PLD_strstr( headers, header_name, 1 );
if (segment_start == NULL) return 1;
segment_end = segment_start +strlen( header_name );
do {
segment_end = strpbrk( segment_end, "\n\r" );
if (delimeter_size==2) segment_end+=2; else segment_end++;
} while (*segment_end==' '||*segment_end=='\t');
p = segment_end;
while (*p)
{
*segment_start = *p;
segment_start++;
p++;
}
*segment_start = '\0';
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_nullify_attachment_clean_headers
Returns Type : int
----Parameter List
1. struct MIMEH_header_info *hinfo,
2. char *headers ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_nullify_attachment_clean_headers( struct MIMEH_header_info *hinfo, char *headers )
{
AM_headers_remove_header( headers, "content-type:" );
AM_headers_remove_header( headers, "content-disposition:");
AM_headers_remove_header( headers, "content-transfer-encoding");
/*
char *p;
char *q;
q = headers;
p = strstr( q, hinfo->filename );
while (( p != NULL )&&( p > q ))
{
if (*p == '_') *p = 'X'; else *p = '_';
q = p+1;
p = strstr( q, hinfo->filename );
}
*/
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_nullify_attachment_recurse
Returns Type : int
----Parameter List
1. struct MIMEH_header_info *hinf,
2. FFGET_FILE *f,
3. FILE *outputfile,
4. pregex_t *preg ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_nullify_attachment_recurse( struct MIMEH_header_info *hinfo, FFGET_FILE *f, FILE *outputfile, regex_t *preg, int match_mode, int iteration )
{
int result = 0;
if (AM_DNORMAL) LOGGER_log("%s:%d:AM_nullify_attachment_recurse: Checking segment-iteration '%d'",FL,iteration);
while (1)
{
int regresult=0;
char *header_ptr=NULL;
char *original_ptr=NULL;
char buffer[1024];
MIMEH_set_doubleCR_save(0);
MIMEH_set_header_longsearch(glb.header_long_search);
MIMEH_set_doubleCR_save(1);
DAM LOGGER_log("%s:%d:AM_nullify_attachment_recurse:DEBUG: Reading headers... Iteration %d",FL);
result = MIMEH_headers_get( hinfo, f );
if (result != 0)
{
DAM LOGGER_log("%s:%d:AM_nullify_attachment_recurse:DEBUG: Failure during header read (EOF?), exiting loop",FL);
break;
}
/** If we detect a signed message and we're not explicitly wanting to alter them, then exit **/
if ((hinfo->content_type == _CTYPE_MULTIPART_SIGNED)&&(glb.alter_signed==0)) {
DAM LOGGER_log("%s:%d:AM_nullify_attachment_recurse:DEBUG: Message is signed, exiting",FL);
return 0;
}
original_ptr = MIMEH_get_headers_original_ptr();
header_ptr = MIMEH_get_headers_ptr();
if (AM_DNORMAL)LOGGER_log("%s:%d:AM_nullify_attachment_recurse:DEBUG: Headers =\n%s\n-----------END OF HEADERS\n",FL, original_ptr );
if (original_ptr == NULL)
{
LOGGER_log("%s:%d:AM_nullify_attachment_recurse:ERROR: Original headers came back NULL",FL);
return 1;
}
if (header_ptr == NULL)
{
LOGGER_log("%s:%d:AM_nullify_attachment_recurse:ERROR: Header ptr (for processing) came back NULL",FL);
return 1;
}
result = MIMEH_headers_process( hinfo, header_ptr );
if (result != 0)
{
LOGGER_log("%s:%d:AM_nullify_attachment:ERROR: While processing headers for mailpack", FL );
break;
}
// Check to see if we have a new boundary
if ((hinfo->content_type >= _CTYPE_MULTIPART_START && hinfo->content_type <= _CTYPE_MULTIPART_END)\
&& (hinfo->boundary_located > 0))
{
BS_push( hinfo->boundary );
}
// Now, determine if this block/segment is the one which contains our file which we must 'nullify'
regresult = 1;
switch (match_mode) {
case AM_NULLIFY_MATCH_MODE_FILENAME:
if (strlen(hinfo->filename) > 0)
{
regresult = regexec( preg, hinfo->filename, 0, NULL, 0 );
if (AM_DNORMAL)LOGGER_log("%s:%d:AM_nullify_attachment: Match result=%d for '%s'", FL, regresult, hinfo->filename);
}
if ((regresult != 0)&&(strlen(hinfo->name) > 0))
{
regresult = regexec( preg, hinfo->name, 0, NULL, 0 );
if (AM_DNORMAL)LOGGER_log("%s:%d:AM_nullify_attachment: Match result=%d for '%s'", FL, regresult, hinfo->name);
}
break;
case AM_NULLIFY_MATCH_MODE_CONTENT_TYPE:
if (strlen(hinfo->content_type_string) > 0)
{
regresult = regexec( preg, hinfo->content_type_string, 0, NULL, 0 );
if (AM_DNORMAL)LOGGER_log("%s:%d:AM_nullify_attachment: Match result=%d for '%s'", FL, regresult, hinfo->content_type_string);
}
break;
default:
LOGGER_log("%s:%d:AM_nullify_attachment: unknown Nullify match mode '%d'", FL, match_mode);
}
// If we're on our first pass, or we've not found the section/block with the attachment
// then write the headers out.
//
// A bit of an issue which comes up is, what to do if the entire email is the attachment
// ie, it's just a something which was 'right-click->email'd.
//
// Until I come up with a better solution, I'll still write the headers, but I'll then
// eliminate all the content [until the next boundary, assuming a boundary exists]
if ((regresult != 0)||(iteration == 1))
{
// If we've got a match which is in the first section of the email, then
// we have to 'modify' the headers rather than not writing them at all.
// this way, we still keep the email structure intact, but we make it so
// that the attachment-related portions of the email changed.
if ((iteration == 1)&&(regresult == 0))
{
AM_nullify_attachment_clean_headers( hinfo, original_ptr );
}
fwrite( original_ptr, sizeof(char), strlen(original_ptr), outputfile );
}
// Clean up the memory allocation
result = MIMEH_headers_cleanup();
if (result != 0)
{
LOGGER_log("%s:%d:AM_nullify_attachment:ERROR: while attempting to clean up headers memory allocation", FL );
break;
}
// Now, if we have a Multipart/Mixed attachment, then, alas, we need to recurse into
// it and see if it contains anything interesting for us to seek out.
if (hinfo->content_type == _CTYPE_RFC822)
{
result=AM_nullify_attachment_recurse( hinfo, f, outputfile, preg, match_mode, 1 );
}
// Now, we shall go through and read the email until we happen across another boundary.
while (FFGET_fgets(buffer, sizeof(buffer), f))
{
int buffer_len = strlen(buffer);
if (regresult != 0)
{
fwrite( buffer, sizeof(char), buffer_len, outputfile );
}
if (BS_cmp( buffer, buffer_len ) == 1) {
break;
}
} // While ffgets
if (FFGET_feof(f)) break;
iteration++;
} // Infinite while(1)
return 0;
}
/*------------------------------------------------------------------------
Procedure: AM_nullify_attachment ID:1
Purpose: Removes any attachment within the mailpack which matches
(minimally) with the attachmentname parameter
Input: mpackname - The mailpack name
attachmentname - The name of attachments which is to be removed
Output: none
Errors: If attachment wasn't found, or could not be removed, a non-zero return value.
------------------------------------------------------------------------*/
int AM_nullify_attachment( char *mpackname, char *attachmentname )
{
struct MIMEH_header_info hinfo;
regex_t preg;
int result = 0;
int match_mode = AM_NULLIFY_MATCH_MODE_NONE;
char tmpfname[256];
char oldfname[256];
FILE *inputfile;
FILE *outputfile;
FFGET_FILE f;
// Nullifying an attachment can sometimes be a little bit tricky, we have to
// dig down into the nesting of the MIME email and keep tabs on our
// boundaries (via a Boundary-stack). This all requires about as much work
// as ripMIME just to get rid of one attachment.
//
// Additionally - we require some special functionality from MIME_headers, so
// so that we can hold-off saving the headers to the file until we've
// checked them to see if we want them or not.
if (AM_DNORMAL) LOGGER_log("%s:%d:AM_nullify_attachment: Starting nullification of file '%s' from mailpack '%s'",FL, attachmentname, mpackname);
BS_init();
if (strcmp( mpackname, "-") == 0)
inputfile = stdin;
else
inputfile = fopen( mpackname, "r" );
if (inputfile == NULL)
{
LOGGER_log("%s:%d:AM_nullify_attachment: Unable to open mailpack '%s' for reading (%s)", FL, mpackname, strerror(errno));
return 1;
}
snprintf( tmpfname, sizeof(tmpfname), "%s.tmp", mpackname );
if (strcmp( mpackname, "-") == 0)
outputfile = stdout;
else
outputfile = fopen( tmpfname, "w" );
if (outputfile == NULL)
{
if (inputfile != NULL) fclose(inputfile);
LOGGER_log("%s:%d:AM_nullify_attachment: Unable to open temporary file '%s' for writing (%s)", FL, tmpfname, strerror(errno));
return 1;
}
FFGET_setstream( &f, inputfile );
MIMEH_set_headers_nosave();
MIMEH_set_headers_save_original(1);
// Determine which mode of matching we'll be using
if (strchr(attachmentname, '/') == NULL) match_mode = AM_NULLIFY_MATCH_MODE_FILENAME;
else match_mode = AM_NULLIFY_MATCH_MODE_CONTENT_TYPE;
// Compile our Regular-expression for the filename.
result = regcomp( &preg, attachmentname, REG_EXTENDED|REG_ICASE|REG_NOSUB );
if (result != 0)
{
LOGGER_log("%s:%d:AM_nullify_attachment: Unable to compile regular expression '%s'", FL, attachmentname );
return 0;
}
SS_init(&(hinfo.ss_filenames));
SS_init(&(hinfo.ss_names));
result=AM_nullify_attachment_recurse( &hinfo, &f, outputfile, &preg, match_mode, 1 );
MIMEH_set_headers_save_original(0);
snprintf(oldfname,sizeof(oldfname),"%s.old", mpackname);
if (strcmp( mpackname, "-") != 0)
{
result = rename( mpackname, oldfname );
if ( result != 0 )
{
LOGGER_log("%s:%d:AM_nullify_attachment_recurse:ERROR: Unable to rename original mailpack '%s' to '%s' (%s)", FL, mpackname, oldfname, strerror(errno));
return 1;
}
result = rename( tmpfname, mpackname );
if (result != 0)
{
LOGGER_log("%s:%d:AM_nullify_attachment_recurse:ERROR: Unable to rename temporary mailpack '%s' to '%s' (%s)", FL, tmpfname, mpackname, strerror(errno));
return 1;
}
result = unlink( oldfname );
if ( result != 0)
{
LOGGER_log("%s:%d:AM_nullify_attachment_recurse:ERROR: Unable to unlink/remove '%s' (%s)", FL, oldfname, strerror(errno));
return 1;
}
}
// Clean up our boundary stack
BS_clear();
return result;
}
/*------------------------------------------------------------------------
Procedure: AM_insert_Xheader ID:1
Purpose: Adds an X-header to the first set of headers in an email mailpack.
Input: char *fname: mailpack name
char *xheader: header string
Output:
Errors:
------------------------------------------------------------------------*/
int AM_insert_Xheader( char *fname, char *xheader)
{
/* Tempfile tmpfile() fix contributed by David DeMaagd - 29/01/2001 */
char line[ AM_1K_BUFFER_SIZE +1];
char tpn[ AM_1K_BUFFER_SIZE +1];
int header_written = 0;
int result = 0;
struct stat st;
FFGET_FILE f;
FILE *fi;
FILE *fo;
// Sanity checks
//
// . check that the x-header string is valid
// . check the x-header to ensure there's no \r or \n's
//
if (!fname) {
LOGGER_log("%s:%d:AM_insert_Xheader:ERROR: Filename is NULL",FL);
return 1;
}
if (!xheader) {
LOGGER_log("%s:%d:AM_insert_Xheader:ERROR: Xheader to insert is NULL",FL);
return 1;
}
if (strlen(fname) < 1) {
LOGGER_log("%s:%d:AM_insert_Xheader:ERROR: Filename is too short",FL);
return 1;
}
if (strlen(xheader) < 1) {
LOGGER_log("%s:%d:AM_insert_Xheader:ERROR: Header to insert is too short",FL);
return 1;
}
if (1)
{
/** Strip off any trailing line breaks **/
char *p;
p = strpbrk(xheader,"\r\n");
if (p) *p = '\0';
}
// Create a temporary file name, but, so that we dont
// overwrite an existing file, we must check the name using
// stat() to see if we get a non-zero response.
snprintf(tpn, AM_1K_BUFFER_SIZE, "%s",fname);
do {
if (strlen(tpn) < (sizeof(tpn) -2))
{
/** Changed the temp filename suffix chat to a hypen because under
** windows appending multiple .'s results in a filename that isn't
** acceptable - Thanks to Nico for bringing this to my attention **/
LOGGER_log("%s:%d:AM_insert_Xheader:NOTICE: Adjusting temp file name for header insert",FL);
strcat(tpn,"X");
} else {
LOGGER_log("%s:%d:AM_insert_Xheader:ERROR: Temporary file name string buffer out of space!",FL);
return 1;
}
// LOGGER_log("DEBUG:%s:%d: Testing filename %s\n",__FILE__,__LINE__,tpn);
} while (0 == stat(tpn,&st));
// Attempt to open up the temporary file in write mode.
// If the open fails, then this whole operation fails.
// Ensure there's lots of good logging here so that when something
// does go wrong, at least people wont be left in the dark as to
// what went on.
//
// Same applies for opening the source file
//
fo = fopen(tpn,"w");
if (!fo)
{
LOGGER_log("%s:%d:AM_insert_Xheader:ERROR: Cannot open temporary file, '%s' for writing (%s)",FL, tpn, strerror(errno));
return 1;
}
fi = fopen(fname,"r");
if (!fi)
{
LOGGER_log("%s:%d:AM_insert_Xheader:ERROR: Cannot open Mailpack file '%s' for reading, (%s)",FL, fname, strerror(errno));
return 2;
}
//setup our FFGET stream
FFGET_setstream(&f, fi);
f.trueblank = 0;
// Load and go through every line in the email file, testing it for "trueblank"
// status. Once a trueblank is found, we write our header, as this marks the
// end of the headers (the trueblank). Beyond that, we keep on reading and
// writing because we obviously want to keep the entire email content.
while (FFGET_fgets(line, AM_1K_BUFFER_SIZE ,&f))
{
// If we've found that the line we just read is a /true/ blank (as apposed to a carefully
// crafted line length to catch out our fgets(), /and/ if we've not written in a header
// then we will insert the header.
if ((0 != f.trueblank)&&(0 == header_written))
{
/** 20041104-12H52:PLD: Changed from \n to \r\n **/
/** 20050204-11H04:PLD: Changed from \r\n to instead use the 'blank line', this ensures
** that the right \r\n or \n combination is used **/
fprintf(fo,"%s%s",xheader,line);
header_written = 1;
}
// In all cases, we must write out the line we read in, even if it was the true-blank line
// Otherwise we'll end up losing a the header seperation, and all of a sudden your emails
// do not make any more sense to email programs.
fprintf(fo,"%s",line);
}
// Close our input files
fclose(fo);
fclose(fi);
// We now can rename the temporary file to the new file name.
// NOTE - previously we removed then renamed, however, on checking
// with the manpages, 'man 2 rename' it seems it's not required, so
// rather than waste CPU cycles, we'll just go by the book, and use
// only rename. There are a couple of situations where rename can/will
// fail, obviously such as if the original file is marked read-only, or
// we do not have write permissions to it.
if (rename(tpn,fname) == -1)
{
result = 1;
LOGGER_log("%s:%d:AM_insert_Xheader:ERROR: while attempting to rename '%s' to '%s' (%s) ", FL, tpn, fname, strerror(errno));
}
return result;
}
/*------------------------------------------------------------------------
Procedure: AM_header_adjust ID:1
Purpose: Adjusts an -existing- header
Input:
char *xheader: header string
Output:
Errors:
------------------------------------------------------------------------*/
int AM_alter_header( char *filename, char *header, char *change, int change_mode )
{
char line[ AM_1K_BUFFER_SIZE +1];
char tpn[ AM_1K_BUFFER_SIZE +1];
int main_headers_complete = 0;
int header_written = 0;
int result = 0;
struct stat st;
FFGET_FILE f;
FILE *fi;
FILE *fo;
// Create a temporary file name, but, so that we dont
// overwrite an existing file, we must check the name using
// stat() to see if we get a non-zero response.
if (AM_DNORMAL) LOGGER_log("%s:%d:AM_alter_header:DEBUG: Start [ %s, %s, %s, %d ]", FL, filename, header, change, change_mode );
snprintf(tpn, AM_1K_BUFFER_SIZE, "%s",filename);
do {
if (strlen(tpn) < (sizeof(tpn) -2))
{
strcat(tpn,".");
}
else
{
LOGGER_log("%s:%d:AM_header_adjust:ERROR: Temporary file name string buffer out of space!\n",FL);
return 1;
}
}
while (0 == stat(tpn,&st));
if (AM_DNORMAL) LOGGER_log("%s:%d:AM_alter_header:DEBUG: Temporary filename = %s", FL, tpn );
// Attempt to open up the temporary file in write mode.
// If the open fails, then this whole operation fails.
// Ensure there's lots of good logging here so that when something
// does go wrong, at least people wont be left in the dark as to
// what went on.
//
// Same applies for opening the source file
//
fo = fopen(tpn,"w");
if (!fo)
{
LOGGER_log("%s:%d:AM_header_adjust:ERROR: Cannot open temporary file '%s' for writing",FL,tpn, strerror(errno));
return 1;
}
fi = fopen(filename,"r");
if (!fi)
{
LOGGER_log("%s:%d:AM_header_adjust:ERROR: Cannot open Mailpack file for reading, %s",FL,filename,strerror(errno));
return 2;
}
//setup our FFGET stream
FFGET_setstream(&f, fi);
// Convert the header we're looking for into lower case so that
// we make it esaier to search for
PLD_strlower( header );
// Load and go through every line in the email file, testing it for "trueblank"
// status. Once a trueblank is found, we write our header, as this marks the
// end of the headers (the trueblank). Beyond that, we keep on reading and
// writing because we obviously want to keep the entire email content.
if (AM_DNORMAL) LOGGER_log("%s:%d:AM_header_adjust:DEBUG: Starting seek through file. header_written = %d", FL, header_written );
while (FFGET_fgets(line, AM_1K_BUFFER_SIZE ,&f))
{
int line_written = 0;
// If we find a true-blank line, then we note that the main-headers section is now
// complete - We do not process any headers after this, as the rest of the data
// is the email body/content [ which we do not wish to alter ].
if ((main_headers_complete == 0)&&(f.trueblank == 1)) main_headers_complete = 1;
// If we're still processing the main headers, and we've not written/altered our
// particular header which we wish to alter, then proceed to check this newly
// read line to see if it is the one we wish to alter,
//
// Irrespective of if we alter this line or not, it will be written at the bottom
// of this while-loop. NOTE - we don't write the line within the header-alteration
// "if" block, as this would only make things more complicated on exit ( ie, did we
// write the line, or are we okay... more logic tests, more chance of a bungle ).
if ((main_headers_complete == 0)&&(header_written == 0))
{
char *p;
char low_line[AM_1K_BUFFER_SIZE +1];
if (AM_DNORMAL) LOGGER_log("%s:%d:AM_header_adjust: line=%s",FL, line);
// Make a copy of the header line, and convert it to lowercase so that we
// can make a string comparison
snprintf( low_line, AM_1K_BUFFER_SIZE, "%s", line );
PLD_strlower( low_line );
p = strstr(low_line, header);
if (p != NULL)
{
char *first_colon_position;
// Because it's quite possible to put a heaer line in which replicates itself, such as...
// Subject: the Subject: is foo
// we need to first check that the header position we got from strstr is /BEFORE/ the first
// colon in the line.
first_colon_position = strchr(low_line, ':');
if ( p < first_colon_position )
{
char *header_start, *header_end;
char startc, finishc;
// Convert the position in the low_line string over to that
// of the normal 'line' string.
p = line +(p - low_line);
if (AM_DNORMAL) LOGGER_log("%s:%d:AM_header_adjust:DEBUG: Located header line",FL);
header_start = strchr(p,':');
if ( header_start == NULL )
{
LOGGER_log("%s:%d:AM_header_adjust:WARNING: Could not locate terminating ':' character on header name (%s)",FL,p);
continue;
}
header_start++;
startc = *header_start;
header_end = strpbrk( header_start, "\r\n;" );
if ( header_end == NULL )
{
LOGGER_log("%s:%d:AM_header_adjust:WARNING: Could not locate end of header value (%s)",FL,header_start);
continue;
}
finishc = *header_end;
// We now have the start and end points of the header string, we now place in the additional
// data ( or entirely new data ) based on what the mode is
switch ( change_mode ) {
case AM_HEADER_ADJUST_MODE_PREFIX:
*header_start = '\0';
fprintf(fo,"%s %s%c%s", line, change, startc, header_start+1);
header_written = 1;
line_written = 1;
if (AM_DNORMAL) LOGGER_log("%s:%d:AM_header_adjust:DEBUG: Prefix mode output written",FL);
break;
case AM_HEADER_ADJUST_MODE_SUFFIX:
*header_end = '\0';
fprintf(fo,"%s %s%c%s", line, change, finishc, header_end+1 );
header_written = 1;
line_written = 1;
if (AM_DNORMAL) LOGGER_log("%s:%d:AM_header_adjust:DEBUG: Suffix mode output written",FL);
break;
case AM_HEADER_ADJUST_MODE_REPLACE:
*header_start = '\0';
fprintf(fo,"%s %s%s", line, change, header_end );
header_written = 1;
line_written = 1;
if (AM_DNORMAL) LOGGER_log("%s:%d:AM_header_adjust:DEBUG: Replace mode output written",FL);
break;
default:
LOGGER_log("%s:%d:AM_header_adjust:ERROR: Unknown header adjustment mode '%d'",FL, change_mode );
} // Switch (change_mode)
} // If p < first_colon_position
} // If p != NULL, ie, we found a matching string for the header
} // If we're still in the headers and we've still not found a matching header
// In all cases, we must write out the line we read in, even if it was the true-blank line
// Otherwise we'll end up losing a the header seperation, and all of a sudden your emails
// do not make any more sence to email programs.
if (line_written == 0) fprintf(fo,"%s",line);
} // While we still have more lines in the file.
// Close our input files
fclose(fo);
fclose(fi);
// We now can rename the temporary file to the new file name.
// NOTE - previously we removed then renamed, however, on checking
// with the manpages, 'man 2 rename' it seems it's not required, so
// rather than waste CPU cycles, we'll just go by the book, and use
// only rename. There are a couple of situations where rename can/will
// fail, obviously such as if the original file is marked read-only, or
// we do not have write permissions to it.
if (rename(tpn,filename) == -1)
{
result = 1;
LOGGER_log("%s:%d:AM_header_adjust:ERROR: while attempting to rename '%s' to '%s' (%s) ", FL, tpn, filename, strerror(errno));
}
return result;
}
/*-----------------------------------------------------------------\
Function Name : AM_attachment_replace_header_filter
Returns Type : int
----Parameter List
1. struct MIMEH_header_info *hinfo,
2. FILE *outputfile,
3. char *new_attachment_name,
4. char *headers ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_attachment_replace_header_filter( struct MIMEH_header_info *hinfo, char *new_attachment_name, char **headers )
{
struct PLD_strreplace replace;
// Because we're dealing with the primary headers, we have to be careful
// about how we search-replace the data within these headers, lest we
// break something we're not supposed to :-(
if (AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_header_filter:DEBUG: Start.",FL);
replace.source = *headers;
replace.searchfor = hinfo->filename;
replace.replacewith = new_attachment_name;
replace.replacenumber = 1; // only 1 replacement thanks!
replace.insensitive = 1; // Emails come in all shapes and sizes - do not assume a case.
replace.postexist = NULL;
replace.preexist = "content-type:";
*headers = PLD_strreplace_general( &replace );
replace.source = *headers;
replace.preexist = "content-disposition:";
*headers = PLD_strreplace_general( &replace );
// Because we can currently only encode our new attachment using BASE64
// we need to make sure that the content-transfer-encoding field is
// appropriately set.
if (hinfo->content_transfer_encoding != _CTRANS_ENCODING_B64)
{
if (strlen(hinfo->content_transfer_encoding_string) < 1)
{
char CTE_string[256];
snprintf(CTE_string, sizeof(CTE_string),"Content-Transfer-Encoding: base64\nContent-Type:");
replace.preexist=NULL;
replace.source=*headers;
replace.searchfor="content-type:";
replace.replacewith=CTE_string;
*headers = PLD_strreplace_general( &replace );
} else {
replace.preexist = "content-transfer-encoding:";
replace.source = *headers;
replace.searchfor = hinfo->content_transfer_encoding_string;
replace.replacewith = "base64";
*headers = PLD_strreplace_general( &replace );
}
}
if (AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_header_filter:DEBUG: End.",FL);
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_attachment_replace_write_data
Returns Type : int
----Parameter List
1. struct MIMEH_header_info *hinfo,
2. char *new_attachment_name,
3. FILE *outputfile ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_attachment_replace_write_data( char *new_attachment_name, FILE *outputfile, char *delimeter )
{
int result = 0;
FILE *newatt;
newatt = fopen( new_attachment_name, "r" );
if (newatt == NULL)
{
LOGGER_log("%s:%d:AM_attachment_replace_write_data:ERROR: Could not open '%s' for reading to insert into mailpack (%s)"\
,FL, new_attachment_name, strerror(errno));
return 1;
}
if(AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_write_data:DEBUG: Writing out new attachment data",FL);
AM_base64_encodef( newatt, outputfile );
fprintf( outputfile, "%s%s", delimeter, delimeter );
if(AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_write_data:DEBUG: done.",FL);
return result;
}
/*-----------------------------------------------------------------\
Function Name : AM_nullify_attachment_recurse
Returns Type : int
----Parameter List
1. struct MIMEH_header_info *hinf,
2. FFGET_FILE *f,
3. FILE *outputfile,
4. pregex_t *preg ,
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_attachment_replace_recurse( struct MIMEH_header_info *hinfo, FFGET_FILE *f, FILE *outputfile, regex_t *preg, char *new_attachment_name, int iteration )
{
int result = 0;
int boundary_exists=0;
if (AM_DNORMAL) LOGGER_log("%s:%d:AM_attachment_replace_recurse:DEBUG: Starting: iteration=%d",FL, iteration );
while (1)
{
int regresult=0;
int attachment_data_written=0;
char *header_ptr=NULL;
char *original_ptr=NULL;
char buffer[1024];
char CR[]="\n";
char CRLF[]="\n\r";
char *delimeter;
MIMEH_set_doubleCR_save(0);
result = MIMEH_headers_get( hinfo, f );
MIMEH_set_doubleCR_save(1);
if (result != 0)
{
break;
}
/* If we're not supposed to be altering Signed EMAILS, then don't
start altering them now. Exit with a 0 return
*/
if ((hinfo->content_type == _CTYPE_MULTIPART_SIGNED)&&(glb.alter_signed==0))
{
return 0;
}
if (AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_recurse:DEBUG: Headers read, now processing", FL );
original_ptr = MIMEH_get_headers_original_ptr();
header_ptr = MIMEH_get_headers_ptr();
if (AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_recurse:DEBUG: Headers=\n%s\n", FL, original_ptr );
if (original_ptr == NULL)
{
LOGGER_log("%s:%d:AM_attachment_replace_recurse:ERROR: Original headers came back NULL",FL);
return 1;
}
if (header_ptr == NULL)
{
LOGGER_log("%s:%d:AM_attachment_replace_recurse:ERROR: Header ptr (for processing) came back NULL",FL);
return 1;
}
// Because we'll be later on adding new lines into the mailpack, we need to know what
// the currently used line-delimeter is, this way we don't end up with a mailpack which
// has multiple personalities and thus potentially confusing the MUA.
if (strstr(original_ptr,CRLF)) delimeter=CRLF; else delimeter=CR;
result = MIMEH_headers_process( hinfo, header_ptr );
if (result != 0)
{
LOGGER_log("%s:%d:AM_attachment_replace_recurse:ERROR: While processing headers for mailpack", FL );
break;
}
// Check to see if we have a new boundary
// Sometimes a new segment in the email reveals a nested MIME encoded
// data within. This is typically indicated by Content-Type: rfc822
//
// Should we have a new boundary, we add it to the top of the current boundary stack
// later, when this MIME segment finishes (and the previous boundary comes back
// into appearance, the old boundary will be popped off the stack automatically
//
if (((hinfo->content_type == _CTYPE_RFC822)\
||(hinfo->content_type >= _CTYPE_MULTIPART_START && hinfo->content_type <= _CTYPE_MULTIPART_END))\
&& (hinfo->boundary_located > 0))
{
if (AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_recurse:DEBUG: pushing BS='%s'",FL, hinfo->boundary );
BS_push( hinfo->boundary );
boundary_exists = 1;
}
// Now, determine if this block/segment is the one which contains our file which we must 'nullify'
regresult=1;
if (strlen(hinfo->filename) > 0)
{
regresult = regexec( preg, hinfo->filename, 0, NULL, 0 );
if(AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_recurse:DEBUG: FileName Regex match = %d [filename = '%s']"\
,FL,regresult,hinfo->filename);
}
// If we're on our first pass, or we've not found the section/block with the attachment
// then write the headers out.
//
// A bit of an issue which comes up is, what to do if the entire email is the attachment
// ie, it's just a something which was 'right-click->email'd.
//
// Until I come up with a better solution, I'll still write the headers, but I'll then
// eliminate all the content [until the next boundary, assuming a boundary exists]
//
if (regresult > 0)
{
fwrite( original_ptr, sizeof(char), strlen(original_ptr), outputfile );
if(AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_recurse:DEBUG: Wrote original headers:\n%s",FL,original_ptr);
}
else
{
// If we did have a filename "HIT" in these headers
//
// We will need to suitably alter, or write new headers
// in order to reflect the new filenames that we'll be
// using for the replaced attachment
char *new_attachment_filename;
// Check for delimeters in the new attachment name, and make
// the new_attachment_filename just the last segment of that.
new_attachment_filename=strrchr(new_attachment_name,'/');
// If looking for the forward-slash failed, try looking for the backslash
if (new_attachment_filename == NULL) new_attachment_filename=strrchr(new_attachment_name,'\\');
// If both forward and backslash searches failed, then just let the new attachment filename
// be the same as the one passed to us via the parameters
if (new_attachment_filename == NULL)
{
new_attachment_filename = new_attachment_name;
} else {
// If we did get a hit - then we increment by one character so that
// we don't have the directory seperator in our way.
new_attachment_filename++;
}
// When it comes to creating the new headers, we have to check to see if we're
// in a suitable situation to either (a) entirely replace the headers with our own
// or (b) modify existing headers.
//
// Existing header modification is required when we're dealing with headers that
// make up the start of the whole MIME package, this is because there's a lot
// more information contained in them than just the file-attachment information
// Thus, for this situation, we'll use a header-modification function.
//
// If the headers are not the primary ones, we can just remove the existing ones
// and write in our own as generated by the content-type, disposition and encoding
if (iteration > 1)
{
// If we're dealing with a non-primary header situation, just replace the old headers
// with our new fabricated ones
if(AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_recurse:DEBUG: Writing clean headers",FL);
fprintf( outputfile, "Content-Type: %s;name=\"%s\"%sContent-Transfer-Encoding: base64%sContent-Disposition: attachment;filename=\"%s\"%s%s"\
, hinfo->content_type_string\
, new_attachment_filename, delimeter, delimeter\
, new_attachment_filename, delimeter, delimeter\
);
} else {
// If we're dealing with a primary-header situation, we have to carefully
// search-replace the old filenames with our own. This has to be done
// within the strict confines between Content-Type:...;\n and/or Content-Disposition
char *duplicate_headers;
if(AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_recurse:DEBUG: Primary header attachment replacement",FL,new_attachment_filename);
duplicate_headers = strdup( original_ptr );
if (duplicate_headers == NULL)
{
LOGGER_log("%s:%d:AM_attachment_replace_recurse:ERROR: Could not allocate memory to hold temporary copy of headers",FL);
return 1;
}
//if(AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_recurse:DEBUG: Seeking and replacing content type, disposition headers in main headers",FL);
AM_attachment_replace_header_filter( hinfo, new_attachment_filename, &duplicate_headers );
if(AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_recurse:DEBUG: Writing recycled headers\n%s",FL, duplicate_headers);
fprintf( outputfile, "%s", duplicate_headers);
if (duplicate_headers != NULL) free( duplicate_headers );
}
}
// Clean up the memory allocation
result = MIMEH_headers_cleanup();
if (result != 0)
{
LOGGER_log("%s:%d:AM_attachment_replace_recurse:ERROR: while attempting to clean up headers memory allocation", FL );
break;
}
// Now, if we have a Multipart/Mixed attachment, then, alas, we need to recurse into
// it and see if it contains anything interesting for us to seek out.
if ((regresult != 0)&&((hinfo->content_type == _CTYPE_RFC822)))
{
result=AM_attachment_replace_recurse( hinfo, f, outputfile, preg, new_attachment_name, 1 );
}
// Now, we shall go through and read the email until we happen across another boundary.
// or we reach the end of the file.
//
//
do {
int buflen;
FFGET_fgets(buffer, sizeof(buffer), f);
if (FFGET_feof(f) == 0)
{
buflen = strlen(buffer);
// Once we have a boundary match, it's a end-of-line situation for this DO
// loop, as we will 'break' out of it once the attachment has been written
// and the trailing 'boundary' written as well.
if ((BS_cmp(buffer, buflen) == 1))
{
if ((AM_DNORMAL)&&(boundary_exists==1))\
LOGGER_log("%s:%d:AM_attachment_replace_recurse:DEBUG: Boundary hit on line %d\nBoundary Exists=%d\nBoundary line=%s"\
,FL,f->linecount,boundary_exists, buffer);
// If we have a match for the attachment replacement, then this is where
// all the work we do going to find this place comes to a head, as here
// we finally insert the encoded attachment into the mailpack we're
// creating anew from the existing one.
if (regresult == 0)
{
AM_attachment_replace_write_data( new_attachment_name, outputfile, delimeter );
attachment_data_written=1;
}
fwrite( buffer, sizeof(char), buflen, outputfile );
break;
} // end of boundary-detect
// If we didn't have a match on the filename with this particular MIME segment
// we simply just write out all the lines we are reading in. This means
// that the data we're saving should be identical to that being read from the
// original file.
if (regresult != 0)
{
fwrite( buffer, sizeof(char), buflen, outputfile );
}
} else {
// If we hit the EOF, then we need to check to see if
// we were supposed to write any attachment data, and, if so
// did we actually get to write it out?
//
// If we haven't written it, then we must write it now.
//
// This situation can occur when there's no trailing boundary
// line at the end of the last MIME segment of the mailpack.
if ((regresult == 0)&&(attachment_data_written == 0))
{
AM_attachment_replace_write_data( new_attachment_name, outputfile, delimeter );
}
break; // break if FEOF occurs
}
} while (1);
if (FFGET_feof(f)) break;
iteration++;
} // Infinite while(1)
if(AM_DNORMAL)LOGGER_log("%s:%d:AM_attachment_replace_recurse:DEBUG: End of function.",FL);
return 0;
}
/*-----------------------------------------------------------------\
Function Name : AM_attachment_replace
Returns Type : int
----Parameter List
1. char *mpackname, mailpack which we're going to replace the file in
2. char *attachmentname, name of the attachment we're looking for in the mailpack [ to replace ]. This is a regular-expression syntax
3. char *new_attachment_name , full path of the file which we're going to use in place of *attachmentname
------------------
Exit Codes :
Side Effects :
--------------------------------------------------------------------
Comments:
Replaces an attachment located in a mailpack with a new file. The
new file is encoded [currently only] in base64. If the new attachment
name / path contains a path component ie, /usr/local/some-file, then
only the last segment of the full path will be used in the headers
[ ie, 'some-file' ]
--------------------------------------------------------------------
Changes:
\------------------------------------------------------------------*/
int AM_attachment_replace( char *mpackname, char *attachmentname, char *new_attachment_name )
{
struct MIMEH_header_info hinfo;
regex_t preg;
int result = 0;
char tmpfname[256];
char oldfname[256];
FILE *inputfile;
FILE *outputfile;
FFGET_FILE f;
// Nullifying an attachment can sometimes be a little bit tricky, we have to
// dig down into the nesting of the MIME email and keep tabs on our
// boundaries (via a Boundary-stack). This all requires abot as much work
// as ripMIME just to get rid of one attachment.
//
// Additionally - we require some special functionality from MIME_headers, so
// so that we can hold-off saving the headers to the file until we've
// checked them to see if we want them or not.
BS_init();
inputfile = fopen( mpackname, "r" );
if (inputfile == NULL)
{
LOGGER_log("%s:%d:AM_replace_attachment: Unable to open mailpack '%s' for reading (%s)", FL, mpackname, strerror(errno));
return 1;
}
snprintf( tmpfname, sizeof(tmpfname), "%s.tmp", mpackname );
outputfile = fopen( tmpfname, "w" );
if (outputfile == NULL)
{
if (inputfile != NULL) fclose(inputfile);
LOGGER_log("%s:%d:AM_replace_attachment: Unable to open temporary file '%s' for writing (%s)", FL, tmpfname, strerror(errno));
return 1;
}
FFGET_setstream( &f, inputfile );
MIMEH_set_headers_nosave();
MIMEH_set_headers_save_original(1);
// Compile our Regular-expression for the filename.
result = regcomp( &preg, attachmentname, REG_EXTENDED|REG_ICASE|REG_NOSUB );
if (result != 0)
{
LOGGER_log("%s:%d:AM_replace_attachment: Unable to compile regular expression '%s'", FL, attachmentname );
return 0;
}
result=AM_attachment_replace_recurse( &hinfo, &f, outputfile, &preg, new_attachment_name, 1 );
MIMEH_set_headers_save_original(0);
snprintf(oldfname,sizeof(oldfname),"%s.old", mpackname);
result = rename( mpackname, oldfname );
if ( result != 0 )
{
LOGGER_log("%s:%d:AM_attachment_replace_recurse:ERROR: Unable to rename original mailpack '%s' to '%s' (%s)", FL, mpackname, oldfname, strerror(errno));
return 1;
}
result = rename( tmpfname, mpackname );
if (result != 0)
{
LOGGER_log("%s:%d:AM_attachment_replace_recurse:ERROR: Unable to rename temporary mailpack '%s' to '%s' (%s)", FL, tmpfname, mpackname, strerror(errno));
return 1;
}
result = unlink( oldfname );
if ( result != 0)
{
LOGGER_log("%s:%d:AM_attachment_replace_recurse:ERROR: Unable to unlink/remove '%s' (%s)", FL, oldfname, strerror(errno));
return 1;
}
// Clear the boundary stack
BS_clear();
return result;
}
//-----------------------------------END----
syntax highlighted by Code2HTML, v. 0.9.1