/*
Copyright (C) 1999-2004 IC & S dbmail@ic-s.nl
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* $Id: mime.c 1550 2005-01-07 12:23:02Z paul $
*
* Functions for parsing a mime mailheader (actually just for scanning for email messages
and parsing the messageID */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "mime.h"
#include "debug.h"
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* extern char *header; */
/* extern u64_t headersize; */
/* extern struct list mimelist; */
/* extern struct list users; */
/* checks if s points to the end of a header. */
static int is_end_of_header(const char *s);
/*
* mime_readheader()
*
* same as mime_list() but adds the number of bytes read to blkidx
* and returns the number of newlines passed
*
* headersize will be set to the actual amount of bytes used to store the header:
* field/value strlen()'s plus 4 bytes for each headeritem: ': ' (field/value
* separator) and '\r\n' to end the line.
*
* newlines within value will be expanded to '\r\n'
*
* if blkdata[0] == \n no header is expected and the function will return immediately
* (headersize 0)
*
* returns -1 on parse failure, -2 on memory error; number of newlines on succes
*/
int mime_readheader(const char *datablock, u64_t * blkidx, struct list *mimelist,
u64_t * headersize)
{
int valid_mime_lines = 0, idx, totallines = 0, j;
int fieldlen, vallen;
size_t prevlen = 0, new_add = 1;
/* u64_t saved_idx = *blkidx; only needed if we bail out on invalid data */
char *endptr, *startptr, *delimiter;
char *blkdata;
struct mime_record *mr, *prev_mr = NULL;
struct element *el = NULL;
int cr_nl_present; /* 1 if a '\r\n' is found */
trace(TRACE_DEBUG, "mime_readheader(): entering mime loop");
blkdata = dm_strdup(datablock);
list_init(mimelist);
*headersize = 0;
if (blkdata[0] == '\n') {
trace(TRACE_DEBUG,
"mime_readheader(): found an empty header\n");
(*blkidx)++; /* skip \n */
dm_free(blkdata);
return 1; /* found 1 newline */
}
/* alloc mem */
mr = (struct mime_record *) dm_malloc(sizeof(struct mime_record));
if (!mr) {
trace(TRACE_ERROR, "mime_readheader(): out of memory\n");
dm_free(blkdata);
return -2;
}
startptr = blkdata;
while (*startptr) {
cr_nl_present = 0;
/* quick hack to jump over those naughty \n\t fields */
endptr = startptr;
while (*endptr) {
/* field-ending: \n + (non-white space) */
if (*endptr == '\n') {
totallines++;
if (is_end_of_header(endptr))
break;
}
if (*endptr == '\r' && *(endptr + 1) == '\n') {
cr_nl_present = 1;
endptr++;
totallines++;
if (is_end_of_header(endptr))
break;
}
endptr++;
}
if (!(*endptr)) {
/* end of data block reached (??) */
dm_free(mr);
*blkidx += (endptr - startptr);
dm_free(blkdata);
return totallines;
}
/* endptr points to linebreak now */
/* MIME field+value is string from startptr till endptr */
if (cr_nl_present)
*(endptr - 1) = '\0';
*endptr = '\0'; /* replace newline to terminate string */
/* parsing tmpstring for field and data */
/* field is name:value */
delimiter = strchr(startptr, ':');
if (delimiter) {
/* found ':' */
valid_mime_lines++;
*delimiter = '\0'; /* split up strings */
/* skip all spaces and colons after the fieldname */
idx = 1;
while ((delimiter[idx] == ':')
|| (delimiter[idx] == ' '))
idx++;
/* &delimiter[idx] is field value, startptr is field name */
fieldlen =
snprintf(mr->field, MIME_FIELD_MAX, "%s",
startptr);
for (vallen = 0, j = 0;
delimiter[idx + j] && vallen < MIME_VALUE_MAX;
j++, vallen++) {
if (delimiter[idx + j] == '\n') {
mr->value[vallen++] = '\r';
/* dont count newline here: it is already counted */
}
mr->value[vallen] = delimiter[idx + j];
}
if (vallen < MIME_VALUE_MAX)
mr->value[vallen] = 0;
else
mr->value[MIME_VALUE_MAX - 1] = 0;
/* snprintf returns -1 if max is readched (libc <= 2.0.6) or the strlen (libc >= 2.1)
* check the value. it does not count the \0.
*/
if (fieldlen == -1 || fieldlen >= MIME_FIELD_MAX)
*headersize += MIME_FIELD_MAX;
else
*headersize += fieldlen;
if (vallen == -1 || vallen >= MIME_VALUE_MAX)
*headersize += MIME_VALUE_MAX;
else
*headersize += vallen;
*headersize += 4; /* <field>: <value>\r\n --> four more */
/* strncpy(mr->field, startptr, MIME_FIELD_MAX);
strncpy(mr->value, &delimiter[idx], MIME_VALUE_MAX);
*/
/* trace(TRACE_DEBUG,"mime_readheader(): mimepair found: [%s] [%s] \n",mr->field, mr->value);
*/
el = list_nodeadd(mimelist, mr, sizeof(*mr));
if (!el) {
trace(TRACE_ERROR,
"mime_readheader(): cannot add element to list\n");
dm_free(mr);
dm_free(blkdata);
return -2;
}
/* restore blkdata */
*delimiter = ':';
} else {
/*
* ok invalid mime header, what now ?
* just add it with an empty field name EXCEPT
* when the previous stored field value ends on a ';'
* in this case probably someone forget to place a \t on the next line
* then we will try to add it to the previous element
*/
new_add = 1;
if (el) {
prev_mr =
(struct mime_record *) (el->data);
prevlen = strlen(prev_mr->value);
new_add =
(prev_mr->value[prevlen - 1] ==
';') ? 0 : 1;
}
if (new_add) {
/* add a new field with no name */
strcpy(mr->field, "");
vallen =
snprintf(mr->value, MIME_VALUE_MAX,
"%s", startptr);
if (vallen == -1
|| vallen >= MIME_VALUE_MAX)
*headersize += MIME_VALUE_MAX;
else
*headersize += vallen;
*headersize += 4; /* <field>: <value>\r\n --> four more */
el = list_nodeadd(mimelist, mr,
sizeof(*mr));
if (!el) {
trace(TRACE_ERROR,
"mime_readheader(): cannot add element to list\n");
dm_free(mr);
dm_free(blkdata);
return -2;
}
} else {
/* try to add the value to the previous one */
if (prevlen <
MIME_VALUE_MAX - (strlen(startptr) +
4)) {
prev_mr->value[prevlen] = '\n';
prev_mr->value[prevlen + 1] = '\t';
strcpy(&prev_mr->
value[prevlen + 2],
startptr);
*headersize +=
(strlen(startptr) + 2);
} else {
trace(TRACE_WARNING,
"mime_readheader(): failed adding data (length would exceed "
"MIME_VALUE_MAX [currently %d])\n",
MIME_VALUE_MAX);
}
}
}
if (cr_nl_present)
*(endptr - 1) = '\r';
*endptr = '\n'; /* restore blkdata */
*blkidx += (endptr - startptr);
(*blkidx)++;
startptr = endptr + 1; /* advance to next field */
if (*startptr == '\n'
|| (*startptr == '\r' && *(startptr + 1) == '\n')) {
/* end of header: double newline */
totallines++;
(*blkidx)++;
(*headersize) += 2;
trace(TRACE_DEBUG,
"mime_readheader(): found double newline; header size: %d lines\n",
totallines);
dm_free(mr);
dm_free(blkdata);
return totallines;
}
}
/* everything down here should be unreachable */
dm_free(mr); /* no longer need this */
trace(TRACE_DEBUG, "mime_readheader(): mimeloop finished\n");
if (valid_mime_lines < 2) {
trace(TRACE_ERROR,
"mime_readheader(): no valid mime headers found\n");
dm_free(blkdata);
return -1;
}
dm_free(blkdata);
/* success ? */
trace(TRACE_DEBUG, " *** mime_readheader() done ***\n");
return totallines;
}
/*
* mime_findfield()
*
* finds a MIME header field
*
*/
void mime_findfield(const char *fname, struct list *mimelist,
struct mime_record **mr)
{
struct element *current;
current = list_getstart(mimelist);
while (current) {
*mr = current->data; /* get field/value */
// if (strncasecmp((*mr)->field, fname, strlen(fname)) == 0)
// some mail is prepended by a line like "From <host> <datetime>"
// using strncasecmp causes problems with this: it is seen as the From: field
if (strcasecmp((*mr)->field, fname) == 0)
return; /* found */
current = current->nextnode;
}
*mr = NULL;
}
int mail_adr_list(char *scan_for_field, struct list *targetlist,
struct list *mimelist)
{
struct element *raw;
struct mime_record *mr;
char *tmpvalue, *ptr, *tmp;
if (!scan_for_field || !targetlist || !mimelist) {
trace(TRACE_ERROR,
"mail_adr_list(): received a NULL argument\n");
return -1;
}
trace(TRACE_DEBUG,
"mail_adr_list(): mimelist currently has [%ld] nodes",
mimelist->total_nodes);
memtst((tmpvalue =
(char *) dm_calloc(MIME_VALUE_MAX, sizeof(char))) == NULL);
trace(TRACE_INFO, "mail_adr_list(): mail address parser starting");
raw = list_getstart(mimelist);
trace(TRACE_DEBUG, "mail_adr_list(): total fields in header %ld",
mimelist->total_nodes);
while (raw != NULL) {
mr = (struct mime_record *) raw->data;
trace(TRACE_DEBUG, "mail_adr_list(): scanning for %s",
scan_for_field);
if ((strcasecmp(mr->field, scan_for_field) == 0)) {
/* Scan for email addresses and add them to our list */
/* the idea is to first find the first @ and go both ways */
/* until an non-emailaddress character is found */
ptr = strstr(mr->value, "@");
while (ptr != NULL) {
/* found an @! */
/* first go as far left as possible */
tmp = ptr;
while ((tmp != mr->value) &&
(tmp[0] != '<') &&
(tmp[0] != ' ') &&
(tmp[0] != '\0') && (tmp[0] != ','))
tmp--;
if ((tmp[0] == '<') || (tmp[0] == ' ')
|| (tmp[0] == '\0')
|| (tmp[0] == ','))
tmp++;
while ((ptr != NULL) &&
(ptr[0] != '>') &&
(ptr[0] != ' ') &&
(ptr[0] != ',') && (ptr[0] != '\0'))
ptr++;
memtst((strncpy(tmpvalue, tmp, ptr - tmp))
== NULL);
/* always set last value to \0 to end string */
tmpvalue[ptr - tmp] = '\0';
/* one extra for \0 in strlen */
memtst((list_nodeadd(targetlist, tmpvalue,
(strlen(tmpvalue) +
1))) == NULL);
/* printf ("total nodes:\n");
list_showlist(&targetlist);
next address */
ptr = strstr(ptr, "@");
trace(TRACE_DEBUG,
"mail_adr_list(): found %s, next in list is %s",
tmpvalue, ptr ? ptr : "<null>");
}
}
raw = raw->nextnode;
}
dm_free(tmpvalue);
trace(TRACE_DEBUG, "mail_adr_list(): found %ld emailaddresses",
targetlist->total_nodes);
trace(TRACE_INFO, "mail_adr_list(): mail address parser finished");
if (targetlist->total_nodes == 0) /* no addresses found */
return -1;
return 0;
}
/* return 1 if this is the end of a header. That is, it returns 1 if
* the next character is a non-whitespace character, or a newline or
* carriage return + newline. If the next character is a white space
* character, but not a newline, or carriage return + newline, the
* header continues.
*/
int is_end_of_header(const char *s)
{
if (!isspace(s[1]))
return 1;
if (s[1] == '\n')
return 1;
if (s[1] == '\r' && s[2] == '\n')
return 1;
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1