/*
 * mimetool: parse a MIME multipart message and delete any body part
 * of a specified type or types.
 *
 * Based on `unhtml', written by the author for SmartList in June 1999.
 *
 * This code is in the public domain except where noted otherwise.
 *
 * Tim Pierce <twp@rootsweb.com>
 * 26 Jul 2000
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>

struct list {
  struct message *data;
  struct list *next;
};

struct typelist {
  char *type;
  struct typelist *next;
};

struct header {
  char *text;
  struct header *next;
};

/*
 * The `message' struct is used to represent a MIME multipart message,
 * as defined in RFC 2045 and RFC 2046.
 *
 * The `header' field is a linked list of message headers, in the
 * order in which they originally appeared.
 *
 * The `preamble' field is a flat text array containg the raw text of
 * the message preamble (any text preceding the first body part).  In
 * the case of a single-part message (e.g. text/plain), the `preamble'
 * field is used to store the whole body.
 *
 * The `epilogue' field stores any text following the last body part.
 *
 * The `content_type' field contains the value of the message's
 * Content-Type header, minus any parameters: for example,
 * "text/plain".  It is NULL if the message lacks a Content-Type
 * header.  The `boundary' field contains the value of the `boundary'
 * parameter to the Content-Type field, if present.
 *
 * The `hsize' parameter is the size of the header, in bytes.  The
 * `bsize' parameter is the size of the body, in bytes.  These
 * parameters are not presently used and may be discarded.  */

struct message {
  struct header *header;
  char *preamble;	/* text (if any) preceding the first body-part */
  char *epilogue;	/* text (if any) following the last body-part */
  char *content_type;
  char *boundary;
  int is8bit;		/* does this message contain 8bit data? */
  int hsize;	/* bytes allocated for hdr (may be more than necessary) */
  int bsize;	/* bytes allocated for body (may be more than necessary) */
  struct list *parts;
};

struct message *mime_parse (char *body);
void mime_write (struct message *msg);
void mime_fatal (char *s);
void mime_destroy (struct message *msg);
int check_msgtype (struct message *msg, char *type);
char *getheader (struct message *msg, char *hdrname);
char *next_boundary (char *body, char *boundary);
int add_subpart (struct message *msg, struct message *part);
void add_header (struct message *msg, char *text);
void change_header (struct message *msg, char *hdr, char *content);
void delete_header (struct message *msg, char *hdr);
void print_msg_info (struct message *msg, int indent);
void mime_decode_qp (struct message *msg);
void mime_decode_b64 (char *buf);
void mime_output_qp (char *text);
void warn (int level, ...);
void prune_types (struct message *msg, struct typelist *typelist, int allow);
int type_match (struct message *msg, struct typelist *typelist);

int verbose = 0;
char *program_name;

int
main (argc, argv)
     int argc;
     char **argv;
{
  struct message *msg;
  int len, c, last_char, bi, bsize;
  char *p, *body;
  struct typelist *reject_attach = NULL;
  struct typelist *allow_attach = NULL;

  program_name = argv[0];

  for (c = 1; c < argc && argv[c][0] == '-'; ++c)
    {
      if (!strcmp(argv[c], "-v") || !strcmp(argv[c], "--verbose"))
	{
	  ++verbose;
	}
      else if (!strcmp(argv[c], "--delete"))
	{
	  if (++c < argc)
	    {
	      struct typelist *nt;
	      nt = (struct typelist *) malloc(sizeof(struct typelist));
	      nt->type = strdup(argv[c]);
	      nt->next = reject_attach;
	      reject_attach = nt;
	    }
	}
      else if (!strcmp(argv[c], "--allow"))
	{
	  if (++c < argc)
	    {
	      struct typelist *nt;
	      nt = (struct typelist *) malloc(sizeof(struct typelist));
	      nt->type = strdup(argv[c]);
	      nt->next = allow_attach;
	      allow_attach = nt;
	    }
	}
      else
	warn (0, "unknown option `%s'", argv[c]);
    }

  if (c < argc)
    {
      if (freopen (argv[c], "r", stdin) == NULL)
	{
	  warn (0, "reopening standard input from %s: %s",
		argv[c], strerror(errno));
	  exit(1);
	}
    }

  /* Read the message. */
  bi = 0;
  body = (char *) malloc (sizeof(char) * 1000);
  if (body == NULL)
    {
      warn (0, "could not malloc memory for body");
      exit(1);
    }

  while ((len = read (fileno(stdin), body+bi, 1000)) == 1000)
    {
      bi += 1000;
      body = (char *) realloc (body, sizeof(char) * (bi+1000));
      if (body == NULL)
	{
	  warn (0, "could not realloc memory for body");
	  exit(1);
	}
    }
  bsize = bi + len;
  body[bsize] = '\0';

  msg = mime_parse (body);

  if (allow_attach)
    prune_types (msg, allow_attach, 1);
  else if (reject_attach)
    prune_types (msg, reject_attach, 0);

  mime_write(msg);

  return 0;
}

