static char const rcsid[] = "$Id: message.c,v 1.5 2004/11/30 17:29:59 dasenbro Exp $";
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#ifndef CL_DEBUG
#define NDEBUG
#endif
#ifdef CL_THREAD_SAFE
#ifndef _REENTRANT
#define _REENTRANT
#endif
#endif
#if C_DARWIN
#include <sys/types.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#ifdef CL_THREAD_SAFE
#include <pthread.h>
#endif
#include "line.h"
#include "mbox.h"
#include "table.h"
#include "blob.h"
#include "text.h"
#include "strrcpy.h"
#include "others.h"
#include "str.h"
#include "filetypes.h"
#ifdef TRUE
#undef TRUE
#endif
#ifdef FALSE
#undef FALSE
#endif
typedef enum { FALSE = 0, TRUE = 1 } bool;
static void messageIsEncoding(message *m);
static const text *binhexBegin(const message *m);
static unsigned char *decodeLine(message *m, encoding_type enctype, const char *line, unsigned char *buf, size_t buflen);
static unsigned char *decode(message *m, const char *in, unsigned char *out, unsigned char (*decoder)(char), bool isFast);
static void sanitiseBase64(char *s);
static unsigned char hex(char c);
static unsigned char base64(char c);
static unsigned char uudecode(char c);
static const char *messageGetArgument(const message *m, int arg);
static void *messageExport(message *m, const char *dir, void *(*create)(void), void (*destroy)(void *), void (*setFilename)(void *, const char *, const char *), void (*addData)(void *, const unsigned char *, size_t), void *(*exportText)(const text *, void *));
static int usefulArg(const char *arg);
static void messageDedup(message *m);
static const struct encoding_map {
const char *string;
encoding_type type;
} encoding_map[] = {
{ "7bit", NOENCODING },
{ "text/plain", NOENCODING },
{ "quoted-printable", QUOTEDPRINTABLE },
{ "base64", BASE64 },
{ "8bit", EIGHTBIT },
{ "binary", BINARY },
{ "x-uuencode", UUENCODE },
{ "x-yencode", YENCODE },
{ NULL, NOENCODING }
};
static struct mime_map {
const char *string;
mime_type type;
} mime_map[] = {
{ "text", TEXT },
{ "multipart", MULTIPART },
{ "application", APPLICATION },
{ "audio", AUDIO },
{ "image", IMAGE },
{ "message", MESSAGE },
{ "video", VIDEO },
{ NULL, TEXT }
};
#define USE_TABLE
#ifdef USE_TABLE
static unsigned char base64Table[256] = {
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,62,255,255,255,63,
52,53,54,55,56,57,58,59,60,61,255,255,255,0,255,255,
255,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,
15,16,17,18,19,20,21,22,23,24,25,255,255,255,255,255,
255,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
41,42,43,44,45,46,47,48,49,50,51,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255
};
#endif
message *
messageCreate(void)
{
message *m = (message *)cli_calloc(1, sizeof(message));
if(m)
m->mimeType = NOMIME;
return m;
}
void
messageDestroy(message *m)
{
assert(m != NULL);
messageReset(m);
free(m);
}
void
messageReset(message *m)
{
int i;
assert(m != NULL);
if(m->mimeSubtype)
free(m->mimeSubtype);
if(m->mimeDispositionType)
free(m->mimeDispositionType);
if(m->mimeArguments) {
for(i = 0; i < m->numberOfArguments; i++)
free(m->mimeArguments[i]);
free(m->mimeArguments);
}
if(m->body_first)
textDestroy(m->body_first);
assert(m->base64chars == 0);
if(m->encodingTypes) {
assert(m->numberOfEncTypes > 0);
free(m->encodingTypes);
}
memset(m, '\0', sizeof(message));
m->mimeType = NOMIME;
}
int
messageSetMimeType(message *mess, const char *type)
{
#ifdef CL_THREAD_SAFE
static pthread_mutex_t mime_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
static table_t *mime_table;
int typeval;
assert(mess != NULL);
assert(type != NULL);
cli_dbgmsg("messageSetMimeType: '%s'\n", type);
while(!isalpha(*type))
if(*type++ == '\0')
return 0;
#ifdef CL_THREAD_SAFE
pthread_mutex_lock(&mime_mutex);
#endif
if(mime_table == NULL) {
const struct mime_map *m;
mime_table = tableCreate();
if(mime_table == NULL) {
#ifdef CL_THREAD_SAFE
pthread_mutex_unlock(&mime_mutex);
#endif
return 0;
}
for(m = mime_map; m->string; m++)
if(!tableInsert(mime_table, m->string, m->type)) {
tableDestroy(mime_table);
mime_table = NULL;
#ifdef CL_THREAD_SAFE
pthread_mutex_unlock(&mime_mutex);
#endif
return 0;
}
}
#ifdef CL_THREAD_SAFE
pthread_mutex_unlock(&mime_mutex);
#endif
typeval = tableFind(mime_table, type);
if(typeval != -1) {
mess->mimeType = typeval;
return 1;
} else if(mess->mimeType == NOMIME) {
if(strncasecmp(type, "x-", 2) == 0)
mess->mimeType = MEXTENSION;
else {
if(strcasecmp(type, "plain") == 0) {
cli_dbgmsg("Incorrect MIME type: `plain', set to Text\n", type);
mess->mimeType = TEXT;
} else {
cli_warnmsg("Unknown MIME type: `%s', set to Application - report to bugs@clamav.net\n", type);
mess->mimeType = APPLICATION;
}
}
return 1;
}
return 0;
}
mime_type
messageGetMimeType(const message *m)
{
assert(m != NULL);
return m->mimeType;
}
void
messageSetMimeSubtype(message *m, const char *subtype)
{
assert(m != NULL);
if(subtype == NULL) {
cli_dbgmsg("Empty content subtype\n");
subtype = "";
}
if(m->mimeSubtype)
free(m->mimeSubtype);
m->mimeSubtype = strdup(subtype);
}
const char *
messageGetMimeSubtype(const message *m)
{
return((m->mimeSubtype) ? m->mimeSubtype : "");
}
void
messageSetDispositionType(message *m, const char *disptype)
{
assert(m != NULL);
if(m->mimeDispositionType)
free(m->mimeDispositionType);
if(disptype == NULL) {
m->mimeDispositionType = NULL;
return;
}
while(*disptype && isspace((int)*disptype))
disptype++;
if(*disptype) {
m->mimeDispositionType = strdup(disptype);
if(m->mimeDispositionType)
strstrip(m->mimeDispositionType);
}
}
const char *
messageGetDispositionType(const message *m)
{
return((m->mimeDispositionType) ? m->mimeDispositionType : "");
}
void
messageAddArgument(message *m, const char *arg)
{
int offset;
assert(m != NULL);
if(arg == NULL)
return;
while(isspace(*arg))
arg++;
if(*arg == '\0')
return;
if(!usefulArg(arg))
return;
for(offset = 0; offset < m->numberOfArguments; offset++)
if(m->mimeArguments[offset] == NULL)
break;
else if(strcasecmp(arg, m->mimeArguments[offset]) == 0)
return;
if(offset == m->numberOfArguments) {
char **ptr;
m->numberOfArguments++;
ptr = (char **)cli_realloc(m->mimeArguments, m->numberOfArguments * sizeof(char *));
if(ptr == NULL) {
m->numberOfArguments--;
return;
}
m->mimeArguments = ptr;
}
m->mimeArguments[offset] = strdup(arg);
if((strncasecmp(arg, "filename=", 9) == 0) || (strncasecmp(arg, "name=", 5) == 0))
if(messageGetMimeType(m) == NOMIME) {
cli_dbgmsg("Force mime encoding to application\n");
messageSetMimeType(m, "application");
}
}
void
messageAddArguments(message *m, const char *s)
{
const char *string = s;
cli_dbgmsg("Add arguments '%s'\n", string);
assert(string != NULL);
while(*string) {
const char *key, *cptr;
char *data, *field;
if(isspace(*string) || (*string == ';')) {
string++;
continue;
}
key = string;
data = strchr(string, '=');
if(data == NULL)
data = strchr(string, ':');
if(data == NULL) {
cli_dbgmsg("Can't parse header \"%s\"\n", s);
return;
}
string = data;
string++;
while(isspace(*string) && (*string != '\0'))
string++;
cptr = string++;
if(*cptr == '"') {
char *ptr;
key = strdup(key);
if(key == NULL)
return;
ptr = strchr(key, '=');
if(ptr == NULL)
ptr = strchr(key, ':');
*ptr = '\0';
cptr++;
string = strchr(cptr, '"');
if((string == NULL) || (strlen(key) == 0)) {
if(usefulArg(key))
cli_warnmsg("Can't parse header (1) \"%s\" - report to bugs@clamav.net\n", s);
free((char *)key);
return;
}
string++;
if(!usefulArg(key)) {
free((char *)key);
continue;
}
data = strdup(cptr);
ptr = (data) ? strchr(data, '"') : NULL;
if(ptr == NULL) {
cli_warnmsg("Can't parse header (2) \"%s\"\n", s);
if(data)
free(data);
free((char *)key);
return;
}
*ptr = '\0';
field = cli_realloc((char *)key, strlen(key) + strlen(data) + 2);
if(field) {
strcat(field, "=");
strcat(field, data);
} else
free((char *)key);
free(data);
} else {
size_t len;
if(*cptr == '\0') {
cli_warnmsg("Ignoring empty field in \"%s\"\n", s);
return;
}
while((*string != '\0') && !isspace(*string))
string++;
len = (size_t)string - (size_t)key + 1;
field = cli_malloc(len);
if(field) {
memcpy(field, key, len - 1);
field[len - 1] = '\0';
}
}
if(field) {
messageAddArgument(m, field);
free(field);
}
}
}
static const char *
messageGetArgument(const message *m, int arg)
{
assert(m != NULL);
assert(arg >= 0);
assert(arg < m->numberOfArguments);
return((m->mimeArguments[arg]) ? m->mimeArguments[arg] : "");
}
const char *
messageFindArgument(const message *m, const char *variable)
{
int i;
size_t len;
assert(m != NULL);
assert(variable != NULL);
len = strlen(variable);
for(i = 0; i < m->numberOfArguments; i++) {
const char *ptr;
ptr = messageGetArgument(m, i);
if((ptr == NULL) || (*ptr == '\0'))
continue;
#ifdef CL_DEBUG
cli_dbgmsg("messageFindArgument: compare %d bytes of %s with %s\n",
len, variable, ptr);
#endif
if(strncasecmp(ptr, variable, len) == 0) {
ptr = &ptr[len];
while(isspace(*ptr))
ptr++;
if(*ptr != '=') {
cli_warnmsg("messageFindArgument: no '=' sign found in MIME header\n");
return NULL;
}
if((*++ptr == '"') && (strchr(&ptr[1], '"') != NULL)) {
char *ret = strdup(++ptr);
char *p;
if(ret == NULL)
return NULL;
ret[strlen(ret) - 1] = '\0';
if((p = strchr(ret, '"')) != NULL)
*p = '\0';
return ret;
}
return strdup(ptr);
}
}
return NULL;
}
void
messageSetEncoding(message *m, const char *enctype)
{
const struct encoding_map *e;
int i = 0;
char *type;
assert(m != NULL);
assert(enctype != NULL);
while((*enctype == '\t') || (*enctype == ' '))
enctype++;
if(strcasecmp(enctype, "8 bit") == 0) {
cli_dbgmsg("Broken content-transfer-encoding: '8 bit' changed to '8bit'\n");
enctype = "8bit";
}
i = 0;
while((type = cli_strtok(enctype, i++, " \t")) != NULL) {
for(e = encoding_map; e->string; e++)
if(strcasecmp(type, e->string) == 0) {
int j;
encoding_type *et;
for(j = 0; j < m->numberOfEncTypes; j++) {
if(m->encodingTypes[j] == e->type) {
cli_dbgmsg("Ignoring duplicate encoding mechanism\n");
break;
}
}
if(j < m->numberOfEncTypes)
break;
et = (encoding_type *)cli_realloc(m->encodingTypes, (m->numberOfEncTypes + 1) * sizeof(encoding_type));
if(et == NULL) {
free(type);
return;
}
m->encodingTypes = et;
m->encodingTypes[m->numberOfEncTypes++] = e->type;
cli_dbgmsg("Encoding type %d is \"%s\"\n", m->numberOfEncTypes, type);
break;
}
if(e->string == NULL) {
cli_warnmsg("Unknown encoding type \"%s\" - report to bugs@clamav.net\n", type);
free(type);
messageSetEncoding(m, "base64");
messageSetEncoding(m, "quoted-printable");
break;
}
free(type);
}
}
encoding_type
messageGetEncoding(const message *m)
{
assert(m != NULL);
if(m->numberOfEncTypes == 0)
return NOENCODING;
return m->encodingTypes[0];
}
int
messageAddLine(message *m, line_t *line)
{
assert(m != NULL);
if(m->body_first == NULL)
m->body_last = m->body_first = (text *)cli_malloc(sizeof(text));
else {
m->body_last->t_next = (text *)cli_malloc(sizeof(text));
m->body_last = m->body_last->t_next;
}
if(m->body_last == NULL)
return -1;
m->body_last->t_next = NULL;
if(line && lineGetData(line)) {
m->body_last->t_line = lineLink(line);
messageIsEncoding(m);
} else
m->body_last->t_line = NULL;
return 1;
}
int
messageAddStr(message *m, const char *data)
{
line_t *repeat = NULL;
assert(m != NULL);
if(data) {
int iswhite = 1;
const char *p;
for(p = data; *p != '\0'; p++)
if(!isspace(*p)) {
iswhite = 0;
break;
}
if(iswhite) {
data = NULL;
}
}
if(m->body_first == NULL)
m->body_last = m->body_first = (text *)cli_malloc(sizeof(text));
else {
assert(m->body_last != NULL);
m->body_last->t_next = (text *)cli_malloc(sizeof(text));
if(m->body_last->t_next == NULL) {
messageDedup(m);
m->body_last->t_next = (text *)cli_malloc(sizeof(text));
if(m->body_last->t_next == NULL) {
cli_errmsg("messageAddStr: out of memory\n");
return -1;
}
}
if(data && m->body_last->t_line && (strcmp(data, lineGetData(m->body_last->t_line)) == 0))
repeat = m->body_last->t_line;
m->body_last = m->body_last->t_next;
}
if(m->body_last == NULL) {
cli_errmsg("messageAddStr: out of memory\n");
return -1;
}
m->body_last->t_next = NULL;
if(data && *data) {
if(repeat)
m->body_last->t_line = lineLink(repeat);
else
m->body_last->t_line = lineCreate(data);
if((m->body_last->t_line == NULL) && (repeat == NULL)) {
messageDedup(m);
m->body_last->t_line = lineCreate(data);
if(m->body_last->t_line == NULL) {
cli_errmsg("messageAddStr: out of memory\n");
return -1;
}
}
if(repeat == NULL)
messageIsEncoding(m);
} else
m->body_last->t_line = NULL;
return 1;
}
int
messageAddStrAtTop(message *m, const char *data)
{
text *oldfirst;
assert(m != NULL);
if(m->body_first == NULL)
return messageAddLine(m, lineCreate(data));
oldfirst = m->body_first;
m->body_first = (text *)cli_malloc(sizeof(text));
if(m->body_first == NULL) {
m->body_first = oldfirst;
return -1;
}
m->body_first->t_next = oldfirst;
m->body_first->t_line = lineCreate((data) ? data : "");
if(m->body_first->t_line == NULL) {
cli_errmsg("messageAddStrAtTop: out of memory\n");
return -1;
}
return 1;
}
static void
messageIsEncoding(message *m)
{
static const char encoding[] = "Content-Transfer-Encoding";
static const char binhex[] = "(This file must be converted with BinHex 4.0)";
const char *line = lineGetData(m->body_last->t_line);
if((m->encoding == NULL) &&
(strncasecmp(line, encoding, sizeof(encoding) - 1) == 0) &&
(strstr(line, "7bit") == NULL))
m->encoding = m->body_last;
else if(
(cli_filetype(line, strlen(line)) == CL_TYPE_MAIL))
m->bounce = m->body_last;
else if((m->uuencode == NULL) &&
((strncasecmp(line, "begin ", 6) == 0) &&
(isdigit(line[6])) &&
(isdigit(line[7])) &&
(isdigit(line[8])) &&
(line[9] == ' ')))
m->uuencode = m->body_last;
else if((m->binhex == NULL) &&
(strncasecmp(line, binhex, sizeof(binhex) - 1) == 0))
m->binhex = m->body_last;
else if((m->yenc == NULL) && (strncmp(line, "=ybegin line=", 13) == 0))
m->yenc = m->body_last;
}
const text *
messageGetBody(const message *m)
{
assert(m != NULL);
return m->body_first;
}
void
messageClean(message *m)
{
text *newEnd = textClean(m->body_first);
if(newEnd)
m->body_last = newEnd;
}
static void *
messageExport(message *m, const char *dir, void *(*create)(void), void (*destroy)(void *), void (*setFilename)(void *, const char *, const char *), void (*addData)(void *, const unsigned char *, size_t), void *(*exportText)(const text *, void *))
{
void *ret;
const text *t_line;
char *filename;
int i;
assert(m != NULL);
if(messageGetBody(m) == NULL)
return NULL;
ret = (*create)();
if(ret == NULL)
return NULL;
if((t_line = binhexBegin(m)) != NULL) {
unsigned char byte;
unsigned long len, l, newlen = 0L;
unsigned char *uptr, *data;
char *ptr;
int bytenumber;
blob *tmp;
const unsigned char hqxtbl[] = {
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0xff,0xff,
0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0xff,0x14,0x15,0xff,0xff,0xff,0xff,0xff,0xff,
0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0xff,
0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0xff,0x2c,0x2d,0x2e,0x2f,0xff,0xff,0xff,0xff,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0xff,0x37,0x38,0x39,0x3a,0x3b,0x3c,0xff,0xff,
0x3d,0x3e,0x3f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
};
tmp = blobCreate();
if(tmp == NULL) {
(*destroy)(ret);
return NULL;
}
while((t_line = t_line->t_next) != NULL)
if(t_line->t_line) {
const char *d = lineGetData(t_line->t_line);
blobAddData(tmp, (unsigned char *)d, strlen(d));
}
data = blobGetData(tmp);
if(data == NULL) {
cli_warnmsg("Couldn't locate the binhex message that was claimed to be there\n");
blobDestroy(tmp);
(*destroy)(ret);
return NULL;
}
if(data[0] != ':') {
cli_warnmsg("8 bit binhex code is not yet supported\n");
blobDestroy(tmp);
(*destroy)(ret);
return NULL;
}
len = blobGetDataSize(tmp);
cli_dbgmsg("decode HQX7 message (%lu bytes)\n", len);
uptr = cli_malloc(len);
if(uptr == NULL) {
blobDestroy(tmp);
(*destroy)(ret);
return NULL;
}
memcpy(uptr, data, len);
bytenumber = 0;
for(l = 1; l < len; l++) {
unsigned char c = uptr[l];
if(c == ':')
break;
if((c == '\n') || (c == '\r'))
continue;
if((c < 0x20) || (c > 0x7f) || (hqxtbl[c] == 0xff)) {
cli_warnmsg("Invalid HQX7 character '%c' (0x%02x)\n", c, c);
break;
}
c = hqxtbl[c];
assert(c <= 63);
switch(bytenumber) {
case 0:
data[newlen] = (c << 2) & 0xFC;
bytenumber = 1;
break;
case 1:
data[newlen++] |= (c >> 4) & 0x3;
data[newlen] = (c << 4) & 0xF0;
bytenumber = 2;
break;
case 2:
data[newlen++] |= (c >> 2) & 0xF;
data[newlen] = (c << 6) & 0xC0;
bytenumber = 3;
break;
case 3:
data[newlen++] |= c & 0x3F;
bytenumber = 0;
break;
}
}
cli_dbgmsg("decoded HQX7 message (now %lu bytes)\n", newlen);
free(uptr);
if(memchr(data, 0x90, newlen)) {
blob *u = blobCreate();
if(u == NULL) {
(*destroy)(ret);
blobDestroy(tmp);
return NULL;
}
for(l = 0L; l < newlen; l++) {
unsigned char c = data[l];
blobAddData(u, &c, 1);
if((l < (newlen - 1L)) && (data[l + 1] == 0x90)) {
int count;
l += 2;
count = data[l];
#ifdef CL_DEBUG
cli_dbgmsg("uncompress HQX7 at 0x%06x: %d repetitive bytes\n", l, count);
#endif
if(count == 0) {
c = 0x90;
blobAddData(u, &c, 1);
} else {
blobGrow(u, count);
while(--count > 0)
blobAddData(u, &c, 1);
}
}
}
blobDestroy(tmp);
tmp = u;
data = blobGetData(tmp);
len = blobGetDataSize(tmp);
cli_dbgmsg("Uncompressed %lu bytes to %lu\n", newlen, len);
} else {
len = newlen;
cli_dbgmsg("HQX7 message (%lu bytes) is not compressed\n",
len);
}
if(len == 0) {
cli_warnmsg("Discarding empty binHex attachment\n");
(*destroy)(ret);
blobDestroy(tmp);
return NULL;
}
byte = data[0];
if(byte >= len) {
(*destroy)(ret);
blobDestroy(tmp);
return NULL;
}
filename = cli_malloc(byte + 1);
if(filename == NULL) {
(*destroy)(ret);
blobDestroy(tmp);
return NULL;
}
memcpy(filename, &data[1], byte);
filename[byte] = '\0';
(*setFilename)(ret, dir, filename);
ptr = cli_malloc(byte + 6);
if(ptr) {
sprintf(ptr, "name=%s", filename);
messageAddArgument(m, ptr);
free(ptr);
}
byte = 1 + byte + 1 + 4 + 4 + 2;
len = ((data[byte] << 24) & 0xFF000000) |
((data[byte + 1] << 16) & 0xFF0000) |
((data[byte + 2] << 8) & 0xFF00) |
(data[byte + 3] & 0xFF);
cli_dbgmsg("Filename = '%s', data fork length = %lu bytes\n",
filename, len);
free((char *)filename);
byte += 10;
l = blobGetDataSize(tmp) - byte;
if(l < len) {
cli_warnmsg("Corrupt BinHex file, claims it is %lu bytes long in a message of %lu bytes\n",
len, l);
len = l;
}
(*addData)(ret, &data[byte], len);
blobDestroy(tmp);
m->binhex = NULL;
}
if(m->numberOfEncTypes == 0) {
filename = (char *)messageFindArgument(m, "filename");
if(filename == NULL) {
filename = (char *)messageFindArgument(m, "name");
if(filename == NULL) {
cli_dbgmsg("Attachment sent with no filename\n");
messageAddArgument(m, "name=attachment");
} else
messageSetEncoding(m, "base64");
}
(*setFilename)(ret, dir, (filename) ? filename : "attachment");
if(filename)
free((char *)filename);
if(m->numberOfEncTypes == 0) {
if(uuencodeBegin(m))
messageSetEncoding(m, "x-uuencode");
else
return exportText(messageGetBody(m), ret);
}
}
for(i = 0; i < m->numberOfEncTypes; i++) {
encoding_type enctype = m->encodingTypes[i];
if((enctype == UUENCODE) || ((i == 0) && uuencodeBegin(m))) {
t_line = uuencodeBegin(m);
if(t_line == NULL) {
(*destroy)(ret);
return NULL;
}
filename = cli_strtok(lineGetData(t_line->t_line), 2, " ");
if(filename == NULL) {
cli_dbgmsg("UUencoded attachment sent with no filename\n");
(*destroy)(ret);
return NULL;
}
cli_chomp(filename);
cli_dbgmsg("Set uuencode filename to \"%s\"\n", filename);
(*setFilename)(ret, dir, filename);
t_line = t_line->t_next;
enctype = UUENCODE;
} else if((enctype == YENCODE) || ((i == 0) && yEncBegin(m))) {
t_line = yEncBegin(m);
filename = (char *)lineGetData(t_line->t_line);
if((filename = strstr(filename, " name=")) != NULL) {
filename = strdup(&filename[6]);
if(filename) {
cli_chomp(filename);
strstrip(filename);
cli_dbgmsg("Set yEnc filename to \"%s\"\n", filename);
}
}
(*setFilename)(ret, dir, (filename) ? filename : "attchment");
if(filename) {
free((char *)filename);
filename = NULL;
}
t_line = t_line->t_next;
enctype = YENCODE;
} else {
filename = (char *)messageFindArgument(m, "filename");
if(filename == NULL) {
filename = (char *)messageFindArgument(m, "name");
if(filename == NULL) {
cli_dbgmsg("Attachment sent with no filename\n");
messageAddArgument(m, "name=attachment");
} else if(enctype == NOENCODING)
messageSetEncoding(m, "base64");
}
(*setFilename)(ret, dir, (filename) ? filename : "attchment");
t_line = messageGetBody(m);
}
if(filename)
free((char *)filename);
if(t_line == NULL) {
cli_warnmsg("Empty attachment not saved\n");
(*destroy)(ret);
return NULL;
}
if(enctype == NOENCODING) {
(void)exportText(t_line, ret);
continue;
}
do {
unsigned char data[1024];
unsigned char *uptr;
const char *line = lineGetData(t_line->t_line);
if(enctype == UUENCODE) {
if(line == NULL)
continue;
if(strcasecmp(line, "end") == 0)
break;
} else if(enctype == YENCODE) {
if(line == NULL)
continue;
if(strncmp(line, "=yend ", 6) == 0)
break;
}
uptr = decodeLine(m, enctype, line, data, sizeof(data));
if(uptr == NULL)
break;
assert(uptr <= &data[sizeof(data)]);
if(uptr != data)
(*addData)(ret, data, (size_t)(uptr - data));
} while((t_line = t_line->t_next) != NULL);
}
if(m->base64chars) {
unsigned char data[4];
unsigned char *ptr;
ptr = decode(m, NULL, data, base64, FALSE);
if(ptr)
(*addData)(ret, data, (size_t)(ptr - data));
m->base64chars = 0;
}
return ret;
}
fileblob *
messageToFileblob(message *m, const char *dir)
{
cli_dbgmsg("messageToFileblob\n");
return messageExport(m, dir, (void *)fileblobCreate, (void *)fileblobDestroy, (void *)fileblobSetFilename, (void *)fileblobAddData, (void *)textToFileblob);
}
blob *
messageToBlob(message *m)
{
return messageExport(m, NULL, (void *)blobCreate, (void *)blobDestroy, (void *)blobSetFilename, (void *)blobAddData, (void *)textToBlob);
}
text *
messageToText(message *m)
{
int i;
text *first = NULL, *last = NULL;
const text *t_line;
assert(m != NULL);
if(m->numberOfEncTypes == 0) {
for(t_line = messageGetBody(m); t_line; t_line = t_line->t_next) {
if(first == NULL)
first = last = cli_malloc(sizeof(text));
else {
last->t_next = cli_malloc(sizeof(text));
last = last->t_next;
}
if(last == NULL) {
if(first)
textDestroy(first);
return NULL;
}
if(t_line->t_line)
last->t_line = lineLink(t_line->t_line);
else
last->t_line = NULL;
}
if(last)
last->t_next = NULL;
return first;
}
for(i = 0; i < m->numberOfEncTypes; i++) {
const encoding_type enctype = m->encodingTypes[i];
cli_dbgmsg("messageToText: export transfer method %d = %d\n",
i, enctype);
if(enctype == NOENCODING) {
for(t_line = messageGetBody(m); t_line; t_line = t_line->t_next) {
if(first == NULL)
first = last = cli_malloc(sizeof(text));
else {
last->t_next = cli_malloc(sizeof(text));
last = last->t_next;
}
if(last == NULL) {
if(first)
textDestroy(first);
return NULL;
}
if(t_line->t_line)
last->t_line = lineLink(t_line->t_line);
else
last->t_line = NULL;
}
continue;
}
if(enctype == UUENCODE) {
t_line = uuencodeBegin(m);
if(t_line == NULL) {
if(first)
textDestroy(first);
return NULL;
}
t_line = t_line->t_next;
} else if(enctype == YENCODE) {
t_line = yEncBegin(m);
if(t_line == NULL) {
if(first)
textDestroy(first);
return NULL;
}
t_line = t_line->t_next;
} else {
if((i == 0) && binhexBegin(m))
cli_warnmsg("Binhex messages not supported yet.\n");
t_line = messageGetBody(m);
}
for(; t_line; t_line = t_line->t_next) {
unsigned char data[1024];
unsigned char *uptr;
const char *line = lineGetData(t_line->t_line);
if(enctype == BASE64) {
if(line == NULL)
continue;
} else if(enctype == UUENCODE)
if(strcasecmp(line, "end") == 0)
break;
uptr = decodeLine(m, enctype, line, data, sizeof(data));
if(uptr == NULL)
break;
assert(uptr <= &data[sizeof(data)]);
if(first == NULL)
first = last = cli_malloc(sizeof(text));
else {
last->t_next = cli_malloc(sizeof(text));
last = last->t_next;
}
if(last == NULL)
break;
if((data[0] == '\n') || (data[0] == '\0'))
last->t_line = NULL;
else if(line && (strncmp(data, line, strlen(line)) == 0)) {
cli_dbgmsg("messageToText: decoded line is the same(%s)\n", data);
last->t_line = lineLink(t_line->t_line);
} else
last->t_line = lineCreate((char *)data);
if(line && enctype == BASE64)
if(strchr(line, '='))
break;
}
if(m->base64chars) {
unsigned char data[4];
memset(data, '\0', sizeof(data));
if(decode(m, NULL, data, base64, FALSE) && data[0]) {
if(first == NULL)
first = last = cli_malloc(sizeof(text));
else {
last->t_next = cli_malloc(sizeof(text));
last = last->t_next;
}
if(last != NULL)
last->t_line = lineCreate((char *)data);
}
m->base64chars = 0;
}
}
if(last)
last->t_next = NULL;
return first;
}
#if 0
const text *
uuencodeBegin(const message *m)
{
const text *t_line;
for(t_line = messageGetBody(m); t_line; t_line = t_line->t_next) {
const char *line = t_line->t_text;
if((strncasecmp(line, "begin ", 6) == 0) &&
(isdigit(line[6])) &&
(isdigit(line[7])) &&
(isdigit(line[8])) &&
(line[9] == ' '))
return t_line;
}
return NULL;
}
#else
const text *
uuencodeBegin(const message *m)
{
return m->uuencode;
}
#endif
const text *
yEncBegin(const message *m)
{
return m->yenc;
}
#if 0
static const text *
binhexBegin(const message *m)
{
const text *t_line;
for(t_line = messageGetBody(m); t_line; t_line = t_line->t_next)
if(strcasecmp(t_line->t_text, "(This file must be converted with BinHex 4.0)") == 0)
return t_line;
return NULL;
}
#else
static const text *
binhexBegin(const message *m)
{
return m->binhex;
}
#endif
#if 0
const text *
bounceBegin(const message *m)
{
const text *t_line;
for(t_line = messageGetBody(m); t_line; t_line = t_line->t_next)
if(cli_filetype(t_line->t_text, strlen(t_line->t_text)) == CL_TYPE_MAIL)
return t_line;
return NULL;
}
#else
const text *
bounceBegin(const message *m)
{
return m->bounce;
}
#endif
#if 0
int
messageIsAllText(const message *m)
{
const text *t;
for(t = messageGetBody(m); t; t = t->t_next)
if(strncasecmp(t->t_text,
"Content-Transfer-Encoding",
strlen("Content-Transfer-Encoding")) == 0)
return 0;
return 1;
}
#else
const text *
encodingLine(const message *m)
{
return m->encoding;
}
#endif
void
messageClearMarkers(message *m)
{
m->encoding = m->bounce = m->uuencode = m->binhex = NULL;
}
static unsigned char *
decodeLine(message *m, encoding_type et, const char *line, unsigned char *buf, size_t buflen)
{
size_t len;
bool softbreak;
char *p2;
char *copy;
assert(m != NULL);
assert(buf != NULL);
switch(et) {
case BINARY:
case NOENCODING:
case EIGHTBIT:
default:
if(line)
buf = (unsigned char *)strrcpy((char *)buf, line);
return (unsigned char *)strrcpy((char *)buf, "\n");
case QUOTEDPRINTABLE:
if(line == NULL) {
*buf++ = '\n';
break;
}
softbreak = FALSE;
while(*line) {
if(*line == '=') {
unsigned char byte;
if((*++line == '\0') || (*line == '\n')) {
softbreak = TRUE;
break;
}
byte = hex(*line);
if((*++line == '\0') || (*line == '\n')) {
*buf++ = byte;
break;
}
byte <<= 4;
byte += hex(*line);
*buf++ = byte;
} else
*buf++ = *line;
line++;
}
if(!softbreak)
*buf++ = '\n';
break;
case BASE64:
if(line == NULL)
break;
copy = strdup(line);
if(copy == NULL)
break;
sanitiseBase64(copy);
p2 = strchr(copy, '=');
if(p2)
*p2 = '\0';
buf = decode(m, copy, buf, base64, (p2 == NULL) && ((strlen(copy) & 3) == 0));
if(p2)
buf = decode(m, NULL, buf, base64, FALSE);
free(copy);
break;
case UUENCODE:
if((line == NULL) || (*line == '\0'))
break;
if(strncasecmp(line, "begin ", 6) == 0)
break;
if(strcasecmp(line, "end") == 0)
break;
if((line[0] & 0x3F) == ' ')
break;
len = *line++ - ' ';
if(len > buflen)
cli_warnmsg("uudecode: buffer overflow stopped, attempting to ignore but decoding may fail\n");
else
buf = decode(m, line, buf, uudecode, (len & 3) == 0);
break;
case YENCODE:
if((line == NULL) || (*line == '\0'))
break;
if(strncmp(line, "=yend ", 6) == 0)
break;
while(*line)
if(*line == '=') {
if(*++line == '\0')
break;
*buf++ = ((*line++ - 64) & 255);
} else
*buf++ = ((*line++ - 42) & 255);
break;
}
*buf = '\0';
return buf;
}
static void
sanitiseBase64(char *s)
{
for(; *s; s++)
if(base64Table[*s] == 255) {
char *p1;
for(p1 = s; p1[0] != '\0'; p1++)
p1[0] = p1[1];
}
}
static unsigned char *
decode(message *m, const char *in, unsigned char *out, unsigned char (*decoder)(char), bool isFast)
{
unsigned char b1, b2, b3, b4;
unsigned char cb1, cb2, cb3;
cb1 = cb2 = cb3 = '\0';
switch(m->base64chars) {
case 3:
cb3 = m->base64_3;
case 2:
cb2 = m->base64_2;
case 1:
cb1 = m->base64_1;
isFast = FALSE;
break;
default:
assert(m->base64chars <= 3);
}
if(isFast)
while(*in) {
b1 = (*decoder)(*in++);
b2 = (*decoder)(*in++);
b3 = (*decoder)(*in++);
*out++ = (b1 << 2) | ((b2 >> 4) & 0x3);
b4 = (*decoder)(*in++);
*out++ = (b2 << 4) | ((b3 >> 2) & 0xF);
*out++ = (b3 << 6) | (b4 & 0x3F);
}
else {
if(in == NULL) {
int nbytes = m->base64chars;
if(nbytes == 0)
return out;
m->base64chars--;
b1 = cb1;
if(m->base64chars) {
m->base64chars--;
b2 = cb2;
if(m->base64chars) {
m->base64chars--;
b3 = cb3;
assert(m->base64chars == 0);
}
}
switch(nbytes) {
case 3:
b4 = '\0';
case 4:
*out++ = (b1 << 2) | ((b2 >> 4) & 0x3);
*out++ = (b2 << 4) | ((b3 >> 2) & 0xF);
*out++ = (b3 << 6) | (b4 & 0x3F);
break;
case 2:
*out++ = (b1 << 2) | ((b2 >> 4) & 0x3);
*out++ = b2 << 4;
break;
case 1:
*out++ = b1 << 2;
break;
default:
assert(0);
}
} else while(*in) {
int nbytes;
if(m->base64chars) {
m->base64chars--;
b1 = cb1;
} else
b1 = (*decoder)(*in++);
if(*in == '\0') {
b2 = '\0';
nbytes = 1;
} else {
if(m->base64chars) {
m->base64chars--;
b2 = cb2;
} else
b2 = (*decoder)(*in++);
if(*in == '\0') {
b3 = '\0';
nbytes = 2;
} else {
if(m->base64chars) {
m->base64chars--;
b3 = cb3;
} else
b3 = (*decoder)(*in++);
if(*in == '\0') {
b4 = '\0';
nbytes = 3;
} else {
b4 = (*decoder)(*in++);
nbytes = 4;
}
}
}
switch(nbytes) {
case 3:
m->base64_3 = b3;
case 2:
m->base64_2 = b2;
case 1:
m->base64_1 = b1;
break;
case 4:
*out++ = (b1 << 2) | ((b2 >> 4) & 0x3);
*out++ = (b2 << 4) | ((b3 >> 2) & 0xF);
*out++ = (b3 << 6) | (b4 & 0x3F);
break;
default:
assert(0);
}
if(nbytes != 4) {
m->base64chars = nbytes;
break;
}
}
}
return out;
}
static unsigned char
hex(char c)
{
if(isdigit(c))
return c - '0';
if((c >= 'A') && (c <= 'F'))
return c - 'A' + 10;
return '=';
}
#ifdef USE_TABLE
static unsigned char
base64(char c)
{
const unsigned char ret = base64Table[(int)c];
if(ret == 255) {
cli_dbgmsg("Illegal character <%c> in base64 encoding\n", c);
return 63;
}
return ret;
}
#else
static unsigned char
base64(char c)
{
if(isupper(c))
return c - 'A';
if(isdigit(c))
return c - '0' + 52;
if(c == '+')
return 62;
if(islower(c))
return c - 'a' + 26;
if(c != '/')
cli_dbgmsg("Illegal character <%c> in base64 encoding\n", c);
return 63;
}
#endif
static unsigned char
uudecode(char c)
{
return(c - ' ');
}
static int
usefulArg(const char *arg)
{
if((strncasecmp(arg, "name", 4) != 0) &&
(strncasecmp(arg, "filename", 8) != 0) &&
(strncasecmp(arg, "boundary", 8) != 0) &&
(strncasecmp(arg, "protocol", 8) != 0) &&
(strncasecmp(arg, "id", 2) != 0) &&
(strncasecmp(arg, "number", 6) != 0) &&
(strncasecmp(arg, "total", 5) != 0) &&
(strncasecmp(arg, "type", 4) != 0)) {
cli_dbgmsg("Discarding unwanted argument '%s'\n", arg);
return 0;
}
return 1;
}
static void
messageDedup(message *m)
{
const text *t1;
size_t saved = 0;
t1 = m->dedupedThisFar ? m->dedupedThisFar : m->body_first;
for(t1 = m->body_first; t1; t1 = t1->t_next) {
const char *d1;
text *t2;
line_t *l1;
unsigned int r1;
if(saved >= 100*1000)
break;
l1 = t1->t_line;
if(l1 == NULL)
continue;
d1 = lineGetData(l1);
if(strlen(d1) < 8)
continue;
r1 = (unsigned int)lineGetRefCount(l1);
if(r1 == 255)
continue;
if(t1 == m->encoding)
continue;
if(t1 == m->bounce)
continue;
if(t1 == m->uuencode)
continue;
if(t1 == m->binhex)
continue;
if(t1 == m->yenc)
continue;
for(t2 = t1->t_next; t2; t2 = t2->t_next) {
const char *d2;
line_t *l2 = t2->t_line;
if(l2 == NULL)
continue;
if((r1 + (unsigned int)lineGetRefCount(l2)) > 255)
continue;
d2 = lineGetData(l2);
if(d1 == d2)
continue;
if(strcmp(d1, d2) == 0) {
if(lineUnlink(l2) == NULL)
saved += strlen(d1);
t2->t_line = lineLink(l1);
if(t2->t_line == NULL) {
cli_errmsg("messageDedup: out of memory\n");
return;
}
}
}
}
m->dedupedThisFar = t1;
}