commit b4e2769abbf5282a8672c86ba10c5edd87a4b7ee
parent 11889e16cc8279152d6623806b9b3a35cb137fbd
Author: Jaromil <jaromil@dyne.org>
Date:   Mon, 14 May 2012 22:08:30 +0200
fetchadds and dotlock files for lbdb
Diffstat:
| A | share/lbdb/dotlock.c |  |  | 538 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | share/lbdb/fetchaddr.c |  |  | 226 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | share/lbdb/helpers.c |  |  | 122 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | share/lbdb/rfc2047.c |  |  | 268 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | share/lbdb/rfc822.c |  |  | 768 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
5 files changed, 1922 insertions(+), 0 deletions(-)
diff --git a/share/lbdb/dotlock.c b/share/lbdb/dotlock.c
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 1998-2000 Thomas Roessler <roessler@does-not-exist.org>
+ * 
+ *     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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,, USA.
+ */ 
+
+/*
+ * Completely stolen from the Mutt mailreader
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <errno.h>
+#include <time.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#ifndef _POSIX_PATH_MAX
+#include <posix1_lim.h>
+#endif
+
+#include "dotlock.h"
+#include "helpers.h"
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#define MAXLINKS 1024 /* maximum link depth */
+
+#define LONG_STRING 1024
+#define MAXLOCKATTEMPT 5
+
+static int DotlockFlags;
+static int Retry = MAXLOCKATTEMPT;
+
+static char *Hostname;
+
+static int dotlock_deference_symlink (char *, size_t, const char *);
+static int dotlock_prepare (char *, size_t, const char *, int fd);
+static int dotlock_check_stats (struct stat *, struct stat *);
+static int dotlock_dispatch (const char *, int fd);
+
+static void usage (const char *);
+
+static void dotlock_expand_link (char *, const char *, const char *);
+
+/* These functions work on the current directory.
+ * Invoke dotlock_prepare () before and check their
+ * return value.
+ */
+
+static int dotlock_try (void);
+static int dotlock_unlock (const char *);
+static int dotlock_unlink (const char *);
+static int dotlock_lock (const char *);
+
+
+#define check_flags(a) if (a & DL_FL_ACTIONS) usage (argv[0])
+
+int main (int argc, char **argv)
+{
+  int i;
+  char *p;
+  struct utsname utsname;
+
+  /* determine the system's host name */
+  
+  uname (&utsname);
+  if (!(Hostname = strdup (utsname.nodename)))
+    return DL_EX_ERROR;
+  if ((p = strchr (Hostname, '.')))
+    *p = '\0';
+
+
+  /* parse the command line options. */
+  DotlockFlags = 0;
+  
+  while ((i = getopt (argc, argv, "dtfur:")) != EOF)
+  {
+    switch (i)
+    {
+      /* actions, mutually exclusive */
+      case 't': check_flags (DotlockFlags); DotlockFlags |= DL_FL_TRY; break;
+      case 'd': check_flags (DotlockFlags); DotlockFlags |= DL_FL_UNLINK; break;
+      case 'u': check_flags (DotlockFlags); DotlockFlags |= DL_FL_UNLOCK; break;
+
+      /* other flags */
+      case 'f': DotlockFlags |= DL_FL_FORCE; break;
+      case 'r': DotlockFlags |= DL_FL_RETRY; Retry = atoi (optarg); break;
+      
+      default: usage (argv[0]);
+    }
+  }
+
+  if (optind == argc || Retry < 0)
+    usage (argv[0]);
+
+  return dotlock_dispatch (argv[optind], -1);
+}
+
+
+static int dotlock_dispatch (const char *f, int fd)
+{
+  char realpath[_POSIX_PATH_MAX];
+
+  /* If dotlock_prepare () succeeds [return value == 0],
+   * realpath contains the basename of f, and we have
+   * successfully changed our working directory to
+   * `dirname $f`.  Additionally, f has been opened for
+   * reading to verify that the user has at least read
+   * permissions on that file.
+   * 
+   * For a more detailed explanation of all this, see the
+   * lengthy comment below.
+   */
+
+  if (dotlock_prepare (realpath, sizeof (realpath), f, fd) != 0)
+    return DL_EX_ERROR;
+
+  /* Actually perform the locking operation. */
+
+  if (DotlockFlags & DL_FL_TRY)
+    return dotlock_try ();
+  else if (DotlockFlags & DL_FL_UNLOCK)
+    return dotlock_unlock (realpath);
+  else if (DotlockFlags & DL_FL_UNLINK)
+    return dotlock_unlink (realpath);
+  else /* lock */
+    return dotlock_lock (realpath);
+}
+
+
+/*
+ * Usage information.
+ * 
+ * This function doesn't return.
+ * 
+ */
+
+static void 
+usage (const char *av0)
+{
+  fprintf (stderr, "usage: %s [-t|-f|-u|-d] [-r <retries>] file\n", av0);
+
+  fputs ("\noptions:"
+	"\n  -t\t\ttry"
+	"\n  -f\t\tforce"
+	"\n  -u\t\tunlock"
+	"\n  -d\t\tunlink"
+	"\n  -r <retries>\tRetry locking"
+	"\n", stderr);
+  
+  exit (DL_EX_ERROR);
+}
+
+
+/*
+ * Access checking: Let's avoid to lock other users' mail
+ * spool files if we aren't permitted to read them.
+ * 
+ * Some simple-minded access (2) checking isn't sufficient
+ * here: The problem is that the user may give us a
+ * deeply nested path to a file which has the same name
+ * as the file he wants to lock, but different
+ * permissions, say, e.g.
+ * /tmp/lots/of/subdirs/var/spool/mail/root.
+ * 
+ * He may then try to replace /tmp/lots/of/subdirs by a
+ * symbolic link to / after we have invoked access () to
+ * check the file's permissions.  The lockfile we'd
+ * create or remove would then actually be
+ * /var/spool/mail/root.
+ * 
+ * To avoid this attack, we proceed as follows:
+ * 
+ * - First, follow symbolic links a la
+ *   dotlock_deference_symlink ().
+ * 
+ * - get the result's dirname.
+ * 
+ * - chdir to this directory.  If you can't, bail out.
+ * 
+ * - try to open the file in question, only using its
+ *   basename.  If you can't, bail out.
+ * 
+ * - fstat that file and compare the result to a
+ *   subsequent lstat (only using the basename).  If
+ *   the comparison fails, bail out.
+ * 
+ * dotlock_prepare () is invoked from main () directly
+ * after the command line parsing has been done.
+ *
+ * Return values:
+ * 
+ * 0 - Evereything's fine.  The program's new current
+ *     directory is the contains the file to be locked.
+ *     The string pointed to by bn contains the name of
+ *     the file to be locked.
+ * 
+ * -1 - Something failed. Don't continue.
+ * 
+ * tlr, Jul 15 1998
+ */
+
+static int
+dotlock_check_stats (struct stat *fsb, struct stat *lsb)
+{
+  /* S_ISLNK (fsb->st_mode) should actually be impossible,
+   * but we may have mixed up the parameters somewhere.
+   * play safe.
+   */
+
+  if (S_ISLNK (lsb->st_mode) || S_ISLNK (fsb->st_mode))
+    return -1;
+  
+  if ((lsb->st_dev != fsb->st_dev) ||
+     (lsb->st_ino != fsb->st_ino) ||
+     (lsb->st_mode != fsb->st_mode) ||
+     (lsb->st_nlink != fsb->st_nlink) ||
+     (lsb->st_uid != fsb->st_uid) ||
+     (lsb->st_gid != fsb->st_gid) ||
+     (lsb->st_rdev != fsb->st_rdev) ||
+     (lsb->st_size != fsb->st_size))
+  {
+    /* something's fishy */
+    return -1;
+  }
+  
+  return 0;
+}
+
+static int
+dotlock_prepare (char *bn, size_t l, const char *f, int _fd)
+{
+  struct stat fsb, lsb;
+  char realpath[_POSIX_PATH_MAX];
+  char *basename, *dirname;
+  char *p;
+  int fd;
+  int r;
+  
+  if (dotlock_deference_symlink (realpath, sizeof (realpath), f) == -1)
+    return -1;
+  
+  if ((p = strrchr (realpath, '/')))
+  {
+    *p = '\0';
+    basename = p + 1;
+    dirname = realpath;
+  }
+  else
+  {
+    basename = realpath;
+    dirname = ".";
+  }
+
+  if (strlen (basename) + 1 > l)
+    return -1;
+  
+  strfcpy (bn, basename, l);
+  
+  if (chdir (dirname) == -1)
+    return -1;
+
+  if (_fd != -1)
+    fd = _fd;
+  else if ((fd = open (basename, O_RDONLY)) == -1)
+    return -1;
+  
+  r = fstat (fd, &fsb);
+  
+  if (_fd == -1)
+    close (fd);
+  
+  if (r == -1)
+    return -1;
+  
+  if (lstat (basename, &lsb) == -1)
+    return -1;
+
+  if (dotlock_check_stats (&fsb, &lsb) == -1)
+    return -1;
+
+  return 0;
+}
+
+/*
+ * Expand a symbolic link.
+ * 
+ * This function expects newpath to have space for
+ * at least _POSIX_PATH_MAX characters.
+ *
+ */
+
+static void 
+dotlock_expand_link (char *newpath, const char *path, const char *link)
+{
+  const char *lb = NULL;
+  size_t len;
+
+  /* link is full path */
+  if (*link == '/')
+  {
+    strfcpy (newpath, link, _POSIX_PATH_MAX);
+    return;
+  }
+
+  if ((lb = strrchr (path, '/')) == NULL)
+  {
+    /* no path in link */
+    strfcpy (newpath, link, _POSIX_PATH_MAX);
+    return;
+  }
+
+  len = lb - path + 1;
+  memcpy (newpath, path, len);
+  strfcpy (newpath + len, link, _POSIX_PATH_MAX - len);
+}
+
+
+/*
+ * Deference a chain of symbolic links
+ * 
+ * The final path is written to d.
+ *
+ */
+
+static int
+dotlock_deference_symlink (char *d, size_t l, const char *path)
+{
+  struct stat sb;
+  char realpath[_POSIX_PATH_MAX];
+  const char *pathptr = path;
+  int count = 0;
+  
+  while (count++ < MAXLINKS)
+  {
+    if (lstat (pathptr, &sb) == -1)
+    {
+      /* perror (pathptr); */
+      return -1;
+    }
+    
+    if (S_ISLNK (sb.st_mode))
+    {
+      char linkfile[_POSIX_PATH_MAX];
+      char linkpath[_POSIX_PATH_MAX];
+      int len;
+
+      if ((len = readlink (pathptr, linkfile, sizeof (linkfile))) == -1)
+      {
+	/* perror (pathptr); */
+	return -1;
+      }
+      
+      linkfile[len] = '\0';
+      dotlock_expand_link (linkpath, pathptr, linkfile);
+      strfcpy (realpath, linkpath, sizeof (realpath));
+      pathptr = realpath;
+    }
+    else
+      break;
+  }
+
+  strfcpy (d, pathptr, l);
+  return 0;
+}
+
+/*
+ * Dotlock a file.
+ * 
+ * realpath is assumed _not_ to be an absolute path to
+ * the file we are about to lock.  Invoke
+ * dotlock_prepare () before using this function!
+ * 
+ */
+
+#define HARDMAXATTEMPTS 10
+
+static int
+dotlock_lock (const char *realpath)
+{
+  char lockfile[_POSIX_PATH_MAX + LONG_STRING];
+  char nfslockfile[_POSIX_PATH_MAX + LONG_STRING];
+  size_t prev_size = 0;
+  int fd;
+  int count = 0;
+  int hard_count = 0;
+  struct stat sb;
+  time_t t;
+  
+  sprintf (nfslockfile, "%s.%s.%d", realpath, Hostname, (int) getpid ());
+  sprintf (lockfile, "%s.lock", realpath);
+
+  
+  unlink (nfslockfile);
+
+  while ((fd = open (nfslockfile, O_WRONLY | O_EXCL | O_CREAT, 0)) < 0)
+  {
+    if (errno != EAGAIN)
+    {
+      /* perror ("cannot open NFS lock file"); */
+      return DL_EX_ERROR;
+    }
+  }
+
+
+  
+  close (fd);
+  
+  while (hard_count++ < HARDMAXATTEMPTS)
+  {
+    link (nfslockfile, lockfile);
+
+    if (stat (nfslockfile, &sb) != 0)
+    {
+      /* perror ("stat"); */
+      return DL_EX_ERROR;
+    }
+
+    if (sb.st_nlink == 2)
+      break;
+
+    if (count == 0)
+      prev_size = sb.st_size;
+
+    if (prev_size == sb.st_size && ++count > Retry)
+    {
+      if (DotlockFlags & DL_FL_FORCE)
+      {
+	unlink (lockfile);
+	count = 0;
+	continue;
+      }
+      else
+      {
+	unlink (nfslockfile);
+	return DL_EX_EXIST;
+      }
+    }
+    
+    prev_size = sb.st_size;
+    
+    /* don't trust sleep (3) as it may be interrupted
+     * by users sending signals. 
+     */
+    
+    t = time (NULL);
+    do {
+      sleep (1);
+    } while (time (NULL) == t);
+  }
+  
+  unlink (nfslockfile);
+
+  return DL_EX_OK;
+}
+
+
+/*
+ * Unlock a file. 
+ * 
+ * The same comment as for dotlock_lock () applies here.
+ * 
+ */
+
+static int
+dotlock_unlock (const char *realpath)
+{
+  char lockfile[_POSIX_PATH_MAX + LONG_STRING];
+  int i;
+
+  sprintf (lockfile, "%s.lock", realpath);
+  
+  i = unlink (lockfile);
+  
+  if (i == -1)
+    return DL_EX_ERROR;
+  
+  return DL_EX_OK;
+}
+
+/* remove an empty file */
+
+static int
+dotlock_unlink (const char *realpath)
+{
+  struct stat lsb;
+  int i = -1;
+
+  if (dotlock_lock (realpath) != DL_EX_OK)
+    return DL_EX_ERROR;
+
+  if ((i = lstat (realpath, &lsb)) == 0 && lsb.st_size == 0)
+    unlink (realpath);
+
+  dotlock_unlock (realpath);
+
+  return (i == 0) ?  DL_EX_OK : DL_EX_ERROR;
+}
+
+
+/*
+ * Check if a file can be locked at all.
+ * 
+ * The same comment as for dotlock_lock () applies here.
+ * 
+ */
+
+static int
+dotlock_try (void)
+{
+  if (access (".", W_OK) == 0)
+    return DL_EX_OK;
+
+  return DL_EX_IMPOSSIBLE;
+}
diff --git a/share/lbdb/fetchaddr.c b/share/lbdb/fetchaddr.c
@@ -0,0 +1,226 @@
+/*
+ *  Copyright (C) 1998-2000  Thomas Roessler <roessler@guug.de>
+ *  Copyright (C) 2000       Roland Rosenfeld <roland@spinnaker.de>
+ *
+ *  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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,, USA.
+ *
+ */
+
+/* $Id: fetchaddr.c,v 1.23 2007-10-28 16:33:35 roland Exp $ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "helpers.h"
+#include "rfc822.h"
+#include "rfc2047.h"
+
+#define MAXHDRS 21
+
+struct header
+{
+  char *tag;
+  char *value;
+  size_t len;
+  size_t taglen;
+};
+
+struct header hdr[MAXHDRS] =
+{
+  { "to:",		NULL, 0,  3 },
+  { "from:",		NULL, 0,  5 },
+  { "cc:",		NULL, 0,  3 },
+  { "resent-from:",	NULL, 0, 12 },
+  { "resent-to:",	NULL, 0, 10 },
+  { NULL, 		NULL, 0,  0 }
+};
+
+void chop(struct header *cur)
+{
+  if(cur->len && cur->value[cur->len - 1] == '\n')
+    cur->value[--cur->len] = '\0';
+}
+
+int writeout(struct header *h, const char *datefmt, 
+	     unsigned char create_real_name)
+{
+  int rv = 0;
+  ADDRESS *addr, *p;
+  time_t timep;
+  char timebuf[256];
+  char *c;
+
+  if(!h->value)
+    return 0;
+  
+  addr = rfc822_parse_adrlist(NULL, h->value);
+  time(&timep);
+
+  rfc2047_decode_adrlist(addr);
+  for(p = addr; p; p = p->next)
+  {
+    if(create_real_name == 1 
+       && (!p->personal || !*p->personal)
+       && p->mailbox)
+    {
+      if(p->personal)
+	FREE(p->personal);
+      p->personal = safe_strdup(p->mailbox);
+      c=strchr(p->personal, '@');
+      if (c)
+	*c='\0';
+    }
+    if(!p->group && p->mailbox && *p->mailbox && p->personal)
+    {
+      if(p->personal && strlen(p->personal) > 30)
+	strcpy(p->personal + 27, "...");
+
+      if ((c=strchr(p->mailbox,'@')))
+	for(c++; *c; c++)
+	  *c=tolower(*c);
+
+      strftime(timebuf, sizeof(timebuf), datefmt, localtime(&timep));
+      printf("%s\t%s\t%s\n", p->mailbox, p->personal && *p->personal ? 
+	     p->personal : "no realname given", timebuf);
+
+      rv = 1;
+    }
+  }
+
+  rfc822_free_address(&addr);
+
+  return rv;
+}
+  
+int main(int argc, char* argv[])
+{
+  char buff[2048];
+  char *t;
+  int i, rv;
+  int partial = 0;
+  struct header *cur_hdr = NULL;
+  char *datefmt = NULL;
+  char *headerlist = NULL;
+  char *fieldname, *next;
+  char create_real_name = 0;
+#ifdef HAVE_ICONV
+  const char **charsetptr = &Charset;
+#endif
+
+  /* process command line arguments: */
+  if (argc > 1) {
+    i = 1;
+    while (i < argc) {
+      if (!strcmp (argv[i], "-d") && i+1 < argc) {
+	datefmt = argv[++i];
+      } else if (!strcmp (argv[i], "-x") && i+1 < argc) {
+	headerlist = argv[++i];
+#ifdef HAVE_ICONV
+      } else if (!strcmp (argv[i], "-c") && i+1 < argc) {
+	*charsetptr = argv[++i];
+#endif
+      } else if (!strcmp (argv[i], "-a")) {
+	create_real_name = 1;
+      } else {
+	fprintf (stderr, "%s: `%s' wrong parameter\n", argv[0], argv[i]);
+      }
+      i++;
+    }
+  }
+
+  if (!datefmt) 
+    datefmt = safe_strdup("%Y-%m-%d %H:%M");
+
+  if (headerlist && strlen (headerlist) > 0 ) {
+    fieldname = headerlist;
+    i = 0;
+    while ( i < MAXHDRS-1 && (next = strchr (fieldname, ':'))) {
+      hdr[i].tag = safe_malloc (next - fieldname + 2);
+      strncpy (hdr[i].tag, fieldname, next - fieldname);
+      hdr[i].tag[next - fieldname] = ':';
+      hdr[i].tag[next - fieldname + 1] = '\0';
+      hdr[i].taglen = next - fieldname + 1;
+      fieldname = next+1;
+      i++;
+    }
+    
+    if (i < MAXHDRS-1 && *fieldname != '\0') {
+      hdr[i].tag = safe_malloc (strlen (fieldname) + 2);
+      strncpy (hdr[i].tag, fieldname, strlen (fieldname));
+      hdr[i].tag[strlen (fieldname)] = ':';
+      hdr[i].tag[strlen (fieldname) + 1] = '\0';
+      hdr[i].taglen = strlen (fieldname) + 1;
+      i++;
+    }
+    
+    hdr[i].tag = NULL;	/* end of hdr list */
+  }
+
+  while(fgets(buff, sizeof(buff), stdin))
+  {
+    
+    if(!partial && *buff == '\n')
+      break;
+
+    if(cur_hdr && (partial || *buff == ' ' || *buff == '\t'))
+    {
+      size_t nl = cur_hdr->len + strlen(buff);
+      
+      safe_realloc((void **) &cur_hdr->value, nl + 1);
+      strcpy(cur_hdr->value + cur_hdr->len, buff);
+      cur_hdr->len = nl;
+      chop(cur_hdr);
+    }
+    else if(!partial && *buff != ' ' && *buff != '\t')
+    {
+      cur_hdr = NULL;
+
+      for(i = 0; hdr[i].tag; i++)
+      {
+	if(!strncasecmp(buff, hdr[i].tag, hdr[i].taglen))
+	{
+	  cur_hdr = &hdr[i];
+	  break;
+	}
+      }
+      
+      if(cur_hdr)
+      {
+	safe_free(&cur_hdr->value);
+	cur_hdr->value = safe_strdup(buff + cur_hdr->taglen);
+	cur_hdr->len = strlen(cur_hdr->value);
+	chop(cur_hdr);
+      }
+    }
+    
+    if(!(t = strchr(buff, '\n')))
+      partial = 1;
+    else
+      partial = 0;
+  }
+
+  for(rv = 0, i = 0; hdr[i].tag; i++)
+  {
+    if(hdr[i].value)
+      rv = writeout(&hdr[i], datefmt, create_real_name) || rv;
+  }
+
+  return (rv ? 0 : 1);
+  
+}
diff --git a/share/lbdb/helpers.c b/share/lbdb/helpers.c
@@ -0,0 +1,122 @@
+/*
+ * The functions in this file have completely been stolen
+ * from the Mutt mail user agent, with some slight
+ * modifications by Thomas Roessler <roessler@guug.de> to
+ * use them in the "little brother database".
+ *
+ * The following copyright notice applies:
+ *
+ * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu>
+ *
+ *     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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,, USA.
+ */
+
+/* $Id: helpers.c,v 1.5 2005-10-29 14:48:08 roland Exp $ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "helpers.h"
+
+
+void *safe_calloc (size_t nmemb, size_t size)
+{
+  void *p;
+
+  if (!nmemb || !size)
+    return NULL;
+  if (!(p = calloc (nmemb, size)))
+  {
+    perror ("Out of memory");
+    sleep (1);
+    exit (1);
+  }
+  return p;
+}
+
+void *safe_malloc (unsigned int siz)
+{
+  void *p;
+
+  if (siz == 0)
+    return 0;
+  if ((p = (void *) malloc (siz)) == 0)
+  {
+    perror ("Out of memory!");
+    sleep (1);
+    exit (1);
+  }
+  return (p);
+}
+
+void safe_realloc (void **p, size_t siz)
+{
+  void *r;
+
+  if (siz == 0)
+  {
+    if (*p)
+    {
+      free (*p);
+      *p = NULL;
+    }
+    return;
+  }
+
+  if (*p)
+    r = (void *) realloc (*p, siz);
+  else
+  {
+    /* realloc(NULL, nbytes) doesn't seem to work under SunOS 4.1.x */
+    r = (void *) malloc (siz);
+  }
+
+  if (!r)
+  {
+    perror ("Out of memory!");
+    sleep (1);
+    exit (1);
+  }
+
+  *p = r;
+}
+
+void safe_free (void *ptr)
+{ 
+  void **p = (void **)ptr;
+  if (*p)
+  { 
+    free (*p);
+    *p = 0;
+  }
+}
+
+char *safe_strdup (const char *s)
+{
+  char *p;
+  size_t l;
+
+  if (!s || !*s) return 0;
+  l = strlen (s) + 1;
+  p = (char *)safe_malloc (l);
+  memcpy (p, s, l);
+  return (p);
+}
+
diff --git a/share/lbdb/rfc2047.c b/share/lbdb/rfc2047.c
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu>
+ * 
+ *     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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,, USA.
+ */ 
+
+/* $Id: rfc2047.c,v 1.5 2007-10-28 16:33:36 roland Exp $ */
+
+#include <ctype.h>
+#include <string.h>
+#ifdef HAVE_ICONV
+#include <iconv.h>
+#include <errno.h>
+#include <limits.h>
+#endif
+
+#include "rfc822.h"
+#include "rfc2047.h"
+#include "helpers.h"
+
+enum
+{
+  ENCOTHER,
+  ENC7BIT,
+  ENC8BIT,
+  ENCQUOTEDPRINTABLE,
+  ENCBASE64,
+  ENCBINARY
+};
+
+const char MimeSpecials[] = "@.,;<>[]\\\"()?/=";
+const char *Charset = "iso-8859-15"; /* XXX - hack */
+
+
+int Index_hex[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,-1, -1,-1,-1,-1,
+     0, 1, 2, 3,  4, 5, 6, 7,  8, 9,-1,-1, -1,-1,-1,-1,
+    -1,10,11,12, 13,14,15,-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,10,11,12, 13,14,15,-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
+};
+
+int 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 IsPrint(c) (isprint((unsigned char)(c)) || \
+	((unsigned char)(c) >= 0xa0))
+
+#define hexval(c) Index_hex[(unsigned int)(c)]
+#define base64val(c) Index_64[(unsigned int)(c)]
+
+static int rfc2047_decode_word (char *d, const char *s, size_t dlen)
+{
+  char *p = safe_strdup (s);
+  char *pp = p;
+  char *pd = d;
+  size_t len = dlen;
+  int enc = 0, filter = 0, count = 0, c1, c2, c3, c4;
+#ifdef HAVE_ICONV
+  char *fromcharset;
+  iconv_t cd;
+  size_t in;
+#endif
+
+  while ((pp = strtok (pp, "?")) != NULL)
+  {
+    count++;
+    switch (count)
+    {
+      case 2:
+	if (strcasecmp (pp, Charset) != 0)
+	{
+	  filter = 1;
+#ifdef HAVE_ICONV
+	  fromcharset = safe_strdup (pp);
+#endif
+	}
+	break;
+      case 3:
+	if (toupper (*pp) == 'Q')
+	  enc = ENCQUOTEDPRINTABLE;
+	else if (toupper (*pp) == 'B')
+	  enc = ENCBASE64;
+	else
+	  return (-1);
+	break;
+      case 4:
+	if (enc == ENCQUOTEDPRINTABLE)
+	{
+	  while (*pp && len > 0)
+	  {
+	    if (*pp == '_')
+	    {
+	      *pd++ = ' ';
+	      len--;
+	    }
+	    else if (*pp == '=')
+	    {
+	      *pd++ = (hexval(pp[1]) << 4) | hexval(pp[2]);
+	      len--;
+	      pp += 2;
+	    }
+	    else
+	    {
+	      *pd++ = *pp;
+	      len--;
+	    }
+	    pp++;
+	  }
+	  *pd = 0;
+	}
+	else if (enc == ENCBASE64)
+	{
+	  while (*pp && len > 0)
+	  {
+	    c1 = base64val(pp[0]);
+	    c2 = base64val(pp[1]);
+	    *pd++ = (c1 << 2) | ((c2 >> 4) & 0x3);
+	    if (--len == 0) break;
+	    
+	    if (pp[2] == '=') break;
+
+	    c3 = base64val(pp[2]);
+	    *pd++ = ((c2 & 0xf) << 4) | ((c3 >> 2) & 0xf);
+	    if (--len == 0)
+	      break;
+
+	    if (pp[3] == '=')
+	      break;
+
+	    c4 = base64val(pp[3]);
+	    *pd++ = ((c3 & 0x3) << 6) | c4;
+	    if (--len == 0)
+	      break;
+
+	    pp += 4;
+	  }
+	  *pd = 0;
+	}
+	break;
+    }
+    pp = 0;
+  }
+  safe_free (&p);
+  if (filter)
+  {
+#ifdef HAVE_ICONV
+    if ((cd = iconv_open (Charset, fromcharset)) == (iconv_t)(-1))
+    {
+#endif
+      pd = d;
+      while (*pd)
+      {
+	if (!IsPrint (*pd))
+	  *pd = '?';
+	pd++;
+      }
+#ifdef HAVE_ICONV
+    } else {
+      p = safe_strdup (d);
+      pp = p;
+      in = strlen (d) + 1;
+      pd = d;
+      /* maximum available buffer length for converted string */
+      len = dlen;
+      while (*pd && iconv (cd, &pp, &in, &pd, &len) == (size_t)(-1))
+      {
+	if (errno == E2BIG)
+	  break;
+
+	*pd = '?';
+	pp++;
+	in--;
+	pd++;
+	len--;
+      }
+      iconv (cd, NULL, NULL, &pd, &len);
+      iconv_close (cd);
+      safe_free (&p);
+    }
+    safe_free (&fromcharset);
+#endif
+  }
+  return (0);
+}
+
+/* try to decode anything that looks like a valid RFC2047 encoded
+ * header field, ignoring RFC822 parsing rules
+ */
+void rfc2047_decode (char *d, const char *s, size_t dlen)
+{
+  const char *p, *q;
+  size_t n;
+  int found_encoded = 0;
+
+  dlen--; /* save room for the terminal nul */
+
+  while (*s && dlen > 0)
+  {
+    if ((p = strstr (s, "=?")) == NULL ||
+	(q = strchr (p + 2, '?')) == NULL ||
+	(q = strchr (q + 1, '?')) == NULL ||
+	(q = strstr (q + 1, "?=")) == NULL)
+    {
+      /* no encoded words */
+      if (d != s)
+	strfcpy (d, s, dlen + 1);
+      return;
+    }
+
+    if (p != s)
+    {
+      n = (size_t) (p - s);
+      /* ignore spaces between encoded words */
+      if (!found_encoded || strspn (s, " \t\r\n") != n)
+      {
+	if (n > dlen)
+	  n = dlen;
+	if (d != s)
+	  memcpy (d, s, n);
+	d += n;
+	dlen -= n;
+      }
+    }
+
+    rfc2047_decode_word (d, p, dlen);
+    found_encoded = 1;
+    s = q + 2;
+    n = strlen (d);
+    dlen -= n;
+    d += n;
+  }
+  *d = 0;
+}
+
+void rfc2047_decode_adrlist (ADDRESS *a)
+{
+  while (a)
+  {
+    if (a->personal && strstr (a->personal, "=?") != NULL)
+      rfc2047_decode (a->personal, a->personal, strlen (a->personal) + 1);
+    a = a->next;
+  }
+}
diff --git a/share/lbdb/rfc822.c b/share/lbdb/rfc822.c
@@ -0,0 +1,768 @@
+/*
+ * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu>
+ * 
+ *     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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,, USA.
+ */ 
+
+/* $Id: rfc822.c,v 1.5 2005-10-29 14:48:11 roland Exp $ */
+
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "helpers.h"
+#include "rfc822.h"
+
+const char RFC822Specials[] = "@.,:;<>[]\\\"()";
+#define is_special(x) strchr(RFC822Specials,x)
+
+int RFC822Error = 0;
+
+/* these must defined in the same order as the numerated errors given in rfc822.h */
+const char *RFC822Errors[] = {
+  "out of memory",
+  "mismatched parenthesis",
+  "mismatched quotes",
+  "bad route in <>",
+  "bad address in <>",
+  "bad address spec"
+};
+
+void rfc822_dequote_comment (char *s)
+{
+  char *w = s;
+
+  for (; *s; s++)
+  {
+    if (*s == '\\')
+    {
+      if (!*++s)
+	break; /* error? */
+      *w++ = *s;
+    }
+    else if (*s != '\"')
+    {
+      if (w != s)
+	*w = *s;
+      w++;
+    }
+  }
+  *w = 0;
+}
+
+void rfc822_free_address (ADDRESS **p)
+{
+  ADDRESS *t;
+
+  while (*p)
+  {
+    t = *p;
+    *p = (*p)->next;
+#ifdef EXACT_ADDRESS
+    FREE (&t->val);
+#endif
+    FREE (&t->personal);
+    FREE (&t->mailbox);
+    FREE (&t);
+  }
+}
+
+static const char *
+parse_comment (const char *s,
+	       char *comment, size_t *commentlen, size_t commentmax)
+{
+  int level = 1;
+  
+  while (*s && level)
+  {
+    if (*s == '(')
+      level++;
+    else if (*s == ')')
+    {
+      if (--level == 0)
+      {
+	s++;
+	break;
+      }
+    }
+    else if (*s == '\\')
+    {
+      if (!*++s)
+	break;
+    }
+    if (*commentlen < commentmax)
+      comment[(*commentlen)++] = *s;
+    s++;
+  }
+  if (level)
+  {
+    RFC822Error = ERR_MISMATCH_PAREN;
+    return NULL;
+  }
+  return s;
+}
+
+static const char *
+parse_quote (const char *s, char *token, size_t *tokenlen, size_t tokenmax)
+{
+  if (*tokenlen < tokenmax)
+    token[(*tokenlen)++] = '"';
+  while (*s)
+  {
+    if (*tokenlen < tokenmax)
+      token[(*tokenlen)++] = *s;
+    if (*s == '"')
+      return (s + 1);
+    if (*s == '\\')
+    {
+      if (!*++s)
+	break;
+    }
+    s++;
+  }
+  RFC822Error = ERR_MISMATCH_QUOTE;
+  return NULL;
+}
+
+static const char *
+next_token (const char *s, char *token, size_t *tokenlen, size_t tokenmax)
+{
+  if (*s == '(')
+    return (parse_comment (s + 1, token, tokenlen, tokenmax));
+  if (*s == '"')
+    return (parse_quote (s + 1, token, tokenlen, tokenmax));
+  if (is_special (*s))
+  {
+    if (*tokenlen < tokenmax)
+      token[(*tokenlen)++] = *s;
+    return (s + 1);
+  }
+  while (*s)
+  {
+    if (isspace (*s) || is_special (*s))
+      break;
+    if (*tokenlen < tokenmax)
+      token[(*tokenlen)++] = *s;
+    s++;
+  }
+  return s;
+}
+
+static const char *
+parse_mailboxdomain (const char *s, const char *nonspecial,
+		     char *mailbox, size_t *mailboxlen, size_t mailboxmax,
+		     char *comment, size_t *commentlen, size_t commentmax)
+{
+  const char *ps;
+
+  while (*s)
+  {
+    SKIPWS (s);
+    if (strchr (nonspecial, *s) == NULL && is_special (*s))
+      return s;
+
+    if (*s == '(')
+    {
+      if (*commentlen && *commentlen < commentmax)
+	comment[(*commentlen)++] = ' ';
+      ps = next_token (s, comment, commentlen, commentmax);
+    }
+    else
+      ps = next_token (s, mailbox, mailboxlen, mailboxmax);
+    if (!ps)
+      return NULL;
+    s = ps;
+  }
+
+  return s;
+}
+
+static const char *
+parse_address (const char *s,
+               char *token, size_t *tokenlen, size_t tokenmax,
+	       char *comment, size_t *commentlen, size_t commentmax,
+	       ADDRESS *addr)
+{
+  s = parse_mailboxdomain (s, ".\"(\\",
+			   token, tokenlen, tokenmax,
+			   comment, commentlen, commentmax);
+  if (!s)
+    return NULL;
+
+  if (*s == '@')
+  {
+    if (*tokenlen < tokenmax)
+      token[(*tokenlen)++] = '@';
+    s = parse_mailboxdomain (s + 1, ".([]\\",
+			     token, tokenlen, tokenmax,
+			     comment, commentlen, commentmax);
+    if (!s)
+      return NULL;
+  }
+
+  token[*tokenlen] = 0;
+  addr->mailbox = safe_strdup (token);
+
+  if (*commentlen && !addr->personal)
+  {
+    comment[*commentlen] = 0;
+    addr->personal = safe_strdup (comment);
+  }
+
+  return s;
+}
+
+static const char *
+parse_route_addr (const char *s,
+		  char *comment, size_t *commentlen, size_t commentmax,
+		  ADDRESS *addr)
+{
+  char token[STRING];
+  size_t tokenlen = 0;
+
+  SKIPWS (s);
+
+  /* find the end of the route */
+  if (*s == '@')
+  {
+    while (s && *s == '@')
+    {
+      if (tokenlen < sizeof (token) - 1)
+	token[tokenlen++] = '@';
+      s = parse_mailboxdomain (s + 1, ".\\[](", token,
+			       &tokenlen, sizeof (token) - 1,
+			       comment, commentlen, commentmax);
+    }
+    if (!s || *s != ':')
+    {
+      RFC822Error = ERR_BAD_ROUTE;
+      return NULL; /* invalid route */
+    }
+
+    if (tokenlen < sizeof (token) - 1)
+      token[tokenlen++] = ':';
+    s++;
+  }
+
+  if ((s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr)) == NULL)
+    return NULL;
+
+  if (*s != '>' || !addr->mailbox)
+  {
+    RFC822Error = ERR_BAD_ROUTE_ADDR;
+    return NULL;
+  }
+
+  s++;
+  return s;
+}
+
+static const char *
+parse_addr_spec (const char *s,
+		 char *comment, size_t *commentlen, size_t commentmax,
+		 ADDRESS *addr)
+{
+  char token[STRING];
+  size_t tokenlen = 0;
+
+  s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr);
+  if (s && *s && *s != ',' && *s != ';')
+  {
+    RFC822Error = ERR_BAD_ADDR_SPEC;
+    return NULL;
+  }
+  return s;
+}
+
+static void
+add_addrspec (ADDRESS **top, ADDRESS **last, const char *phrase,
+	      char *comment, size_t *commentlen, size_t commentmax)
+{
+  ADDRESS *cur = rfc822_new_address ();
+  
+  if (parse_addr_spec (phrase, comment, commentlen, commentmax, cur) == NULL)
+    return;
+
+  if (*last)
+    (*last)->next = cur;
+  else
+    *top = cur;
+  *last = cur;
+}
+
+ADDRESS *rfc822_parse_adrlist (ADDRESS *top, const char *s)
+{
+  const char *begin, *ps;
+  char comment[STRING], phrase[STRING];
+  size_t phraselen = 0, commentlen = 0;
+  ADDRESS *cur, *last = NULL;
+  
+  RFC822Error = 0;
+
+  last = top;
+  while (last && last->next)
+    last = last->next;
+
+  SKIPWS (s);
+  begin = s;
+  while (*s)
+  {
+    if (*s == ',')
+    {
+      if (phraselen)
+      {
+	phrase[phraselen] = 0;
+	add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
+      }
+      else if (commentlen && last && !last->personal)
+      {
+	comment[commentlen] = 0;
+	last->personal = safe_strdup (comment);
+      }
+
+#ifdef EXACT_ADDRESS
+      if (last && !last->val)
+	last->val = mutt_substrdup (begin, s);
+#endif
+      commentlen = 0;
+      phraselen = 0;
+      s++;
+      begin = s;
+      SKIPWS (begin);
+    }
+    else if (*s == '(')
+    {
+      if (commentlen && commentlen < sizeof (comment) - 1)
+	comment[commentlen++] = ' ';
+      if ((ps = next_token (s, comment, &commentlen, sizeof (comment) - 1)) == NULL)
+      {
+	rfc822_free_address (&top);
+	return NULL;
+      }
+      s = ps;
+    }
+    else if (*s == ':')
+    {
+      cur = rfc822_new_address ();
+      phrase[phraselen] = 0;
+      cur->mailbox = safe_strdup (phrase);
+      cur->group = 1;
+
+      if (last)
+	last->next = cur;
+      else
+	top = cur;
+      last = cur;
+
+#ifdef EXACT_ADDRESS
+      last->val = mutt_substrdup (begin, s);
+#endif
+
+      phraselen = 0;
+      commentlen = 0;
+      s++;
+      begin = s;
+      SKIPWS (begin);
+    }
+    else if (*s == ';')
+    {
+      if (phraselen)
+      {
+	phrase[phraselen] = 0;
+	add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
+      }
+      else if (commentlen && !last->personal)
+      {
+	comment[commentlen] = 0;
+	last->personal = safe_strdup (comment);
+      }
+#ifdef EXACT_ADDRESS
+      if (last && !last->val)
+	last->val = mutt_substrdup (begin, s);
+#endif
+
+      /* add group terminator */
+      cur = rfc822_new_address ();
+      if (last)
+      {
+	last->next = cur;
+	last = cur;
+      }
+
+      phraselen = 0;
+      commentlen = 0;
+      s++;
+      begin = s;
+      SKIPWS (begin);
+    }
+    else if (*s == '<')
+    {
+      phrase[phraselen] = 0;
+      cur = rfc822_new_address ();
+      if (phraselen)
+      {
+	if (cur->personal)
+	  FREE (&cur->personal);
+	/* if we get something like "Michael R. Elkins" remove the quotes */
+	rfc822_dequote_comment (phrase);
+	cur->personal = safe_strdup (phrase);
+      }
+      if ((ps = parse_route_addr (s + 1, comment, &commentlen, sizeof (comment) - 1, cur)) == NULL)
+      {
+	rfc822_free_address (&top);
+	rfc822_free_address (&cur);
+	return NULL;
+      }
+
+      if (last)
+	last->next = cur;
+      else
+	top = cur;
+      last = cur;
+
+      phraselen = 0;
+      commentlen = 0;
+      s = ps;
+    }
+    else
+    {
+      if (phraselen && phraselen < sizeof (phrase) - 1)
+	phrase[phraselen++] = ' ';
+      if ((ps = next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL)
+      {
+	rfc822_free_address (&top);
+	return NULL;
+      }
+      s = ps;
+    }
+    SKIPWS (s);
+  }
+  
+  if (phraselen)
+  {
+    phrase[phraselen] = 0;
+    comment[commentlen] = 0;
+    add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1);
+  }
+  else if (commentlen && last && !last->personal)
+  {
+    comment[commentlen] = 0;
+    last->personal = safe_strdup (comment);
+  }
+#ifdef EXACT_ADDRESS
+  if (last)
+    last->val = mutt_substrdup (begin, s);
+#endif
+
+  return top;
+}
+
+void rfc822_qualify (ADDRESS *addr, const char *host)
+{
+  char *p;
+
+  for (; addr; addr = addr->next)
+    if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL)
+    {
+      p = safe_malloc (strlen (addr->mailbox) + strlen (host) + 2);
+      sprintf (p, "%s@%s", addr->mailbox, host);
+      safe_free (&addr->mailbox);
+      addr->mailbox = p;
+    }
+}
+
+void
+rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials)
+{
+  if (strpbrk (value, specials))
+  {
+    char tmp[256], *pc = tmp;
+    size_t tmplen = sizeof (tmp) - 3;
+
+    *pc++ = '"';
+    for (; *value && tmplen > 1; value++)
+    {
+      if (*value == '\\' || *value == '"')
+      {
+	*pc++ = '\\';
+	tmplen--;
+      }
+      *pc++ = *value;
+      tmplen--;
+    }
+    *pc++ = '"';
+    *pc = 0;
+    strfcpy (buf, tmp, buflen);
+  }
+  else
+    strfcpy (buf, value, buflen);
+}
+
+void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS *addr)
+{
+  size_t len;
+  char *pbuf = buf;
+  char *pc;
+  
+  if (!addr)
+    return;
+
+  buflen--; /* save room for the terminal nul */
+
+#ifdef EXACT_ADDRESS
+  if (addr->val)
+  {
+    if (!buflen)
+      goto done;
+    strfcpy (pbuf, addr->val, buflen);
+    len = strlen (pbuf);
+    pbuf += len;
+    buflen -= len;
+    if (addr->group)
+    {
+      if (!buflen)
+	goto done;
+      *pbuf++ = ':';
+      buflen--;
+      *pbuf = 0;
+    }
+    return;
+  }
+#endif
+
+  if (addr->personal)
+  {
+    if (strpbrk (addr->personal, RFC822Specials))
+    {
+      if (!buflen)
+	goto done;
+      *pbuf++ = '"';
+      buflen--;
+      for (pc = addr->personal; *pc && buflen > 0; pc++)
+      {
+	if (*pc == '"' || *pc == '\\')
+	{
+	  if (!buflen)
+	    goto done;
+	  *pbuf++ = '\\';
+	  buflen--;
+	}
+	if (!buflen)
+	  goto done;
+	*pbuf++ = *pc;
+	buflen--;
+      }
+      if (!buflen)
+	goto done;
+      *pbuf++ = '"';
+      buflen--;
+    }
+    else
+    {
+      if (!buflen)
+	goto done;
+      strfcpy (pbuf, addr->personal, buflen);
+      len = strlen (pbuf);
+      pbuf += len;
+      buflen -= len;
+    }
+
+    if (!buflen)
+      goto done;
+    *pbuf++ = ' ';
+    buflen--;
+  }
+
+  if (addr->personal || (addr->mailbox && *addr->mailbox == '@'))
+  {
+    if (!buflen)
+      goto done;
+    *pbuf++ = '<';
+    buflen--;
+  }
+
+  if (addr->mailbox)
+  {
+    if (!buflen)
+      goto done;
+    strfcpy (pbuf, addr->mailbox, buflen);
+    len = strlen (pbuf);
+    pbuf += len;
+    buflen -= len;
+
+    if (addr->personal || (addr->mailbox && *addr->mailbox == '@'))
+    {
+      if (!buflen)
+	goto done;
+      *pbuf++ = '>';
+      buflen--;
+    }
+
+    if (addr->group)
+    {
+      if (!buflen)
+	goto done;
+      *pbuf++ = ':';
+      buflen--;
+      if (!buflen)
+	goto done;
+      *pbuf++ = ' ';
+      buflen--;
+    }
+  }
+  else
+  {
+    if (!buflen)
+      goto done;
+    *pbuf++ = ';';
+    buflen--;
+  }
+done:
+  /* no need to check for length here since we already save space at the
+     beginning of this routine */
+  *pbuf = 0;
+}
+
+/* note: it is assumed that `buf' is nul terminated! */
+void rfc822_write_address (char *buf, size_t buflen, ADDRESS *addr)
+{
+  char *pbuf = buf;
+  size_t len = strlen (buf);
+  
+  buflen--; /* save room for the terminal nul */
+
+  if (len > 0)
+  {
+    if (len > buflen)
+      return; /* safety check for bogus arguments */
+
+    pbuf += len;
+    buflen -= len;
+    if (!buflen)
+      goto done;
+    *pbuf++ = ',';
+    buflen--;
+    if (!buflen)
+      goto done;
+    *pbuf++ = ' ';
+    buflen--;
+  }
+
+  for (; addr && buflen > 0; addr = addr->next)
+  {
+    /* use buflen+1 here because we already saved space for the trailing
+       nul char, and the subroutine can make use of it */
+    rfc822_write_address_single (pbuf, buflen + 1, addr);
+
+    /* this should be safe since we always have at least 1 char passed into
+       the above call, which means `pbuf' should always be nul terminated */
+    len = strlen (pbuf);
+    pbuf += len;
+    buflen -= len;
+
+    /* if there is another address, and its not a group mailbox name or
+       group terminator, add a comma to separate the addresses */
+    if (addr->next && addr->next->mailbox && !addr->group)
+    {
+      if (!buflen)
+	goto done;
+      *pbuf++ = ',';
+      buflen--;
+      if (!buflen)
+	goto done;
+      *pbuf++ = ' ';
+      buflen--;
+    }
+  }
+done:
+  *pbuf = 0;
+}
+
+/* this should be rfc822_cpy_adr */
+ADDRESS *rfc822_cpy_adr_real (ADDRESS *addr)
+{
+  ADDRESS *p = rfc822_new_address ();
+
+#ifdef EXACT_ADDRESS
+  p->val = safe_strdup (addr->val);
+#endif
+  p->personal = safe_strdup (addr->personal);
+  p->mailbox = safe_strdup (addr->mailbox);
+  p->group = addr->group;
+  return p;
+}
+
+/* this should be rfc822_cpy_adrlist */
+ADDRESS *rfc822_cpy_adr (ADDRESS *addr)
+{
+  ADDRESS *top = NULL, *last = NULL;
+  
+  for (; addr; addr = addr->next)
+  {
+    if (last)
+    {
+      last->next = rfc822_cpy_adr_real (addr);
+      last = last->next;
+    }
+    else
+      top = last = rfc822_cpy_adr_real (addr);
+  }
+  return top;
+}
+
+/* append list 'b' to list 'a' and return the last element in the new list */
+ADDRESS *rfc822_append (ADDRESS **a, ADDRESS *b)
+{
+  ADDRESS *tmp = *a;
+
+  while (tmp && tmp->next)
+    tmp = tmp->next;
+  if (!b)
+    return tmp;
+  if (tmp)
+    tmp->next = rfc822_cpy_adr (b);
+  else
+    tmp = *a = rfc822_cpy_adr (b);
+  while (tmp && tmp->next)
+    tmp = tmp->next;
+  return tmp;
+}
+
+#ifdef TESTING
+void safe_free (void *ptr)
+{ 
+  void **p = (void **)ptr;
+  if (*p)
+  { 
+    free (*p);
+    *p = 0;
+  }
+}
+
+int main (int argc, char **argv)
+{
+  ADDRESS *list;
+  char buf[256];
+  char *str = "michael, Michael Elkins <me@cs.hmc.edu>, testing a really complex address: this example <@contains.a.source.route@with.multiple.hosts:address@example.com>;, lothar@of.the.hillpeople (lothar)";
+  
+  list = rfc822_parse_adrlist (NULL, str);
+  buf[0] = 0;
+  rfc822_write_address (buf, sizeof (buf), list);
+  rfc822_free_address (&list);
+  puts (buf);
+  exit (0);
+}
+#endif