/*
 * mime_parse: process an RFC 2046 multipart message and return
 *	a message structure with all the necessary fields filled in.
 *
 * The BODY argument is a character array containing the raw text of the
 * message to be parsed.
 *
 * In the event of a fatal system error (should only happen in the
 * case of insufficient memory) or a fatal MIME parsing error, a
 * message will be printed on standard error and the return value
 * will be NULL.
 */

struct message *
mime_parse (body)
     char *body;
{
  char *p, *bodyp;
  int len;
  struct message *msg;

  /* Initialize the message. */
  msg = (struct message *) malloc (sizeof(struct message));
  if (msg == NULL)
    {
      warn (0, "mime_parse could not malloc new message struct");
      exit(1);
    }
  msg->header = NULL;
  msg->preamble = NULL;
  msg->epilogue = NULL;
  msg->content_type = NULL;
  msg->boundary = NULL;
  msg->parts = NULL;
  msg->is8bit = 0;

  /* Get the header. */
  /* Special case for message with zero-length header. */
  if (body[0] == '\n')
    {
      msg->hsize = 0;
      bodyp = body + 1;
    }
  else
    {
      char *hp1, *hp2;

      hp1 = hp2 = body;
      while ((hp2 = strchr(hp2, '\n')) != NULL)
	{
	  char *text;

	  if (hp2[1] == ' ' || hp2[1] == '\t')
	    {
	      ++hp2;
	      continue;
	    }
	  text = (char *) malloc(sizeof(char) * (hp2 - hp1 + 1));
	  strncpy (text, hp1, hp2-hp1);
	  text[hp2-hp1] = '\0';
	  add_header(msg, text);
	  if (hp2[1] == '\n')
	    break;
	  hp1 = ++hp2;
	}

      if (hp2 != NULL)
	bodyp = hp2 + 2;
    }

  /* Find the content-type. */
  p = getheader (msg, "Content-Type");
  if (p == NULL)
    p = "text/plain";
  msg->content_type = strdup(p);
  if (msg->content_type == NULL)
    {
      warn (0, "mime_parse could not malloc Content-Type buffer");
      mime_destroy (msg);
      return NULL;
    }

  /*
   * If this is a message/rfc822, then the body is an encapsulated
   * message.  Parse it, attach the result to the current message,
   * and we're done.
   */
  if (check_msgtype(msg, "message/rfc822"))
    {
      msg->parts = (struct list *) malloc (sizeof(struct list));
      if (msg->parts == NULL)
	{
	  warn (0, "mime_parse could not malloc attachment list");
	  return NULL;
	}
      msg->parts->data = mime_parse (bodyp);
      msg->parts->next = NULL;
      return msg;
    }

  /* Find the message boundary. */
  if (check_msgtype(msg, "multipart"))
    {
      /* Skip to next semicolon and see what keyword follows it. */
      p = msg->content_type;
      while ((p = strchr (p, ';')) != NULL)
	{
	  ++p;
	  p += strspn (p, " \t\v\r\n");
	  if (strncasecmp (p, "boundary", 8) == 0)
	    {
	      char *dest;
	      p += 8 + strspn (p+8, " \t\v\r\n");
	      if (*p++ != '=')
		{
		  warn (0, "fatal: expected `=' after `boundary' parameter");
		  mime_destroy (msg);
		  return NULL;
		}
	      p += strspn (p, " \t\v\r\n");
	      dest = msg->boundary = (char *) malloc (strlen(p));
	      if (dest == NULL)
		{
		  warn (0, "mime_parse could not malloc boundary");
		  mime_destroy (msg);
		  return NULL;
		}
	      /* If next char is a quote, read the following quoted-string. */
	      if (*p == '"')
		{
		  ++p;
		  while (*p != '\0' && *p != '"')
		    {
		      if (*p == '\\')
			++p;
		      *dest++ = *p;
		      ++p;
		    }
		}
	      else	/* Generic non-special characters. */
		{
		  while (*p != '\0' && !strchr ("()<>@,;:\\\"/[]?=", *p))
		    {
		      *dest++ = *p;
		      ++p;
		    }
		}
	      *dest = '\0';
	      break;
	    }
	}
      if (msg->boundary == NULL)
	{
	  warn (0, "fatal: Content-Type lacks required `boundary' parameter");
	  mime_destroy (msg);
	  return NULL;
	}
    }

  /* Break up multiparts. */
  if (check_msgtype (msg, "multipart"))
    {
      char *nextpart;
      struct message *part;

      /* Preamble. */
      p = next_boundary (bodyp, msg->boundary);
      if (p == NULL)
	msg->preamble = strdup (bodyp);
      else
	{
	  int psize = p - bodyp - strlen(msg->boundary) - 3;

	  /* Special case: a boundary line may occur at the very beginning
	     of the body, which means that no newline precedes it and psize
	     is negative. */
	  if (psize < 0)
	    psize = 0;
	  msg->preamble = (char *) malloc (sizeof(char) * (psize+1));
	  if (msg->preamble == NULL)
	    {
	      warn (0, "mime_parse could not malloc preamble buffer");
	      mime_destroy (msg);
	      return NULL;
	    }
	  strncpy (msg->preamble, bodyp, psize);
	  msg->preamble[psize] = '\0';
	}

      /* Scan to each boundary and parse the body part contained therein. */
      while (p != NULL && strncmp (p, "--\n", 3) != 0)
	{
	  nextpart = next_boundary (++p, msg->boundary);
	  if (nextpart == NULL)
	    {
	      char buf[512];
	      warn (0, "warning: no terminating `%s' boundary found",
		    msg->boundary);
	      break;
	    }
	  else
	    {
	      char *part_end = nextpart - strlen(msg->boundary) - 3;
	      char c = *part_end;

	      /* XXX: Parsing a body part should not require munging
		 the buffer passed to mime_parse. */
	      *part_end = '\0';
	      part = mime_parse (p);
	      *part_end = c;
	      if (part == NULL)
		{
		  mime_destroy (msg);
		  return NULL;
		}
	      if (!add_subpart (msg, part))
		{
		  mime_destroy (msg);
		  return NULL;
		}
	    }
	  p = nextpart;
	}

      /* Get epilogue. */
      if (p != NULL)
	{
	  while (*p++ != '\n')
	    ;
	  if ((msg->epilogue = strdup(p)) == NULL)
	    {
	      warn (0, "mime_parse could not strdup message epilogue");
	      mime_destroy (msg);
	      return NULL;
	    }
	}
    }
  else	/* not multipart */
    {
      msg->preamble = strdup (bodyp);
      if (msg->preamble == NULL)
	{
	  warn (0, "mime_parse could not strdup message body");
	  mime_destroy (msg);
	  return NULL;
	}
    }

  /* Decode the body (preamble), if appropriate. */
  p = getheader (msg, "Content-Transfer-Encoding");
  if (p != NULL)
    {
      if (!strcasecmp (p, "quoted-printable"))
	mime_decode_qp (msg);
      else if (!strcasecmp (p, "base64"))
	mime_decode_b64 (msg->preamble);
      else if (strcasecmp (p, "7bit") != 0 &&
	       strcasecmp (p, "8bit") != 0)
	warn (0, "warning: unknown Content-Transfer-Encoding `%s'", p);
    }

  return msg;
}

