/*************************************************
*     Exim - an Internet mail transport agent    *
*************************************************/

/* Copyright (c) University of Cambridge 1995 - 1999 */
/* See the file NOTICE for conditions of use and distribution. */

/* Functions for writing spool files. */


#include "exim.h"



/*************************************************
*            Open file under temporary name      *
*************************************************/

/* This is used for opening spool files under a temporary name,
with a single attempt at deleting if they already exist.

Argument: temporary name for spool header file
Returns:  file descriptor of open file, or < 0 on failure, with errno unchanged
*/

int
spool_open_temp(char *temp_name)
{
int fd = open(temp_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);

/* Try to make the directory if it isn't there. */

if (fd < 0 && errno == ENOENT)
  {
  directory_make(spool_directory, "input", INPUT_DIRECTORY_MODE, TRUE);
  fd = open(temp_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
  }

/* If the file already exists, something has gone wrong. This process may well
have previously created the file if it is delivering more than one address, but
it should have renamed it almost immediately. A file could, however, be left
around as a result of a system crash, and by coincidence this process might
have the same pid. We therefore have one go at unlinking it before giving up.
*/

if (fd < 0 && errno == EEXIST)
  {
  DEBUG(2) debug_printf("%s exists: unlinking\n", temp_name);
  unlink(temp_name);
  fd = open(temp_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
  }

/* If the file has been opened, make sure the file's group is the Exim gid
if exim_uid exists (can't have exim_uid set without exim_gid), and double-check
the mode because the group setting doesn't always get set automatically. */

if (fd >= 0 && exim_uid_set)
  {
  fchown(fd, exim_uid, exim_gid);
  fchmod(fd, SPOOL_MODE);
  }

return fd;
}



/*************************************************
*          Write the header spool file           *
*************************************************/

/* Returns the size of the file for success; zero for failure. The file is
written under a temporary name, and then renamed. It's done this way so that it
works with re-writing the file on message deferral as well as for the initial
write. Whenever this function is called, the data file for the message should
be open and locked, thus preventing any other exim process from working on this
message.

Argument: the message id
Returns:  negative on failure (error number in errno); the size of the header
            texts on success
*/

int
spool_write_header(char *id)
{
int fd;
int i;
int size_correction;
FILE *f;
header_line *h;
struct stat statbuf;
char name[256];
char temp_name[256];

sprintf(temp_name, "%s/input/%s/hdr.%d", spool_directory, message_subdir,
  (int)getpid());
fd = spool_open_temp(temp_name);
if (fd < 0)
  {
  DEBUG(2) debug_printf("Failed to open %s\n", temp_name);
  return -1;
  }

f = fdopen(fd, "w");
DEBUG(9) debug_printf("Writing spool header file\n");

/* We now have an open file to which the header data is to be written. Start
with the file's leaf name, to make the file self-identifying. Continue with the
identity of the submitting user, followed by the sender's address. The sender's
address is enclosed in <> because it might be the null address. Then write the
received time and the number of warning messages that have been sent. */

fprintf(f, "%s-H\n", message_id);
fprintf(f, "%.63s %d %d\n", originator_login, (int)originator_uid,
  (int)originator_gid);
fprintf(f, "<%s>\n", sender_address);
fprintf(f, "%d %d\n", received_time, warning_count);

/* If there is information about a sending host, remember it. */

if (sender_host_address != NULL)
  {
  fprintf(f, "-host_address %s\n", sender_host_address);
  if (sender_host_name != NULL)
    fprintf(f, "-host_name %s\n", sender_host_name);
  if (sender_helo_name != NULL)
    fprintf(f, "-helo_name %s\n", sender_helo_name);
  }

/* Likewise for any ident information; for local messages this is
likely to be the same as originator_login, but will be different if
the originator was root, forcing a different ident. */

if (sender_ident != NULL) fprintf(f, "-ident %s\n", sender_ident);

/* Ditto for the received protocol */

if (received_protocol != NULL)
  fprintf(f, "-received_protocol %s\n", received_protocol);

/* Now any other data that needs to be remembered. */

fprintf(f, "-body_linecount %d\n", body_linecount);

if (deliver_firsttime) fprintf(f, "-deliver_firsttime\n");
if (deliver_freeze) fprintf(f, "-frozen %d\n", deliver_frozen_at);
if (sender_local) fprintf(f, "-local\n");
if (local_error_message) fprintf(f, "-localerror\n");
if (deliver_manual_thaw) fprintf(f, "-manual_thaw\n");
if (header_names != header_names_normal) fprintf(f, "-resent\n");
if (user_null_sender) fprintf(f, "-user_null_sender\n");

#ifdef SUPPORT_DSN
if (dsn_envid != NULL) fprintf(f, "-dsn_envid %s\n", dsn_envid);
if (dsn_ret != 0) fprintf(f, "-dsn_ret %d\n", dsn_ret);
#endif

/* To complete the envelope, write out the tree of non-recipients, followed by
the list of recipients. These won't be disjoint the first time, when no
checking has been done. */

tree_write(tree_nonrecipients, f);
fprintf(f, "%d\n", recipients_count);
for (i = 0; i < recipients_count; i++)
  {
  recipient_item *r = recipients_list + i;
  if (r->flags == 0
      #ifdef SUPPORT_DSN
      && r->orcpt == NULL
      #endif
     ) fprintf(f, "%s\n", r->address);

  else
    {
    #ifdef SUPPORT_DSN
    if (r->orcpt != NULL && r->orcpt[0] != 0)
      fprintf(f, "%s %s %d,%d,%d\n", r->address, r->orcpt, r->flags, r->pno,
        (int)strlen(r->orcpt));
    else
    #endif

    fprintf(f, "%s %d,%d,0\n", r->address, r->flags, r->pno);
    }
  }

/* Put a blank line before the headers */

fprintf(f, "\n");

/* Save the size of the file so far so we can subtract it from the final length
to get the actual size of the headers. */

fflush(f);
fstat(fd, &statbuf);
size_correction = statbuf.st_size;

/* Finally, write out the message's headers. To make it easier to read them
in again, precede each one with the count of its length. Make the count fixed
length to aid human eyes when debugging and arrange for it not be included in
the size. It is followed by a space for normal headers, a flagging letter for
various other headers, or an asterisk for old headers that have been rewritten.
These are saved as a record for debugging. */

for (h = header_list; h != NULL; h = h->next)
  {
  fprintf(f, "%03d%c %s", h->slen, h->type, h->text);
  size_correction += 5;
  }

/* Flush and check for any errors while writing */

if (fflush(f) != 0 || ferror(f))
  {
  unlink(temp_name);
  fclose(f);
  DEBUG(2) debug_printf("Error while writing %s\n", temp_name);
  return -1;
  }

/* Force the file's contents to be written to disc. Note that fflush()
just pushes it out of C, and fclose() doesn't guarantee to do the write
either. That's just the way Unix works... */

if (fsync(fileno(f)) < 0)
  {
  unlink(temp_name);
  fclose(f);
  DEBUG(2) debug_printf("fsync on %s failed: %s\n", temp_name, strerror(errno));
  return -1;
  }

/* Get the size of the file, and close it. */

fstat(fd, &statbuf);
if (fclose(f) != 0)
  {
  unlink(temp_name);
  DEBUG(2) debug_printf("fclose on %s failed: %s\n", temp_name,
    strerror(errno));
  return -1;
  }

/* Rename the file to its correct name, thereby replacing any previous
incarnation. */

sprintf(name, "%s/input/%s/%s-H", spool_directory, message_subdir, id);

if (rename(temp_name, name) < 0)
  {
  unlink(temp_name);
  DEBUG(2) debug_printf("Failed to rename %s as %s\n", temp_name, name);
  return -1;
  }

/* Return the number of characters in the headers, which is the file size, less
the prelimary stuff, less the additional count fields on the headers. */

DEBUG(2)
  debug_printf("Size of headers = %d\n", statbuf.st_size - size_correction);

return statbuf.st_size - size_correction;
}

/* End of spool_out.c */
