/* -*-C-*-

   $Id: arffilter.c,v 1.1.1.1 2006/03/03 20:16:11 steve Exp $
   arffilter.c

   Distribution site: http://arf.word-to-the-wise.com/arffilter/
   Author:            Steve Atkins <steve@blighty.com>

   arffilter is a unix filter to rewrite ARF complaints into
   a format that is more useful in an MUA or simple ticketing
   system.

   It annotates the subject line using metadata from the ARF
   message/feedback-report object.

   It's very lightweight, not doing any MIME extraction, simply assuming
   that the metadata is in the first message/feedback-report attachment.

   To compile:

      cc -o arffilter arffilter.c

   To use it with procmail, add a rule such as the following, before other
   processing is done:

      # ARF rewrite script
      :0fw
      | /path/to/arffilter


   It accepts four commandline parameters:

     -h            Show usage

     -V            Show version

     -l <number>   Look for message/feedback-report only in this number
                   of lines from the start of the message (default 200)

     -s <template> Replace the subject line of the message with this
                   string. Within the template tokens of the format
                   %key% will be replaced with metadata taken from the
                   ticket. The key should be lowercase ascii and '-' only.
                   The key 'subject' will be replaced with the original
                   email subject. The key report-subject will be replaced
                   with the subject of the original report. Other keys will
                   be replaced with the corresponding values from the
                   message/feedback-report section.
                   (default [%source-ip%] %subject%)

   If you like this, you'll love Abacus, the only ticketing system built
   from the ground up to support ISP abuse and security workers -
   http://word-to-the-wise.com/abacus/
*/

/*
   (c) Word to the Wise, LLC, 2005

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:
      
   1. Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
      
   2. Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the
      distribution.

   3. This source code may not be used in any manner such that the resulting
      binary form is covered by any version of the GNU Public License.

   THIS SOFTWARE IS PROVIDED BY WORD TO THE WISE, LLC ``AS IS'' AND
   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
   PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WORD TO THE
   WISE, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
   OF THE POSSIBILITY OF SUCH DAMAGE.

*/

const char * const VERSION="0.3";

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdlib.h>

struct lines;

typedef struct lines {
  char *line;
  struct lines *next;
  struct lines *prev;
} lines;

struct meta;

typedef struct meta {
  char *key;
  char *value;
  struct meta *next;
  struct meta *prev;
} meta;

void saveline(lines **tail, const char *line)
{
  lines *me = (lines*)malloc(sizeof(lines));
  me->prev = *tail;
  me->next = 0;
  me->line = strdup(line);
  (*tail)->next = me;
  *tail = me;
}

const char feedbackstart[] = "content-type: message/feedback-report";