/*
 * prune_types: scan MSG for body-parts with types in TYPELIST.
 *	If ALLOW is true, then permit only parts that match
 *	TYPELIST, deleting all others; otherwise, delete any
 *	part that matches TYPELIST.
 */

void
prune_types (msg, typelist, allow)
     struct message *msg;
     struct typelist *typelist;
     int allow;
{
  struct list *p;
  int i, j;

  if (msg == NULL)
    return;

  if (check_msgtype(msg, "multipart") ||
      check_msgtype(msg, "message/rfc822"))
    {
      for (p = msg->parts; p != NULL; p = p->next)
	prune_types(p->data, typelist, allow);
    }
  else
    {
      /*
       * We wish to delete a body part if `allow' is set and if
       * msg's content type does *not* match `typelist', or if
       * `allow' is *not* set and the content type *does* match
       * `typelist'.
       */
      if (allow ^ type_match(msg, typelist))
	{
	  if (msg->preamble != NULL)
	    free(msg->preamble);
	  if (msg->epilogue != NULL)
	    free(msg->epilogue);
	  if (msg->boundary != NULL)
	    free(msg->boundary);
	  for (p = msg->parts; p != NULL; p = p->next)
	    mime_destroy(p->data);
	  msg->parts = NULL;

	  /*
	   * squeeze extra spaces from the content-type header, for
	   * prettier diagnostics.  We're about to kill it, anyway.
	   */
	  for (i = 0, j = 0; msg->content_type[i] != '\0'; ++i, ++j)
	    if (isspace(msg->content_type[i]))
	      {
		msg->content_type[j] = ' ';
		while (isspace(msg->content_type[i+1]))
		  ++i;
	      }
	    else
	      msg->content_type[j] = msg->content_type[i];
	  msg->content_type[j] = '\0';

	  warn (1, "deleted `%s' attachment\n", msg->content_type);

	  msg->preamble = (char *) malloc(256);
	  sprintf (msg->preamble,
		   "A `%s' attachment was deleted by RootsWeb's filters.\n",
		   msg->content_type);
	  free(msg->content_type);
	  msg->content_type = strdup("text/plain");
	  change_header(msg, "Content-Type", "text/plain");
	  delete_header(msg, "Content-Transfer-Encoding");
	  delete_header(msg, "Content-Disposition");
	}
    }
}

