From 567a1fa1b3230b5d4fe69ce08b4e7cede9791e83 Mon Sep 17 00:00:00 2001 From: alexhudson Date: Sun, 7 Sep 2008 13:01:20 +0000 Subject: [PATCH] Store agent now uses GMime to parsing incoming mails. --- configure.ac | 2 + m4/gmime.m4 | 36 + src/agents/store/Bongo.rules | 6 +- src/agents/store/mail-parser.c | 1195 -------------------------------- src/agents/store/mail-parser.h | 90 --- src/agents/store/mail.c | 377 +++------- src/agents/store/stored.c | 6 + 7 files changed, 131 insertions(+), 1581 deletions(-) create mode 100644 m4/gmime.m4 delete mode 100644 src/agents/store/mail-parser.c delete mode 100644 src/agents/store/mail-parser.h diff --git a/configure.ac b/configure.ac index 646aa62..0e9b0f7 100644 --- a/configure.ac +++ b/configure.ac @@ -358,6 +358,8 @@ PKG_CHECK_MODULES(GNUTLS, gnutls, AM_PATH_LIBGCRYPT() +AC_CHECK_GMIME() + AM_PATH_PYTHON(2.3) PYTHON_INST_PREFIX=`$PYTHON -c 'import sys; print sys.prefix'` PYTHON_CPPFLAGS="-I$PYTHON_INST_PREFIX/include/python$PYTHON_VERSION" diff --git a/m4/gmime.m4 b/m4/gmime.m4 new file mode 100644 index 0000000..1c5cb7f --- /dev/null +++ b/m4/gmime.m4 @@ -0,0 +1,36 @@ +dnl Check for GMime Library. + +AC_DEFUN([AC_CHECK_GMIME], [ + have_gmime="no" + ac_gmime="no" + + # exported variables + GMIME_LIBS="" + GMIME_CFLAGS="" + + AC_ARG_WITH(gmime, AC_HELP_STRING([--with-gmime[=dir]], [Compile with libgmime at given dir]), + [ ac_gmime="$withval" + if test "x$withval" != "xno" -a "x$withval" != "xyes"; then + ac_gmime="yes" + ac_gmime_dir=$withval + fi ], + [ ac_gmime="auto" ] ) + + AC_MSG_CHECKING([for GMime-2.4]) + + if test "x$PKG_CONFIG" != "xno"; then + if test "x$ac_gmime" = "xyes"; then + ac_gmime_tmppkgconfig=$PKG_CONFIG_PATH + export PKG_CONFIG_PATH=${ac_gmime_dir}/lib/pkgconfig/ + echo Setting path to $PKG_CONFIG_PATH + PKG_CHECK_MODULES(GMIME, gmime-2.4, ac_gmime="no") + export PKG_CONFIG_PATH=$ac_gmime_tmppkgconfig + fi + + if test "x$ac_gmime" = "xauto"; then + PKG_CHECK_MODULES(GMIME, gmime-2.4, ac_gmime="no") + fi + fi + + AC_MSG_RESULT([$ac_gmime]) +]) diff --git a/src/agents/store/Bongo.rules b/src/agents/store/Bongo.rules index 726cc1d..8cbc6f8 100644 --- a/src/agents/store/Bongo.rules +++ b/src/agents/store/Bongo.rules @@ -4,6 +4,7 @@ sbin_PROGRAMS += bongostore bongostore_CPPFLAGS = $(AM_CPPFLAGS) \ -Wall -Werror \ -I$(top_srcdir)/src/agents/store \ + $(GMIME_CFLAGS) \ $(SQLITE_CFLAGS) # this is a hack because certain automakes don't seem to output .o @@ -33,8 +34,6 @@ bongostore_SOURCES = \ src/agents/store/mail.c \ src/agents/store/mail.h \ src/agents/store/maildir.c \ - src/agents/store/mail-parser.c \ - src/agents/store/mail-parser.h \ src/agents/store/main.cpp \ src/agents/store/messages.h \ src/agents/store/mime.c \ @@ -65,7 +64,8 @@ bongostore_LDADD = \ libbongoutil.la \ libbongoxpl.la \ $(top_builddir)/import/log4c/log4c/libbongolog4c.la \ - $(SQLITE_LIBS) \ + $(SQLITE_LIBS) \ + $(GMIME_LIBS) \ $(PTHREAD_LIBS) \ $(KSTAT_LIBS) \ $(ALL_LIBS) diff --git a/src/agents/store/mail-parser.c b/src/agents/store/mail-parser.c deleted file mode 100644 index f2aeca8..0000000 --- a/src/agents/store/mail-parser.c +++ /dev/null @@ -1,1195 +0,0 @@ -/**************************************************************************** - * - * Copyright (c) 2005, 2006 Novell, Inc. All Rights Reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of version 2 of the GNU General Public License - * as published by the Free Software Foundation. - * - * 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, contact Novell, Inc. - * - * To contact Novell about this file by physical or electronic mail, you - * may find current contact information at www.novell.com. - * - ****************************************************************************/ - -/* Routines for parsing and manipulating mail messages */ - -#include -#include "stored.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mail-parser.h" -#include "mime.h" - -typedef struct { - MailHeaderName name; - union { - struct { - char *email; - char *name; - }; - char *content_type; - uint64_t date; - }; -} MailHeader; - -/* FIXME This is a third-assed mail header parser. */ - -struct MailParser { - FILE *stream; - MailParserParticipantsCallback participantscb; - MailParserUnstructuredCallback unstructuredcb; - MailParserDateCallback datecb; - MailParserMessageIdCallback messageidcb; - MailParserOffsetCallback offsetcb; - void *cbarg; - - char line[2048]; - char headerName[128]; - char *p; -}; - - -#define NEXTLINE(_retval) \ - if (!fgets(parser->line, 2048, parser->stream)) { \ - return _retval; \ - } \ - parser->p = parser->line; - - - - -/* requires: parser->p points to the first char following - the comment's opening '(' - - returns: -1 on EOF/error, o/w 0 - - modifies: parser->p will be advanced to the next char - following the closing ')' - */ - -static int -ParseComment(MailParser *parser) -{ - int c; - - while (1) { - switch (*parser->p++) { - case 0: - NEXTLINE(-1); - continue; - case '(': - if (ParseComment(parser)) { - return -1; - } - continue; - case ')': - return 0; - case '\\': /* quoted pair */ - c = *parser->p++; - if (0 == c) { - NEXTLINE(-1); - } - continue; - default: - continue; - } - } -} - - -/* returns: 0 - EOF or run-on comment - '\n' - non-folded CRLF - char - next non-whitespace char - - modifies: parser->line may be refreshed to the next line - parser->p will be advanced to the next non-whitespace character - */ - -static char -NextNonCFWS(MailParser *parser) -{ - int c; -#if 0 - if (parser->p == parser->line) { - if ('\r' == *parser->p || '\n' == *parser->p) { - /* we are at the end of the headers */ - return *parser->p; - } - } -#endif - while (1) { - switch (*parser->p++) { - case 0: - NEXTLINE(0); - continue; - case ' ': - case '\t': - continue; - case '\r': - c = *parser->p++; - if (0 == c) { - NEXTLINE(0); - c = *parser->p++; - } - if ('\n' == c) { - case '\n': - NEXTLINE(0); - if (' ' != *parser->p && '\t' != *parser->p) { - return '\n'; /* CRLF */ - } - continue; - } else { - /* again, I think this is illegal, treat as space */ - continue; - } - case '(': - if (ParseComment(parser)) { - return 0; - } - continue; - default: - return *--parser->p; - } - } -} - - -/* requires: points to a string of up to 80 characters to be examined to - see if they are encoded text. specifies the capacity of - * points to the first character following the last string - successfully decoded by this function, or NULL if the last string - was not encoded. - - modifies: if contains rfc2047-encoded text, then it will be decoded - in place into its UTF-8 representation, and * will be - set to the first character following the decoded string. - - returns: the length of the decoded token minus (buf - *prevtext) - */ - -static int -DecodeWord(char *buf, int len, char **prevtext) -{ - char token[81]; /* max encoded word length is 75 characters, we accept up to 80 */ - char charset[81]; - const char *encoding = NULL; - char *pbuf = *prevtext ? *prevtext : buf; - - BongoStream *stream = NULL; - - char *t; /* input ptr to */ - char *d; /* output ptr to buf */ - int tlen; - int elen; - - tlen = strlen(buf); - assert(tlen < 80); - - strncpy(token, buf, 80); - *prevtext = NULL; - - /* encoded-word = "=?" charset "?" encoding "?" encoded-text "?=" */ - - if ('=' != token[0] || '?' != token[1]) { - return tlen; - } - - /* find charset: */ - - t = token + 2; - d = charset; - - while (*t) { - if (!*t) { - return tlen; - } else if ('?' == *t) { - *d = 0; - ++t; - break; - } else { - *d++ = *t; - } - t++; - } - - /* find encoding: */ - - if ('B' == toupper(*t)) { - encoding = "BASE64"; - } else if ('Q' == toupper(*t)) { - encoding = "Q"; - } else { - return tlen; - } - - if ('?' != *++t) { - return tlen; - } - - /* verify encoded-text: */ - - d = ++t; - elen = 0; - while (1) { - if (0 == *d) { - return tlen; - } else if (' ' == *d) { - return tlen; - } else if ('?' == *d) { - if ('=' != d[1] || 0 != d[2]) { - return tlen; - } - break; - } - ++elen; - ++d; - } - - /* attempt to decode */ - - stream = BongoStreamCreate(NULL, 0, FALSE); - if (!stream) { - return tlen; - } - - if (BongoStreamAddCodec(stream, encoding, FALSE)) { - Log(LOG_ERROR, "Mail parser doesn't understand encoding %s", encoding); - goto done; - } - - if (BongoStreamAddCodec(stream, charset, FALSE)) { - Log(LOG_ERROR, "Mail parser doesn't understand charset %s", charset); - goto done; - } - - if (BongoStreamAddCodec(stream, "UTF-8", TRUE)) { - Log(LOG_ERROR, "Mail parser couldn't convert to UTF-8"); - goto done; - } - - BongoStreamPut(stream, t, elen); - tlen = BongoStreamGet(stream, pbuf, len); - *prevtext = pbuf + tlen; - tlen -= (buf - pbuf); - -done: - if (stream) { - BongoStreamFree(stream); - } - - return tlen; -} - - -static int -ParseUnstructured(MailParser *parser, char *buf, int len) -{ - int i = 0; - int c; - char *prevtext = NULL; - int tokstart = 0; - - while (1) { - c = *parser->p++; - - switch (c) { - case 0: - if (!fgets(parser->line, 2048, parser->stream)) { - goto done; - } - parser->p = parser->line; - continue; - case '\r': - c = *parser->p++; - /* fall through */ - case '\n': - if (i < len) { - if (i > tokstart && '=' == buf[tokstart] && i - tokstart < 80) { - buf[i] = 0; - i = tokstart + - DecodeWord(buf + tokstart, len - tokstart, &prevtext); - } - } - - if ('\n' == c) { - if (!fgets(parser->line, 2048, parser->stream)) { - goto done; - } - parser->p = parser->line; - - if (' ' != *parser->p && '\t' != *parser->p) { - goto done; - } - if (i < len) { - buf[i++] = *parser->p++; - } - tokstart = i; - } else { - /* again, I think this is illegal, treat as space */ - if (i < len) { - buf[i++] = ' '; - tokstart = i; - if (i < len) { - buf[i++] = c; - } - } - } - break; - case ' ': - case '\t': - if (i < len) { - if (i > tokstart && '=' == buf[tokstart] && i - tokstart < 80) { - buf[i] = 0; - i = tokstart + - DecodeWord(buf + tokstart, len - tokstart, &prevtext); - } - buf[i++] = c; - } - tokstart = i; - break; - - default: - if (i < len) { - buf[i++] = c; - } - } - } - -done: - buf[i < len ? i++ : len - 1] = 0; - - return i; -} - - -static int -ParseQuotedString(MailParser *parser, char *buf, int len) -{ - /* - qtext = NO-WS-CTL / ; Non white space controls - - %d33 / ; The rest of the US-ASCII - %d35-91 / ; characters not including "\" - %d93-126 ; or the quote character - - qcontent = qtext / quoted-pair - - quoted-string = [CFWS] - DQUOTE *([FWS] qcontent) [FWS] DQUOTE - [CFWS] - */ - - int c; - int i = 0; - - if ('"' != *parser->p++) { - return -1; - } - - while (1) { - c = *parser->p++; - - switch (c) { - case 0: - NEXTLINE(-1); - continue; - case '\r': - c = *parser->p++; - if ('\n' == c) { - case '\n': - NEXTLINE(-1); - if (' ' != *parser->p && '\t' != *parser->p) { - return -1; - } - c = NextNonCFWS(parser); - if ('\n' == c || 0 == c) { - return -1; - } - if (i < len) { - buf[i++] = ' '; - } - } else { - if (i < len) { - buf[i++] = ' '; - if (i < len) { - buf[i++] = c; - } - } - } - break; - case '"': - goto done; - case '\\': - c = *parser->p++; - if (0 == c) { - NEXTLINE(-1); - c = *parser->p++; - } - /* fall through: */ - default: - if (i < len) { - buf[i++] = c; - } - } - } - -done: - buf[i < len ? i++ : len - 1] = 0; - - return i; -} - - -/* returns: approximate number of tokens in phrase, -1 on error */ - -static int -ParsePhrase(MailParser *parser, char *buf, int len) -{ - int c; - int i = 0; - int tokens = 0; - int n; - int tokstart; - char *prevtext = NULL; - - --len; - -loop: - tokstart = i; - - c = NextNonCFWS(parser); - if ('\n' == c || 0 == c) { - goto done; - } else { - tokens++; - while (1) { - c = *parser->p; - switch (c) { - case 0: - if (!fgets(parser->line, 2048, parser->stream)) { - goto done; - } - parser->p = parser->line; - continue; - case '"': - if (-1 == (n = ParseQuotedString(parser, buf + i, len - i))) { - return -1; - } - i += n; - continue; - case '<': - case '>': - case '@': - case ':': - case ';': - case ',': - goto done; - case ' ': - case '\t': - case '\r': - if (i < len) { - if ('=' == buf[tokstart] && i - tokstart < 80) { - buf[i] = 0; - i = tokstart + - DecodeWord(buf + tokstart, len - tokstart, &prevtext); - } - buf[i++] = ' '; - } - goto loop; - case '\n': - if (i < len) { - buf[i++] = ' '; - } - goto done; - case '(': - parser->p++; - if (ParseComment(parser)) { - goto done; - } - continue; - default: - if (i < len) { - buf[i++] = c; - } - } - ++parser->p; - } - } -done: - if (i && ' ' == buf[i-1]) { - buf[i-1] = 0; - } else { - buf[i] = 0; - } - return tokens; -} - - -static int -ParseDotAtom(MailParser *parser, char *buf, int len) -{ - /* bad */ - if (0 == ParsePhrase(parser, buf, len)) { - return -1; - } - return strlen(buf); -} - -static int -ParseLocalPart(MailParser *parser, char *buf, int len) -{ - return ParseDotAtom(parser, buf, len); -} - - -static int -ParseDomain(MailParser *parser, char *buf, int len) -{ - return ParseDotAtom(parser, buf, len); -} - -static int -ParseAngleAddr(MailParser *parser, char *buf, int len) -{ - int cnt; - int pcode; - - ++parser->p; - - cnt = pcode = ParseLocalPart(parser, buf, len - 1); - if (-1 == pcode) { - return 0; - } - if ('@' != NextNonCFWS(parser)) { - return 0; - } - ++parser->p; - buf[cnt++] = '@'; - cnt += pcode = ParseDomain(parser, buf + cnt, len - cnt); - if (-1 == pcode) { - return 0; - } - - if ('>' != NextNonCFWS(parser)) { - return 0; - } - ++parser->p; - - return cnt; -} - - -static int ParseAddress(MailParser *parser, MailHeaderName header); -static int ParseAddressList(MailParser *parser, MailHeaderName header); - - -static int -ParseMailbox(MailParser *parser, MailHeaderName header) -{ - /* FIXME */ - return ParseAddress(parser, header); -} - - -static int -ParseMailboxList(MailParser *parser, MailHeaderName header) -{ - /* 1*([mailbox] [CFWS] "," [CFWS]) [mailbox] */ - - /* FIXME */ - return ParseAddressList(parser, header); -} - - -/* returns: 1 - address found - 0 - no address found - -1 - error -*/ - - -static int -ParseAddress(MailParser *parser, - MailHeaderName header) -{ - /* mailbox / group */ - - char c; - MailAddress addr; - char phrase[1024]; - - c = NextNonCFWS(parser); - - if ('\n' == c || ';' == c || 0 == c) { - return 0; - } else if (',' == c) { - /* forgive empty addresses in addresslists. */ - return 0; - } else if ('<' == c) { - if (-1 == ParseAngleAddr(parser, phrase, sizeof(phrase))) { - /* Sometimes the address has the illegal form "BLAH <>" */ - phrase[0] = 0; - } - addr.displayName = NULL; - addr.address = phrase; - if (parser->participantscb) { - parser->participantscb(parser->cbarg, header, &addr); - } - - return 1; - } else { - - if (0 == ParsePhrase(parser, phrase, sizeof(phrase))) { - return -1; - } - - c = NextNonCFWS(parser); - if (':' == c) { - /* we have a group */ - ++parser->p; - - if (-1 == ParseMailboxList(parser, header)) { - return -1; - } - c = NextNonCFWS(parser); - if (';' == c) { - ++parser->p; - return 0; - } - return -1; - } else if ('@' == c) { - int len; - addr.displayName = NULL; - len = strlen(phrase); - phrase[len++] = '@'; - ++parser->p; - if (-1 == ParseDomain(parser, phrase + len, sizeof(phrase) - len)) { - return -1; - } - addr.address = phrase; - if (parser->participantscb) { - parser->participantscb(parser->cbarg, header, &addr); - } - - return 1; - } else if ('<' == c) { - char tmp[1024]; - memcpy(tmp, phrase, sizeof(tmp)); - addr.displayName = tmp; - if (-1 == ParseAngleAddr(parser, phrase, sizeof(phrase))) { - return -1; - } - addr.address = phrase; - if (parser->participantscb) { - parser->participantscb(parser->cbarg, header, &addr); - } - - return 1; - } else { - return -1; - } - } -} - - -static int -ParseAddressList(MailParser *parser, - MailHeaderName header) -{ - /* (address *("," address)) */ - int pcode; - - pcode = ParseAddress(parser, header); - if (-1 == pcode) { - return -1; - } - while (1) { - char c = NextNonCFWS(parser); - if (',' == c) { - ++parser->p; - return ParseAddressList(parser, header); - } else { - return 0; - } - } -} - -static int -ParseUnstructuredHeader(MailParser *parser, MailHeaderName header) -{ - char buffer[2048]; - int c; - - c = NextNonCFWS(parser); - - if (0 == c || '\n' == c) { - return 0; - } - if (-1 == ParseUnstructured(parser, buffer, sizeof(buffer))) { - return -1; - } - - if (parser->unstructuredcb) { - parser->unstructuredcb(parser->cbarg, header, parser->headerName, buffer); - } - return 1; -} - -static int -ParseDateHeader(MailParser *parser, MailHeaderName header) -{ - char buffer[2048]; - int c; - uint64_t date; - - c = NextNonCFWS(parser); - - if (':' == c) { - /* forgive Groupwise spam filter messages */ - ++parser->p; - c = NextNonCFWS(parser); - } - - - if (0 == c || '\n' == c) { - return 0; - } - - if (-1 == ParseUnstructured(parser, buffer, sizeof(buffer))) { - return -1; - } - - date = MsgParseRFC822DateTime(buffer); - - if (0 == date) { - return 0; - } - - if (parser->datecb) { - parser->datecb(parser->cbarg, header, date); - } - - return 1; -} - -static int -ParseIdLeft(MailParser *parser, char *buf, int len) -{ - return ParseDotAtom(parser, buf, len); -} - -static int -ParseIdRight(MailParser *parser, char *buf, int len) -{ - return ParseDotAtom(parser, buf, len); -} - -static int -ParseAngleMessageId(MailParser *parser, char *buf, int len) -{ - int cnt; - int pcode; - - ++parser->p; - - cnt = pcode = ParseIdLeft(parser, buf, len - 1); - if (-1 == pcode) { - return -1; - } - if ('@' != NextNonCFWS(parser)) { - return -1; - } - ++parser->p; - buf[cnt++] = '@'; - cnt += pcode = ParseIdRight(parser, buf + cnt, len - cnt); - if (-1 == pcode) { - return -1; - } - - if ('>' != NextNonCFWS(parser)) { - return -1; - } - ++parser->p; - - return cnt; -} - -static int -ParseMessageId(MailParser *parser, - MailHeaderName header) -{ - /* mailbox / group */ - - char c; - char phrase[1024]; - - c = NextNonCFWS(parser); - - if ('\n' == c || ';' == c || 0 == c) { - return 0; - } else if ('<' == c) { - int length; - - phrase[0] = '<'; - length = ParseAngleMessageId(parser, phrase + 1, sizeof(phrase) - 1); - if (length == -1 || (length - 1) >= (int)sizeof(phrase)) { - return -1; - } - phrase[length + 1] = '>'; - phrase[length + 2] = '\0'; - - if (parser->messageidcb) { - parser->messageidcb(parser->cbarg, header, phrase); - } - - return 1; - } - - return 0; -} - -static int -ParseInReplyTo(MailParser *parser, - MailHeaderName header) -{ - /* Grab the first and only the first msg id */ - int ret = 0; - char c; - - c = NextNonCFWS(parser); - - while (ret >= 0 && !('\n' == c || 0 == c)) { - if ('<' == c && ret != 1) { - ret = ParseMessageId(parser, header); - } else { - parser->p++; - c = NextNonCFWS(parser); - } - } - - return ret; -} - -static int -ParseMessageIdList(MailParser *parser, - MailHeaderName header) -{ - /* (msg-id *(msg-id)) */ - int pcode; - - pcode = ParseMessageId(parser, header); - - while (pcode == 1) { - pcode = ParseMessageId(parser, header); - } - - return pcode; -} - -static int -ParseHeader(MailParser *parser, - MailHeaderName header) -{ - int ret; - - switch (header) { - case MAIL_FROM: - return ParseMailboxList(parser, header); - case MAIL_SENDER: - case MAIL_X_AUTH_OK: - case MAIL_X_BEEN_THERE: - case MAIL_X_LOOP: - ret = ParseMailbox(parser, header); - /* FIXME: this is awkward, and sometimes shoots us into the message body */ - NextNonCFWS(parser); - return ret; - case MAIL_REPLY_TO: - case MAIL_TO: - case MAIL_CC: - case MAIL_BCC: - return ParseAddressList(parser, header); - case MAIL_DATE: - return ParseDateHeader(parser, header); - case MAIL_SPAM_FLAG: - case MAIL_SUBJECT: - case MAIL_LIST_ID: - case MAIL_OPTIONAL: - return ParseUnstructuredHeader(parser, header); - case MAIL_MESSAGE_ID: - ret = ParseMessageId(parser, header); - NextNonCFWS(parser); - return ret; - case MAIL_IN_REPLY_TO: - return ParseInReplyTo(parser, header); - case MAIL_REFERENCES: - return ParseMessageIdList(parser, header); - case MAIL_OFFSET_BODY_START: - return 1; - } - - return -1; -} - -int -MailParseHeaders(MailParser *parser) -{ - char *q; - - MailHeaderName header; - - if (!fgets(parser->line, 2048, parser->stream)) { - return -1; - } - parser->p = parser->line; - - while (1) { - if (!*parser->p) { - /* early EOF, this is an error */ - break; - } else if ('\r' == *parser->p && '\n' == *(parser->p + 1)) { - break; - } else if ('\n' == *parser->p) { - break; - } - - q = strchr(parser->p, ':'); - if (!q) { - /* we are probably at the beginning of the message body */ - break; - } - - *q = 0; - - header = MAIL_OPTIONAL; - - switch (parser->p[0]) { - case 'B': case 'b': - if (!XplStrCaseCmp(parser->p, "Bcc")) { - header = MAIL_BCC; - } - break; - - case 'C': case 'c': - if (!XplStrCaseCmp(parser->p, "Cc")) { - header = MAIL_CC; - } - break; - - case 'D': case 'd': - if (!XplStrCaseCmp(parser->p, "Date")) { - header = MAIL_DATE; - } - break; - - case 'F': case 'f': - if (!XplStrCaseCmp(parser->p, "From")) { - header = MAIL_FROM; - } - break; - - case 'L': case 'l': - if (!XplStrCaseCmp(parser->p, "List-Id")) { - header = MAIL_LIST_ID; - } - break; - - case 'M': case 'm': - if (!XplStrCaseCmp(parser->p, "Message-Id")) { - header = MAIL_MESSAGE_ID; - } - break; - - case 'I': case 'i': - if (!XplStrCaseCmp(parser->p, "In-Reply-To")) { - header = MAIL_IN_REPLY_TO; - } - break; - - case 'R': case 'r': - if (!XplStrCaseCmp(parser->p, "Reply-to")) { - header = MAIL_REPLY_TO; - } else if (!XplStrCaseCmp(parser->p, "References")) { - header = MAIL_REFERENCES; - } - break; - - case 'S': case 's': - if (!XplStrCaseCmp(parser->p, "Sender")) { - header = MAIL_SENDER; - } else if (!XplStrCaseCmp(parser->p, "Subject")) { - BongoStrNCpy(parser->headerName, parser->p, sizeof(parser->headerName)); - header = MAIL_SUBJECT; - } - break; - - case 'T': case 't': - if (!XplStrCaseCmp(parser->p, "To")) { - header = MAIL_TO; - } - break; - - case 'X': case 'x': - if (parser->p[1] != '-') { - break; - } - - /* there are a lot of X- headers so switch on these as well */ - switch (parser->p[2]) { - case 'A': case 'a': - if (!XplStrCaseCmp(parser->p, "X-Auth-OK")) { - header = MAIL_X_AUTH_OK; - } - break; - - case 'B': case 'b': - if (!XplStrCaseCmp(parser->p, "X-BeenThere")) { - header = MAIL_X_BEEN_THERE; - } - break; - - case 'H': case 'h': - if (!XplStrCaseCmp(parser->p, "X-Bongo-Ipecac")) { - return -1; - } - break; - - case 'L': case 'l': - if (!XplStrCaseCmp(parser->p, "X-Loop")) { - header = MAIL_X_LOOP; - } - break; - - case 'S': case 's': - if (!XplStrCaseCmp(parser->p, "X-Spam-Flag")) { - BongoStrNCpy(parser->headerName, parser->p, sizeof(parser->headerName)); - header = MAIL_SPAM_FLAG; - } - break; - } - break; - } - - if (header == MAIL_OPTIONAL) { - BongoStrNCpy(parser->headerName, parser->p, sizeof(parser->headerName)); - } - - *q = ':'; - parser->p = q + 1; - - if (-1 == ParseHeader(parser, header)) { - Log(LOG_ERROR, "Parsing headers, didn't understand this: %d: %s", header, q + 1); - if (parser->p != parser->line) { - /* nevertheless, we've got to soldier on */ - NEXTLINE(0); - } - } - } - - if (parser->offsetcb) { - parser->offsetcb(parser->cbarg, - MAIL_OFFSET_BODY_START, ftell(parser->stream)); - } - return 0; -} - -int -MailParseBody(MailParser *parser) -{ - return 0; -} - -#if 0 -static void -Participants(void *arg, - MailHeaderName header, - MailAddress *addr) -{ - - fprintf (stderr, "[%s] (%s) <%s>\r\n", - MAIL_FROM == header ? "From" : - MAIL_SENDER == header ? "Sender" : - MAIL_REPLY_TO == header ? "Reply-to" : - MAIL_TO == header ? "To" : - MAIL_CC == header ? "Cc" : - MAIL_BCC == header ? "Bcc" : - "Unknown", - addr->displayName, - addr->address); - -} -#endif - -MailParser * -MailParserInit(FILE *stream, void *cbData) -{ - MailParser *p = MemMalloc0(sizeof(MailParser)); - - p->stream = stream; - p->cbarg = cbData; - - return p; - -} - -void -MailParserDestroy(MailParser *p) -{ - MemFree(p); -} - -void -MailParserSetParticipantsCallback(MailParser *p, - MailParserParticipantsCallback cbfunc) -{ - p->participantscb = cbfunc; -} - -void -MailParserSetUnstructuredCallback(MailParser *p, - MailParserUnstructuredCallback cbfunc) -{ - p->unstructuredcb = cbfunc; -} - -void -MailParserSetDateCallback(MailParser *p, - MailParserDateCallback cbfunc) -{ - p->datecb = cbfunc; -} - -void -MailParserSetOffsetCallback(MailParser *p, - MailParserOffsetCallback cbfunc) -{ - p->offsetcb = cbfunc; -} - -void -MailParserSetMessageIdCallback(MailParser *p, - MailParserMessageIdCallback cbfunc) -{ - p->messageidcb = cbfunc; -} - - - diff --git a/src/agents/store/mail-parser.h b/src/agents/store/mail-parser.h deleted file mode 100644 index cc1c6ff..0000000 --- a/src/agents/store/mail-parser.h +++ /dev/null @@ -1,90 +0,0 @@ -/**************************************************************************** - * - * Copyright (c) 2005-6 Novell, Inc. All Rights Reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of version 2 of the GNU General Public License - * as published by the Free Software Foundation. - * - * 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, contact Novell, Inc. - * - * To contact Novell about this file by physical or electronic mail, you - * may find current contact information at www.novell.com. - * - ****************************************************************************/ - -#ifndef MAIL_PARSER_H -#define MAIL_PARSER_H - -#include "stored.h" - -XPL_BEGIN_C_LINKAGE - -#define XPL_MAX_RFC822_LINE 1000 - -typedef enum { - MAIL_OPTIONAL = 0, - - MAIL_FROM = 1 << 1, - MAIL_SENDER = 1 << 2, - MAIL_REPLY_TO = 1 << 3, - MAIL_TO = 1 << 4, - MAIL_CC = 1 << 5, - MAIL_BCC = 1 << 6, - - MAIL_DATE = 1 << 7, - MAIL_SUBJECT = 1 << 8, - MAIL_SPAM_FLAG = 1 << 9, - - MAIL_MESSAGE_ID = 1 << 10, - MAIL_IN_REPLY_TO = 1 << 11, - MAIL_REFERENCES = 1 << 12, - MAIL_OFFSET_BODY_START = 1 << 13, - MAIL_X_AUTH_OK = 1 << 14, - - MAIL_X_BEEN_THERE = 1 << 15, - MAIL_X_LOOP = 1 << 16, - MAIL_LIST_ID = 1 << 17, -} MailHeaderName; - -typedef struct MailAddress { - char *displayName; - char *address; -/* struct MailAddress *next; */ -} MailAddress; - -typedef struct MailParser MailParser; - -typedef void (*MailParserParticipantsCallback)(void *cbdata, MailHeaderName name, MailAddress *address); -typedef void (*MailParserUnstructuredCallback)(void *cbdata, MailHeaderName name, const char *nameRaw, const char *data); -typedef void (*MailParserMessageIdCallback)(void *cbdata, MailHeaderName name, const char *data); -typedef void (*MailParserDateCallback)(void *cbdata, MailHeaderName name, uint64_t date); -typedef void (*MailParserOffsetCallback)(void *cbdata, MailHeaderName name, uint64_t offset); - -MailParser *MailParserInit(FILE *stream, void *cbData); -void MailParserDestroy(MailParser *p); - -void MailParserSetParticipantsCallback(MailParser *p, - MailParserParticipantsCallback cbfunc); - -void MailParserSetUnstructuredCallback(MailParser *p, - MailParserUnstructuredCallback cbfunc); -void MailParserSetDateCallback(MailParser *p, - MailParserDateCallback cbfunc); -void MailParserSetOffsetCallback(MailParser *p, - MailParserOffsetCallback cbfunc); -void MailParserSetMessageIdCallback(MailParser *p, - MailParserMessageIdCallback cbfunc); - -int MailParseHeaders(MailParser *parser); -int MailParseBody(MailParser *parser); - -XPL_END_C_LINKAGE - -#endif diff --git a/src/agents/store/mail.c b/src/agents/store/mail.c index 2152bb3..9f55eaf 100644 --- a/src/agents/store/mail.c +++ b/src/agents/store/mail.c @@ -1,237 +1,19 @@ -/**************************************************************************** - * - * Copyright (c) 2005-6 Novell, Inc. All Rights Reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of version 2 of the GNU General Public License - * as published by the Free Software Foundation. - * - * 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, contact Novell, Inc. - * - * To contact Novell about this file by physical or electronic mail, you - * may find current contact information at www.novell.com. - * - ****************************************************************************/ - -/* Routines for parsing and manipulating mail messages */ +// parse incoming mail messages using GMime #include #include "stored.h" +#include + +#include +#include + #include #include -#include -#include -#include -#include -#include -#include -#include #include "mail.h" -// #include "conversations.h" -#include "mail-parser.h" #include "messages.h" -typedef struct { - StoreObject *document; - - BongoStringBuilder from; - BongoStringBuilder sender; - BongoStringBuilder to; - BongoStringBuilder cc; - BongoStringBuilder bcc; - BongoStringBuilder xAuthOK; - BongoStringBuilder references; - - char *subject; - char *messageId; - char *parentMessageId; - char *inReplyTo; - char *listId; - - BOOL haveListId; - - uint64_t headerStartOffset; - uint64_t headerSize; -} IncomingParseData; - -static void -IncomingParticipantCb(void *datap, MailHeaderName name, MailAddress *address) -{ - IncomingParseData *data = datap; - const char *type = NULL; - char buf[1024]; - snprintf(buf, 1024, "%s%s<%s>\n", - address->displayName ? address->displayName : "", - address->displayName ? " " : "", - address->address); - - switch(name) { - case MAIL_FROM: - BongoStringBuilderAppend(&data->from, buf); - type = "from"; - break; - case MAIL_SENDER: - BongoStringBuilderAppend(&data->sender, buf); - type = "sender"; - - /* For listservs that don't set a good listid, we use the - * sender. Save the sender as the listid if no listid - * has been saved. */ - if (!data->listId) { - snprintf(buf, 1024, "<%s>", address->address); - data->listId = MemStrdup(buf); - } - - break; - case MAIL_TO: - BongoStringBuilderAppend(&data->to, buf); - type = "to"; - break; - case MAIL_CC: - BongoStringBuilderAppend(&data->cc, buf); - type = "cc"; - break; - case MAIL_BCC: - BongoStringBuilderAppend(&data->bcc, buf); - type = "bcc"; - break; - case MAIL_X_AUTH_OK: - BongoStringBuilderAppend(&data->xAuthOK, buf); - type = "xAuthOK"; - break; - case MAIL_X_BEEN_THERE: - case MAIL_X_LOOP : - snprintf(buf, 1024, "<%s>", address->address); - - data->haveListId = TRUE; - - if (data->listId) { - MemFree(data->listId); - } - data->listId = MemStrdup(buf); - break; - default : - /* Ignore */ - return; - } - - Log(LOG_DEBUG, "IPCB got %s %s <%s>\n", type, address->displayName, address->address); -} - -static void -IncomingUnstructuredCb(void *datap, MailHeaderName name, const char *headerName, const char *buffer) -{ - IncomingParseData *data = datap; - - switch(name) { - case MAIL_SUBJECT: - data->subject = MemStrdup(buffer); - break; - case MAIL_SPAM_FLAG : - if (tolower(buffer[0]) == 'y') { - data->document->type |= STORE_MSG_FLAG_JUNK; - } - break; - case MAIL_LIST_ID: - /* If no proper listid is given, the sender will be saved - * in the listid */ - data->haveListId = TRUE; - break; - default: - if (!XplStrCaseCmp(headerName, "X-Listprocessor-Version")) { - /* If no proper listid is given, the sender will be saved - * in the listid */ - data->haveListId = TRUE; - } - break; - } -} - -static void -IncomingDateCb(void *datap, MailHeaderName name, uint64_t date) -{ - // IncomingParseData *data = datap; - - switch(name) { - case MAIL_DATE: - // FIXME - // data->info->data.mail.sent = date; - break; - default : - /* Ignore */ - return; - } -} - -static void -IncomingOffsetCb(void *datap, MailHeaderName name, uint64_t offset) -{ - IncomingParseData *data = datap; - - switch(name) { - case MAIL_OFFSET_BODY_START: - data->headerSize = offset - data->headerStartOffset; - break; - default : - /* Ignore */ - return; - } -} - -static void -IncomingMessageIdCb(void *datap, MailHeaderName name, const char *messageId) -{ - IncomingParseData *data = datap; - - switch(name) { - case MAIL_MESSAGE_ID: - if (!data->messageId) { - data->messageId = MemStrdup(messageId); - } - break; - case MAIL_IN_REPLY_TO : - if (!data->inReplyTo) { - data->inReplyTo = MemStrdup(messageId); - - MemFree(data->parentMessageId); - data->parentMessageId = MemStrdup(messageId); - } - - break; - case MAIL_REFERENCES : - if (data->references.len > 0) { - BongoStringBuilderAppend(&data->references, ", "); - } - - BongoStringBuilderAppend(&data->references, messageId); - - if (!data->inReplyTo) { - /* If In-Reply-To isn't set, use the last References: - * messageId as the parent */ - MemFree(data->parentMessageId); - data->parentMessageId = MemStrdup(messageId); - } - - break; - default : - /* Ignore */ - return; - } -} - - -/* returns: NULL on success - errormsg o/w -*/ - static void SetDocProp(StoreClient *client, StoreObject *doc, char *pname, char *value) { @@ -246,66 +28,90 @@ SetDocProp(StoreClient *client, StoreObject *doc, char *pname, char *value) } } +struct wanted_header { + const char const *header; + const char const *propname; +}; + +// properties we want to look for and set automatically +static struct wanted_header header_list[] = { + { "From", "bongo.from" }, + { "To", "bongo.to" }, + { "CC", "bongo.cc" }, + { "Sender", "bongo.sender" }, + { "In-Reply-To", "bongo.inreplyto" }, + { "References", "bongo.references" }, + { "X-Auth-OK", "bongo.xauthok" }, + { "List-Id", "bongo.listid" }, + { "Subject", "bongo.subject" }, + { NULL, NULL } +}; + const char * StoreProcessIncomingMail(StoreClient *client, StoreObject *document, const char *path) { - const char *result = NULL; - MailParser *p; - IncomingParseData data = {0, }; - char prop[XPL_MAX_PATH + 1]; + GMimeMessage *message; + GMimeParser *parser; + GMimeStream *stream; StoreObject conversation; StoreConversationData conversation_data; - - FILE *fh; - - fh = fopen(path, "rb"); - if (!fh) return MSG4224CANTREADMBOX; - - data.headerStartOffset = 0; - data.document = document; - - p = MailParserInit(fh, &data); - MailParserSetParticipantsCallback(p, IncomingParticipantCb); - MailParserSetUnstructuredCallback(p, IncomingUnstructuredCb); - MailParserSetDateCallback(p, IncomingDateCb); - MailParserSetOffsetCallback(p, IncomingOffsetCb); - MailParserSetMessageIdCallback(p, IncomingMessageIdCb); - - if (MailParseHeaders(p) == -1) { - result = MSG4226BADEMAIL; - goto finish; - } - - SetDocProp(client, document, "bongo.from", data.from.value); - SetDocProp(client, document, "bongo.to", data.to.value); - SetDocProp(client, document, "bongo.sender", data.sender.value); - SetDocProp(client, document, "bongo.cc", data.cc.value); - SetDocProp(client, document, "bongo.inreplyto", data.inReplyTo); - SetDocProp(client, document, "bongo.references", data.references.value); - SetDocProp(client, document, "bongo.xauthok", data.xAuthOK.value); - SetDocProp(client, document, "bongo.listid", data.listId); - - if (data.messageId) { - SetDocProp(client, document, "nmap.mail.messageid", data.messageId); - } else { - snprintf(prop, XPL_MAX_PATH, "%u." GUID_FMT "@%s", document->time_created, document->guid, - StoreAgent.agent.officialName); - SetDocProp(client, document, "nmap.mail.messageid", prop); - } - - SetDocProp(client, document, "nmap.mail.parentmessageid", data.parentMessageId); - SetDocProp(client, document, "nmap.mail.subject", data.subject); - - if (data.headerSize > 0) { - snprintf(prop, XPL_MAX_PATH, FMT_UINT64_DEC, data.headerSize); - SetDocProp(client, document, "nmap.mail.headersize", prop); - } + struct wanted_header *headers = header_list; + char prop[XPL_MAX_PATH+1]; + char *result = NULL; + char *header_str = NULL; + int fd; memset(&conversation_data, 0, sizeof(StoreConversationData)); memset(&conversation, 0, sizeof(conversation)); - conversation_data.subject = MemStrdup(data.subject); + + // open up the mail + fd = open(path, O_RDONLY); + if (fd == -1) return MSG4224CANTREADMBOX; + + stream = g_mime_stream_fs_new(fd); + parser = g_mime_parser_new_with_stream(stream); + g_mime_parser_set_scan_from (parser, FALSE); + g_object_unref(stream); + message = g_mime_parser_construct_message(parser); + g_object_unref(parser); + + if (message == NULL) { + // message didn't parse. What should we do? + goto finish; + } + + while (headers->header != NULL) { + const char *value = g_mime_object_get_header(GMIME_OBJECT(message), headers->header); + + if (value != NULL) + SetDocProp(client, document, (char *)headers->propname, (char *)value); + + headers++; + } + + // treat message ID specially because we want to invent one if it + // doesn't already exist. + if (message->message_id) { + SetDocProp(client, document, "nmap.mail.messageid", message->message_id); + } else { + snprintf(prop, XPL_MAX_PATH, "%u." GUID_FMT "@%s", document->time_created, document->guid, + StoreAgent.agent.officialName); + prop[XPL_MAX_PATH] = '\0'; + SetDocProp(client, document, "nmap.mail.messageid", prop); + } + + header_str = g_mime_object_get_headers(GMIME_OBJECT(message)); + + if (header_str != NULL) { + snprintf(prop, XPL_MAX_PATH, FMT_UINT64_DEC, strlen(header_str)); + SetDocProp(client, document, "nmap.mail.headersize", prop); + g_free(header_str); + } + + // now, see if we can find a matching conversation + conversation_data.subject = MemStrdup(message->subject); conversation_data.date = 0; // FIXME: should be 7 days ago or similar if (StoreObjectFindConversation(client, &conversation_data, &conversation) != 0) { @@ -349,26 +155,11 @@ StoreProcessIncomingMail(StoreClient *client, goto finish; } - // fix up conversation meta-data? - // TODO - finish: - if (conversation_data.subject) MemFree(conversation_data.subject); - MemFree(data.from.value); - MemFree(data.to.value); - MemFree(data.cc.value); - MemFree(data.sender.value); - MemFree(data.messageId); - MemFree(data.inReplyTo); - MemFree(data.references.value); - MemFree(data.parentMessageId); - MemFree(data.xAuthOK.value); - MemFree(data.listId); - - MailParserDestroy(p); - - fclose(fh); - + if (conversation_data.subject != NULL) MemFree(conversation_data.subject); + g_object_unref(message); + close(fd); + return result; } diff --git a/src/agents/store/stored.c b/src/agents/store/stored.c index 4602285..3a09ca5 100644 --- a/src/agents/store/stored.c +++ b/src/agents/store/stored.c @@ -23,6 +23,7 @@ #include "stored.h" #include "messages.h" +#include #include #include #include @@ -232,6 +233,9 @@ _XplServiceMain(int argc, char *argv[]) Log(LOG_FATAL, "Couldn't initialize calendaring library"); return -1; } + + // initialize the mail parsing code + g_mime_init(0); // create lock pool StoreInitializeFairLocks(); @@ -295,5 +299,7 @@ _XplServiceMain(int argc, char *argv[]) XplUnloadApp(XplGetThreadID()); MsgClearRecoveryFlag("store"); + g_mime_shutdown(); + return 0; }