int main(int argc, char **argv)
{
  char linebuff[20000];
  lines head;
  lines *tail=&head;
  meta mhead;
  meta *mtail=&mhead;
  char *subject=strdup("[%source-ip%] %subject%");
  char *p;
  int headlimit=0;
  meta *m;
  int seenarf=0;

  while(--argc) {
    p = *++argv;
    if(*p != '-') {
      fprintf(stderr, "Unrecognised parameter: %s\n", p);
      exit(1);
    }
    switch(p[1]) {
    case 'l':
      headlimit = atoi(*++argv);
      --argc;
      break;
    case 's':
      subject = strdup(*++argv);
      --argc;
      break;
    case 'h':
      fputs("Usage: arffilter [-s <subject_template>] [-l <line_limit>]\n\n"
"arffilter accepts four commandline parameters:\n\n"

"  -h            Show usage\n\n"

"  -V            Show version\n\n"

"  -l <number>   Look for message/feedback-report only in this number\n"
"                of lines from the start of the message (default 200)\n\n"

"  -s <template> Replace the subject line of the message with this\n"
"                string. Within the template tokens of the format\n"
"                %key% will be replaced with metadata taken from the\n"
"                report. The key should be lowercase ascii and '-' only.\n"
"                The key 'subject' will be replaced with the original\n"
"                email subject. The key 'report-subject' will be replaced\n"
"                with the subject of the original report. Other keys will\n"
"                be replaced with the corresponding values from the\n"
"                message/feedback-report section.\n"
"                (default [%source-ip%] %subject%)\n\n"
"(see http://arf.word-to-the-wise.com/)\n", stderr);
      exit(1);
    case 'V':
      printf("arffilter version %s\n", VERSION);
      exit(1);
    default:
      fprintf(stderr, "Unrecognised flag: %s\n", p);
      exit(1);
    }
  }
  if(headlimit==0) {
    headlimit = 200;
  }

  /* pass through the header before Subject: */
  while(fgets(linebuff, sizeof(linebuff), stdin) &&
	0!=memcmp(linebuff, "Subject: ", 9)) {
    fputs(linebuff, stdout);
  }
  
  if(0!=memcmp(linebuff, "Subject: ", 9)) {
    /* No subject line until we hit the end of the message */
    exit(0);
  }

  /* grab the Subject: line */
  m = (meta*)malloc(sizeof(meta));
  m->prev = mtail;
  m->next = 0;
  m->key = "report-subject";
  m->value = strdup(linebuff+9);
  mtail->next = m;
  mtail = m;

  /* Find start of feedback section */
  while(headlimit && fgets(linebuff, sizeof(linebuff), stdin)) {
    headlimit--;
    saveline(&tail, linebuff);
    p = linebuff;
    while(*p) {
      if(isascii(*p) && isupper(*p)) {
	*p = tolower(*p);
      }
      p++;
    }
    if(0==memcmp(linebuff, feedbackstart, sizeof(feedbackstart)-1)) {
      seenarf = 1;
      break;
    }
  }
  
  while(headlimit && fgets(linebuff, sizeof(linebuff), stdin)) {
    headlimit--;
    saveline(&tail, linebuff);
    if(isspace(*linebuff))
      break;
  }

  /* Record the metadata */
  while(headlimit && fgets(linebuff, sizeof(linebuff), stdin)) {
    headlimit--;
    saveline(&tail, linebuff);
    
    p = linebuff;
    while(*p) {
      if(isascii(*p) && isupper(*p)) {
	*p = tolower(*p);
      }
      if(*p == ':') {
	*p = '\0';
	m =(meta*)malloc(sizeof(meta));
	m->prev = mtail;
	m->next = 0;
	m->key = strdup(linebuff);
	m->value = strdup(p+1);
	mtail->next = m;
	mtail = m;
	break;
      }
      p++;
    }

    if(isspace(*linebuff))
      break;
  }

  /* Look for the embedded Subject:. Hope it's not base64ed. */

  while(headlimit && fgets(linebuff, sizeof(linebuff), stdin)) {
    headlimit--;
    saveline(&tail, linebuff);

    if(0==memcmp("Subject:", linebuff, 8)) {
      p = linebuff+8;
      if(*p && isascii(*p) && isspace(*p))
	p++;
      m =(meta*)malloc(sizeof(meta));
      m->prev = mtail;
      m->next = 0;
      m->key = "subject";
      m->value = strdup(p);
      mtail->next = m;
      mtail = m;
      break;      
    }
  }

  if(!seenarf) {
    subject = strdup("%report-subject%");
  }

  /* Replace the subject line with the template */
  fputs("Subject: ", stdout);
  p = subject;
  while(*p) {
    if(*p == '%') {
      const char *key = ++p;
      while(*p && *p != '%') {
	p++;
      }
      if(*p == '\0') {
	break;
      }
      *p = '\0';
      m = mhead.next;
      while(m) {
	if(0==strcmp(key, m->key)) {
	  const char *q=m->value;
	  while(*q && isspace(*q)) {
	    q++;
	  }
	  while(*q && *q != '\r' && *q != '\n') {
	    putchar(*q);
	    ++q;
	  }
	}
	m = m->next;
      }
    } else {
      putchar(*p);
    }
    ++p;
  }
  /* I'm sure this should really be \r\n, but... */
  fputs("\n", stdout);

  /* Write out the rest of the message */
  tail = head.next;
  while(tail) {
    fputs(tail->line, stdout);
    tail = tail->next;
  }

  while(fgets(linebuff, sizeof(linebuff), stdin)) {
    fputs(linebuff, stdout);
  }
  return 0;
}