/*
 * type_match: compare a type against a list of types, and return
 *	1 if there was a match.
 */
int
type_match (msg, typelist)
     struct message *msg;
     struct typelist *typelist;
{
  struct typelist *p;

  for (p = typelist; p != NULL; p = p->next)
    {
      if (check_msgtype(msg, p->type))
	return 1;
    }
  return 0;
}

/*
 * check_msgtype: check the type of a MIME message structure and return 1 if
 *	the message is of the desired type, 0 otherwise.
 *
 *	Major and minor types are checked independently, so both
 *	check_msgtype("application/frotznib") and check_msgtype("application")
 *	will operate as expected.
 */

int
check_msgtype (msg, type)
     struct message *msg;
     char *type;
{
  int tl = strlen(type);

  return (msg->content_type
	  && !strncasecmp (msg->content_type, type, tl)
	  && (msg->content_type[tl] == ';'
	      || msg->content_type[tl] == '/'
	      || msg->content_type[tl] == '\0'));
}

/* ================================================== */
/* Header-manipulating functions. */

/*
 * getheader: find the contents of a specific header line.
 *
 * The MSG argument is a message structure containing a parsed MIME message.
 * The HDRNAME argument is the name of the desired header, e.g. "Content-Type".
 *
 * Return a pointer to the contents of header HDRNAME.
 */

char *
getheader (msg, hdrname)
     struct message *msg;
     char *hdrname;
{
  struct header *p;
  char *hvp = NULL;
  int hdrnamelen;

  hdrnamelen = strlen(hdrname);

  for (p = msg->header; p != NULL; p = p->next)
    {
      if (!strncasecmp(p->text, hdrname, hdrnamelen)
	  && p->text[hdrnamelen] == ':')
	{
	  hvp = p->text + hdrnamelen + 1;
	  while (isspace(*hvp))
	    hvp++;
	  break;
	}
    }

  return hvp;
}

/*
 * add_header: add to MSG's header list the header line TEXT (which
 *	should be a dynamically allocated block).
 */
void
add_header (msg, text)
     struct message *msg;
     char *text;
{
  struct header *hdr;

  if (msg->header == NULL)
    {
      msg->header = hdr = (struct header *) malloc (sizeof(struct header));
    }
  else
    {
      for (hdr = msg->header; hdr->next != NULL; hdr = hdr->next)
	;
      hdr->next = (struct header *) malloc (sizeof(struct header));
      hdr = hdr->next;
    }

  hdr->text = text;
  hdr->next = NULL;
}

/*
 * change_header: rewrite MSG's header structure, changing the value
 *	of HDR's text to CONTENT.
 */

void
change_header (msg, hdr, content)
     struct message *msg;
     char *hdr;
     char *content;
{
  int hlen;
  struct header *h;

  hlen = strlen(hdr);
  for (h = msg->header; h != NULL; h = h->next)
    {
      if (!strncasecmp(h->text, hdr, hlen) && h->text[hlen] == ':')
	{
	  char *nh = (char *) malloc(sizeof(char) * (strlen(hdr) + hlen + 3));
	  sprintf (nh, "%s: %s", hdr, content);
	  free(h->text);
	  h->text = nh;
	  return;
	}
    }
}

/*
 * delete_header: remove the named header from MSG's header structure.
 */

