Improve company productivity with a Business Account.Sign Up

  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 349
  • Last Modified:

Configuring sendmail to archive mail

I'd like to have our mail server keep an archive copy of all email traffic.

I've got incomming mail covered (sending a copy to a dummy account using /etc/aliases), but I'm not sure how to keep a copy of outgoing mail.
1 Solution
There are two ways you can accomplish that.

The "-X some-log-file" argument to sendmail is the cheap and dirty way. The disadvantage is that it logs everything, the raw sendmail interactions, local delivery, the works. The data is there, but the format isn't very usable as as a message may be split up in the log file, interspersed with parts of other messages (it's a real time log).

The second and better way is to use Axel Reinhold's sendmail logging mod. It simply logs, in normal unix mailbox format, each message received by sendmail, both from external mailservers and from internal clients. The only disadvantage to it is that you do have to incorporate it into the the sendmail source (get it the 8.9.3 sources from and compile your own sendmail, but it works really, really well. (I can probably help with compiling sendmail also)

On the chance that you are willing to use the latter method, I've put the mod below. Instructions for adding it to sendmail and using it are in the comments at the beginning. Paste it into a file named "logall.c" and follow its instructions.

---snip, snip---begin logall.c---
/* ------------------------------------------------------------
 *      "LogAll" feature to log any message through this MTA
 *      (c) /ARX cleanware 1998
#define LOGALLHEADER      "X-Logged"
#define LOGALLLEVEL      12

      char logallrcsid[] =
            "$Id: logall.c,v 1.11 1998/09/19 10:12:04 root Exp root $";
 *      $Log: logall.c,v $
 *      Revision 1.11  1998/09/19 10:12:04  root
 *      published
 *      Revision 1.7  1998/09/17 07:12:49  root
 *      clobbered qtimestr
 *      ABSTRACT
 *      This routine logs every message in a mail folder
 *      before it is actually transmitted. The log is
 *      "envelope-oriented" which means it logs every message
 *      once - not for every recipient. You must also see
 *      the sendmail syslog for actual delivery. The log
 *      includes full message bodys.
 *      The logging code is compiled into the sendmail binary
 *      with the documented and often overlooked checkcompat()
 *      routine:
 *      see "Bryan Costales & Eric Allman: sendmail 2nd Edition
 *            §20 `The checkcompat() Cookbook' p.285ff"
 *      To check whether a message was already logged it adds
 *      a new header "X-Logged" to the message with the logging
 *      host, the envelope id and queued date/time as values.
 *      As long as this info doesn't change the message is no
 *      more logged.
 *      The logfile is configuerd in a new macro:
 *      D{LogAll}path
 *      where path is the full pathname of the logfile, which
 *      should be mode 600. REMEMBER THIS IS A FULL BODY LOG.
 *      So be prepared for a huge file on site with much traffic.
 *      The file must exist and every mail is appended to it.
 *      The macro should be inserted into a m4-config-file for
 *      example like this:
 *            LOCAL_CONFIG
 *            D{LogAll}/var/log/mail.log
 *      This source fragment must be included into the source-file:
 *      .../sendmail-8.x.y/src/conf.c
 *      at the following position (with the full path name of its
 *      current location - here "/root/adm/mail/sendmail/logall.c"):
 *>>old            if (tTd(49, 1))
 *>>old                  printf("checkcompat(to=%s, from=%s)\n",            
 *>>old                        to->q_paddr, e->e_from.q_paddr);          
 *>>new            #include "/root/adm/mail/sendmail/logall.c"
 *>>old            # ifdef EXAMPLE_CODE
 *      the sendmail binary must be remaked and reinstalled at
 *      its proper position (normally /usr/sbin/sendmail).
 *      Since the logfile produced by LogAll is a standard Unix-mailfolder,
 *      the logged mails can be managed with every Unix mail-reader or MUA.
 *      The mails inside the log can be deleted, remailed, read with any
 *      mail user agent. If the logfile is the standard mail folder of a
 *      special "LogAll-user" this is especially easy and even "mail" can
 *      or a Windows-based IMAP-client can manage the logfile.
 *      LogAll is tested with sendmail-8.8.5 and sendmail-8.9.1
 *      under Linux 2.0.29.
 *      AUTHOR
 *      Axel Reinhold -
 *      The software is provided "AS IS" without warranties of any kind,
 *      either expressed or implied, including, but not limited to the
 *      implied warranties of merchantability and fitness for a particular
 *      purpose. The entire risc of the software is with you. In no event
 *      we will be liable for any damages, including any lost profits,
 *      lost savings or other incidental damages arising out of the use
 *      or inability to use the software, even if we have been advised
 *      of the possibility of such damages, or for any claim by another party.
 *   ------------------------------------------------------------

      int mid;                  /* sendmail macro id                */
      char *logall = NULL;            /* log file path from macro LogAll  */
      char bfpath[512];            /* body file path                */
      char *hlogged = LOGALLHEADER;      /* header field                      */
      char hlogval[MAXLINE];            /* header value                      */
      char *hlogvalp;                  /* header value pointer                */
      char qtimestr[80];            /* queued time string from asctime  */
      FILE *lf, *bf;                  /* log and body files                */
      ADDRESS *a;                  /* address structure                */
      HDR *h;                        /* header structure                */
      int toflag = 0;                  /* to header flag                */
      int fromflag = 0;            /* from header flag                */
      long bfpos;                  /* remember file pos                */
      size_t b_read;                  /* bytes read counter                */
      unsigned char b_buf[512];      /* buffer for read/write body          */

      mid = macid("{LogAll}", NULL);      /* get our config macro                */
      logall = macvalue(mid, e);      /* to check whether and where to log*/

      /* Use logging if macro is set and we can open the logfile          */
      if ((logall!=NULL) && ((lf = fopen(logall, "a"))!=NULL)) {
          if (tTd(49, 1))            /* debug use of logging                */
            printf("checkcompat: LogAll=%s e_id=%s\n", logall, e->e_id);

          /* construct the "X-Logged" header field */
          strcpy(qtimestr, asctime(localtime(&e->e_ctime))); /* get time  */
          if (qtimestr[strlen(qtimestr)-1]=='\n')      /* remove trailing  */
                qtimestr[strlen(qtimestr)-1]='\0';      /* line-feed asctime*/
          sprintf(hlogval, "Logged by %s as %s at %s",
            MyHostName, e->e_id, qtimestr);            /* our header field */

          /* add the header if it doesn't already exist                */
          hlogvalp = hvalue(hlogged, e->e_header);      /* get actual field */
          if (hlogvalp==NULL) addheader(hlogged, hlogval, &e->e_header);

          /* Log the message if our header didn't exist or was not from us*/
          if ((hlogvalp==NULL) || (strcmp(hlogvalp, hlogval)!=0)) {

            /* lock the logfile exclusive                            */
            if (lockfile(fileno(lf), logall, NULL, LOCK_EX)) ;
            fseek(lf, 0, SEEK_END);      /* go the eof for appending message */

            /* syslog the usage of LogAll                            */
            if (LogLevel >= LOGALLLEVEL) sm_syslog(LOG_INFO, e->e_id,
                  "LogAll to %s at %s", logall, qtimestr);

            /* print the Unix From line to separate messages in log file*/
            fprintf(lf, "From %s %s\n", e->e_from.q_user, qtimestr);

            /* scan all headers                                  */
            for (h = e->e_header; h != NULL; h = h->h_link) {

                  /* check if our header is from us and recent          */
                  /* if not change the field value to our field          */
                  if (strcasecmp(h->h_field, hlogged)==0) {
                        if (hlogvalp!=NULL) {
                              h->h_value =
                               realloc(h->h_value, strlen(hlogval)+1);
                              strcpy(h->h_value, hlogval);
                        h->h_flags &= ~H_DEFAULT;      /* set flag */

                  /* log the header if appropiate                      */
                  if ((h->h_value!=NULL) && ((h->h_flags & H_RESENT)==0)) {
                        fprintf(lf, "%s: %s\n", h->h_field, h->h_value);

                  if (h->h_flags & H_RCPT) toflag = 1;      /* to-header*/

            if (!toflag) {      /* if no to-header construct from envelope  */
                  fprintf(lf, "To:");
                  for (a = e->e_sendqueue; a != NULL; a = a->q_next)
                        fprintf(lf, " %s", a->q_paddr);
                  fprintf(lf, "\n");
            fprintf(lf, "\n");      /* separate headers from body          */

            /* now add the body from temp file or queued data file          */
            sprintf(bfpath, "%s/df%s", QueueDir, e->e_id); /* body file */
            if (e->e_dfp != NULL) {      /* if body temp file is open use it */
                  bfpos = ftell(e->e_dfp);/* remember old position    */
                  rewind(e->e_dfp);      /* read from beginning          */
                  while ((b_read = fread(b_buf, 1L, sizeof(b_buf), e->e_dfp)) != 0)
                        fwrite(b_buf, 1L, b_read, lf);
                  fseek(e->e_dfp, bfpos, SEEK_SET); /* set old pos    */
            else if ((bf = fopen(bfpath, "r")) != NULL) { /* body file  */
                  while ((b_read = fread(b_buf, 1, sizeof(b_buf), bf)) != 0)
                        fwrite(b_buf, 1, b_read, lf);
            else      { /* no body could be opened                      */
                  if (tTd(49, 1))
                      printf("checkcompat: fopen(%s) failed\n", bfpath);
            fprintf(lf, "\n");

            if (lockfile(fileno(lf), logall, NULL, LOCK_UN)) ; /* unlock*/

/*   ------------------------------------------------------------
 *      LogAll End
 *   ------------------------------------------------------------
sprinkmeierAuthor Commented:
Thanks, worked exactly as required!
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Upgrade your Question Security!

Your question, your audience. Choose who sees your identity—and your question—with question security.

Tackle projects and never again get stuck behind a technical roadblock.
Join Now