void
delete_header (msg, hdr)
     struct message *msg;
     char *hdr;
{
  struct header *h;
  int hlen;

  hlen = strlen(hdr);
  if (!strncasecmp(msg->header->text, hdr, hlen)
      && msg->header->text[hlen] == ':')
    {
      h = msg->header->next;
      free(msg->header->text);
      free(msg->header);
      msg->header = h;
    }
  else
    {
      for (h = msg->header; h->next != NULL; h = h->next)
	{
	  if (!strncasecmp(h->next->text, hdr, hlen)
	      && h->next->text[hlen] == ':')
	    {
	      struct header *g;
	      g = h->next->next;
	      free(h->next->text);
	      free(h->next);
	      h->next = g;
	      break;
	    }
	}
    }

  return;
}

/*
 * next_boundary: find the next MIME multipart boundary in a message.
 *	The return value is a pointer to the end of the boundary text,
 *	or NULL if no boundary can be found in this message.
 *
 * The BODY argument is a character array containing the message body.
 * The BOUNDARY argument is a character array containing the boundary
 *	delimiter.
 *
 * Because the return value points to the end of the boundary, it
 * will point to `\n' if this is an ordinary boundary or `--\n' if
 * it is a final boundary.
 */

char *
next_boundary (body, boundary)
     char *body;
     char *boundary;
{
  char *p;

  /* For efficiency reasons, look for the boundary first and then
     examine the characters around it. */

  p = strstr (body, boundary);
  if (p != NULL && strncmp (p-3, "\n--", 3) == 0)
    return p + strlen(boundary);
  return NULL;
}

/*
 * add_subpart: append one message to the list of sub-parts for another
 *	message.
 *
 * The argument MSG is a message structure representing a multipart message.
 * The argument PART is another message (possibly multipart) which is to
 *	be added to MSG's list of sub-parts.
 *
 * Return 1 on success.  If a fatal error arises, return 0.
 */

int
add_subpart (msg, part)
     struct message *msg;
     struct message *part;
{
  struct list *p;

  if (msg->parts == NULL)
    {
      msg->parts = (struct list *) malloc (sizeof(struct list));
      if (msg->parts == NULL)
	{
	  warn (0, "add_subpart could not malloc attachment list");
	  return 0;
	}
      msg->parts->data = part;
      msg->parts->next = NULL;
    }
  else
    {
      for (p = msg->parts; p->next != NULL; p = p->next)
	;
      p = p->next = (struct list *) malloc (sizeof(struct list));
      if (p == NULL)
	{
	  warn (0, "add_subpart could not malloc attachment buffer");
	  return 0;
	}
      p->data = part;
      p->next = NULL;
    }

  return 1;
}

void
mime_write (msg)
     struct message *msg;
{
  if (msg->header)
    {
      struct header *h;
      for (h = msg->header; h != NULL; h = h->next)
	{
	  fputs (h->text, stdout);
	  putc ('\n', stdout);
	}
    }
  putc('\n', stdout);

  /* message/rfc822 needs special handling. */
  if (check_msgtype(msg, "message/rfc822"))
    {
      mime_write (msg->parts->data);
      return;
    }

  /* XXX: watch out for 8bit data here. */
  fputs (msg->preamble, stdout);

  if (msg->parts != NULL)
    {
      struct list *p;
      for (p = msg->parts; p != NULL; p = p->next)
	{
	  printf ("\n--%s\n", msg->boundary);
	  mime_write (p->data);
	}
      printf ("\n--%s--\n", msg->boundary);
      fputs (msg->epilogue, stdout);
    }
}

void
mime_destroy (msg)
     struct message *msg;
{
  struct list *p, *q;

  if (msg->header != NULL)
    {
      struct header *h, *g;
      h = msg->header;
      while (h != NULL)
	{
	  g = h->next;
	  free(h->text);
	  free(h);
	  h = g;
	}
    }

  if (msg->preamble != NULL)
    free (msg->preamble);
  if (msg->epilogue != NULL)
    free (msg->epilogue);
  if (msg->content_type != NULL)
    free (msg->content_type);
  if (msg->boundary != NULL)
    free (msg->boundary);

  p = msg->parts;
  while (p != NULL)
    {
      if (p->data != NULL)
	mime_destroy (p->data);
      q = p;
      p = p->next;
      free (q);
    }
}

void
print_msg_info (msg, indent)
     struct message *msg;
     int indent;
{
  char indbuf[80];

  indbuf[indent--] = '\0';
  while (indent >= 0)
    indbuf[indent--] = ' ';

  printf ("%sHeader:\n", indbuf);
  printf ("%s--BEGIN--\n", indbuf);
  printf ("%s\n", msg->header);
  printf ("%s--END--\n", indbuf);
  printf ("%sContent-Type: %s\n", indbuf, msg->content_type);
  printf ("%sBoundary: %s\n", indbuf, msg->boundary);
  printf ("%s----------------------------------------\n", indbuf);

  if (msg->parts != NULL) {
    struct list *p;
    for (p = msg->parts; p != NULL; p = p->next) {
      print_msg_info (p->data, indent + 4);
    }
  }
}

int
hex2dec_char(ch)
     char ch;
{
  if (isdigit(ch))
    return ch-'0';
  else if (isupper(ch))
    return ch-'A'+10;
  else
    return ch-'a'+10;
}

/*
 * mime_decode_qp: convert the preamble of MSG from a quoted-printable
 *	encoding to raw text.
 */
void
mime_decode_qp (msg)
     struct message *msg;
{
  unsigned char *src, *dst;

  dst = src = msg->preamble;
  while (*src != '\0')
    {
      if (*src == '=')
	{
	  if (*++src == '\n')
	    {
	      ++src;
	      continue;
	    }
	  else
	    {
	      int hi, lo;
	      hi = hex2dec_char(*src++);
	      lo = hex2dec_char(*src);
	      *dst = hi*16 + lo;
	      if (*dst > 0x7f)
		msg->is8bit = 1;
	    }
	}
      else
	*dst = *src;
      ++dst, ++src;
    }
  *dst = '\0';
}

void
mime_output_qp (text)
     char *text;
{
  /* XXX: write this. */
}

void
warn (int level, ...)
{
  va_list ap;
  char *fmt;

  va_start(ap, level);
  fmt = va_arg(ap, char *);
  if (level >= verbose)
    {
      fprintf (stderr, "%s: ", program_name);
      vfprintf (stderr, fmt, ap);
      putc('\n', stderr);
    }
  va_end(ap);
}

/*
 * The char64 macro and `mime_decode_b64' routine are taken from
 * metamail 2.7, which is copyright (c) 1991 Bell Communications
 * Research, Inc. (Bellcore).  The following license applies to all
 * code below this point:
 *
 * Permission to use, copy, modify, and distribute this material 
 * for any purpose and without fee is hereby granted, provided 
 * that the above copyright notice and this permission notice 
 * appear in all copies, and that the name of Bellcore not be 
 * used in advertising or publicity pertaining to this 
 * material without the specific, prior written permission 
 * of an authorized representative of Bellcore.  BELLCORE 
 * MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY 
 * OF THIS MATERIAL FOR ANY PURPOSE.  IT IS PROVIDED "AS IS", 
 * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
 */

static char index_64[128] = {
    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
    52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
    -1, 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,-1, -1,-1,-1,-1,
    -1,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,-1, -1,-1,-1,-1
};

#define char64(c)  (((c) < 0 || (c) > 127) ? -1 : index_64[(c)])

void
mime_decode_b64 (src) 
     char *src;
{
  char *dst;
  int c1, c2, c3, c4;
  int newline = 1, DataDone = 0;

  dst = src;
  while ((c1 = *src++) != '\0')
    {
      if (isspace(c1)) {
	if (c1 == '\n') {
	  newline = 1;
	} else {
	  newline = 0;
	}
	continue;
      }
      if (DataDone) continue;
      newline = 0;
      do {
	c2 = *src++;
      } while (c2 != '\0' && isspace(c2));
      do {
	c3 = *src++;
      } while (c3 != '\0' && isspace(c3));
      do {
	c4 = *src++;
      } while (c4 != '\0' && isspace(c4));
      if (c2 == '\0' || c3 == '\0' || c4 == '\0')
	{
	  warn(0, "Warning: base64 decoder saw premature EOF!\n");
	  return;
        }
      if (c1 == '=' || c2 == '=') {
	DataDone=1;
	continue;
      }
      c1 = char64(c1);
      c2 = char64(c2);
      *dst++ = (c1<<2) | ((c2&0x30)>>4);
      if (c3 == '=')
	DataDone = 1;
      else
	{
	  c3 = char64(c3);
	  *dst++ = ((c2&0XF) << 4) | ((c3&0x3C) >> 2);
	  if (c4 == '=')
	    DataDone = 1;
          else
	    {
	      c4 = char64(c4);
	      *dst++ = ((c3&0x03) <<6) | c4;
            }
        }
    }
  *dst = '\0';
}

